栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java基本集合源码解读-JDK8/11

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java基本集合源码解读-JDK8/11

文章目录

前言详尽的debugger底层查看源码配置一.集合体系图二.List类集合

2.1.1 ArrayList2.1.2ArrayList底层源码分析

结论:2.1.3 使用ArrayList无参构造2.1.4**`无参构造下ArrayList.add()扩容机制`**2.1.5ArrayList有参构造 2.2.1vector

结论2.2.2以添加方式为例2.2.3vector的扩容机制无参构造 2.3.1 linkedList结论

2.3.2以添加方法为例2.3.3删除例子 2.4 List集合选择 三.set集合

3.1 HashSet

结论3.1.1 HashSet无参构造增添元素3.1.2 扩容机制和转成红黑树机制 3.2 linkedHashSet3.3 TreeSet

3.3.1 无参构造 增添元素 四.Map类集合

4.1 HashMap

4.1.1无参构造 put方法源码4.2 Hashtable

4.2.1 Hashtable扩容机制 4.3 Properties4.4 TreeMap 五.Collections工具类


前言

本文章主要针对源码解读.扩展内容很少.

详尽的debugger底层查看源码配置

一.集合体系图


增强for循环的底层也是迭代器,因此只要实现了Collection接口的都可以使用

二.List类集合 2.1.1 ArrayList

ArrayList可以加入多个null,可以放任何值ArrayList是由数组实现数据存储的ArrayList基本等同于Vector,除了ArrayList线程不安全,执行效率高.(在多线程的情况下不建议用ArrayList)

// ArrayList线程不安全,可以看源码没有synchronized
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
2.1.2ArrayList底层源码分析 结论:

1. ArrayList 中维护了一个Object类型的数组 transient Object[] elementData;

2. 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍.
3. 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。

2.1.3 使用ArrayList无参构造

ArrayList无参构造器为我们创造了一个空的elementData数组

2.1.4**无参构造下ArrayList.add()扩容机制**

可能有人会对add中size的参数产生疑惑.现做以下解答:

全局变量
char‘/u0000’
byte0
short0
int0
long0L
float0.0L
double0.0d
booleanfalse
引用类型复杂类型null

