`
wdhdmx
  • 浏览: 300714 次
  • 性别: Icon_minigender_1
  • 来自: 山西
博客专栏
D4bbb5f7-9aa4-3e66-8194-f61b3f0241c2
天天编程
浏览量:21566
社区版块
存档分类
最新评论

HashMap源码理解

阅读更多

看看HashMap对应的源码。

1.类、接口关系

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

克隆和序列化不懂,先看Map。

2.实现的接口 Map

public interface Map<K,V> {
     //这些方法就不用写注释了吧,一看就懂。
     int size();
     boolean isEmpty();
     boolean containsKey(Object key);
     boolean containsValue(Object value);
     V get(Object key);
     V put(K key, V value);
     V remove(Object key);
     void putAll(Map<? extends K, ? extends V> m);
     void clear();
     //这里是返回的不重复的集合
     Set<K> keySet();
     //这里返回的是一个集合
     Collection<V> values();
     Set<Map.Entry<K, V>> entrySet();
     boolean equals(Object o);
     int hashCode();
     //还有一个内部的接口,这个相当于一个key-value数据。
     interface Entry<K,V> {
     	K getKey();
        V getValue();
	V setValue(V value);
        boolean equals(Object o);
	int hashCode();
    }
}

3.继承的抽象类AbstractMap

这个方法有700行,我就挑出几个说一说

3.1构造方法

public abstract class AbstractMap<K,V> implements Map<K,V> {
    protected AbstractMap() {
    }
}

3.2 几个方法

简单介绍几个,都没什么实际意思。

//声明了抽象变量,里面直接是Map的内部类,这个Set包含着所有的数据key+value
public abstract Set<Entry<K,V>> entrySet();

//Map长度
public int size() {
      return entrySet().size();
}

//是否为空
public boolean isEmpty() {
	return size() == 0;
}

//是否包含某个值
public boolean containsValue(Object value) {
        //要查找出是否包含,于是开始遍历这个Set。
        //这里可以使用 for each,但是这个方法写于jdk1.2,所以当时还没有。遍历的过程很简单。
	Iterator<Entry<K,V>> i = entrySet().iterator();
        //这里还有一个判空。
	if (value==null) {
	    while (i.hasNext()) {
		Entry<K,V> e = i.next();
		if (e.getValue()==null)
		    return true;
	    }
	} else {
	    while (i.hasNext()) {
		Entry<K,V> e = i.next();
                //注意:比较用的是equals,而不是==
		if (value.equals(e.getValue()))
		    return true;
	    }
	}
	return false;
}

//查找这个值,一样遍历Set
public V get(Object key) {
	Iterator<Entry<K,V>> i = entrySet().iterator();
	if (key==null) {
	    while (i.hasNext()) {
		Entry<K,V> e = i.next();
		if (e.getKey()==null)
		    return e.getValue();
	    }
	} else {
	    while (i.hasNext()) {
		Entry<K,V> e = i.next();
		if (key.equals(e.getKey()))
		    return e.getValue();
	    }
	}
	return null;
}

//这个方法是要求重写的。
public V put(K key, V value) {
	throw new UnsupportedOperationException();
}

//删除某个键值对
public V remove(Object key) {
	Iterator<Entry<K,V>> i = entrySet().iterator();
	Entry<K,V> correctEntry = null;
        //key值可以为null,查出相应key值所对应的键值对。
	if (key==null) {
	    while (correctEntry==null && i.hasNext()) {
		Entry<K,V> e = i.next();
		if (e.getKey()==null)
		    correctEntry = e;
	    }
	} else {
	    while (correctEntry==null && i.hasNext()) {
		Entry<K,V> e = i.next();
		if (key.equals(e.getKey()))
		    correctEntry = e;
	    }
	}
        //这里开始删除操作
	V oldValue = null;
	if (correctEntry !=null) {
	    oldValue = correctEntry.getValue();
            //用迭代器的删除方法。
	    i.remove();
	}
        //返回删除的value。
	return oldValue;
}

//添加一个Map操作,直接循环添加
public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
}

//直接使用了set的clear方法
public void clear() {
	entrySet().clear();
}

AbstractMap就看到这里,毕竟主要是看HashMap。

4.HashMap

4.0文字介绍

HashMap首先会开辟一个16空间的Entry(键值对)数组,

Entry类中包括

final K key;
V value;
Entry<K,V> next;
final int hash;
 

然后对存入的key进行hash运算,算到一个数字(1-15),存入对应位置。

如果对应位置一有数据,则替代当前数据,然后将原数据的引用给新数据,即赋值给next。当存到一定的值后,会扩展空间。

查询的时候将key进行计算得到(1-15),然后查询对应位置,一直去next,知道取到数据或结束。

4.1构造函数。

public HashMap() {
        //一个倍数 0.75f
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        //临界值 16*0.75=12,表示存了12个值以后要开辟新的空间
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        //初始空间16
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        //init里面没有内容。
        init();
}

4.2 put方法

public V put(K key, V value) {
        //如果key为空,单独做插入操作。
        if (key == null)
            //插入key为null的值,返回之前此处的值。
            return putForNullKey(value);
        //计算hash值,下面的两个方法
        int hash = hash(key.hashCode());
        //根据hash值获得在数组中的位置
        int i = indexFor(hash, table.length);
        //循环位置。直到为空位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如果包含key值和hash值都相等。
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                //将原有的键值对的value值替换。
                V oldValue = e.value;
                e.value = value;
                //这是替换要执行的方法,现在内部还没有处理。
                e.recordAccess(this);
                //将就得value返回。
                return oldValue;
            }
        }
        //这里代表这个key值要新加入这个map。
        //修改次数
        modCount++;
        //加入
        addEntry(hash, key, value, i);
        return null;
}
//添加一个空的key。添加到数组的0位置。
private V putForNullKey(V value) {
        //判断对应位置时候为空,
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            //对应key值不为空
            if (e.key == null) {
                //更新数据。
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                //返回被替换掉的值。
                return oldValue;
            }
        }
        modCount++;
        //添加一个0位置的。
        addEntry(0, null, value, 0);
        return null;
}
//添加一个键值对,传入对应hash值和数组位置。
void addEntry(int hash, K key, V value, int bucketIndex) {
        //将这个位置的原有值取出来,
	Entry<K,V> e = table[bucketIndex];
        //新建一个,传入hash值,还有它的下一个键值对。所以同一个位置,后面插入的数据会查询快一点点。
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        //如果size大于临界值。
        if (size++ >= threshold)
            //将原来空间扩大为两倍
            resize(2 * table.length);
}
//开辟新空间
void resize(int newCapacity) {
        Entry[] oldTable = table;
        //旧数组的长度
        int oldCapacity = oldTable.length;
        //最大空间数。
        if (oldCapacity == MAXIMUM_CAPACITY) {
            //边界值为int的最大值。
            threshold = Integer.MAX_VALUE;
            //结束。
            return;
        }
        //新建一个数组,大小为传入的数字。
        Entry[] newTable = new Entry[newCapacity];
        //转移新数组
        transfer(newTable);
        //给table赋值。
        table = newTable;
        //边界值更新。
        threshold = (int)(newCapacity * loadFactor);
}
//将数据全部重新插入新数组,规则使用新得数组空间值计算。
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;
                    //根据hash值获取对应数组位置。
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
}

4.3 get方法

//获取key值对应信息。
public V get(Object key) {
        //key为null时候
        if (key == null)
            //直接从0号位置开始查。
            return getForNullKey();
        //计算key值对应hash。
        int hash = hash(key.hashCode());
        //使用indexFor计算出对应table的位置,然后开始遍历。直到找到或者结束。
        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;
}
//获取key值为null的value。
private V getForNullKey() {
        //先查找0号位,然后依次找next,直到找到key为null的值或键值对为空,返回。
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
}

4.4一些常用的方法

4.4.1 public boolean containsKey(Object key)

将key进行hash计算,然后找到键值对,然后返回结果

4.4.2 public boolean containsValue(Object value)

遍历数组,知道找到value。很费性能。

4.4.3  public void clear()

遍历数组,全部置为null。

4.4.4 public V remove(Object key)

找出对应key,去掉,更新关联值。

4.4.5 public Set<K> keySet()

将整个数组返回,这个Set是单独实现的一个类,里面重新实现了迭代器 ,size,包含,清除,将迭代的结果返回Map的key。其实就是返回了完整的Map内容。没有开辟任何多余空间,所以这个方法会很快。

4.4.6 public Collection<V> values()

这个和上面的一样,直接返回的是Map的整体数据,将Collection实现了迭代器 ,size,包含,清除,将迭代的结果返回Map的value。

 

5.使用的hash算法

int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
        return h & (length-1);
}

计算都是通过hashCode来计算,所以存入HashMap的对象最好不要将hashCode写成一个值。hashCode各个类都有自己的生成方法,似乎Object默认的是内存位置,但是刚刚观察Integer和String都覆写了的。

int占32位,无符号左移20位,空位补0,无符号右移20位。算了,这个二进制运算符要系统的看,先这样把。

6.结束

看到这里都差不多了,主要是理解HashMap的结构和hash算法。

同时产生了两个疑问,先记下来。

疑问一:只实现抽象类AbstractMap不行么?为什么还要implements Map ,不重复吗?

疑问二:transient volatile 这样声明变量是为什么?

 

0
1
分享到:
评论

相关推荐

    HashMap jdk1.8源码阅读.md

    本人源码阅读理解, 基于jdk1.7 和jdk 1.8的java HashMap源码的理解, 希望对你理解java源码有作用

    深入理解Java之HashMap源码剖析

    主要介绍了深入理解Java之HashMap源码剖析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    HashMap讲解注释版本.java

    对HashMap 源码逐行进行注释,带你深入理解HashMap原理,使面试不在困难,

    java7hashmap源码-AndroidOffer:只为帮助您获得更好的报价

    hashmap源码 AndroidOffer Java Java 进阶 HashMap 对比:Hashtable、HashMap、LinkedHashMap、ConcurrentHashMap、TreeMap (看第六条就可以) HashMap 用什么数据结构实现的 加载因子是什么 HashMap 初始化传入的...

    java7hashmap源码-AnnotationDemo:注解基础知识,编译时注解和运行时注解例子

    hashmap源码 注解 理解Java注解 注解就相当于对源代码打的标签,给代码打上标签和删除标签对源代码没有任何影响。有的人要说了,你尽几把瞎扯,没有影响,打这些标签干毛线呢?其实不是这些标签自己起了什么作用,...

    java7hashmap源码-Interview:面试宝典

    hashmap源码 Interview 一、Java基础 String类为什么是final的。 理解final 修辞类,类不能被继承 修辞方法,方法不能被重写 修辞类变量,变量必须初始化 字符串池String Pool 字符串创建方式 String str = ...

    HashMap.java

    JDK7的HashMap源码阅读,几乎给每个方法和属性都加上了中文注释。 可以帮助大家更好的阅读源码,可能有理解不对的地方,望指正。

    HashMap新增数据原理.docx

    深入理解HASHMAP底层原理,通过对源码进行解析分析HASHMAP执行过程,一篇文章帮助你深刻理解HASHMAP

    Java面试专属视频

    面试必考之HashMap源码分析与实现 ,微服务架构之Spring Cloud Eureka 场景分析与实战,高性能必学之Mysql主从架构实践 ,架构师不得不知道的Spring事物不能回滚的深层次原因 ,分库分表之后分布式下如何保证ID全局...

    深入解读大厂java面试必考基本功-HashMap集合配套文档代码资料

    在本套课程中,将会非常深入、非常详细、非常全面的解读HashMap以及源码底层设计的思想。从底层的数据结构到底层源码分析以及怎样使用提高HashMap集合的效率问题等进行分析。如果掌握本套课程,那么再看其他javase的...

    看完还不懂HashMap算我输(附职场面试常见问题)

    你看过HashMap源码吗,知道底层的原理吗 为什么使用数组+链表 用LinkedList代替数组可以吗 既然是可以的,为什么不用反而用数组。 重要变量介绍: ps:都是重要的变量记忆理解一下最好。 DEFAULT_INITIAL_CAPACITY ...

    集合底层源码分析.doc

     哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为“链表的数组”  一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到...

    java8stream源码-doc:一步一步建立起分布式系统:SOA->微服务->云原生的一套完整技术栈

    HashMap源码分析 并发 IO JVM 类加载机制与编译优化 JVM运行时数据区的内存管理机制 垃圾收集算法与垃圾收集器 性能监控与故障处理 常用设计模式 ​ 用好设计模式能帮助我们更好的解决实际问题,设计模式最重要的是...

    java8源码-cainiao:自娱自乐

    说明:文件夹以类型为目录,如并发、JVM、设计模式,文件名称尽量描述主题,如HASHMAP源码分析、代理模式分析 Books中存放分布式技术学习和书籍阅读后笔记、总结和一些面试搜集的问题,具体查看Books中ReadMe.md ...

    HashMap的工作原理和底层实现(二)红黑树的左旋、右旋

     HashMap是java最常用的容器之一,本文会通过阅读源码的方式来理解HashMap中是如何进行红黑树的左旋和右旋 一、什么是左旋和右旋  红黑树的性质 每个节点要么是黑色,要么是红色。 根节点是黑色。 每个叶子...

    HashMap.pdf

    自学笔记,如有错误,或者知识补充,或理解不到位或错误,欢迎评论指教(灰常感谢)

    最新Java面试题视频网盘,Java面试题84集、java面试专属及面试必问课程

    面试必考之HashMap源码分析与实现.mp4 │ │ │ ├─2.探索JVM底层奥秘ClassLoader源码分析与案例讲解 │ │ 2.探索JVM底层奥秘ClassLoader源码分析与案例讲解.wmv │ │ │ ├─3.锁、分布式锁、无锁实战全局性ID...

    动力节点Java基础301集_史上最全的Java基础教程

    4: 源码分析分析讲的特别到位,尤其是HashMap的工作原理和源码分析,真正的把jdk源码翻了一遍,要是拿着这个去面试绝对是秒杀级神器。 5:使用多线程模拟用户去ATM取钱讲的也非常不错,后续还提了一个小Timer定时任务...

    spring-annotation:1.Spring 5.X源码分析2.手写框架3.设计模式4.Springcloud2 5.互联网高并发场景6.互联网安全架构

    天道酬请,一步一个坑弹簧注释...源码分析1.1 Spring 5.X源码分析1.1.1 Spring5源码深度解析(一)之理解配置注解1.1.2 Spring5源码深度分析(二)之理解@ Conditional,@ Import注解1.1.3 Spring5深度源码分析(三)之...

Global site tag (gtag.js) - Google Analytics