Java (11) - Kontejnery II.
Po minulém obecném úvodu do světa kolekcí se podíváme na jednotlivá rozhraní
a implementace. Správný výběr je důležitý pro tvorbu rychlých a úsporných programů.
6.4.2005 15:00 |
Lukáš Jelínek
| Články autora
| přečteno 79511×
Kategorie kontejnerových rozhraní
Jak jsem říkal už minule, namísto práce s implementačními objekty je vhodné
při práci používat rozhraní. Systém kolekcí je na rozhraní založen, existuje
jich zde celá hierarchie a my můžeme pracovat na té úrovni, která nám nejlépe
vyhovuje (resp. na té nejvyšší použitelné).
Máme dvě základní množiny kontejnerů, každá z nich vychází z jednoho rozhraní.
První z nich jsou běžné (normální) kolekce odvozené od rozhraní Collection ,
do druhé kategorie patří kolekce asociativní ("mapované", obsahující dvojice
klíč-hodnota) implementující rozhraní Map .
Normální kolekce
Typickým znakem "normálních" kolekcí je to, že pracujeme s jednoduchými prvky
(prvek je jeden objekt). Vnitřní implementace kontejneru může být jakákoli
a liší se podle toho, k čemu je tento kontejner určen.
V rámci CF rozeznáváme dvě skupiny těchto kolekcí - množiny a seznamy
(sekvence). Podívejme se na ně blíže:
-
Množiny (rozhraní
Set ) - každý prvek může být v kontejneru pouze jednou.
Není zde obdoba kontejneru multiset (s možností vícenásobné přítomnosti
téhož prvku) známého z STL v C++. Prvek obecně nemá určenu žádnou
polohu v množině, na základě které by k němu bylo možné přistupovat.
Zvláštním případem je podrozhraní SortedSet , což je seřazená množina.
-
Seznamy (rozhraní
List ) - prvek může být v kontejneru vícekrát a má určenu
jednoznačnou polohu (index).
Asociativní kolekce
Často jsme v situaci, kdy potřebujeme k datům přistupovat ne podle číselného
indexu, ale podle nějakého obecného klíče. Asociativní kolekce obsahují
prvky jakožto dvojice klíč-hodnota, kdy každý člen této dvojice může být
libovolného referenčního typu.
Obdobně jako u množin, ani zde nemáme něco jako multimap z STL. Ke každému
klíči může příslušet nejvýše jedna hodnota. Další shodná vlastnost
s rozhraním Set je existence seřazené varianty - je to rozhraní
SortedMap .
Obecné implementační třídy
Nyní přichází čas na bližší seznámení s různými implementací kolekcí.
Každá se hodí pro určitý způsob použití.
HashSet - množina s hešovací tabulkou
Třída HashSet používá pro ukládání dat hešovací tabulku. Práce je velice
rychlá (většina operací probíhá v konstatním čase), nezaručuje ale pořadí
prvků. Prázdné reference jsou povoleny.
HashSet můžeme použít prakticky vždy, kdy nepotřebujeme pracovat s prvky
v konkrétním pořadí (tj. ve většině případů).
Pro optimální výkon je důležité správně zvolit výchozí kapacitu kontejneru
- příliš velká způsobuje zbytečnou alokaci paměti a navíc i pomalejší
sekvenční přístup (přes iterátor). Proto je dobré před použitím promyslet,
kolik prvků se zde bude obvykle vyskytovat a podle toho kontejner nadimenzovat
(výchozí kapacita je 101 prvků).
Set s = new HashSet(); // vytvoření nové množiny
s.add(new Integer(15)); // vložíme postupně 3 prvky
s.add(new Integer(-2));
s.add(new Integer(123));
s.add(null); // vložíme null - je to povoleno
s.add(new Integer(-2)); // -2 už v množině je, podruhé se nevloží
System.out.println(s.size()); // vypíše počet prvků, tedy 4 (včetně hodnoty null)
TreeSet - množina se stromovou strukturou
Pro práci se seřazenými prvky použijeme třídu TreeSet . Protože je to seřazená
množina, implementuje rozhraní SortedSet . Většina operací se provádí
v logaritmickém čase.
Okruh použití TreeSet je vymezen potřebou seřazené množiny. Pokud vkládané
prvky neimplementují rozhraní Comparable , musíme pro ně definovat komparátor
(tj. implementovat rozhraní Comparator ) a předat ho konstruktoru objektu
TreeSet .
SortedSet s = new TreeSet(); // vytvoření množiny
s.add(new Double(12.3)); // vložíme 3 prvky
s.add(new Double(100.456));
s.add(new Double(-0.001));
System.out.println("První prvek je: " + s.first()); // vypíše první prvek
Iterator it = s.iterator(); // iterujeme přes všechny prvky
while (it.hasNext()) { // budou se vypisovat ve vzestupném pořadí
System.out.println(it.next());
}
ArrayList - pole proměnné velikosti
Podobně jako minule zmíněná třída Vector (o ní ještě bude řeč) poskytuje
i tato třída implementaci pole o proměnné délce. Přístup přes index je
v konstatním čase, přidávání na začátek nebo modifikace uvnitř posloupnosti
v čase lineárním.
ArrayList použijeme ve všech případech, kde bychom jinak použili běžné pole a
potřebujeme měnit délku. Výjimkou jsou případy, kdy velmi často dochází
k přidávání prvků na začátek nebo provádět změny uvnitř seznamu - tam
se ArrayList nehodí.
Následující příklad ukazuje podobnost práce s polem a s kontejnerem typu
ArrayList , a samozřejmě výhodu použití kontejneru:
String sa[] = new String[3]; // vytvoření pole pro 3 položky
sa[0] = ""; // vložíme do něj prvky
sa[1] = "abcd";
sa[2] = "123w";
// tady končíme - pole nejde zvětsit
List l = new ArrayList(3); // vytvoření seznamu pro 3 položky
l.add(0, ""); // vložení prvků
l.add(1, "abcd");
l.add(2, "123w");
// přidání dalších prvků - 1. varianta (vhodná pro přidání více prvků)
l.ensureCapacity(5); // zvětšíme kapacitu seznamu (zde na 5)
l.add(3, "tohle JDE"); // přidáme prvek
l.add(4, "pokračujeme"); // přidáme prvek...
// přidání dalších prvků - 2. varianta (vhodná pro jednotlivé prvky)
l.add("tohle také JDE"); // přidáme prvek
l.add("pokračujeme"); // přidáme prvek...
LinkedList - spojový seznam
Třída LinkedList představuje dobře známý spojový seznam prvků. Co to znamená,
lze snadno domyslet. Přístup podle indexové pozice je v lineárním čase,
zatímco přidávání a mazání probíhá konstantní rychlostí.
Doména použití třídy LinkedList je omezena na případy s častými modifikacemi
na jiných místech, než je konec seznamu.
Opět připomínám, i přes snadný přístup přes index prvků je při sekvenčním přístupu obecně mnohem
efektivnější používat iterátory (pro tuto třídu to platí dvojnásob).
HashMap - asocitativní kontejner s hešovací tabulkou
Obdoba HashSet pro asociativní přístup k hodnotám. Pro rychlost i paměťovou
náročnost platí to, co bylo řečeno u třídy HashSet . Prázdné (null ) klíče
a hodnoty jsou povoleny.
Použijeme prakticky ve všech případech, kdy nepotřebujeme seřazené klíče. Viz následující příklad
s příponami a MIME typy dat:
Map m = new HashMap(); // vytvoří se asociativní kontejner
m.put("txt", "text/plain"); // vložení dvojic klíč-hodnota
m.put("html", "text/html");
m.put("jpg", "image/jpeg");
m.put("mpg", "video/mpeg");
// nyní pomocí klíče přistupujeme k hodnotám
System.out.println("Přípona .jpg má typ: " + m.get("jpg")); // vypíše "image/jpeg"
System.out.println("Přípona .gif má typ: " + m.get("gif")); // vypíše "null" (nebylo nalezeno)
TreeMap - asocitativní kontejner se stromovou strukturou
Opět obdoba - tentokrát TreeSet . Oproti HashMap přináší seřazení klíčů.
Používá se při nutnosti mít seřazené klíče. Samozřejmostí je splnění
podmínek pro porovnatelnost klíčů (viz HashSet ).
Speciální implementační třídy
Jsou určité případy, kdy nám žádná z výše uvedených implementací nevyhoví.
Proto máme k dispozici ještě další implementace, s jejichž pomocí můžeme
dosáhnout požadovaných cílů. Podívejme se na některé z nich:
LinkedHashSet - zřetězená množina s hešovací tabulkou
Kompromis mezi HashSet a TreeSet , má zaručené pořadí prvků při rychlosti
řádově stejné jako HashSet . Prvky jsou uloženy v pořadí, v jakém se vkládají,
pokud ovšem nedojde k vícenásobnému vložení téhož prvku (prvek se znovu nevkládá,
zůstane na původní pozici). Je třeba si uvědomit, že
i když je zaručeno pořadí, nejedná se o seřazenou množinu a třída tedy
neimplementuje rozhraní SortedSet .
LinkedHashMap - zřetězený asociativní kontejner s hešovací tabulkou
Obdoba LinkedHashSet pro asociativní kontejner. Platí to, co bylo řečeno
u LinkedHashSet . Tato třída se hodí pro tvrobu různých pamětí s omezenou
asociativitou (LRU apod.).
K další příkladům speciálních implementací se dostaneme při seznámení
se změnami CF v Javě 5.0.
Zastaralé implementace
Již před vznikem CF existovaly v Javě určité implementace kontejnerů.
Protože zde přetrvávají i nadále, není od věci je také trochu prozkoumat.
Vector - pole proměnné velikosti
Chování třídy Vector je prakticky shodné s třídou ArrayList s drobnými
rozdíly. Vector má některé metody navíc (např. hledání od určitého indexu),
lze mu stanovit přírůstek kapacity a je synchronizovaný (viz dále).
V běžných případech dnes nemáme důvod používat Vector namísto třídy ArrayList .
Hashtable - hešovací tabulka
Chování obdobné jako u třídy HashMap , nepovoluje však prázdné klíče ani
hodnoty a má synchronizaci.
Wrappery pro práci s kolekcemi
Existuje více způsobů, jak měnit vlastnosti instancí nějakých objektových tříd
- lze to udělat např. parametry v konstruktoru nebo použitím tzv. wrapperů,
což jsou objekty, které se "předřadí" před původní objekty, převezmou
navenek jejich chování a přidají, odeberou či změní právě to, co je třeba.
Pro změnu chování kontejnerů tu takové wrappery máme.
Synchronizační wrappery
Všechny standardní implementace kolekcí jsou bez synchronizace vícenásobného
přístupu k objektu, takže se ve vícevláknovém prostředí může stát, že
k témuž objektu přistupuje více vláken současně a dojde k přečtení
nekonzistentních dat nebo k poškození objektu.
Máme-li záruku, že ke kolizi nemůže dojít (program je pouze jednovláknový),
není třeba zajišťovat synchronizaci. Na tento předpoklad ale nemůžeme
prakticky nikdy spoléhat (zejména pokud píšeme opakovaně použitelný kód) a
musíme synchronizaci zajistit buď explicitně (což je pracnější, přestože
výkonnostně efektivnější) nebo použít synchronizační wrapper. Netýká se to
"starých" kontejnerů Vector a Hashtable , ty mají
synchronizaci již v sobě.
Wrapper funguje prostě tak, že příslušné statické metodě z třídy
Collections (pozor, neplést s rozhraním Collection !)
předáme původní objekt a metoda nám vrátí wrapper k tomuto
objektu, který už pak používáme stejně jako původní objekt (zde je jasně
vidět, jak se hodí pracovat s rozhraním namísto konkrétní implementace).
Více ukáže příklad:
// typický příklad - nepotřebujeme nesynchronizovanou instanci
List l = Collections.synchronizedList(new LinkedList());
// pro speciální použití - zachovává přístup k původní instanci
SortedSet s1 = new TreeSet();
SortedSet s2 = Collections.synchronizedSortedSet(s1);
Wrappery pro zákaz modifikací
Někdy je třeba zakázat jakékoliv změny v kontejneru. Nejčastěji tehdy,
když z nějakého uceleného systému (který se stará o přípravu dat) předáváme
někam ven referenci na kontejner a nechceme, aby ho někdo zvenku měnil.
Způsob vytvoření wrapperu je obdobný jako v případě synchonizačního wrapperu,
opět je to zřejmé z příkladu. Pokus o modifikaci wrapperového kontejneru
způsobí výjimku UnsupportedOperationException . Původní kontejner
samozřejmě lze (přes referenci na něj) i nadále měnit.
// vytvoření seznamu
List lst = new ArrayList();
// příprava dat apod.
// nyní se vytvoří neměnná kolekce
Collection c = Collections.unmodifiableCollection(lst);
// pokus o změnu - vyvolá výjimku UnsupportedOperationException
c.add(new Object());
Praktická ukázka
Kdo by měl zájem, může si prohlédnout a vyzkoušet praktickou ukázku použití
kontejnerů. V souboru
PhoneBook.java
najdete velice jednoduchý program, který
ukazuje práci s kolekcemi na primitivním telefonním seznamu.
V Javě 5.0 došlo u kontejnerů k poměrně významným změnám - přibyla typová
bezpečnost, nová rozhraní a implementace atd. K tomu se dostaneme příště,
podobně jako k algoritmům, které mohou nad kolekcemi pracovat.
Verze pro tisk
|
Příspívat do diskuze mohou pouze registrovaní uživatelé.
|
|
Vyhledávání software
Vyhledávání článků
28.11.2018 23:56 /František Kučera Prosincový sraz spolku OpenAlt se koná ve středu 5.12.2018 od 16:00 na adrese Zikova 1903/4, Praha 6. Tentokrát navštívíme organizaci CESNET. Na programu jsou dvě přednášky: Distribuované úložiště Ceph (Michal Strnad) a Plně šifrovaný disk na moderním systému (Ondřej Caletka). Následně se přesuneme do některé z nedalekých restaurací, kde budeme pokračovat v diskusi.
Komentářů: 1
12.11.2018 21:28 /Redakce Linuxsoft.cz 22. listopadu 2018 se koná v Praze na Karlově náměstí již pátý ročník konference s tématem Datová centra pro business, která nabídne odpovědi na aktuální a často řešené otázky: Jaké jsou aktuální trendy v oblasti datových center a jak je optimálně využít pro vlastní prospěch? Jak si zajistit odpovídající služby datových center? Podle jakých kritérií vybírat dodavatele služeb? Jak volit vhodné součásti infrastruktury při budování či rozšiřování vlastního datového centra? Jak efektivně datové centrum spravovat? Jak co nejlépe eliminovat možná rizika? apod. Příznivci LinuxSoftu mohou při registraci uplatnit kód LIN350, který jim přinese zvýhodněné vstupné s 50% slevou.
Přidat komentář
6.11.2018 2:04 /František Kučera Říjnový pražský sraz spolku OpenAlt se koná v listopadu – již tento čtvrtek – 8. 11. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma umění a technologie, IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář
4.10.2018 21:30 /Ondřej Čečák LinuxDays 2018 již tento víkend, registrace je otevřená.
Přidat komentář
18.9.2018 23:30 /František Kučera Zářijový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 20. 9. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář
9.9.2018 14:15 /Redakce Linuxsoft.cz 20.9.2018 proběhne v pražském Kongresovém centru Vavruška konference Mobilní řešení pro business.
Návštěvníci si vyslechnou mimo jiné přednášky na témata: Nejdůležitější aktuální trendy v oblasti mobilních technologií, správa a zabezpečení mobilních zařízení ve firmách, jak mobilně přistupovat k informačnímu systému firmy, kdy se vyplatí používat odolná mobilní zařízení nebo jak zabezpečit mobilní komunikaci.
Přidat komentář
12.8.2018 16:58 /František Kučera Srpnový pražský sraz spolku OpenAlt se koná ve čtvrtek – 16. 8. 2018 od 19:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát jsou tématem srazu databáze prezentaci svého projektu si pro nás připravil Standa Dzik. Dále bude prostor, abychom probrali nápady na využití IoT a sítě The Things Network, případně další témata.
Přidat komentář
16.7.2018 1:05 /František Kučera Červencový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 19. 7. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát bude přednáška na téma: automatizační nástroj Ansible, kterou si připravil Martin Vicián.
Přidat komentář
Více ...
Přidat zprávičku
Poslední diskuze
31.7.2023 14:13 /
Linda Graham iPhone Services
30.11.2022 9:32 /
Kyle McDermott Hosting download unavailable
13.12.2018 10:57 /
Jan Mareš Re: zavináč
2.12.2018 23:56 /
František Kučera Sraz
5.10.2018 17:12 /
Jakub Kuljovsky Re: Jaký kurz a software by jste doporučili pro začínajcího kodéra?
Více ...
|