LinkedHashSet: примеры (JAVA)
LinkedHashSet: classОбщее описание LinkedHashSet
Класс LinkedHashSet из пакета java.util представляет реализацию множества (Set), которая сохраняет порядок вставки элементов. В основе лежит LinkedHashMap, что обеспечивает уникальность элементов по контракту equals/hashCode и воспроизведение порядка добавления при итерации.
Основные случаи применения: хранение уникальных элементов при необходимости воспроизвести их порядок вставки, удаление дубликатов с сохранением последовательности, преобразование потоков в упорядоченное множество.
Конструкторы и их параметры:
LinkedHashSet()- создаёт пустое множество с начальными настройками внутренней карты.LinkedHashSet(int initialCapacity)- задаёт начальную вместимость внутренней карты. Полезно для оптимизации при заранее известном числе элементов.LinkedHashSet(int initialCapacity, float loadFactor)- задаёт вместимость и коэффициент заполнения (load factor) для внутренней карты. Коэффициент контролирует момент перераспределения (rehash).LinkedHashSet(Collection extends E> c)- создаёт множество и добавляет в него все элементы заданной коллекции в порядке, который будет соответствовать порядку итерации этой коллекции при добавлении.
Ключевые методы и возвращаемые значения:
boolean add(E e)- добавляет элемент; возвращаетtrue, если элемент отсутствовал и был добавлен,false, если элемент уже присутствовал.boolean remove(Object o)- удаляет объект; возвращаетtrue, если элемент был найден и удалён.boolean contains(Object o)- проверяет наличие; возвращаетtrue, если элемент присутствует.Iterator- возвращает итератор, проходящий элементы в порядке их вставки.iterator() int size(),boolean isEmpty(),void clear()- стандартные операции над коллекцией.Object clone()- создаёт поверхностную (shallow) копию множества; элементы не клонируются.Object[] toArray()и<T> T[] toArray(T[] a)- преобразование в массив.- Методы bulk-операций:
addAll,removeAll,retainAll,containsAll- возвращают стандартные логические значения согласно контрактуCollection.
Особенности поведения:
- Порядок итерации соответствует порядку вставки элементов (вставленный раньше элемент будет возвращён раньше при итерации), при повторной вставке уже существующего элемента порядок не меняется. Если элемент удалён и затем добавлен снова, он окажется в конце порядковой последовательности.
- Допускается значение
null(один элементnull), реализация хранит ключи в карте и может обрабатыватьnullкак обычный элемент. - Класс не потокобезопасен; при совместном доступе из нескольких потоков требуется внешняя синхронизация или использование потокобезопасных обёрток.
- Производительность: операции добавления, удаления и проверки присутствия - амортизированно O(1). Накладные расходы выше, чем у простого
HashSet, из-за связанного списка для порядка.
Короткие примеры использования
Пример 1. Простое добавление и итерация в порядке вставки.
import java.util.LinkedHashSet;
public class Example1 {
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("one");
set.add("two");
set.add("three");
set.add("two"); // дубликат игнорируется
for (String s : set) {
System.out.println(s);
}
}
}
one two three
Пример 2. Конструктор с коллекцией и проверка возвращаемых значений методов.
import java.util.Arrays;
import java.util.LinkedHashSet;
public class Example2 {
public static void main(String[] args) {
LinkedHashSet<Integer> numbers = new LinkedHashSet<>(Arrays.asList(3, 1, 4, 1, 5));
System.out.println(numbers.contains(1)); // true
System.out.println(numbers.add(4)); // false, 4 уже есть
System.out.println(numbers.add(9)); // true
System.out.println(numbers.size()); // 5
System.out.println(numbers); // порядок вставки
}
}
true false true 5 [3, 1, 4, 5, 9]
Пример 3. Удаление во время итерации с использованием итератора (корректный способ).
import java.util.Iterator;
import java.util.LinkedHashSet;
public class Example3 {
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("a"); set.add("b"); set.add("c");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String s = it.next();
if ("b".equals(s)) {
it.remove();
}
}
System.out.println(set);
}
}
[a, c]
Аналоги в Java и их особенности
- HashSet - не гарантирует порядок итерации, чуть более быстрый и менее затратный по памяти. Предпочтение при отсутствии требования к порядку.
- TreeSet - упорядочивает элементы по естественному порядку или по заданному Comparator; операции логарифмической сложности. Выбор при необходимости сортировки.
- EnumSet - специализированная и очень эффективная реализация для элементов-перечислений; не сохраняет порядок вставки, но предоставляет компактное внутреннее представление.
- CopyOnWriteArraySet - потокобезопасная реализация на базе копирования при записи; пригодна для небольшого числа записей и множества чтений.
- LinkedHashMap - не Set, но может использоваться для аналогичных задач с дополнительными возможностями (например, режим доступа для реализации LRU). LinkedHashSet основан на LinkedHashMap.
Аналоги в других языках и их отличия
JavaScript - Set в ECMAScript сохраняет порядок вставки. Операции похожи, но отсутствует прямой контроль коэффициента заполнения или начальной вместимости.
const s = new Set(['a','b','a']);
console.log([...s]);
[ 'a', 'b' ]
Python - встроенный set не гарантирует определённый порядок (реализация непредсказуема), для сохранения порядка часто применяются dict.fromkeys() или коллекции из сторонних библиотек. Начиная с CPython 3.7 гарантии касаются dict, но set остаётся неупорядоченным.
# сохранение порядка и удаление дубликатов
lst = [3,1,4,1,5]
unique_ordered = list(dict.fromkeys(lst))
print(unique_ordered)
[3, 1, 4, 5]
PHP - нет встроенного Set; часто используются ассоциативные массивы с ключами-значениями для обеспечения уникальности и порядка:
$arr = ['a','b','a'];
$uniq = array_keys(array_flip($arr));
print_r($uniq);
Array
(
[0] => a
[1] => b
)
C# - HashSet<T> не гарантирует порядок. Для сохранения порядка можно комбинировать List<T> и HashSet<T> или использовать сторонние реализации упорядоченного множества.
// C# пример: сохранить порядок вручную
var set = new HashSet<int>();
var list = new List<int>();
int[] input = {1,2,1,3};
foreach (var x in input) {
if (set.Add(x)) list.Add(x);
}
Console.WriteLine(string.Join(",", list));
1,2,3
Kotlin - есть linkedSetOf() и LinkedHashSet с поведением, совпадающим с Java-реализацией.
val set = linkedSetOf(1,2,1,3)
println(set) // [1, 2, 3]
[1, 2, 3]
Go - нет встроенного типа Set; часто используется map[T]struct{}. Для сохранения порядка применяется дополнительный срез (slice) для индексации последовательности.
// упрощённый пример
input := []int{1,2,1,3}
seen := make(map[int]struct{})
var order []int
for _, v := range input {
if _, ok := seen[v]; !ok {
seen[v] = struct{}{}
order = append(order, v)
}
}
fmt.Println(order)
[1 2 3]
Lua - таблицы используются как множества, порядок не гарантируется для ключей; если порядок критичен, применяется список ключей отдельно.
В целом отличия: в Java LinkedHashSet обеспечивает простое сохранение порядка и стандартные коллекционные интерфейсы; во многих языках для аналогичной функциональности требуется комбинирование структур или использование специализированных коллекций.
Типичные ошибки и ловушки
- ConcurrentModificationException: попытка модификации множества напрямую во время итерации (кроме
Iterator.remove()) приводит к исключению.
import java.util.LinkedHashSet;
public class FailMod {
public static void main(String[] args) {
LinkedHashSet<String> s = new LinkedHashSet<>();
s.add("a"); s.add("b");
for (String x : s) {
s.remove("a"); // ConcurrentModificationException
}
}
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:...)
...
- Изменяемые ключи: хранение объектов, у которых меняются поля, участвующие в
equals/hashCode, нарушает корректность множества: элемент может стать «невидимым» для поиска.
class Mutable {
int id;
Mutable(int id){this.id=id;}
public int hashCode(){return id;}
public boolean equals(Object o){return o instanceof Mutable && ((Mutable)o).id==id;}
}
// затем в множестве элемент изменяет id и поиск по старому значению не удаётся
Непредсказуемое поведение: contains() вернёт false для изменённого объекта, хотя он присутствует в структуре.
- Ожидание сортировки: LinkedHashSet не сортирует элементы; для сортировки применяется TreeSet или сортировка отдельной коллекции.
Изменения в реализации и заметные правки
Архитектура LinkedHashSet основана на LinkedHashMap и в современных версиях JDK не претерпела существенных изменений в публичном API. Непосредственно в LinkedHashSet не вводилось новых конструкторов или флагов в последних релизах. Улучшения производительности и оптимизации затрагивали внутренние карты и коллекции в целом, но контракт класса остался прежним. В новых версиях языка появились более мощные возможности работы с потоками (Stream API) и фабрики коллекций, которые упрощают создание неизменяемых или компактных наборов, но они не заменяют семантику LinkedHashSet.
Расширенные и редкие сценарии использования
Пример A. Реализация простого ограниченного буфера (FIFO) с сохранением уникальности - если размер превышен, удаляется самый старый элемент.
import java.util.Iterator;
import java.util.LinkedHashSet;
public class BoundedSet {
private final int limit;
private final LinkedHashSet<E> set;
public BoundedSet(int limit) {
this.limit = limit;
this.set = new LinkedHashSet<>();
}
public boolean add(E e) {
if (set.contains(e)) return false;
set.add(e);
if (set.size() > limit) {
Iterator<E> it = set.iterator();
if (it.hasNext()) it.next();
it.remove();
}
return true;
}
public String toString(){ return set.toString(); }
public static void main(String[] args) {
BoundedSet<Integer> buf = new BoundedSet<>(3);
buf.add(1); buf.add(2); buf.add(3);
buf.add(4); // удалится 1
buf.add(2); // 2 уже есть, порядок не меняется
System.out.println(buf);
}
}
[2, 3, 4]
Пояснение: LinkedHashSet применяется как упорядоченный контейнер уникальных элементов; удаление самого старого происходит через итератор.
Пример B. Сохранение порядка при объединении коллекций и последующая фильтрация с использованием Stream API.
import java.util.*;
import java.util.stream.Collectors;
public class MergeKeepOrder {
public static void main(String[] args) {
List<String> a = Arrays.asList("x","y","z");
List<String> b = Arrays.asList("y","w");
LinkedHashSet<String> merged = new LinkedHashSet<>();
merged.addAll(a);
merged.addAll(b);
// фильтрация по длине строки с сохранением порядка
List<String> result = merged.stream()
.filter(s -> s.length() == 1)
.collect(Collectors.toList());
System.out.println(result);
}
}
[x, y, z, w]
Пример C. Резервирование начальной ёмкости для больших множеств, чтобы уменьшить количество реhash-операций.
LinkedHashSet<Integer> big = new LinkedHashSet<>(1000, 0.75f);
for (int i=0; i<1000; i++) big.add(i);
System.out.println(big.size());
1000
Пример D. Создание потокобезопасного множества с сохранением порядка через обёртку.
import java.util.Collections;
import java.util.Set;
import java.util.LinkedHashSet;
Set<String> sync = Collections.synchronizedSet(new LinkedHashSet<>());
// при итерации требуется синхронизация вручную
synchronized(sync) {
for (String s : sync) {
// обход
}
}
Нет конкретного вывода; стиль демонстрирует требуемую синхронизацию при итерации.
Пример E. Клонирование и сохранение порядка (shallow copy).
LinkedHashSet<String> orig = new LinkedHashSet<>();
orig.add("a"); orig.add("b");
@SuppressWarnings("unchecked")
LinkedHashSet<String> copy = (LinkedHashSet<String>) orig.clone();
System.out.println(copy);
[a, b]
Пример F. Использование в качестве промежуточной структуры для удаления дубликатов при сохранении порядка элементов входного массива.
String[] input = {"apple","banana","apple","pear"};
LinkedHashSet<String> unique = new LinkedHashSet<>(Arrays.asList(input));
String[] out = unique.toArray(new String[0]);
System.out.println(Arrays.toString(out));
[apple, banana, pear]
Пример G. Комбинация с WeakHashMap для «слабого» множества (не входит в стандартную библиотеку; схема для примера).
// В стандартной библиотеке нет WeakHashSet, но можно реализовать обёртку
// на базе WeakHashMap<E, Boolean> и вспомогательного списка для порядка.
// Такой код требует аккуратной реализации и не является тривиальным примером.
Нет вывода; иллюстрация концепции и предостережение о дополнительной сложности.