Java 笔记 -HashMap

HashMap使用的数据结构,专业术语“链表散列”,代码实

    /**
     * 定义了一个Entry的数组用来存储数据。
     */
     transient Entry[] table;
```java
    /**
     * Entry是内部定义的类
     */
     static class Entry<K,V> implements Map.Entry<K,V> {

     /**
      * 定义了两个常量key,就是HashMap的key
      * value,key对应的value
      * next,下一个Entry对象,故Entry的数据结构是一个单项的链表
      * 常量hash值,可以说他是bucket,存储的其实是table数组的下标值。
      */
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;

        /**
         * 构造方法.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }
        /**
        设置value的同时会返回旧值
        */
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
        *    会在调用HashMap put方法时调用它,但是它本身是空方法,
        *目前了解的是LinkedHashMap会重写这个方法,
        *每次put时会将对象放到双向链表的尾,
        *因为LinkeHashMap要实现LRU算法,我会专门写LinkedHashMap,
        *要详解可以移步那里看看。
        */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }
  • transient 实现了Serializable的类中,被标识了transient 的属性序列化时会被忽略。

    if (loadFactor <= 0 || Float.isNaN(loadFactor))
    throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
    

    返回true意味着传入的loadFactor 为NaN = 0.0f / 0.0f;

    /**
    hashMap存储数据的地方,长度永远是2的次幂(因为定位bucket时有对长度进行‘与’操作,其他作用后续说),默认初始16
    */
    transient Entry[] table;
    
    /**
    table表里允许存储的最大数据,其实可以计算出来(table.size()*loadFactor+1)但程序中多处使用,使用常量表示更方便。
    */
    final float loadFactor;
    
    /**
    负载因子,意味着数据可以存储相对于总容量的百分比,默认0.75, 当数据超过了loadFactor(table.size()*loadFactor+1),会调用resize(),增大table容量,重新hash定位所有数据,耗费性能,所以对于数据集可以提前可以预知数据大小的,可以初始化时设置此值还有初始容量initialCapacity
    */
    final float loadFactor;
    
    /**
    当hashMap结构发生改变(移除数据或者添加数据),会修改这个值,
    modCount++,主要的作用在于hashMap是非线程安全的,在调用迭代器每次取数据时会判断modCount在这段期间内是否被修改,如果修改了说明数据发 * 生变化,会抛出ConcurrentModificationException,一种安全机制吧。
    */
    transient int modCount;
    
    /**
    hashMap本身是散列表结构,它定位元素的逻辑是先取key的hashcode,再通过此hash函数
    再次hash,之后调用indexFor()根据table表的长度取余,找到存储的偏移量,之后再比较value的值。
    此次hash的目的是因为key的hashcode无法保证高质量的将元素散列开来。
    NULL的key,将定位到0
    */
    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
```java
/**
get方法,其实没啥好说的,时间复杂度因为散列数据结构故O(1)
*/
  public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        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.equals(k)))
                return e.value;
        }
        return null;
    }
/**
这个也挺简单的,主要说下recordAccess(this),Entry中的方法,在hashMap中,是没有操作的,
放这个方法还有recordRemoval,是留给其他类使用的如LinkedHashMap :用来实现LRU置换算法。
感兴趣的可以看下LinkedHashMap
addEntry(hash, key, value, i)是将value添加到key的链表上。
*/
public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        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;
    }
/**
新建一个Entry,头插法。
主要在resize()方法
*/
 void addEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }
/**
当hashMap中存储的size不在小于threshold(capacity * load factor)时,需要
重新调整table的大小,每次调整完都会将元素重新排序,当数据量大了这会是一个耗时操作,
并且我们使用的时候应该尽量让我们初始的hashMap容量接近要使用的容量/loadFactor,减少置换次数。
*/
 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];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

    /**
     * 将旧表中的数据重新hash,放入新表中。
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }
```java

 /**
     remove操作
     */
    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.hashCode());
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        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 (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }
```java
/**
hashMap中是否含有相应的value,还有一个containsKey方法没说,
想提下这个是因为,判断包含value需要循环比对,时间复杂度O(n)。慎重使用。
*/
public boolean containsValue(Object value) {
        if (value == null)
            return containsNullValue();

        Entry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
        return false;
    }
```java
/**
最后的是hashMap的迭代了,一起来说,HashIterator内部私有类,几个属性里说下expectedModCount
上面的hashMap添加删除操作,都会更新一个modCount,开头已经说了,是hashMap是非线程安全的,这个属性
是在迭代时用来判断迭代过程中,hashMap的值是否被更新过,expectedModCount就是在Iterateor构造时赋值
迭代过程中判断。
所以,当我们使用迭代器遍历hashmap时,如果想同时修改hashMap内容(只能删除)。使用Iterator提供的remove方法
*/
private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;        // next entry to return
        int expectedModCount;   // For fast-fail
        int index;              // current slot
        Entry<K,V> current;     // current entry

        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                //定位到第一个value中不为null的位置。
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            //迭代过程中,元素被修改,抛出ConcurrentModificationException
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
            current = e;
            return e;
        }

        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }

    }
