【java基础】集合框架的一些细节

ArrayList

扩容

add方法扩容时,如果数组将要满。则扩容至原容量的1.5倍
addAll方法扩容时,如果要扩容,在扩容容量*1.5原来数组容量+新增数组大小两个值中选择最大值进行扩容。

初始化

无参情况

  • 1.8之前默认为10
  • 1.8 使用懒加载,未被使用时为0,使用后为10

有参情况

  • 为设置的参数。

迭代器处理策略 Fail-fast和Fail-safe

两种策略为迭代器在迭代的时候如果有其他线程修改集合,所给出的不同处理方法。

Fail-fast

在迭代过程中,只要有其他线程修改集合,就检测出来报错。

原理

private class Itr implements Iterator<E> {
    int expectedModCount = modCount;
    
  final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
 ...       

迭代器内部有一个整数值expectedModCount,在迭代器初始化的时候将其变为集合中的modCount,
集合中modCount保存的是集合中被修改的次数。
若集合被修改,modcount++,但是迭代器内部expectedModCount没有改变,当两者不相同时报出错误ConcurrentModificationException
应用场景
Arraylist

Fail-safe

在迭代过程中牺牲一致性,保证代码继续执行下去。
遍历的同时可以修改,原理是读写分离。

原理
将当前遍历数组记录下来作为一个副本snapshot,在副本中继续遍历。

 public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

在添加元素时新建一个数组,将原数组值拷贝到新数组,然后再进行添加。

应用场景
CopyOnWriteArrayList

ArrayList和LinkList的区别

  1. ArrayList基于数组的,内存空间连续,LinkList基于链表,内存空间不连续。
  2. ArrayLIst能够随机访问,实现了RandomAccess接口。LinkList不能随机访问。
  3. (重要)增删性能区别,很多人都说数组在增加删除元素慢,修改元素快,链表增加删除元素快,修改元素慢。但是这是一个很错误的说法。 增删性能上,ArrayList末尾增加删除新元素有较好的性能,LinkList在头部和尾部增删性能较好,但对于中间的增删改,LinkList的性能远远不如ArrayList。
  4. 内存上,ArrayList内存空间连续,根据局部性原理可以很好地支持缓存。LinkList内存空间不连续,而且因为存储指针,占用内存大小也大于ArrayList。

HashMap

new Node(Hash,key,value,next);

底层数据结构

1.8 数组+链表+红黑树
1.7 数组+链表
链表元素多的时候转化为红黑树,红黑树元素少的适合转化为链表

最大优点

能够快速查找元素
原理
Object.hashcode->二次hash->按位与运算->桶下标

扩容

扩容是为了让链表长度缩小,提升元素查找效率。

1 扩容因子为什么是0.75?

  • 在空间占用和查询时间做出的较好的平衡,
  • 大于这个值,节省了空间,但是链表可能较长。
  • 小与这个值,链表可能端,但是空间占用多。

2 扩容容量为什么是2的倍数

(1) 计算索引时, n%length==n&(length-1) 在2的n次方时有效 :按位与运算。

(2) 扩容时,hash值与原始容量与运算,如果是0,不变,不是0,则移动到原始位置+原始容量的位置。传承链表后只移动一次。

扩容容量为2的倍数是为了性能更高。即使质数分布更均匀。

3 二次hash的目的是什么

扰动,让索引的hashcode分布更均匀

return (key==null)?0:(h=key.hashCode())^(h>>>16);  
//原始hash和右移16位得到的新hash进行异或   1.8中

4 扩容后的位置怎么确定

扩容后会重新计算桶下标。将二次hash值与原始容量与运算,如果为0则在原来位置,如果不为0在向后移动原始容量个位置,最终位置为原始容量+原始位置。

红黑树

红黑树是平衡二叉查找树,左边的都比父节点小,右边的都比父节点大。

1 什么时候转换为红黑树

当链表长度大于8的时候,并数组容量大于64的时候。

2 为什么阈值为8?

转换为红黑树为异常情况,正常业务中的元素分布符合泊松分布,元素个数大于8的可能性为一亿分之6,添加红黑树的主要目的是为了防止Dos攻击。

3 采用红黑树的好处有什么

采用红黑树可以在链表较长时提高元素查找效率,红黑树查找元素的时间复杂度为log2N。

4 红黑树什么时候退化为链表

  1. 扩容后,当红黑树元素个数小与等于6时。
  2. 删除元素后,当根节点,根节点的右孩子,左孩子,左孙子有一个为null的时候。

索引

索引如何计算:key-》hashcode->2次hash-》按位与得出桶下标。 前提:2的n次幂
当做key的对象一般要重写hashcode和equal方法,同时key对象内容要求不可变。

hashCode和equal

两者方法都需要重写才能作为key。
hashcode要求结果尽可能均匀。
hashcode相同equal不一定相同,但equal相同,hashcode一定相同。

put流程

  1. 懒惰创建数组,首次使用时创建数组
  2. 计算索引
  3. 如果索引没有元素,创建node,返回。
  4. 如果索引有元素,用equal方法判断值是否相同,值相同则覆盖,值不同则:
    1. 如果是链表,则添加元素,添加后检查是否满足条件转换为红黑树
    2. 如果是红黑树,则添加节点。
  5. 添加完成元素后,检查容量是否大于负载因子,如果大于,进行扩容。

多线程环境

1 数据错乱问题

1.7和1.8都存在 ,发生数据丢失。

2 扩容死链问题

1.7中存在。

当添加元素是进行扩容,多线程环境下可能会导致元素相互依赖,产生循环链。进而在查找元素的时候产生死循环。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值