The standard library contains the basic implementations of several maps, including: HashMap, TreeMap, Linkedhashmap, Weekhashmap, Concurrenthashmap, and Identityhashmap. They all have the same basic interface map, but the behavior characteristics are different, which is manifested in efficiency, the preservation and rendering order of key-value pairs, the preservation period of objects, how the mapping table works in multi-threaded programs and the strategy of determining "key" equivalence.
Map can map keys to values. A map cannot contain duplicate keys, and each key can be mapped to at most one value. The map interface provides three collection views that allow you to view the contents of a map in the form of a keyset, value set, or key-value mapping relationship set. The mapping order is defined as the order in which iterators return their elements on the mapped collection view. Some mapping implementations can explicitly guarantee their order, such as the TreeMap class, while others do not guarantee the order, such as the HashMap class.
The HashMap in these maps is the most efficient map,linkedhashmap of the query is only slower than HashMap, but it can traverse the keyword faster, treemap keywords are sorted, so you can output sequentially.
hashmap* |
Map is based on the implementation of the hash table (it replaces the Hashtable). The cost of inserting and querying "key-value pairs" is fixed. You can set capacity and load factors through the constructor to adjust the performance of the container. |
Linkedhashmap |
Similar to HashMap, but when iterating through it, the order in which to get "key-value pairs" is its insertion order, or the least recently used (LRU) order. It is only a bit slower than hashmap, but faster when iterating, because it uses the list to maintain the internal order. |
TreeMap |
Based on the implementation of red-black trees. When you look at key or key-value pairs, they are sorted (the order is determined by comparable or comparator). TreeMap is characterized by the fact that the resulting result is sorted, and TreeMap is the only map with Submap method, which can return a subtree. |
Weekhashmap |
Weak key (week key) mapping, which allows you to release the object that the map points to, which is designed to solve some particular class of problems. If there is no reference outside the map to a key, the key can be reclaimed by the garbage collector. |
Concurrenthashmap |
A thread-safe map that does not involve synchronous locking. |
Identityhashmap |
Hash maps that use = = instead of equals to compare "keys" are designed to solve special problems. |
(part of the main) method in the Map interface
ContainsKey (Object Key): Returns TRUE if this map contains a mapping relationship for the specified key;
Containsvalue (Object value): Returns TRUE if the map maps one or more keys to the specified value;
EntrySet (): Returns the Set view of the mapping relationships contained in this map;
Get (Object Key): Returns the value mapped by the specified key, or null if the mapping does not contain a mapping of the key;
KeySet (): Returns the Set view of the keys contained in this map;
Put (K key, V value): Associates the specified value with the specified key in this map (optional operation).
The ABSTRACTMAP provides a backbone implementation of the map interface to minimize the work required to implement the map interface. To implement a non-modifiable mapping, the programmer simply extends this class and provides the implementation of the EntrySet method, which returns a mapped set view of mappings. Typically, the returned set is implemented sequentially on Abstractset. This set does not support the Add or remove methods, and its iterators do not support the Remove method. To implement a modifiable mapping, the programmer must override the put method of this class (otherwise it will throw unsupportedoperationexception), and EntrySet (). iterator () The returned iterator must also implement its Remove method in addition.
HashMap and its Realization method
HashMap is implemented based on a hash table, which implements the map interface, while allowing NULL to key and NULL to value (except for non-synchronous and null, the HashMap class is roughly the same as Hashtable). HashMap does not guarantee the order of the mappings, especially because it does not guarantee that the order is constant.
It is assumed that the hash function distributes elements appropriately between buckets, providing stable performance for basic operations (get and put). The time required to iterate the collection view is proportional to the capacity of the HashMap instance (the number of buckets) and its size (number of key-value mappings). Therefore, if iteration performance is important, do not set the initial capacity too high (or set the load factor too low).
An instance of HashMap has two parameters that affect its performance: initial capacity and load factor. Capacity is the number of buckets in the hash table, and the initial capacity is just the capacity at the time of creation of the Hashtable. A load factor is a scale in which a hash table can reach a full amount before its capacity increases automatically. When the number of entries in the hash table exceeds the product of the load factor with the current capacity, the Hashtable is rehash (that is, rebuilding the internal data structure) so that the Hashtable will have about twice times the number of buckets. When setting the initial capacity, you should take into account the number of entries required in the mapping and their loading factors to minimize the number of rehash operations.
Data structure of HashMap
The initial capacity and load factor when comparing two important parameters in HashMap:
Public HashMap (int initialcapacity, float loadfactor);
After the construction is completed, the Loadfactor will be recorded, the initialcapacity will become the minimum number of 2, and the Loadfactor multiplied by the threshold:
Find a power of 2 >= initialcapacity
int capacity = 1;
while (Capacity < initialcapacity)
Capacity <<= 1;
This.loadfactor = Loadfactor;
threshold = (int) math.min (capacity * Loadfactor, maximum_capacity + 1);
Table = new Entry[capacity];
All the elements of this hashmap are placed in an array of entry, entry equivalent to the "bucket" in the hash table (which is only called the entry array in the HashMap, not the entry in the list), and its interior contains the key, Value and a pointer to the next and previous entry.
This "bucket" is the table array in HashMap:
Transient entry<k,v>[] table;
The Get method of HashMap is actually to find a entry, the entry key is equal to the given object:
Public V get (Object key) { if (key = = null) return Getfornullkey (); entry<k,v> Entry = Getentry (key); return NULL = = entry? Null:entry.getValue ();} Final entry<k,v> getentry (Object key) {//Based on key lookup Entry int hash = (key = = null)? 0:hash (key);//Find the hash value and determine the position of the bucket C5/>for (entry<k,v> e = table[indexfor (hash, table.length)];//finds the first element in the bucket and determines whether E! = is null in the bucket ; E = e.next) { Object k; if (E.hash = = Hash &&//Determine if the element in the bucket is the element to be added (k = e.key) = = Key | | (Key! = null && key.equals (k) ))) return e; } return null;}
The Put method is to put the data into a specific entry:
Public V put (K key, V value) {if (key = = null) return Putfornullkey (value); int hash = hash (key); int i = indexfor (hash, table.length);//Calculate position for (entry<k,v> e = table[i]; e = null; e = e.next) {//Determine if the current bucket is Contains the element (to determine if the key already exists) Object K; if (E.hash = = Hash && (k = e.key) = = Key | | key.equals (k))) {V oldValue = E.value; E.value = value; E.recordaccess (this); return oldValue; }} modcount++; AddEntry (hash, key, value, I); return null;} void AddEntry (int hash, K key, V value, int bucketindex) {if (size >= threshold) && (null! = Table[buc Ketindex]) {//Prevent space shortage for rehash resize (2 * table.length); hash = (Null! = key)? Hash (key): 0; Bucketindex = Indexfor (hash, table.length); } createentry (hash, key, value, bucketindex);//Insert node}void createentry (int hash, K Key, V value, int bucketindex) {entry<k,v> e = table[bucketindex];//Saves the current bucketindex element table[bucket Index] = new entry<> (hash, key, value, e);//sets the current Bucketindex element, and sets the original element E, set as the successor of the new Element (the head interpolation method of the list, the new element inserts the head) size++;}
When you use Indexfor to find the subscript in the put, you need to be aware that the element in the current bucket already exists for the given key, then you need to traverse all the elements in the bucket, and then when you determine the element that does not have a given key, you can insert the current given element into the first position of the bucket (the other elements move back)
When doing a put operation, there may be insufficient space in the bucket (that is, the size is larger than threshold, when the likelihood of a conflict is very large), you need to rehash once, the space becomes twice times the current space (that is, resize (2 * table.length)), Then move all the buckets into the new bucket:
void Resize (int newcapacity) {entry[] oldtable = table; int oldcapacity = Oldtable.length; if (oldcapacity = = maximum_capacity) {threshold = Integer.max_value; Return } entry[] newtable = new Entry[newcapacity]; Boolean oldalthashing = usealthashing; Usealthashing |= sun.misc.VM.isBooted () && (newcapacity >= holder.alternative_hashing_threshold) ; Boolean rehash = oldalthashing ^ usealthashing; Transfer (newtable, rehash);//The transfer operation is performed here. Table = newtable; threshold = (int) math.min (newcapacity * loadfactor, maximum_capacity + 1);} void Transfer (entry[] newtable, Boolean rehash) {int newcapacity = newtable.length; for (entry<k,v> e:table) {//Moves the bucket in the element while (null! = e) {entry<k,v> next = E.next; if (rehash) {E.hash = NULL = = E.key? 0:hash (E.key); }int i = indexfor (E.hash, newcapacity); E.next = newtable[i];//still uses the head interpolation method, will cause the element in the barrel to reverse newtable[i] = e; e = next; } }}
The same is true for the delete operation, where the corresponding bucket is found, then the elements in the bucket are traversed and deleted after the element is found:
Public V Remove (Object key) {entry<k,v> E = Removeentryforkey (key); return (E = = null? null:e.value);} Final entry<k,v> Removeentryforkey (Object key) {int hash = (key = = null)? 0:hash (key); int i = indexfor (hash, table.length); Entry<k,v> prev = table[i]; entry<k,v> e = prev; while (E! = null) {//iterates over the elements in the bucket entry<k,v> next = e.next; Object K; if (E.hash = = Hash &&//Check to target element (k = e.key) = = Key | | (Key! = null && key.equals (k)))) {modcount++; size--; if (prev = = e)//The first element is the target element table[i] = next; else//the first element is not a target element prev.next = next; E.recordremoval (this); return e; } prev = e; e = next; } return E;
TreeMap and its implementation (the implementation of TREEMAP depends on the implementation of red and black trees)
TreeMap is based on the NAVIGABLEMAP implementation of the red-black tree (Red-black). The mapping is sorted according to the natural order of its keys, or sorted based on the Comparator provided when the mapping was created, depending on the construction method used. It ensures that the time overhead for ContainsKey, get, put, and remove operations is log (n). The red-black tree algorithm is implemented based on the red-black tree algorithm in the introduction of the algorithm.
The roots of the red and black trees are preserved in the TreeMap:
private transient entry<k,v> root = null;
When you want to insert data using the Put method, the data is inserted into a particular location according to the red-black tree's insertion algorithm, because the red-black tree itself is a two-fork sort tree, so you can find the target location according to the size of the node, insert the current position, and then maintain the structure of the red and black tree so that its structure
When you use the Get method to get data, the elements from the root to the leaf node are compared according to the rules of the binary sort tree until the target element is found or cannot be reached:
Public V get (Object key) { entry<k,v> p = getentry (key); Return (P==null null:p.value);} Final entry<k,v> getentry (Object key) { //Offload comparator-based version for sake of performance if (comp Arator! = null) return Getentryusingcomparator (key); if (key = = null) throw new NullPointerException (); comparable<? Super k> K = (comparable<? super k>) key; entry<k,v> p = root; while (P! = null) { int cmp = K.compareto (p.key); if (CMP < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null;}
The ContainsKey (Object) method is consistent with the Get method and also uses Getentry to find the target element.
The Remove (Object) method is accomplished by finding the node in the red-black tree, then deleting the node, and maintaining the characteristics of the red-black tree.
For TreeMap, it is important to traverse the method, whose traversal method is implemented by Entryiterator, which inherits from Privateentryiterator. It can be found in the privateentryiterator that it uses the algorithm of the red-black tree to seek the precursor of the current node (the largest element smaller than the current element) and the successor (larger and smallest element than the current element) to traverse.
Linkedhashmap and its Realization method
Linkedhashmap is an implementation of the map interface based on a hash table and a linked list, which preserves the order in which the data is inserted into the list (using the additional HashMap list). The difference between this implementation and HASHMAP is that the latter maintains a double-link list that runs on all items. This list of links defines the order of iterations, which is usually the order in which the keys are inserted into the map (in order of insertion). Note that if you reinsert the key in the map, the insertion order is not affected. (If M.containskey (k) returns true before calling M.put (K, v), the key k is reinserted into the mapping m when called. )
Note: Although the lists and buckets are drawn separately, the nodes in the list (except the header) are actually shared.
Linkedhashmap inherits from HashMap, so it is slightly worse than HashMap, but can maintain the insertion order between elements (using a doubly linked list to save the order):
Private transient entry<k,v> header;private static class Entry<k,v> extends Hashmap.entry<k,v> { These fields comprise the doubly linked list used for iteration. Entry<k,v> before, after;.......//omitted}
When the put method is called to insert an element, the put method of HashMap is called, and the method calls the AddEntry () method, which is redefined in Linkedhashmap:
Linkedhashmap AddEntry method void AddEntry (int hash, K key, V value, int bucketindex) {super.addentry (hash, key, Valu E, Bucketindex);//Call HashMap in the AddEntry method, the node is created, and the newly created node is maintained in the doubly linked list//Remove eldest entry if instructed ENTRY&L T k,v> eldest = Header.after; if (Removeeldestentry (eldest)) {Removeentryforkey (Eldest.key); AddEntry method in}}//hashmap void addentry (int hash, K key, V value, int bucketindex) {if (size >= threshold) && Amp (Null! = Table[bucketindex])) {Resize (2 * table.length); hash = (Null! = key)? Hash (key): 0; Bucketindex = Indexfor (hash, table.length); } createentry (hash, key, value, Bucketindex);} Createentry in Linkedhashmap, covering createentryvoid createentry in HashMap (int hash, K key, V value, int bucketindex) {Ha shmap.entry<k,v> old = Table[bucketindex]; entry<k,v> e = new entry<> (hash, key, value, old); Table[bucketindex] = e; E.addbefore (header); size++;}
From the above code we can see the Linkedhashmap put method of the process, first linkedhashmap there is no put method, so will call HashMap in the Put method, the Put method will check whether the data in the map, If you do not call the AddEntry method, because Linkedhashmap overrides the AddEntry method of the parent class, the AddEntry method of Linkedhashmap is called directly, and HashMap's AddEntry method is called in this method. , AddEntry calls the Createentry method, which is also Linkedhashmap HashMap, which creates nodes into the table, The front and back elements of the Entry (inherited from the Hashmap.entry Linkedhashmap.entry) are also maintained.
The Createentry method in HashMap, comparing the Createentry method in the above Linkedhashmap, found that in addition to putting entry in the bucket, Linkedhashmap also maintains Entry pointers to previous and subsequent elements void createentry (int hash, K key, V value, int bucketindex) { entry<k,v> E = Table[bucketindex]; Table[bucketindex] = new entry<> (hash, key, value, e); size++;}
In short, the entry in Linkedhashmap is a reference to an object that is referenced before and after its own insertion of the map, and in the put element, first checks whether the data is already in the map, and if not, creates the entry. Also, insert this entry record into the list of previous elements (and not really simply create a list node, but the list itself is the entry element). These entry themselves are not but the elements of table in the map, or the list elements.
When traversing, it uses keyiterator, while Keyiterator inherits from Linkedhashiterator, and within Linkedhashiterator there is a list of the next element that the head pointer points to:
entry<k,v> nextentry = Header.after;
Since these entry itself are linked list elements, which are also elements in table, it is possible to find all the elements directly after they have been found. The rest of the traversal process is the traversal of a linked list, and each traversal to a entry can get its key and value.
In addition, Linkedhashmap maintains a last-least-visited sequence, essentially maintaining a entry pointer, which is inserted into the tail of the map each time the element is accessed using get, so that the list header is the least recently accessed element. The entire list is the order of least recent visits to the most recent visits.
The way to do this is to find the Recordaccess method that invokes the element after it finds the element to get in the get, which adjusts the entry's front and back pointers.
Linkedhashmap Get method public V get (Object key) { entry<k,v> e = (entry<k,v>) getentry (key); if (E = = null) return null; E.recordaccess (this);//adjust pointer return e.value;} Entry Recordaccess method, the parameter m is a linkedhashmapvoid recordaccess (hashmap<k,v> m) {linkedhashmap<k,v> lm = ( linkedhashmap<k,v>) m; if (Lm.accessorder) {//whether the lm.modcount++ is arranged according to the least recent access ; Remove ();//delete oneself Addbefore (lm.header) from the current chain;//join to the tail of the list }}
In general, for all collection classes, for list, if random access more than the possibility of modifying the end-to-end elements, you should choose ArrayList, if you want to implement a similar queue or stack function or add more functions, you should choose LinkedList; for set, HashSet is a common set, after all, the set operation is usually insert and query, but if you want to produce a set with sort can use TreeSet, want to record the insertion order to use Linkedhashset, and map and set similar, If you need to quickly query and add, you can use HashMap, if you need the map elements in order to sort the rules, you can use TreeMap, if you need to record data to join the map, or need to use the least recently used rules, you can use Linkedhashmap.
Collections in Java (MAP)