Set learning 2 -- Map interface and its implementation class

Map interface: stored in the form of key value pairs. Key value pairs exist. Key values cannot be repeated, and value can be repeated.
Methods under Map interface:


Interpretation of common methods in the collection:

// V put(K key, V value) adds a key value pair to the collection
        hashMap.put("A","a");
        
System.out.println(hashMap.size());
        //void clear() clears the collection elements
//        hashMap.clear();

//boolean containsKey(Object key) determines whether the specified key exists in the collection. If the key exists, it returns true and if not, it returns false
        boolean key = hashMap.containsKey("A");
//        System.out.println(key);

//boolean containsValue(Object value) determines whether the specified value exists in the collection. Value returns true if it exists and false if it does not exist
        hashMap.containsValue("zhangsna");
//        System.out.println(hashMap.size());

//V get(Object key) get value through key
        String s = hashMap.get("A");

Traversal form of collection under Map interface:

	//Traversing key value pairs through entrySet
        Iterator <Map.Entry <String, String>> iterator = hashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry <String, String> entry = iterator.next();
            System.out.println("key:"+entry.getKey()+",value:"+entry.getValue()+ " ");
        }
	//Traverse keys through keySet
        Iterator <String> iterator1 = hashMap.keySet().iterator();
        while (iterator1.hasNext()) {
            String key1 = iterator1.next();
            System.out.print(key1+" ");
        }
	//Traverse the values through values()
        Iterator <String> iterator2 = hashMap.values().iterator();
        while (iterator2.hasNext()) {
            String value = iterator2.next();
            System.out.print(value+" ");
        }

Introduction to HashMap

characteristic:

1. The stored data is in the form of key value pairs. The key cannot be repeated and the value value can be repeated
2. Both key and value can be null
3. The order of internal elements cannot be guaranteed
4. The underlying data structure is a hash table

Hash table: a data structure in which a key is mapped to a specific value through a hash function
Hash conflict: hash function f (x), f (m) = f (n), M is not equal to n
Hash function: direct hash, modulo...
Hash conflict: chain address method, detection method (linear detection, random detection)...

Chain address method: as shown in the figure:

Research on HashMap source code

Inheritance relationship

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable   

HashMap inherits from AbstractMap. This abstract class implements common methods of Map interface to facilitate subclass reuse
Implement the class Map interface and have all the methods provided in the Map
Implement clonable and Serializable interfaces

Constructor

//The HashMap object is instantiated by the initial capacity and load factor
public HashMap(int initialCapacity, float loadFactor) {
      //Check the validity of parameters
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
	this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

	//The HashMap is instantiated by the initial capacity, and the load factor is the default value of 0.75 
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

	//Instantiate the default initial capacity 16 through parameterless construction, and the default loading factor is 0.75
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    
	//Constructing HashMap instances from map collection instances
    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,DEFAULT_INITIAL_CAPACITY),
             DEFAULT_LOAD_FACTOR);
        //Initialize the table and threshold parameters and instantiate the hash table
        inflateTable(threshold);
        //Store the key value pairs in the map combination into the HashMap
        putAllForCreate(m);
    }

Properties and default values

//Default initial capacity: 16 
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//Maximum capacity
static final int MAXIMUM_CAPACITY = 1 << 30;

//Default load factor: 0.75 
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//Default empty table
static final Entry<?,?>[] EMPTY_TABLE = {};

//The table property for storing data is an array of entry type
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//Number of stored key value pairs
transient int size;

//Capacity expansion threshold: judge threshold =table.length()*loadFactor during capacity expansion
int threshold;

//Loading factor
final float loadFactor;

//version control 
transient int modCount;

//key hash correlation
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
//key hash correlation
transient int hashSeed = 0;

Underlying data structure

//Data storage location  
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
   static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
    }

It can be seen from the above that the data stored at the bottom of HashMap is in the form of data plus linked list, that is, hash table structure

Capacity expansion mechanism
Capacity expansion opportunity: when size > threshold, capacity expansion will be performed