add方法中的grow调用了扩容方法.
modCount++,记录当前集合被修改的次数.防止多线程同时修改.

    
     //minCapacity 传入1
    private Object[] grow(int minCapacity) {
    	// 将旧数据复制到新扩容的数组,保护原数据还在
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

    private Object[] grow() {
    	// 无参构造size为类变量,初始值为0;因此这里参数为1
        return grow(size + 1);
    }
    
    
     
     // minCapacity为1
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        // 保存当前数组长度
        int oldCapacity = elementData.length;
        // 新长度 = 旧长度 + 旧长度/2. 即为原长度的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 当是空数组,即无参第一次添加元素执行
        if (newCapacity - minCapacity <= 0) {
        	// 当数组长度为0
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            	//获得二者较大值,DEFAULT_CAPACITY默认为10. 
            	// 这里比较的原因:当是有参构造就可以初始化min,同样可以符合条件进入比较。这时如果初始化的数值大于10就取min
                return Math.max(DEFAULT_CAPACITY, minCapacity);
                // 越界错误
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        // 判断新开辟的数组容量是否超过虚拟机设定内存的最大限度
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
2.1.5ArrayList有参构造

ArrayList有参构造扩容机制和无参类似.只是初始化容量给多少就是多少.

2.2.1vector 结论

Vector的底层也是一个对象数组,protected Object[] elementData;Vector是线程安全的如果是无参,默认10满后,就按2倍扩容;如果指定大小,则每次直接按2倍扩容 2.2.2以添加方式为例


2.2.3vector的扩容机制 无参构造

vector底层类似于Arraylist,仅多了同步锁和初始默认10不做具体解释,请看源码
默认构造容量为10的数组

2.3.1 linkedList 结论

linkedList底层实现了双向链表和双端队列特点可以添加任意元素(元素可以重复),包括null线程不安全linkedList底层维护了一个双向链表linkedList中维护了两个属性first和last分别指向首节点和尾节点每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.所以linkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。 2.3.2以添加方法为例

last维护整个链表的尾节点,first维护整个链表的头节点.Node的三个参数.代表单个节点的前驱,元素,后继.

2.3.3删除例子

默认删除第一个元素


    
    private E unlinkFirst(Node f) {
        // assert f == first && f != null;
        final E element = f.item;
        // 这里的赋值和c指针不同! next和f.next均指向同一块内存地址.并非next指向f.next
        final Node next = f.next;
        // 删除首部节点元素
        f.item = null;
        // 将首部节点的后继置null
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
        // 将 next的前驱置null;使首部节点未被引用被gc回收
            next.prev = null;
        size--;
        // 记录当前修改次数
        modCount++;
        return element;
    }
2.4 List集合选择
集合底层结构增删效率改查效率
ArrayList可变数组较低,数组扩容快,索引定位
linkedList双向链表较高.通过链表追加
三.set集合

元素无序(但按照一定算法规则输出,取出的顺序是固定的),没有索引不允许重复添加同一个元素,只能包含一个null子类有HashSet和TreeSet 3.1 HashSet 结论

HashSet的底层是HashMap (HashMap的底层是数组+链表结构+红黑树)HashSet不保证元素是有序的,取决于hash后,再确定索引的结果.可以存放null,但只能是一个不能有重复的元素或值(相同的值经过hash算法得到的索引位置的值equalsequals依据程序员标准比较.相同放弃添加,不相同,在该位置生成链表追加)

1. HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75 =12

2. 如果table数组使用到了临界值12,就会扩容到16*2 = 32,新的临界值就是32*0.75 = 24,依次类推

3. 在Java8/11中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

4.只要添加了元素,size++.也就是说总元素到达阈值就会触发扩容或树化

5. 若链表元素到了8个,而table表的大小不到64,那么table表会按照2倍扩容

3.1.1 HashSet无参构造增添元素

1.HashsSet底层就是HashMap

2.执行add方法添加"java"值


putVal

    
     //  return putVal(hash(key), key, value, false, true);
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // 定义的辅助变量
        Node[] tab; Node p; int n, i;
        // talbe 是hashMap的一个放Node节点的数组
        // if 表示如果当前table是null,或大小等于0,就是第一次扩容到16个
        if ((tab = table) == null || (n = tab.length) == 0)
        	// 下面细讲resize方法,这里是重新计算并开辟表的空间.无参构造第一次初始化为16
            n = (tab = resize()).length;
        // 根据传入key的hash值去计算该key应该放到table表的哪个索引位置
        //(& 按位与操作,一定比最小的还要小!控制了不会越界) 
        // 并把这个索引对应的对象赋给p 
        // 判断这个p是否为null
        	// 如果为null,就创建一个节点Node(key="java",value=PRESENT)
        if ((p = tab[i = (n - 1) & hash]) == null)
        // value是被共享的PRESENT对象
            tab[i] = newNode(hash, key, value, null);
        else {
        	// 开发技巧提示: 定义辅助变量时,在需要局部变量(辅助变量)的时候再创建
            Node e; K k;
            // 如果当前索引位置对应链表的的第一个元素和准备添加的key的hash值一样
            //满足两个条件之一:如果p指向Node的key元素和准备加入的元素是同一个对象  或者 键不为空,内容相同(equals可能被重写) 就不能加入
            if (p.hash == hash && 
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
                // 判断p是不是一颗红黑树
                // 如果是一颗红黑树就调用putTreeval方法,下面详解putTreeval红黑树
            else if (p instanceof TreeNode)
                e = ((TreeNode)p).putTreeval(this, tab, hash, key, value);
                // 启用索引位置处链表循环比较
            else {
            	// 依次和该链表每一个元素比较
                for (int binCount = 0; ; ++binCount) {
            		// 到最后都不相同,则加入到链表的最后
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 添加后,立即判断该节点是否到达8个节点
                        // 对当前链表,进行树化(红黑树)
                        // 注意,在转成红黑树时,还得进行判断表的容量必须大于64.否则先扩容表容量
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 在比较的过程中,如果有相同的直接break
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 统计修改次数
        ++modCount;
        // 检查大小是否是原表的3/4.第一次就是12.超过就扩容
        if (++size > threshold)
            resize();
         // hashmap交给子类如linkedHashMap使用
        afterNodeInsertion(evict);
        // 返回null代表成功
        return null;
    }

3.1.2 扩容机制和转成红黑树机制

1. HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75 =12

2. 如果table数组使用到了临界值12,就会扩容到16*2 = 32,新的临界值就是32*0.75 = 24,依次类推

3. 在Java8/11中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

4.只要添加了元素,size++.也就是说总元素到达阈值就会触发扩容或树化

模拟代码

import java.util.HashSet;


public class HashSetStructure {

    public static void main(String[] args) {
        HashSet objects = new HashSet<>();
        // 模拟扩容
        for (int i=1;i<=100;i++){
            objects.add(i);
        }
        // 模拟树化
        for(int i=1;i<=12;i++){
            objects.add(new A(i));
        }
    }

}

class  A{
    private int n;

    public A(int n) {
        this.n = n;
    }

    public A() {
    }
    @Override
    public int hashCode() {
        return 100;
    }
}

 

resize扩容

    
    final Node[] resize() {
    	// 存储当前表的数据
        Node[] oldTab = table;
        // 当返回0,则是初始化表.否则保存旧表的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 保存旧阈值
        int oldThr = threshold;
        int newCap, newThr = 0;
        // 当旧数据元素>0
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        // 首次无参构造初始化进入
        else {               // zero initial threshold signifies using defaults
        	// 新容量默认值 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
            newCap = DEFAULT_INITIAL_CAPACITY;
            // 装填因子 DEFAULT_LOAD_FACTOR=0.75 ; DEFAULT_INITIAL_CAPACITY 同上注解
            // 即newThr为12时扩容,有个扩容提前量作为缓冲,防止突然大量线程填入,造成堵塞
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
       // 获得新阈值
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node[] newTab = (Node[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node loHead = null, loTail = null;
                        Node hiHead = null, hiTail = null;
                        Node next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

当在Java8/11中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),并且table的大小>= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

putTreeval
红黑树的解读

        
        final TreeNode putTreeval(HashMap map, Node[] tab,
                                       int h, K k, V v) {
            Class kc = null;
            boolean searched = false;
            
            TreeNode root = (parent != null) ? root() : this;
            for (TreeNode p = root;;) {
                int dir, ph; K pk;
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode q, ch;
                        searched = true;
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;
                    }
                    dir = tieBreakOrder(k, pk);
                }

                TreeNode xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node xpn = xp.next;
                    TreeNode x = map.newTreeNode(h, k, v, xpn);
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    xp.next = x;
                    x.parent = x.prev = xp;
                    if (xpn != null)
                        ((TreeNode)xpn).prev = x;
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。当元素小于 8 个的时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于 8 个的时候, 红黑树搜索时间复杂度是 O(logn),而链表是 O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。用这个树结构保证了,树深较小.

3.2 linkedHashSet

1) linkedHashSet是 HashSet的子类
2) linkedHashSet底层是一个 linkedHashMap,底层维护了一个数组+双向链表
3)linkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
4)linkedHashSet 不允许添重复元素
5)添加第一次时,直接将数组table 扩容到16,存放的结点类型是 linkedHashMap$Entry
6)数组是 HashMap$Node[]存放的元素/数据是 linkedHashMap$Entry类型


详细结构和HashSet如出一辙.

3.3 TreeSet

Integer有默认排序规则, String有默认排序规则. 自定义的类存储的时候出现异常(没有顺序)可以排序, 但是需要指定排序规则如果想把自定义类的对象存入TreeSet进行排序, 那么必须实现Comparable接口

重写compareTo()方法使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则TreeSet底层不存在转型机制,因此只能添加同种类型TreeSet底层是TreeMap 3.3.1 无参构造 增添元素

    public V put(K key, V value) {
        Entry t = root;
        if (t == null) {
             //第一次添加,把k-V封装到 Entry对象,放入root
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry parent;
        // split comparator and comparable paths
        // 用户构造的比较器赋值给cpr
        Comparator cpr = comparator;
        // 用户构造了比较器执行
        if (cpr != null) {
            do {
                parent = t;
                // 调用用户的比较器规则
                cmp = cpr.compare(key, t.key);
                // 有序树
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                // 如果出现值相同,则不能加入
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable k = (Comparable) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

详细结构和TreeMap如出一辙

四.Map类集合 4.1 HashMap

大部分已在HashSet中讲述
1) Map接口的常用实现类:HashMap、Hashtable和Properties.
2) HashMap是 Map接口使用频率最高的实现类。
3) HashMap 是以 key-val对的方式来存储数据(HashMap$Node类型)[案例Entry]
4) key不能重复,但是值可以重复,允许使用null键和nulk值。
5)如果添加相同的key,则会覆盖原来的key-val ,等同于修改.(key不会替换,val会替换)
6)与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的.
7) HashMap没有实现同步,因此是线程不安全的

