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 .