/**
三个私有的迭代器,也就是说我们可以用这三种方式迭代HashMap。
*/
    private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }

    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }

    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
    }

    // Subclass overrides these to alter behavior of views‘ iterator() method
    Iterator<K> newKeyIterator()   {
        return new KeyIterator();
    }
    Iterator<V> newValueIterator()   {
        return new ValueIterator();
    }
    Iterator<Map.Entry<K,V>> newEntryIterator()   {
        return new EntryIterator();
    }

    /**
    好吧,三面的三种迭代初始化,只能提供包内部使用,真正暴露出来的方法是下面三个,但是换汤不换药,
    各位判官自行看看就好。可以根据自己的需求调用相应的Iterator
    这三种的初始赋值使用的分别是keySet,values和entrySet。
    */
    public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

    public Collection<V> values() {
        Collection<V> vs = values;
        return (vs != null ? vs : (values = new Values()));
    }

    private final class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return newValueIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

    private transient Set<Map.Entry<K,V>> entrySet = null;

     /**
     Note:这里,这里,说实话我也没搞懂,为什么要这种写法entrySet()再调entrySet0()。
     网上找了找,也没啥令人信服的说法。如果哪位了解,请务必要告诉我一下。
     */
    public Set<Map.Entry<K,V>> entrySet() {
        return entrySet0();
    }

    private Set<Map.Entry<K,V>> entrySet0() {
        Set<Map.Entry<K,V>> es = entrySet;
        return es != null ? es : (entrySet = new EntrySet());
    }

    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
时间: 2024-08-11 09:56:19

Java 笔记 -HashMap的相关文章

9.5-全栈Java笔记:Map接口中的实现类HashMap

上节聊到「Map接口和实现类」,今天我们深入探讨其实现类中的HashMap如何进行底层实现. Hashmap基本结构讲解 哈希表的基本结构就是"数组+链表".我们打开HashMap源码,发现有如下两个核心内容: public class   HashMap<K,V> extends   AbstractMap<K,V> implements   Map<K,V>, Cloneable, Serializable { /** * The default

java学习笔记——Java中HashMap和TreeMap的区别深入理解

本文转载自Java中HashMap和TreeMap的区别深入理解 首先介绍一下什么是Map.在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value.这就是我们平时说的键值对. HashMap通过hashcode对其内容进行快速查找,而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的). HashMap 非线程安全 

JDK源码笔记-java.util.HashMap

HashMap 的存储实现 当程序试图将多个 key-value 放入 HashMap 中时,以如下代码片段为例: Java代码 HashMap<String , Double> map = new HashMap<String , Double>(); map.put("语文" , 80.0); map.put("数学" , 89.0); map.put("英语" , 78.2); HashMap 采用一种所谓的&quo

4.5-全栈Java笔记:垃圾回收机制

垃圾回收机制(Garbage  Collection) Java引入了垃圾回收机制,令C++程序员最头疼的内存管理问题迎刃而解.JAVA程序员可以将更多的精力放到业务逻辑上而不是内存管理工作上,大大的提高了开发效率. 垃圾回收原理和算法 1)内存管理 Java的内存管理很大程度指的就是对象的管理,其中包括对象空间的分配和释放. 对象空间的分配:使用new关键字创建对象即可 对象空间的释放:将对象赋值null即可.垃圾回收器将负责回收所有"不可达"对象的内存空间. 2)垃圾回收过程 任何

java笔记整理

Java 笔记整理 包含内容     Unix Java 基础, 数据库(Oracle jdbc Hibernate pl/sql), web, JSP, Struts, Ajax Spring, Ejb, java和模式 Linux/Unix笔记 inode :存储编号(地址) ls -k:查看磁盘分区 ls -li:显示当前文件的inode号. 目录的大小跟文件的大小有关,跟目录里的文件(目录)数量无关. 一行多个命令的话,中间用分号分开.如:pwd;cal;date last | grep

9.4-全栈Java笔记:Map接口和实现类

现实生活中,我们经常需要成对存储某些信息.比如,我们使用的微信,一个手机号只能对应一个微信账户.这就是一种成对存储的关系. Map就是用来存储"键(key)-值(value) 对".  Map类中存储的"键值对"通过键来标识,所以"键对象"不能重复. Map 接口的实现类有: HashMap.TreeMap.HashTable.Properties等. 常用的方法: 方法 说明 Object   put(Object key, Object va

Java中HashMap的实现原理

最近面试中被问及Java中HashMap的原理,瞬间无言以对,因此痛定思痛觉得研究一番. 一.Java中的hashCode和equals 1.关于hashCode hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的 如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同 如果对象的equals方法被重写,那么对象的hashCode也

9.8-全栈Java笔记:Iterator(迭代器)接口

迭代器介绍 所有实现了Collection接口的容器类都有一个iterator方法用以返回一个实现了Iterator接口的对象. Iterator对象称作迭代器,用以方便的实现对容器内元素的遍历操作. Iterator接口定义了如下方法: boolean hasNext();  //判断是否有元素没有被遍历 Object next();      //返回游标当前位置的元素并将游标移动到下一个位置 void remove(); //删除游标左面的元素,在执行完next之后该操作只能执行一次 图1

9.9-全栈Java笔记:遍历集合的N种方式总结&Collections工具类

遍历集合的N种方式总结 [示例1]遍历List方法1,使用普通for循环 for(int i=0;i<list.size();i++){         //list为集合的对象名 String temp = (String)list.get(i); System.out.println(temp); } [示例2]遍历List方法2,使用增强for循环(使用泛型定义类型!) for (String   temp : list) { System.out.println(temp); } [示例