* Множества: хеш таблици и червено-черни дървета
Публикувано на 15 ноември 2015 в раздел ПИК3 Java.
Множествата са по принцип по-прости структури спрямо речниците. Те се базират на един основен интерфейс Set. Докато при речниците имахме комбинации от Key и Value, при множествата има само един единствен елемент, който е едновремено Key и Value. Или казано по друг начин множествата са речници при които всяко Value се използва за Key само за себе си. Веднага можете да се досетите, че е много лесно един Set да бъде реализиран чрез Map - например ако подавате един и същи обект за Key и Value в който и да е Map, той ще работи като Set.
С казаното дотук не е изненада, че в Java популярните имплементиращи интерфейс Set класове HashSet, LinkedHashSet и TreeSet всъщност използват именно HashMap, LinkedHashMap и TreeMap вътре в себе си. Например извиквайки метод add на един HashSet, той извиква метод put на вградения в него HashMap, като му подава параметри put(yourObject, dummyObject), където yourObject е вашия обект (използва се за key), а DummyObject e вградена в HashSet референция към следното:
private static final Object dummyObject = new Object();
Както виждате този dummyObject е винаги един и същи за всички елементи на множеството. В така създадения речник не можете да добавите друг Value за същия Key, защото интерфейс Set и неговите стандартни имплементации няма да ви предожат подобен метод. Другите популярни класове в стандартните библиотеки - LinkedHashSet и TreeSet - също използват тази техника.
От всичко казано дотук, можем да си изведем и най-важното свойство на класовете имплементиращи Set - не позволяват един и същи обект да бъде вкаран два пъти в множеството! Действително ако вие се опитате да добавите един и същи обект два пъти в Set, това ще кореспондира на дублираща се комбинация Key (обекта) и Value (dummy обекта) в съответния Map. Забележете, че това няма общо с колизиите при HashSet - и тук е възможно два или повече различни обекта да генерират един и същи хеш код и в тези случаи обектите отново се подреждат в линеен списък.
1. Интерфейсът Set
Интерфейс Set се различава съществено от интерфейс Map не само по входните параметри на методите (вместо два вече ще е само един), но и по имената им.
- boolean add(E e) - добавя елемент в множеството. Ако такъв елемент вече съществува, връща false, иначе true;
- boolean addAll(Collection<? extends E> c) - добавя всички елементи от дадена колекция в множеството. Ако има някаква промяна в множеството (имало е недублиращ елемент и той е добавен), връща true. Ако всички елементи вече са били налични в множеството, връща false;
- void clear() - изтрива всички елементи в множеството;
- boolean contains(Object o) - проверява дали подадения елемент присъства в множеството;
- boolean containsAll(Collection<?> c) - проверява дали всички елементи от подадената колекция се съдържат в множеството;
- boolean equals(Object o) - сравнява множеството с друго множество;
- int hashCode() - хеш код за самото множество (ще е нужно ако самото то бъде използвано като елемент в друго множество);
- boolean isEmpty() - показва дали множеството е празно или не;
- Iterator<E> iterator() - дава итератор;
- boolean remove(Object o) - изтрива елемент от множеството. Ако е имало такъв елемент и е изтрит, връща true. В противен случай връща false;
- boolean removeAll(Collection<?> c) - изтрива всички елементи от множеството, които съвпадат с подадената колекция. Ако поне един е изтрит, връща true. В противен случай връща false;
- boolean retainAll(Collection<?> c) - премахва от множеството всички елементи, които не присъстват в подадената колекция. Ако поне един е изтрит, връща true. В противен случай връща false;
- int size() - указва колко елемента има записани в множеството;
- default Spliterator<E> spliterator() - връща Spliterator;
- Object[] toArray() - превръща множеството в масив от обекти;
- <T> T[] toArray(T[] a) - превръща множеството в масив от обекти, като типа на масива ще съвпадне с типа на подадения като входен параметър обект. Това е доста специфичен метод, който прави лесен преход между колекциите и масивите - ще покажем как се използва по-долу.
2. Примери
Задача 1. Намерете уникалните думи във файла problems.txt и ги изведете на екрана.
Решение: Понеже не се изисква сортиране, ще използваме HashSet:
import java.util.HashSet; import java.util.Scanner; import java.io.FileReader; import java.io.IOException; import java.util.Iterator; public class HashSetExample{ public static void main(String[] args){ Scanner sc = null; try{ sc = new Scanner(new FileReader("problems.txt")); } catch(IOException e){ System.out.println("Error reading the file"); return; } HashSet<String> uniqueWords = new HashSet<String>(); while(sc.hasNext()){ uniqueWords.add(sc.next()); } System.out.println("Уникалните думи са:"); for(String word: uniqueWords){ System.out.println(word); } System.out.println("\nСъщото нещо реализирано чрез iterator:"); Iterator<String> it = uniqueWords.iterator(); while(it.hasNext()){ System.out.println(it.next()); } } }
Задача 2. Решете задача 1, но този път изведете думите подредени по азбучен ред
Решение: Просто заменете HashSet с TreeSet.
Задача 3. Демонстрирайте как горния HashSet се превръща в обикновен масив
Решение:
import java.util.HashSet; public class HashSetExample{ public static void main(String[] args){ HashSet hs = new HashSet(); hs.add("abc"); hs.add("def"); hs.add("abc"); // нарочно - ще бъде пропуснато String[] arr = hs.toArray(new String[0]); for(String el:arr) System.out.println(el); } }
3. Множествата в многонишкова среда
HashSet, LinkedHashSet и TreeSet не са thread-safe, т.е. не са синхронизирани. Синхронизацията трябва да извършвате или ръчно, или чрез удобния Collections.synchronizedSet(Set set) метод. Ето как можем да направим това:
import java.util.Set; import java.util.HashSet; import java.util.Collections; public class HashSetExample{ public static void main(String[] args){ Set<String> hs = Collections.synchronizedSet(new HashSet<String>()); hs.add("abc"); hs.add("def"); hs.add("abc"); // нарочно - ще бъде пропуснато hs.forEach(System.out::println); } }
Трябва да знаете, че Java не предлага еквивалентен на ConcurrentHashMap метод! За сметка на това можете много лесно да си създадете без проблем метод newSetFromMap на клас Collections, в който като параметър да подадете ConcurrentHashMap обект. Горният пример ще изглежда по следния начин:
import java.util.Set; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import java.util.Collections; public class HashSetExample{ public static void main(String[] args){ Set<String> hs = Collections.newSetFromMap(new ConcurrentHashMap<String,Boolean>()); hs.add("abc"); hs.add("def"); hs.add("abc"); // нарочно - ще бъде пропуснато hs.forEach(System.out::println); } }
Поради същите съображения, които бяха изложени в предишната статия, втория пример е по-ефективен от първия.
Добави коментар