Перейти к основному содержимому

Работа со списком списков в Java

· 5 мин. чтения

1. Обзор

Список — довольно часто используемая структура данных в Java. Иногда нам может понадобиться вложенная структура List для некоторых требований, таких как List<List<T>> .

В этом руководстве мы более подробно рассмотрим эту структуру данных «Список списков» и рассмотрим некоторые повседневные операции.

2. Массив списков против списка списков

Мы можем рассматривать структуру данных «Список списков» как двумерную матрицу. Итак, если мы хотим сгруппировать несколько объектов List<T> , у нас есть два варианта:

  • На основе массива: List<T>[]
  • На основе списка: Список<Список<T>>

Далее, давайте посмотрим, когда выбрать какой из них.

Массив быстр для операций « получить » и « установить », которые выполняются за время O(1) . Однако, поскольку длина массива фиксирована, изменение размера массива для вставки или удаления элементов обходится дорого .

С другой стороны, List более гибок в операциях вставки и удаления , которые выполняются за время O(1) . Вообще говоря, List медленнее, чем Array , в операциях «получить/установить». Но некоторые реализации List , такие как ArrayList , внутренне основаны на массивах. Так что обычно разница между производительностью Array и ArrayList на операциях «получить/установить» не заметна. [](/lessons/b/-java-add-element-to-array-vs-list)

Поэтому в большинстве случаев мы выбираем структуру данных List<List<T>> для большей гибкости .

Конечно, если мы работаем над критически важным для производительности приложением и не меняем размер первого измерения — например, мы никогда не добавляем и не удаляем внутренние списки — мы можем рассмотреть возможность использования List<T>[] структура.

В этом руководстве мы в основном обсудим List<List<T>> .

3. Общие операции со списком списков

Теперь давайте рассмотрим некоторые повседневные операции над List<List<T>> .

Для простоты мы будем манипулировать объектом List<List<String>> и проверять результат в методах модульного тестирования.

Кроме того, чтобы увидеть изменение напрямую, давайте также создадим метод для печати содержимого List of List s:

private void printListOfLists(List<List<String>> listOfLists) {
System.out.println("\n List of Lists ");
System.out.println("-------------------------------------");
listOfLists.forEach(innerList -> {
String line = String.join(", ", innerList);
System.out.println(line);
});
}

Далее, давайте сначала инициализируем список списков.

3.1. Инициализация списка списков

Мы будем импортировать данные из файла CSV в объект List<List<T>> . Давайте сначала посмотрим на содержимое файла CSV:

Linux, Microsoft Windows, Mac OS, Delete Me
Kotlin, Delete Me, Java, Python
Delete Me, Mercurial, Git, Subversion

Допустим, мы назовем файл example.csv и поместим его в каталог resources/listoflists .

Далее создадим метод для чтения файла и сохранения данных в объекте List<List<T>> :

private List<List<String>> getListOfListsFromCsv() throws URISyntaxException, IOException {
List<String> lines = Files.readAllLines(Paths.get(getClass().getResource("/listoflists/example.csv")
.toURI()));

List<List<String>> listOfLists = new ArrayList<>();
lines.forEach(line -> {
List<String> innerList = new ArrayList<>(Arrays.asList(line.split(", ")));
listOfLists.add(innerList);
});
return listOfLists;
}

В методе getListOfListsFromCsv мы сначала считываем все строки из CSV-файла в объект List<String> . Затем мы проходим по списку строк и преобразуем каждую строку ( String ) в List<String> .

Наконец, мы добавляем каждый преобразованный объект List<String> в listOfLists . Таким образом, мы инициализировали список списков.

Любознательные глаза могли заметить, что мы оборачиваем Arrays.asList(..) в новый ArrayList<>() . Это связано с тем, что метод Arrays.asList создаст неизменный список List . Однако позже мы внесем некоторые изменения во внутренние списки. Поэтому мы оборачиваем его в новый объект ArrayList .