common method
Insert element: put(key,value)

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            //When the table array is empty, the initialization table data will be entered. When the put operation is called for the first time, the initialization table data will be entered
            inflateTable(threshold);
        }

	//key is null. Store the data in slot 0
        if (key == null)
            return putForNullKey(value);

	//Processing when key is not null
        //Find the location of the corresponding storage card slot through the key
        int hash = hash(key);
        int i = indexFor(hash, table.length);

	for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            //Find the data linked list of card slot i through position i, and traverse from the beginning to find out whether the key exists
            //The judgment conditions are hash and key, and the equal value is updated
            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;
            }
        }
       //The key does not exist in position i, so you need to add an entry node to store data
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

	private V putForNullKey(V value) {
      //If the key is null, the hash position will be No. 0. You need to traverse the linked list of No. 0 card slot and judge whether the key is null
      //Update the value in the entry and return it to the old value
      //If it does not exist, create an entry entity to store the put data
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

	//Find the corresponding card slot through the key hash
final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

	h ^= k.hashCode();

	// This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

	adopt key The hash value of was found at a location in the hash table
static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    
       The capacity is 16
           0001 0000     16
           0000 1111     15
           1010 1001
           -------------
           0000 1001    9 
        return h & (length-1);
    }
	void addEntry(int hash, K key, V value, int bucketIndex) {
        //When the stored data size is greater than the capacity expansion threshold, capacity expansion is performed
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //2X expansion of HashMap
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            //Get the position of the new card slot that the current key should be inserted into
            bucketIndex = indexFor(hash, table.length);
        }

	createEntry(hash, key, value, bucketIndex);
    }

	//Capacity expansion
void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

	//The newly created array is twice as large as the original one
        Entry[] newTable = new Entry[newCapacity];
        //Re hash each entry entity in the original map to the new map
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        //threshold = table.length*loadFactor
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

	//Insert data using header interpolation
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++;
    }

Operation steps:
1. First, judge whether the key is null and store it in slot 0 of the hash table for special processing
2. Whether the data with null key exists, traverse the linked list of the position of card slot 0. If it exists (= =), update value and return
3. If it does not exist, create a new node (addentry)
4. The key is not null. Use the key to calculate the hash value and find the card slot position (hash,indexFor) in the hash table
5. Obtain the linked list in the corresponding card slot, traverse to find out whether the key exists. If it exists (hash & & equals), update the value and return
6. key does not exist in the linked list. Create a new node (addEntry)
7. Consider whether to expand the capacity (size > threshold). If the capacity needs to be expanded, double the original size, and then re hash the data in the original hash table to the new hash table, and update the card slot position of the currently inserted node
8. Insert the new entry node into the given card slot position with head plug

Get element: get(key)

	public V get(Object key) {
        //If the key is null, go directly to the slot 0 to get the result
        if (key == null)
            return getForNullKey();
        //key is not null
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
    }

	private V getForNullKey() {
       //If the map is empty, null is returned
        if (size == 0) {
            return null;
        }
        //At the position of card slot 0, traverse the linked list to find out whether the key is null. If it exists, find value in the entry and return it. Otherwise, return null
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

	adopt key Find corresponding entry example
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

	//Find the card slot position corresponding to the key through the hash of the key 
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {
            Object k;
            if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

Query process:
1. If the key is null, traverse the linked list query at the 0 card slot. If the key exists, return the value of entry; otherwise, return null
2. If the key is not null, hash the key, find the corresponding card slot, traverse the linked list, judge whether the key exists (hash,key.equals), and return the value of entry; otherwise, return null

Delete element: remove(key)
Delete the entry entity through the key

public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

	final Entry<K,V> removeEntryForKey(Object key) {
      //If the collection is empty, null is returned directly
        if (size == 0) {
            return null;
        }
       //Find the corresponding card slot through the key hash (the key is null and the card slot is 0)
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

	//Deleting a node is to solve the problem of deleting a one-way linked list: solution: give two pointers, one before and one after, and the front pointer represents the node to be deleted,
    ,  //Connect the node with the next of the previous node pointer through the latter pointer
        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                //If the head node is deleted, the latter node will be used as the head node of the current card slot
                if (prev == e)
                    table[i] = next;
                else
                    //The latter pointer is prex to connect the node with the next of the previous node pointer
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

	return e;
    }

Delete process:
1. Find the card slot position through the key hash (the key is null in slot 0)
2. Traverse and search the linked list corresponding to the card slot. Given two pointers, the former pointer finds the node to be deleted, and the latter pointer establishes the relationship with the next node

Summarize the characteristics of HashMap

1. Data structure: hash table (array + linked list)
2. Default array size: 16
3. Capacity expansion mechanism: the size is twice the length of the original array
4. Expansion factor: 0.75f
5. Expansion condition: 0.75 * array length
6. Thread safe: this collection is thread unsafe
7. The type of stored data is key value pair (key and value)
8. Both key and value can be null
9. The stored data is out of order
10. key cannot be repeated, value can be repeated
11. If the key is the same, the value will be overwritten

Application scenario

Do data statistics

LinkedHashMap introduction

According to the collection framework diagram, LinkedHashMap belongs to the implementation subclass of HashMap

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

Basic features:

  • 1. key cannot be repeated and value can be repeated
  • 2. LinkedHashMap is insert ordered / access ordered accessOrder:true access ordered false: insert ordered
  • 3. The underlying data structure is a hash table
  • 4. Both key and value can be null

How is LinkedHashMap organized?
data structure

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

The inheritance relationship through LinkedHashMap is inherited from HashMap. If the properties and methods of HashMap are inherited, the table property and entry type will be inherited, and its internal structure is also a hash table structure.

There are also attributes inside

//Head node
private transient Entry<K,V> header;
   //accessOrder controls whether to insert or access in order. The default is false, that is, insert in order. True: access in order
    private final boolean accessOrder;

	class Entry<K,V> extends HashMap.Entry<K,V> {
        Entry<K,V> before, after;
}

The entry class in LinkedHashMap inherits from HashMap.Entry, that is, it has attributes such as key\value\hash\next. In addition, two new attributes are before and after of entry type.
The implementation of LInkedHashMap is hash table + bidirectional linked list

Store data through hash table structure
Based on the hash table, the nodes of the two-way linked list are connected in the order of insertion or access

Insert data procedure: put

//Parent class HashMap implementation
public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            //If the table is empty, initialize the hash table according to the default 16 size
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            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;
    }

//Implementation of linkedhashmap in entry
void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
  //If the current access is ordered, delete the current node from the two-way linked list, and then insert it into the before processing of the head

//Implementation in linkedHashmap
    void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);
        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }
//addentry in HashMap
void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (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
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        //Handle the two-way linked list and insert it into the before position of the header
        e.addBefore(header);
        size++;
    }

Differences between the implementation of LinkedHashMap insertion and that of HashMap
1. When inserting and the key exists, if the access is orderly, insert the node from the two-way linked list into the last position
2. When the key does not exist and the entry node is considered to be added, after the hash table is inserted, the two-way linked list needs to be maintained, that is, the tail of the two-way linked list needs to be inserted

Application scenario:
Statistics, according to the access order

HashTable

Inheritance relationship:
HashTable inheritance relationship:

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable

Inheritance relationship of HashMap:

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

Dictionary implementation:

public abstract class Dictionary<K,V> {
    public Dictionary() {
    }
	
	abstract public int size();
    abstract public boolean isEmpty();
    abstract public Enumeration<K> keys();  //Gets the collection of keys
    abstract public Enumeration<V> elements(); //Gets a collection of values
    abstract public V put(K key, V value);
    abstract public V remove(Object key);
}

AbstractMap class:

//Implement some of these methods

Similarities and differences between HashTable and HashMap

*
* Similarities:
* 1,The underlying data organization is a hash table( JDK 1.7)
* 2,key-value Key value alignment, key It can't be repeated, value It can be repeated
* 3,HashMap and Hashtable Are inserted unordered
*
* difference:
* 1,HashTable Inherited from Dictionary Class, which is provided earlier map Parent class, now recommended AbstractMap class
* 2,HashTable The default initial value for is 11
* 3,HashTable Is thread safe(By adding synchronized crux)
* 4,HashTable in key and value Can't be null
* 5,HashTable Middle pair key Hash procedure and HashMap It's different
* 6,HashTable The capacity of is expanded according to the size of 2 times plus 1((oldCapacity << 1) + 1)
*/

TreeMap collection

Basic features:
treeMap features
1. Keys are sorted by size. By default, key s are sorted from small to large
2. key cannot be null, value can be null
3. key cannot be repeated and value can be repeated

How does TreeMap achieve key ordering?
The underlying data structure of TreeMap is red black tree

TreeMap inheritance relationship

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

treemap implements the NavigableMap interface, supports a series of navigation methods, and returns the key set with order

The NavigableMap interface is declared as follows:

	public interface NavigableMap<K,V> extends SortedMap<K,V>

NavigableMap interface inherits from SortedMap interface. SortedMap interface has sorting function and Comparator class Comparator.

Comparator class description:
Comparison between Comparable and Comparator
Both comparators are interfaces.

Methods provided in the Comparable interface:

	public interface Comparable<T> {
  	  public int compareTo(T o);
	}

This interface provides a compareTo method,
The return values are - 1, 0 and 1 respectively
It is used as a comparator inside the class. Once the comparison attribute is determined, it cannot be changed

