Java—Map原码分析

Map接口是保存一元偶对象的最大接口:

**区别:**TreeMap和HashMap(都是有序的Map集合):

LinkedHashMap是HashMap的子类,有序Map,序指的是插入顺序,元素的添加顺序;

而TreeMap有序Map,序指的是Comparator或Compareable

常用方法?

put和get

3.Map集合遍历

Map—>Set?

Set接口与Map接口的关系:

Set接口是穿了马甲的Map接口,本质上Set接口的子类都是使用Map来存储元素的,都是讲元素存储到Mapkey值而已,value都是用共同的一个空Object对象。

4.常见子类的解析:

##HashMap:

1.成员变量:树化和数据结构:

初始化容量:(桶的数量)

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //16

static final int MAXIMUM_CAPACITY = 1 << 30;

负载因子:(扩容时用到的)

static final float DEFAULT_LOAD_FACTOR = 0.75f;

树化的阈值(门限值)

static final int TREEIFY_THRESHOLD = 8;

树化的最少元素个数 默认64

static final int MIN_TREEIFY_CAPACITY = 64;

解树化,返回链表的阈值:

static final int UNTREEIFY_THRESHOLD = 6;

真正存储元素的Hash表

transient Node<K,V>[] table;

树化逻辑:当一个桶中链表元素个数>=8并且哈希表中所有元素个数加起来超过64,此时会将此桶中链表转为红黑数结构。

将链表变为哈希数的原因是,为了提高查找效率,由于链表过长而导致查找太慢,由原来的O(n)变成Olog(n),最主要的是减少Hash碰撞(安全性问题)

若只是链表个数大于8,哈希表元素不超过64,此时只是简单的resize而已,并不会树化

2.构造函数

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

HashMap的无参构造只是初始化负载因子而已(不在对象产生是初始化哈希表)。HashMap同样采用lazy—load策略

HashMap可以传入初始化容量和负载因子。

3.put与get流程

put():

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //取得当前元素的哈希表
    //如果当前hash表还未初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        //resize()完成哈希表的初始化操作
        n = (tab = resize()).length;
    //哈希表的(长度-1)取哈希,根据key值hash后的桶得到桶下标,并且此时桶中元素个数为空
    if ((p = tab[i = (n - 1) & hash]) == null)
        //将要保存的结点放置到此桶的第一个元素
        tab[i] = newNode(hash, key, value, null);
    else {//Hash表已经初始化
        Node<K,V> e; K k;
        //否则当key值相等时,替换value值
        //p.hash是当前要放置元素的hash。 
        //要插入的结点是当前桶,要插入的k与
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //hash(key)桶中元素不为空,判断此桶是否已经树化
        else if (p instanceof TreeNode)
            //调用书画有的方法将新结点添加到红黑树中
            e = ((TreeNode<K,V>)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);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    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;
    //判断添加元素之后整个哈希表是否超过threshold = 容量(默认16)*赋载因子
    if (++size > threshold)
        //若超过,调用resize()方法扩容
        resize();
    afterNodeInsertion(evict);
    return null;
}

1.若hash还未初始化,先进性哈希表的初始化操作(默认初始化为16个桶)。

2.对传入的key值做hash得出要存放该元素的桶编号。

​ a.若无发生碰撞,即头结点为空,将该节点直接存放到桶中,作为头结点

​ b.若发生碰撞,

​ 此桶中的链表已经树化,将结点构造成树节点后加入红黑树

​ 链表还为树化,将结点作为链表最后一个元素插入链表

3.若hash表中存在key值相同的元素,替换最新的value值。

4.若桶满了 ,判断resize++是否大于Threshold,调用resize()方法扩容hash表

threshold=容量(默认16)*赋载因子

4.哈希算法,扩容,性能

HashMap中的hash方法:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

1.调用key值的hashCode():但是因为数值太大,如果作为桶下标,所以无法碰撞,将高位的16移到低位。hash表就和普通数组没有区别。(每个元素Hash出来都在不同的桶中);

2.为何取出h>>>16?

为何取出key值的高16位右移16位参与hash运算?

因为hash基本上是在高16位进行hash运算。

3.为何HashMap容量均为2n(如果传入的容量不知2n,将其扩容成最近的2^n)?

要用与运算代替取模运算提高效率。

(n-1)&hash:当n为2^n,此时的位运算就相当于hash%(n-1);

get()

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