4.1.1无参构造 put方法源码

HashMap的底层是数组+链表结构+红黑树)不保证元素是有序的,取决于hash后,再确定索引的结果.可以存放null,但只能是一个不能有重复的元素或值(相同的值经过hash算法得到的索引位置的值equalsequals依据程序员标准比较.相同放弃添加,不相同,在该位置生成链表追加)

1. HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75 =12,树化后时0.8,这和数学概率论中的泊松分布有关

2. 如果table数组使用到了临界值12,就会扩容到16*2 = 32,新的临界值就是32*0.75 = 24,依次类推

3. 在Java8/11中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

4.只要添加了元素,size++.也就是说总元素到达阈值就会触发扩容或树化

5. 若链表元素到了8个,而table表的大小不到64,那么table表会按照2倍扩容

装填因子在树化前是0.75的原因

Node[] table的初始化长度length(默认值是16),Load
factor为负载因子(默认值是0.75),threshold是HashMap所能容纳键值对的最大值。threshold = length

Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。

默认的loadFactor是0.75,0.75是对空间和时间效率的一个平衡选择,一般不要修改,除非在时间和空间比较特殊的情况下 :

如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值 。

相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。

putVal

    
     //  return putVal(hash(key), key, value, false, true);
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // 定义的辅助变量
        Node[] tab; Node p; int n, i;
        // talbe 是hashMap的一个放Node节点的数组
        // if 表示如果当前table是null,或大小等于0,就是第一次扩容到16个
        if ((tab = table) == null || (n = tab.length) == 0)
        	// 下面细讲resize方法,这里是重新计算并开辟表的空间.无参构造第一次初始化为16
            n = (tab = resize()).length;
        // 根据传入key的hash值去计算该key应该放到table表的哪个索引位置
        //(& 按位与操作,一定比最小的还要小!控制了不会越界) 
        // 并把这个索引对应的对象赋给p 
        // 判断这个p是否为null
        	// 如果为null,就创建一个节点Node(key="java",value=PRESENT)
        if ((p = tab[i = (n - 1) & hash]) == null)
        // value是被共享的PRESENT对象
            tab[i] = newNode(hash, key, value, null);
        else {
        	// 开发技巧提示: 定义辅助变量时,在需要局部变量(辅助变量)的时候再创建
            Node e; K k;
            // 如果当前索引位置对应链表的的第一个元素和准备添加的key的hash值一样
            //满足两个条件之一:如果p指向Node的key元素和准备加入的元素是同一个对象  或者 键不为空,内容相同(equals可能被重写) 就不能加入
            if (p.hash == hash && 
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
                // 判断p是不是一颗红黑树
                // 如果是一颗红黑树就调用putTreeval方法,下面详解putTreeval红黑树
            else if (p instanceof TreeNode)
                e = ((TreeNode)p).putTreeval(this, tab, hash, key, value);
                // 启用索引位置处链表循环比较
            else {
            	// 依次和该链表每一个元素比较
                for (int binCount = 0; ; ++binCount) {
            		// 到最后都不相同,则加入到链表的最后
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 添加后,立即判断该节点是否到达8个节点
                        // 对当前链表,进行树化(红黑树)
                        // 注意,在转成红黑树时,还得进行判断表的容量必须大于64.否则先扩容表容量
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 在比较的过程中,如果有相同的直接break
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 如果传入的key不为空
            if (e != null) { // existing mapping for key
            	// 获得该索引位置的值
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                	// 将值替换成当前key
                    e.value = value;
                afterNodeAccess(e);
                // 返回被覆盖的key
                return oldValue;
            }
        }
        // 统计修改次数
        ++modCount;
        // 检查大小是否是原表的3/4.第一次就是12.超过就扩容
        if (++size > threshold)
            resize();
         // hashmap交给子类如linkedHashMap使用
        afterNodeInsertion(evict);
        // 返回null代表成功
        return null;
    }

4.2 Hashtable

1)存放的元素是键值对:即K-V2) hashtable的键和值都不能为null
2) hashTable使用方法基本上和HashMap一样
3) hashTable是线程安全的,hashMap是线程不安全的
4)扩容方式,当达到阈值时,就扩容当前容量的2倍+1


