LINUXSOFT.cz Přeskoč levou lištu

ARCHIV



   

> 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 79515×

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

pridej.cz

 

DISKUZE

"Kolekce.java": cannot find symbol; symbol : method ensureCapacity(int), location: interface java.util.List at line 87, column 11 18.7.2006 14:17 Martin Landa
ArrayList - pole proměnné velikosti 19.7.2006 18:28 Martin Landa




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 ...

ISSN 1801-3805 | Provozovatel: Pavel Kysilka, IČ: 72868490 (2003-2024) | mail at linuxsoft dot cz | Design: www.megadesign.cz | Textová verze