getNode(int hash, Object key)

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            //当前节点key值刚好是第一个节点,变量桶的其他节点,返回其value
            return first;
        //遍历桶的其他节点找到指定key返回其value
        if ((e = first.next) != null) {
            //树的遍历
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            //链表的遍历
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

1.当表还未初始化,或者key值为null,返回空值

2.表已经初始化并且key不为null

​ a.key值刚好是桶的头结点,直接返回

​ b.遍历桶的其他节点

​ ·若已经树化,调用树的变量方式找到key对应的结点Node返回

​ ·调用链表的遍历方式找到指定key对应的Node返回

重点

HashMap的resize()方法:

扩容桶的个数,扩容为原Hash表的二倍,

原来桶中的元素会进行一个rehash的过程,要么在原桶,要么在其二倍的桶中。

final Node<K,V>[] resize() {
    //当前hash表
    Node<K,V>[] oldTab = table;
    //如果当前表为空,容量为0
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        //当前Hash表已经到大最大值,返回最大值
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //扩容原hash表的二倍
        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
        newCap = DEFAULT_INITIAL_CAPACITY;
        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<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    
    //对原hash表的元素进行rehash
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> 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<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> 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;
}

1、赋值哈希表的初始化操作

2、当表中元素个数到达阈值:容量*负载因子后进行扩容操作

3.扩容后,原来元素进行rehash:要么元素还呆在原桶中,要么呆在double桶中

性能问题

1.在多线程线程下:

在竞争激烈的场景下使用HashMap会造成CPU彪到100%,解决:

使用:ConcurrenHashMap来代替HashMap

2.性能的主要开销:

在resize()方法后的rehash过程,并且开销越来越大。

解决:在能预估存放元素个数的前提下传入适当的初始化参数来尽量避免resize()过程

补充:

在resize过程中若发现桶下的红黑树结点,小于<=UNTREEIFY_THRESHOLD,会将红黑树解除树化还原为链表结构。

HashTable

单纯的Hash表实现,用的是synchronized;

在put、get、remove等方法上使用方法级别的内建锁,锁的是Hashtable对象,即整个Hash表。

问什么效率低:两个线程共同访问Hashtable,但是访问是不同的桶,这时线程1,拿到锁,即便线程2,要进入的桶与线程1不冲突,还是无法访问桶。

如何此性能问题优化?

JDK8之前ConcurrentHashMap思路:(JDK7)

通过锁细粒度化,将整表拆分为多个锁进行优化。

实现思路:

将原先的16个桶设计为16个Segment,每个Segment都有独立的一把锁,拆分后的每个Segment都有相当于原先的一个HashMap(double—hash设计,并且Segment在初始化后无法扩容,每个Segment对应的哈希表可以扩容,扩容Segment下的哈希表)。

线程安全:使用ReentranLock保证相应Segment下的线程安全

JDK8下的ConcurrentHashMap

整体结构与HashMap别无二致,都是使用哈希表+红黑数结构

线程安全:使用内建锁synchronized+CAS锁每个桶的头结点,使得锁进一步细粒度化。

ConcurrentHashMap不允许键值对为空。

JDK7和JDK8 ConcurrentHashMap的变化:

1.结构上的变化:取消原来的Segment设计,取而代之的是使用与HashMap同样的数据结构,

即哈希表+红黑数,并引入了懒加载。

2.线程安全上变化:

锁粒度更细:由原来的锁一篇区域到锁桶的头结点,

由原先的RenntrantLock替换为Synchronize+CAS:现版本的synchronized已经进过了不断优化性能上与ReentrantLock基本没有差异,并且相对于ReentrantLock,节省了大量内存空间,这是问什么替换的原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
原码一位乘法是指对两个数的二进制表示进行乘法运算。为了实现这个功能,我们可以按照以下步骤进行处理: 1. 首先,我们需要确定操作数的长度,即确定要进行乘法运算的两个数的位数。 2. 对于两个n位的操作数A和B,我们需要创建一个长度为2n的数组保存乘积结果。 3. 然后,我们从右到左遍历A的每一位。对于A的第i位,如果它为1,则将B左移i位,并将结果与乘积数组相加。 4. 遍历完A的所有位后,我们就可以得到结果数组。最后,我们将结果数组转换为十进制形式,即可得到原码一位乘法的结果。 下面是一个简单的Java代码实现示例: ```java public class SignedMultiplication { public static void main(String[] args) { int a = -5; // 第一个操作数 int b = 3; // 第二个操作数 int n = Integer.SIZE - Integer.numberOfLeadingZeros(Math.max(Math.abs(a), Math.abs(b))); int[] product = new int[2 * n]; int sign = (a < 0 ^ b < 0) ? -1 : 1; // 确定结果的符号位 a = Math.abs(a); b = Math.abs(b); for (int i = 0; i < n; i++) { if ((a & 1) == 1) { product = add(product, shiftLeft(b, i)); } a >>= 1; } int result = toDecimal(product) * sign; System.out.println("The product is: " + result); } // 数组相加函数 private static int[] add(int[] a, int[] b) { int[] sum = new int[Math.max(a.length, b.length)]; int carry = 0; for (int i = 0; i < sum.length; i++) { int temp = carry; if (i < a.length) { temp += a[i]; } if (i < b.length) { temp += b[i]; } sum[i] = temp % 2; carry = temp / 2; } if (carry > 0) { sum = Arrays.copyOf(sum, sum.length + 1); sum[sum.length - 1] = carry; } return sum; } // 数组左移函数 private static int[] shiftLeft(int value, int positions) { int[] result = new int[positions + Integer.SIZE]; for (int i = 0; i < positions; i++) { result[i] = 0; } for (int i = positions; i < result.length; i++) { result[i] = (value >> (i - positions)) & 1; } return result; } // 数组转换为十进制数字 private static int toDecimal(int[] binaryArray) { int decimal = 0; for (int i = 0; i < binaryArray.length; i++) { decimal += binaryArray[i] * Math.pow(2, binaryArray.length - 1 - i); } return decimal; } } ``` 这个代码实现了原码一位乘法的功能。我们可以通过给定不同的操作数来测试代码,并得到相应的结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值