突破程序员基本功的16课-2(Set,Map)

3.常见的Java集合的实现细节

 

set和map

map的key就是一个set,而且map提供了一个返回key集合:Set<k> ketset()java

而从set到map,set中的每一个元素都是k-v对便可算法

 

因为给出key,map总能够找到对应的value,因此能够将value当作key的附属物数组

HashSet与HashMap性能

两个都是经过Hash算法来计算集合元素的存储位置this

集合存储Java对象并非指将java对象放在集合中,而是集中保留了这些对象的引用(指针),这些引用变量指向实际的java对象spa

能够看出,list中存储了两个对象,这个两个对象最终仍是指向了堆内存中的实际java对象,即说明list中存储的是对象的引用debug

 

关于Map.Entry

它是Map的一个内部接口,每一个Map.Entry实际就是一个k-v,而且系统在存储k-v对时,没有考虑v,而是仅仅根据k来计算Entry的位置,位置肯定以后,value也随之保存了指针

 

当调用put方法时,即试图将一个k-v对放入一个HashMap时,首先根据key的hashCode()返回值决定Entry的存储位置,若是两个Entry的key的hashCode值相同,那么他们对应的v就被新的v覆盖,k不会覆盖,若是不一样,则新添加的Entry将于原有的Entry造成Entry链,而且新添加的Entry位于Entry链的头部code

 

HashMap

public HashMap(int initialCapacity, float loadFactor) {
//初始容量不能为负数
        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);
//计算出大于initCapacity的最小的2的n次方幂
/*当实际容量小于初始容量时,使用位移运算,将实际容量不断的乘以2(最终的效果就是乘以2的n次方),直至实际容量大于初始容量*/
        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
//设置容量极限等于容量乘以负载因子
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
//初始化table数组
        table = new Entry[capacity];
        init();
    }

通过debug发现初始化数组的时候,居然也会调用上面这个HashMap的构造方法…对象

查看源码发现

HashMap的底层经过数组来存储数据的,该数组的长度不必定是指定的初始长度,而是比初始值大的最小的2的n次方,因此尽可能使初始值是2的n次方,这样能够减少系统开销。

HashMap及其子类采用Hash算法来决定集合中元素的位置,系统初始化HashMap时,系统会建立一个长度为capacity的数组Entry,该数组里能够存储元素的位置被称为桶(bucket),每一个bucket都有其指定的索引,经过索引,系统能够快速的访问到bucket里存储的元素

每一个bucket只能存储一个元素,即一个Entry,可是因为Entry对象能够包含一个引用变量用于指向下一个Entry,因此可能出现bucket中只有一个Entry,但这个Entry指向另外一个Entry,造成Entry链。

若是bucket只有一个Entry,系统先计算某个key的hashCode值,在根据hashCode值找到该key在table数组中的索引,而后取出该索引处的Entry,最后返回该key对应的value

若是bucket存储的是一个Entry链,那么找到该Entry以后还要遍历该Entry链才能找到特定的Entry

源码

public V get(Object key) {
//若是key是null,调用getForNullKey取出value
        if (key == null)
            return getForNullKey();
//计算key的hashCode
        int hash = hash(key.hashCode());
//直接取出table数组中指定索引处的值
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
//搜索该Entry链的下一个entry
             e = e.next) {
            Object k;
//若是该Entry的key和被搜索的key相同
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
}

建立HashMap时,有一个默认的负载因子(load factor),默认为0.75,增大负载因子能够减小hash表所占的空间,但会增长查询时间,而最频繁的操做put和get都须要用到查询;反之,减少负载因子会提升查询效率可是会下降存储空间

 

HashSet

查看HashSet源码

它只是封装了一个HashMap,它所存储的元素其实是由HashMap的key来保存的,而该HashMap的value则是一个PRESENT,一个静态的java对象

HashMap和HashSet判断重复的标准包括两个对象的hashCode值相等,而且二者equals~!若是要判断本身的类对象是否重复,也须要重写hashCode和equals~!

例如:

若是不重写hashCode则不能正确判断两个对象是否相等

TreeMap和TreeSet

TreeSet底层实际上也是有TreeMap实现的

对于TreeMap,它采用红黑树的排序二叉树来保存Map中每一个Entry—每一个Entry都被当作红黑树的一个节点来对待。

当向TreeMap中存入数据的时候,首先会把第一个元素的Entry做为根节点,后面添加的元素的Entry都做为新节点添加到已知的红黑树中,这样就保证了TreeMap中的key按照由小到大的顺序排列。(该二叉树自己就有默认的排序)

TreeMap和HashMap相比,性能较低,由于在查询和插入元素的时候须要不断的循环,找到合适Entry(的位置),可是它优点是有序。

关于红黑树:

TreeMap的关键就是put(K key,V vlaue), 该方法实现将Entry放入到TreeMap的Entry链。

源码:

分析源代码:

首先该map若是为空,即没有任何元素,t==null,那么就建立一个新的Entry,并将该Entry做为root;

接着,就要去寻找合适的put的位置了,经过判断用户是否认制了Comparator,若是有定制,则使用定制的排序方式,不然就使用默认的(新建的)Comparaotor,该比较器的做用是找到合适的put的节点,并把该节点做为即将被put的元素的父亲节点;

找寻的方式是:首先将根节点做为父亲节点,而后经过Compaerator判断该父亲节点的key与被put的key的大小,若是父亲节点大,那么就将父亲节点的左节点做为新的父亲节点;反之则将父亲节点的右节点做为新的父亲节点;若是相等,则覆盖原有节点上的value,并经过return返回,再也不循环;

一轮循环结束后再去判断新的父亲节点是否真实存在,若是存在,则继续循环遍历,不然退出循环。

最终的结果就是,找到了该TreeMap中合适位置的节点

最后将循环找到的节点做为新添加节点的父节点,再次去判读父节点的key和新添加节点的key的大小,父亲节点大,则添加在父亲节点的左节点,反之则在右节点(不存在相等了)。

使用get方法查找元素时:

最终调用的仍是getEntry()方法:

能够看出,getEntry()方法也是首先判断是否有定制的Comparator,而后经过Comparator去遍历查询出须要的节点,遍历方式和put方法一致。

 

综上:TreeMap本质上就是一颗红黑树,而每一个Entry就是树的一个节点。

相关文章
相关标签/搜索