Hashtable底层就是Entry数组

4.2.1 Hashtable扩容机制
public synchronized V put(K key, V value) {
        // Make sure the value is not null
        // 值为空抛出异常
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        int hash = key.hashCode();
        // 降低hash碰撞,用hash值确定索引位置
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry entry = (Entry)tab[index];
        // 遍历数组
        for(; entry != null ; entry = entry.next) {
        //如果出现键相同,并且key的值也相同.就覆盖并返回被覆盖的值
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
		// 添加新元素
        addEntry(hash, key, value, index);
        return null;
    }

    private void addEntry(int hash, K key, V value, int index) {
        Entry tab[] = table;
        // 当元素个数大于阈值,就扩容
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();
            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry e = (Entry) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
        modCount++;
    }
 protected void rehash() {
        int oldCapacity = table.length;
        Entry[] oldMap = table;

        // overflow-conscious code
        // 扩容方式,当达到阈值时,就扩容当前容量的2倍+1
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry[] newMap = new Entry[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry old = (Entry)oldMap[i] ; old != null ; ) {
                Entry e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry)newMap[index];
                newMap[index] = e;
            }
        }
    }
4.3 Properties

Properties类继承自Hashtable类并且实现了Map接口.可以用来读取Properties配置文件
java读取Propertis配置文件