Если объект списка списков создан правильно, у нас должно быть три элемента, что является количеством строк в CSV-файле, во внешнем списке.

Более того, каждый элемент — это внутренний список, и каждый из них должен содержать четыре элемента. Далее давайте напишем метод модульного тестирования, чтобы проверить это. Также мы напечатаем инициализированный список списков:

List<List<String>> listOfLists = getListOfListsFromCsv();

assertThat(listOfLists).hasSize(3);
assertThat(listOfLists.stream()
.map(List::size)
.collect(Collectors.toSet())).hasSize(1)
.containsExactly(4);

printListOfLists(listOfLists);

Если мы выполним метод, тест пройдет и выдаст результат:

List of Lists           
-------------------------------------
Linux, Microsoft Windows, Mac OS, Delete Me
Kotlin, Delete Me, Java, Python
Delete Me, Mercurial, Git, Subversion

Далее, давайте внесем некоторые изменения в объект listOfLists . Но сначала давайте посмотрим, как применить изменения к внешнему списку.

3.2. Применение изменений к внешнему списку

Если мы сосредоточимся на внешнем списке, мы можем сначала игнорировать внутренний список. Другими словами, мы можем рассматривать List<List<String>> как обычный List<T>.

Таким образом, изменить обычный объект List не составляет труда. Мы можем вызывать методы List , такие как add и remove , для управления данными.

Далее добавим во внешний список новый элемент:

List<List<String>> listOfLists = getListOfListsFromCsv();
List<String> newList = new ArrayList<>(Arrays.asList("Slack", "Zoom", "Microsoft Teams", "Telegram"));
listOfLists.add(2, newList);

assertThat(listOfLists).hasSize(4);
assertThat(listOfLists.get(2)).containsExactly("Slack", "Zoom", "Microsoft Teams", "Telegram");

printListOfLists(listOfLists);

Элемент внешнего списка на самом деле является объектом List<String> . Как показывает метод выше, мы создаем список популярных утилит онлайн-общения. Затем мы добавляем новый список в listOfLists в позицию с index=2 .

Опять же, после утверждений мы печатаем содержимое listOfLists :

List of Lists           
-------------------------------------
Linux, Microsoft Windows, Mac OS, Delete Me
Kotlin, Delete Me, Java, Python
Slack, Zoom, Microsoft Teams, Telegram
Delete Me, Mercurial, Git, Subversion

3.3. Применение изменений к внутренним спискам

Наконец, давайте посмотрим, как манипулировать внутренними списками.

Поскольку listOfList является вложенной структурой List , нам нужно сначала перейти к объекту внутреннего списка, который мы хотим изменить. Если мы точно знаем индекс, мы можем просто использовать метод get :

List<String> innerList = listOfLists.get(x);
// innerList.add(), remove() ....

Однако, если мы хотим применить изменение ко всем внутренним спискам, мы можем пройти через объект списка списков через цикл или Stream API .

Далее давайте посмотрим на пример, который удаляет все записи « Удалить меня » из объекта listOfLists :

List<List<String>> listOfLists = getListOfListsFromCsv();

listOfLists.forEach(innerList -> innerList.remove("Delete Me"));

assertThat(listOfLists.stream()
.map(List::size)
.collect(Collectors.toSet())).hasSize(1)
.containsExactly(3);

printListOfLists(listOfLists);

Как мы видели в приведенном выше методе, мы перебираем каждый внутренний список через listOfLists.forEach{…} и используем лямбда-выражение для удаления записей « Удалить меня » из innerList .

Если мы выполним тест, он пройдет и выдаст следующий результат:

List of Lists           
-------------------------------------
Linux, Microsoft Windows, Mac OS
Kotlin, Java, Python
Mercurial, Git, Subversion

4. Вывод

В этой статье мы обсудили структуру данных списка списков.

Кроме того, мы рассмотрели общие операции со списком списков на примерах.

Как обычно, полный код этой статьи можно найти на GitHub .