Dlaczego implementacja HashSet w Sun Java używa HashMap jako swojego zaplecza?

Patrząc na źródło Java 6, HashSet<E> jest faktycznie zaimplementowane przy użyciu HashMap<E,Object>, używając atrapy obiektu na każdym wpisie zestawu.

Myślę, że marnuje to 4 bajty (na maszynach 32-bitowych) dla rozmiaru samego wpisu.

Ale dlaczego jest nadal używany? Czy jest jakiś powód, aby go używać poza ułatwieniem utrzymania kodów?

Author: Bakuriu, 2010-02-10

7 answers

Właściwie to nie tylko HashSet. wszystkie implementacje interfejsu Set w Javie 6 są oparte na bazowym interfejsie Map. To nie jest wymóg, to po prostu sposób wdrożenia. Możesz zobaczyć na własne oczy, sprawdzając dokumentację dla różnych implementacji Set.

Twoje główne pytania to

Ale dlaczego jest nadal używany? Czy jest każdy powód, aby go używać, poza tym, że łatwiejsze w utrzymaniu kody?

Zakładam, że konserwacja kodu jest dużym czynnikiem motywującym. Tak samo zapobiega duplikacji i wzdęcia.

Set i {[2] } są podobnymi interfejsami, ponieważ duplikaty elementów nie są dozwolone. (Myślę, że jedynym Set Nie wspierany przez Map jest CopyOnWriteArraySet, który jest niezwykłym zbiorem, ponieważ jest niezmienny.)

Konkretnie:

Z dokumentacji Set:

Zbiór, który nie zawiera duplikat żywioły. Bardziej formalnie, zestawy nie zawierają pary elementów e1 i e2 takie, że e1.równa się (e2), a przy większość jednego elementu null. Jak wynika z jego nazwę, interfejs ten modeluje abstrakcja zbioru matematycznego.

Interfejs zestawu umieszcza dodatkowe postanowienia, poza tymi odziedziczonymi z interfejsu kolekcji, na Kontrakty wszystkich konstruktorów i na umowy add, równa się i metody hashCode. Deklaracje dla innymi metodami dziedziczenia są również zawarte tutaj dla wygody. (The specyfikacje towarzyszące tym deklaracje zostały dostosowane do Ustawić interfejs, ale nie zawierają wszelkie dodatkowe postanowienia.)

Dodatkowe postanowienie o Konstruktorów jest, nic dziwnego,, że wszyscy konstruktorzy muszą stworzyć zestaw, który nie zawiera duplikatów elementy (jak zdefiniowano powyżej).

I od Map:

Obiekt, który mapuje Klucze do wartości. Mapa nie może zawiera zduplikowane klucze; każdy klucz może mapować co najwyżej jedną wartość.

Jeśli możesz zaimplementować swoje Set s przy użyciu istniejącego kodu, każda korzyść (na przykład szybkość), którą możesz zrealizować z istniejącego kodu, narasta również do twojego Set.

Jeśli zdecydujesz się zaimplementować Set bez podkładu Map, musisz zduplikować kod zaprojektowany, aby zapobiec duplikowaniu elementów. Pyszna ironia.

To powiedziawszy, nic nie stoi na przeszkodzie, aby wdrożyć swoje Sets inaczej.

 17
Author: JXG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-30 12:26:53

Zgaduję, że nigdy nie pojawił się jako istotny problem dla prawdziwych aplikacji lub ważnych benchmarków. Po co komplikować kod bez realnych korzyści?

Zauważ również, że rozmiary obiektów są zaokrąglane w górę w wielu implementacjach JVM, więc w rzeczywistości może nie być zwiększenia rozmiaru (Nie wiem na ten przykład). Również kod HashMap może być skompilowany i w pamięci podręcznej. Inne rzeczy są równe, więcej kodu = > więcej błędów pamięci podręcznej = > niższa wydajność.

 4
Author: Tom Hawtin - tackline,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-21 08:26:49

Domyślam się, że HashSet został pierwotnie zaimplementowany w kategoriach HashMap, aby zrobić to szybko i łatwo. Jeśli chodzi o linie kodu, HashSet jest ułamkiem HashMap.