4.4 TreeMap

和TreeSet唯一不同的是value变成动态可替换的值.基本包装类型根据key进行自然排序,String类型根据从首开始比出ascll大小终.Integer有默认排序规则, String有默认排序规则. 自定义的类存储的时候出现异常(没有顺序)可以排序, 但是需要指定排序规则如果想把自定义类的对象存入TreeMap进行排序, 那么必须实现Comparable接口

    public V put(K key, V value) {
        Entry t = root;
        if (t == null) {
        //第一次添加,把k-V封装到 Entry对象,放入root
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry parent;
        // split comparator and comparable paths
        // 用户构造的比较器赋值给cpr
        Comparator cpr = comparator;
        // 用户构造了比较器执行
        if (cpr != null) {
            do {
                parent = t;
                // 调用用户的比较器规则
                cmp = cpr.compare(key, t.key);
                // 有序树
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                // 如果出现值相同,则不能加入
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
        // key为空抛出异常
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable k = (Comparable) key;
            // 根据key进行自然排序
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                // 发现值相同不能添加
                    return t.setValue(value);
            } while (t != null);
        }
        Entry e = new Entry<>(key, value, parent);
        // 有序树
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
五.Collections工具类

Collections是一个操作 Set、List 和 Map等集合的工具类详见jdk文档

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号