Comparator interface:

	public interface Comparator<T> {
    		int compare(T o1, T o2);
	}

The compare method is provided in the interface,
The returned result of this method is greater than 0, equal to 0 and less than 0
Class, which is used by the user-defined implementation comparison process

Usage scenario: sort the data and select TreeMap

weakHashMap

There is no special data structure in weakHashMap, which is mainly to optimize the JVM. The JVM can more intelligently recycle useless objects during garbage collection
Weakhashmap is mainly applicable to caching scenarios. When a key object is recycled by the garbage collector, the value object of the response will be deleted from the map. Weakhashmap can save memory space and cache some unnecessary data
The difference between weakhashmap and other maps is that the key is a weak reference type, and the key in other maps is a strong reference type,
In Java, there are four types of references: strong, soft, weak, and phantom

reference type

Strong reference
Strong reference is a common reference type. Objects created in the form of new are strong reference objects

	Object o =  new Object();

Objects with strong references are not cleaned up by GC at any time
As long as the strong reference exists, GC will never recycle the referenced object. The reference associated with the object created through the keyword new is a strong reference. As long as the strong reference also points to an object, it means that the object still exists. This will not be touched by the garbage collector, that is, when the memory is insufficient, the JVM throws an OOM (OutOfMemoryError) Exceptions also do not recycle strongly referenced objects.

Soft Reference, if Reference and virtual Reference all exist in the java.lang.ref package, and the parent class is the Reference class
Reference is the constructor of the abstract parent class

	//referent is the object that the reference points to
	Reference(T referent) {
        this(referent, null);
    }

//ReferenceQueue can be understood as a Queue. Before GC, the referenced object is placed in the Queue to facilitate the cleaning of the referenced object itself
    	Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

For example:

	Object o = new Obeject();
	ReferenceQueue queue = new ReferenceQueue();
	WeakReference wk =new WeakReference(o,queue );

At this time, wk is a weak reference and points to the o object. o will be cleaned up by GC at a certain time, but the work of wk object depends on the Queue object. When wk appears in the Queue, it indicates that the object it points to is invalid and can be cleaned up safely.

Soft reference: SoftReference
When a GC operation occurs and the memory is sufficient, the object used by the soft reference will not be recycled. When the memory is insufficient, the object used by the soft reference will be recycled when the GC operation is triggered.

	ReferenceQueue<A> queue = new ReferenceQueue<A>();
        SoftReference<A> w = new SoftReference<A>(new A(), queue);

	System.out.println(w.get());
        System.out.println(w.isEnqueued());
        System.out.println(queue.poll());
        /**
         * main.java.com.tulun.WeakHashMapGY02$A@1e3ac11b
         * false
         * null
         */

	/**
         * Key points:
         * //The memory required here is limited and not enough. Therefore, GC is triggered to recycle soft reference objects
         *
         */
        byte[] array = new byte[7*1024*1024+500*1024];
        System.out.println(array[0]);
        System.gc();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

	System.out.println(w.get());
        System.out.println(w.isEnqueued());
        System.out.println(queue.poll());

Weak reference: WeakReference
When a GC operation occurs, even if the memory space is sufficient, the GC operation will reclaim the object acted by the weak reference.

	ReferenceQueue<A> queue = new ReferenceQueue<A>();
        WeakReference<A> w = new WeakReference<A>(new A(), queue);

 	System.out.println(w.get());
        System.out.println(w.isEnqueued());
        System.out.println(queue.poll());
//        main.java.com.tulun.WeakHashMapGY02$A@20724356
//        false
//        null

 	System.gc();
        System.out.println("---------------------");

        System.out.println(w.get());
        System.out.println(w.isEnqueued());
        System.out.println(queue.poll());

Virtual reference: phantom reference
Virtual reference is the weakest reference relationship. Whether an object has a virtual reference will not affect the object lifetime, nor can it be used to obtain an object instance through virtual reference,
A virtual reference exists to determine whether an object is recycled

 	ReferenceQueue<A> queue = new ReferenceQueue<A>();
        PhantomReference<A> ptr = new PhantomReference<A>(new A(), queue);

	System.gc(); //Start GC here and recycle the A object. The queue receives A notification that the object has been recycled

	if(ptr.isEnqueued()){
            System.out.println("ptr.isEnqueued()");
        }else{
            System.out.println("not ptr.isEnqueued()");
        }

Keywords: Java set map

Added by flight553 on Wed, 01 Dec 2021 18:03:22 +0200