Heim >Java >javaLernprogramm >Java-Programmiergedanken-Lernklasse (4) Kapitel 17 – Ausführliche Diskussion von Containern
1 Satz- und Speicherreihenfolge
Das zu Set
hinzugefügte Element muss sein definierteequals()
Methode, um die Einzigartigkeit des Objekts sicherzustellen.
hashCode()
ist nur erforderlich, wenn diese Klasse in HashSet
oder LinkedHashSet
platziert ist. Aus Gründen eines guten Programmierstils sollten Sie jedoch immer die Methode hashCode() überschreiben, wenn Sie die Methode equal() überschreiben.
Wenn ein Objekt in einem beliebigen sortierten Container verwendet wird, z. B. SortedSet
(TreeSet
ist die einzige Implementierung von it) , dann muss es die Schnittstelle Comparable
implementieren.
Beachten Sie, dass SortedSet
„Elemente nach der Vergleichsfunktion des Objekts sortieren“ bedeutet, obwohl dies der Fall ist beziehen sich nicht auf „die Reihenfolge, in der Elemente eingefügt werden“. Einfügungsauftrag wird mit LinkedHashSet
gespeichert.
stellt gleichzeitige Anwendungen in die Warteschlange. Die einzigen zwei Implementierungen von Queue in Java SE5 sind LinkiedList
und PriorityQueue
, sie unterscheiden sich nur im Sortierverhalten und es gibt keinen Unterschied in der Leistung.
Die Reihenfolge der Prioritätswarteschlange PriorityQueue
wird auch durch die Implementierung von Comparable
gesteuert.
Kartentabelle (auch bekannt als Assoziatives ArrayAssoziativ Array).
HashMap
verwendet einen speziellen Wert, der als Hash-Code (Hash-Code) bezeichnet wird, um den Schlüssel „Suche langsam“ zu ersetzen. Ein Hash-Code ist ein „relativ eindeutiger“ Wert, der zur Darstellung eines Objekts verwendet wird. Er wird durch die Umwandlung int
bestimmter Informationen über das Objekt erreicht und generiert. hashCode() ist eine Methode in der Stammklasse Object, sodass alle Objekte Hash-Codes generieren können.
equals()
haben Methoden;
hashCode()
-Methode verfügen >Wenn ein Schlüssel für
TreeMap
implementieren. Comparable
4 Hash und Hash-Code
Die Standardeinstellung Object.equals() vergleicht nur die Adresse des Objekts equals()
. Wenn Sie Ihre eigene Klasse als Schlüssel von HashMap
verwenden möchten, müssen Sie sowohl als auch gleichzeitig überschreiben. hashCode()
Die richtige equals()
Methode muss die folgenden fünf Bedingungen erfüllen: equals()
Reflexivität.
Symmetrie.
Transitivität.
Konsistenz.
Für jedes
, das nicht null ist, mussx
x.equals(null)
false
4.1 Hash-Konzept
die Abfragegeschwindigkeit zu erhöhen
. Der Wert von Hashing liegt in der Geschwindigkeit
Hashing macht Abfragen schnell. Da der Engpass bei Abfragegeschwindigkeit liegt, besteht eine der Lösungen darin, die Schlüssel sortiert zu halten und dann für die Abfrage zu verwenden.
Collections.binarySearch()
Hashing geht noch einen Schritt weiter und speichert den Schlüssel irgendwo, damitschnell gefunden werden kann. Die schnellste Datenstruktur zum Speichern einer Reihe von Elementen ist ein Array. Verwenden Sie es daher zur Darstellung der Schlüsselinformationen (beachten Sie sorgfältig, dass ich die Schlüsselinformationen meine, nicht den Schlüssel selbst). Da das Array seine Kapazität jedoch nicht anpassen kann, gibt es ein Problem: Wir möchten eine unbestimmte Anzahl von Werten in der Map speichern, aber was ist, wenn die Anzahl der Schlüssel durch die Kapazität des Arrays begrenzt ist?
Die Antwort lautet: Arrays speichern nicht die Schlüssel selbst. Stattdessen wird aus dem Schlüsselobjekt eine Zahl als -Index des Arrays generiert. Diese Zahl ist der Hash-Code, dargestellt durch die Methode
hashCode()
, die in Object definiert und möglicherweise von Ihrer Klasse überschrieben wird (in der Informatiksprache genannt). Hash-Funktion ) wird generiert.Um das Problem der festen Array-Kapazität zu lösen, können verschiedene Schlüssel denselben Index erzeugen. Das heißt, es kann zu Kollisionen kommen, d. h. Hashcodes müssen nicht eindeutig sein. Daher spielt es keine Rolle, wie groß das Array ist, jeder Schlüssel findet immer seinen Platz im Array.
Zusammenfassend besteht Hash darin, eine Zahl aus einem Objekt zu generieren und diese (als unteren Teil) zu speichern des Arrays) (Standard) und finden Sie dann einfach direkt die Nummer, wenn Sie nach diesem Objekt suchen. Der Zweck des Hashings besteht also darin, die Suchgeschwindigkeit zu verbessern, und das Mittel darin Kombinieren Sie die von einem Objekt generierte Zahl mit der zugehörigen und gespeicherten Zahl (über ein Array, das als Hash-Tabelle bezeichnet wird). Die generierte Zahl ist der Hash-Code . Die Methode zur Generierung dieses Hash-Codes heißt Hash-Funktion (hashCode()
).
Daher ist der Prozess der Abfrage eines HashMap
in key
:
Zuerst berechnen der Hash-Wert Spaltencode
Dann verwenden Sie den Hash-Code, um das Array abzufragen (der Hash-Code wird als variabler Array-Index verwendet)
Wenn kein Konflikt vorliegt, wird dieser generiert. Es gibt nur ein Hash-Code-Objekt, dann ist die Position des Array-Index, der dem Hash-Code entspricht, das zu findende Element
Wenn ja Bei einem Konflikt befindet sich der Array-Index, der dem Hash-Code entspricht. Das Element speichert ein list
und führt dann mit der list
-Methode eine lineare Abfrage für den Wert in equals()
durch.
Anstatt also das gesamte list
abzufragen, springt schnell zu einer bestimmten Position im Array und vergleicht nur sehr wenige Elemente . Deshalb HashMap
warum es so schnell ist.
Der Slot (Slot) in der Hash-Tabelle wird normalerweise als Bucket-Position(Bucket)
Primzahl (Es ist eine Primzahl in JDK5 und In JDK7 Powered ist es bereits eine Ganzzahl von 2 ).
2 hoch . Division und Rest sind die langsamsten Operationen auf modernen Prozessoren. Bei Verwendung einer Hash-Tabelle mit einer ganzzahligen Potenz von 2 der Länge kann die -Maske anstelle der Division verwendet werden. Da get() die am häufigsten verwendete Operation ist, ist die %-Operation zum Ermitteln des Restes der teuerste Teil, und die Verwendung der ganzzahligen Potenz von 2 kann diesen Overhead eliminieren (dies kann auch Auswirkungen auf hashCode() haben).
Gleiches berechnen können Standort .
package net.mrliuli.containers; import java.util.*;public class SimpleHashMap<K, V> extends AbstractMap<K, V> { // Choose a prime number for the hash table size, to achieve a uniform distribution: static final int SIZE = 997; // You can't have a physical array of generics, but you can upcast to one: @SuppressWarnings("unchecked") LinkedList<MapEntry<K,V>>[] buckets = new LinkedList[SIZE]; @Override public V put(K key, V value){ int index = Math.abs(key.hashCode()) % SIZE; if(buckets[index] == null){ buckets[index] = new LinkedList<MapEntry<K,V>>(); } LinkedList<MapEntry<K,V>> bucket = buckets[index]; MapEntry<K,V> pair = new MapEntry<K,V>(key, value); boolean found = false; V oldValue = null; ListIterator<MapEntry<K,V>> it = bucket.listIterator(); while(it.hasNext()){ MapEntry<K,V> iPair = it.next(); if(iPair.equals(key)){ oldValue = iPair.getValue(); it.set(pair); // Replace old with new found = true; break; } } if(!found){ buckets[index].add(pair); } return oldValue; } @Override public V get(Object key){ int index = Math.abs(key.hashCode()) % SIZE; if(buckets[index] == null) return null; for(MapEntry<K,V> iPair : buckets[index]){ if(iPair.getKey().equals(key)){ return iPair.getValue(); } } return null; } @Override public Set<Map.Entry<K,V>> entrySet(){ Set<Map.Entry<K,V>> set = new HashSet<Map.Entry<K, V>>(); for(LinkedList<MapEntry<K,V>> bucket : buckets){ if(bucket == null) continue; for(MapEntry<K,V> mpair : bucket){ set.add(mpair); } } return set; } public static void main(String[] args){ SimpleHashMap<String, String> m = new SimpleHashMap<String, String>(); for(String s : "to be or not to be is a question".split(" ")){ m.put(s, s); System.out.println(m); } System.out.println(m); System.out.println(m.get("be")); System.out.println(m.entrySet()); } }4.5 Überschreiben von hashCode() Beim Entwerfen von „hashCode()“ zu berücksichtigende Faktoren:
Der Aufruf von hashCode() für dasselbe Phasenobjekt sollte denselben Wert erzeugen, wann immer .
aussagekräftigen Identifikationsinformationen innerhalb des Objekts verwendet werden. Das heißt, es muss einen Hash-Code basierend auf dem Inhalt des Objekts generieren.
„Effective Java™ Programming Language Guide (Addison-Wesley, 2001)“ gibt eine grundlegende Anleitung zum Schreiben eines anständigen hashCode():
-Variable int
eine Konstante mit einem Wert ungleich Null zu, z. B. result
17
为对象内每个有意义的域f
(即每个可以做equals()
操作的域)计算出一个int
散列码c
:
域类型 | 计算 |
---|---|
boolean | c=(f?0:1) |
byte、char、short或int | c=(int)f |
long | c=(int)(f^(f>>>32)) |
float | c=Float.floatToIntBits(f); |
double | long l = Double.doubleToLongBits(f); |
Object,其equals()调用这个域的equals() | c=f.hashCode() |
数组 | 对每个元素应用上述规则 |
3. 合并计算散列码:result = 37 * result + c;
4. 返回result。
5. 检查hashCode()
最后生成的结果,确保相同的对象有相同的散列码。
已证明
0.0
是包含在Math.random()
的输出中的,按照数学术语,即其范围是[0,1)
。
HashMap中的一些术语:
容量(Capacity):表中的桶位数(The number of buckets in the table)。
初始容量(Initial capacity):表在创建时所拥有的桶位数。HashMap
和HashSet
都具有允许你指定初始容量的构造器。
尺寸(Size):表中当前存储的项数。
负载因子(Loadfactor):尺寸/容量。空表的负载因子是0
,而半满表的负载因子是0.5
,依此类推。负载轻的表产生冲突的可能性小,因此对于插入和查找都是最理想的(但是会减慢使用迭代器进行遍历的过程)。HashMap
和HashSet
都具有允许你指定负载因子的构造器,表示当负载情况达到该负载的水平时,容器将自动增加其容量(桶位数),实现方式是使容量大致加倍,并重新将现有对象分布到新的桶位集中(这被称为再散列)。
HashMap
使用的默认负载因子是0.75
(只有当表达到四分之三满时,才进行再散列),这个因子在时间和空间代价之间达到了平衡。更高的负载因子可以降低表所需的空间,但会增加查找代价,这很重要,因为查找是我们在大多数时间里所做的操作(包括get()
和put()
)。
Collections类有办法能够自动同步整个容器。其语法与“不可修改的”方法相似:
package net.mrliuli.containers; import java.util.*;public class Synchronization { public static void main(String[] args){ Collection<String> c = Collections.synchronizedCollection(new ArrayList<String>()); List<String> list = Collections.synchronizedList(new ArrayList<String>()); Set<String> s = Collections.synchronizedSet(new HashSet<String>()); Set<String> ss = Collections.synchronizedSortedSet(new TreeSet<String>()); Map<String, String> m = Collections.synchronizedMap(new HashMap<String, String>()); Map<String, String> sm = Collections.synchronizedSortedMap(new TreeMap<String, String>()); } }
Java容器有一种保护机制能够防止多个进行同时修改同一个容器的内容。Java容器类类库采用快速报错(fail-fast)机制。它会探查容器上的任何除了你的进程所进行的操作以外的所有变化,一旦它发现其他进程修改了容器,就会立刻抛出ConcurrentModificationException
异常。这就是“快速报错”的意思——即,不是使用复杂的算法在事后来检查问题。
package net.mrliuli.containers; import java.util.*;public class FailFast { public static void main(String[] args){ Collection<String> c = new ArrayList<>(); Iterator<String> it = c.iterator(); c.add("An Object"); try{ String s = it.next(); }catch(ConcurrentModificationException e){ System.out.println(e); } } }
相关文章:
Das obige ist der detaillierte Inhalt vonJava-Programmiergedanken-Lernklasse (4) Kapitel 17 – Ausführliche Diskussion von Containern. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!