Domyślam się, że powodem, dla którego wciąż nie został zoptymalizowany, jest strach przed zmianą.

Jednak marnotrawstwo jest znacznie gorsze niż myślisz. Zarówno w wersji 32-bitowej, jak i 64-bitowej HashSet jest 4x większy niż jest to konieczne, a HashMap 2x większy niż jest to konieczne. HashMap może być zaimplementowany tablicą z kluczami i wartości w nim (Plus łańcuchy dla kolizji). Oznacza to dwa wskaźniki na wpis lub 16 bajtów na 64-bitowej maszynie wirtualnej. W rzeczywistości HashMap zawiera obiekt Entry dla każdego wpisu, który dodaje 8 bajtów dla wskaźnika do wpisu i 8 bajtów dla nagłówka obiektu Entry. HashSet używa również 32 bajtów na element, ale strata wynosi 4x zamiast 2x, ponieważ wymaga tylko 8 bajtów na element.

 4
Author: Craig P. Motlin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-07 16:48:37

Tak masz rację, mała ilość marnotrawstwa jest tam definitywnie. Mały, ponieważ dla każdego wpisu używa tego samego obiektu PRESENT (który jest zadeklarowany jako końcowy). Dlatego jedynym marnotrawstwem jest wartość każdego wpisu w Hashmapie.

Głównie myślę, że przyjęły takie podejście do konserwacji i ponownego użycia. (Programiści JCF pomyśleli, że i tak przetestowaliśmy HashMap, dlaczego nie użyć go ponownie.)

Ale jeśli masz ogromne zbiory i jesteś maniakiem pamięci, możesz wybierz lepsze alternatywy, takie jak Trove lub Google Collections .
 3
Author: Suraj Chandran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-10 09:50:22

Spojrzałem na twoje pytanie i trochę mi zajęło przemyślenie tego, co powiedziałeś. Oto moja opinia odnośnie implementacji HashSet.

Jest konieczne, aby instancja atrapy wiedziała, czy wartość jest lub nie jest obecna w zestawie.

Spójrz na metodę dodawania

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

Abd przyjrzyjmy się teraz zwracanej wartości put

@ zwraca poprzednią wartość skojarzoną z key lub null, Jeśli dla key nie było mapowania. (Null return can zaznacz również, że mapa wcześniej powiązana z NULL Z key.)

Tak więc obiekt PRESENT jest po prostu używany do reprezentowania, że zbiór zawiera wartość e. Myślę, że zapytałeś dlaczego nie użyć null zamiast PRESENT. Ale, nie będziesz w stanie odróżnić, czy wpis był wcześniej na mapie, ponieważ map.put(key,value) zawsze zwróci null i nie będziesz miał sposobu, aby wiedzieć, czy klucz istnieje.


To powiedziawszy można argumentować, że mogli użyć implementacji like this

   public boolean add(E e) {

        if( map.containsKey(e) ) {
            return false;
        }

        map.put(e, null);

        return true;

}

Myślę, że marnują 4 bajty, aby uniknąć obliczenia hashCode, ponieważ może to być drogie, klucza dwa razy (jeśli klucz ma być dodany).


Jeśli masz pytanie, dlaczego użyli HashMap, który marnuje 8 bajtów (z powodu Map.Entry) zamiast innej struktury danych, używając podobnego wpisu tylko 4, to tak, powiedziałbym, że zrobili to z powodów, o których wspomniałeś.

 3
Author: Lombo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-10 10:26:52

Po przeszukaniu takich stron zastanawiając się, dlaczego lekko nieefektywna implementacja standardu znalazła com.carrotsearch.hppc.IntOpenHashSet

 0
Author: clwhisk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-18 19:44:31

Twoje pytanie: Myślę, że marnuje to 4 bajty (na maszynach 32-bitowych) dla rozmiaru samego wpisu.

Tylko jedna zmienna obiektowa jest tworzona dla całej struktury danych hashset i dzięki temu unikniesz ponownego pisania całego kodu hashMap.

private static final Object PRESENT = new Object();

Wszystkie klucze mają jedną wartość tj. PRESENT object.

 -3
Author: Srujan Kumar Gulla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-19 21:42:48