集合框架相关整理

Java8容器源码-目录
ArrayList
  • 底层数据结构是数组。线程不安全
  • 它是动态数组,可以扩容(int[] aa = new int[5],是静态数组不可扩容) ,add(E e),调用copyOf()
  // 扩容为原来的1.5倍
  int newCapacity = oldCapacity + (oldCapacity >> 1);
  // 拷贝一个新的数组放进去
  elementData = Arrays.copyOf(elementData, newCapacity);
  • add(int index, E element)

    System.arraycopy() 是个 native 方法, 而 JVM 调用 native 方法有一定开销的; 在拷贝小数组的时候, 使用 System.arraycopy() 和使用 for 循环的方式哪种更高效?

  • ArrayList是基于动态数组实现的,在增删时候,需要数组的拷贝复制

  • ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍

  • 删除元素时不会减少容量,若希望减少容量则调用trimToSize()

  • 它不是线程安全的。它能存放null值。

  • 如果想要ArrayList实现同步,可以使用Collections的方法:List list = Collections.synchronizedList(new ArrayList(…));,就可以实现同步了~,LinkedList也是

    还有另一个区别:

    ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。

LinkedList
  • 底层数据结构是链表。线程不安全

LinkedList实现了Deque接口, Deque双向队列的声明接口,而LinkedList内部实现是链表,所以说LinkedList是一个双向链表

LinkedList的类图相对于ArrayList少了RandomAccess接口,因此LinkedList是不支持随机访问的,因此LinkedList根据索引位置查找元素的速度比ArrayList

add方法调用的是linkLast也就是在链表末尾添加元素。之前咱们再介绍ArrayList新增元素的时候,每次都需要调用ensureCapacityInternal(int minCapacity)方法,确保数组空间足够,不够的话要进行扩容。

LinkedList就不需要,这也是LinkedList的主要优点:它不需要初始化容器容量大小,也就无需动态扩容。也就是说LinkedListArrayList更省内存空间,插入速度更快。

但是如果需要根据索引位置插入元素,效率就比较低,因为需要先逐个查找元素,这边看下 add(int index, E element)的实现

1、按需分配空间,不需要预先分配很多空间,更加节省内存空间
2、不支持随机访问,按照索引位置访问效率比较低,必须从头或尾顺着链表找 效率为O(N/2)
3、不管列表是否已经排序,只要按照内容查找元素,效率都比较低,必须逐个比较,效率为O(N)
4、根据索引位置进行插入和删除元素,要先定位,效率比较低,为O(N/2),但修改本身效率很高为O(1)

Vector
  • 底层数据结构是数组。线程安全,不能存放null
  • Vector是扩展1倍。

https://github.com/ZhongFuCheng3y/3y

String,StringBuffer,StringBuilder的区别
  • String value前面有final修饰,是不可变的,每次拼接都会生成新的对象
  • StringBuffer和StringBuilder都继承了AbstractStringBuilder,功能是一样的
  • StringBuffer是线程安全的,所有的public方法都加了synchronized 修饰
看Map底层原理
  • 桶排序 int [length] [10] ,先找出最大值,确定循环几次,然后%10,循环排序,先排个位,十位,百位…
  • 散列表是链表数组实现的,每个列表称为桶. 散列冲突,JDK1.8中,桶满时会从链表变成平衡二叉树,装填因子默认为0.75,如果表中超过了75%的位置已经填入了元素,那么这个表就会用双倍的桶数自动进行再散列
  • 红黑树
  • 堆排序
HashMap
  • 为什么HashMap的长度是2的整数次幂?

    位置计算公式是hash(key)&(length-1)

    (length-1): hash(key)是一个很长的数,当length是2的整数次幂时,length-1是1111,当它们做与运算之后,相当于取了hash(key)的低位数,这样不会超出散列表长度. 如果length不是2的次幂,比如length为15,则length-1为14,对应的二进制为1110,在于h与操作,最后一位都为0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,同时加大了碰撞

    hash(key): 设计者将key的哈希值的高位也做了运算(与高16位做异或运算,使得在做&运算时,此时的低位实际上是高位与低位的结合),这就增加了随机性,减少了碰撞冲突的可能性

  • 为什么HashMap的key允许空值,而HashTable却不允许

  • HashMap允许键值为null. 初始容量是16,最大容量2^31. 默认装载因子0.75f(存储量达到75%时进行扩容,扩容到大于所需长度且最接近2的整数次幂).

    为什么时默认装载因子时0.75f ? 为了达到空间与时间的平衡,散列表越满,碰撞概率越大,这是设计者经过测试得出的. 根据统计学的结果, hash冲突是符合泊松分布的, 而冲突概率最小的是在7-8之间, 都小于百万分之一了; 所以HashMap.loadFactor选取只要在7-8之间的任意值即可, 但是为什么就选了3/4这个值, 我们看了HashMap的扩容机制也就知道了;

  • put()

    判断Map为空则进行扩容resize()

    如果没有发生碰撞,则直接存入

    如果发生碰撞,且新key已存在,则覆盖旧值并返回旧值

    ------>新key不存在

    ------------->如果是TreeNode 调用二叉树添加元素((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

    ------------->如果是链表,先添加,然后判断长度是否大于8,若大于8且散列表.length<64,为HashMap扩容

    -------------------------------------------------------------------------->若大于8且散列表.length>64,转为红黑树结构

    如果当前散列表存储超过阈值(0.75f),则进行扩容

  • resize()

    1 如果table == null, 则为HashMap的初始化, 生成空table返回即可;
    2 如果table不为空, 需要重新计算table的长度, newCap= oldCap << 1(注, 如果原oldLength已经到了上限, 则newCap = oldCap); 计算出新的阈值 newThr = oldThr << 1 (或者newCap * loadFactor)
    3 遍历oldTable:
    3.1 首节点为空, 本次循环结束;
    3.2 无后续节点, 重新计算hash位, 本次循环结束;
    3.3 当前是红黑树, 走红黑树的重定位;
    3.4 当前是链表, JAVA7时还需要重新计算hash位, 但是JAVA8做了优化, 通过(e.hash & oldCap) == 0来判断是否需要移位; 如果为真则在原位不动, 否则则需要移动到当前hash槽位 + oldCap的位置;

LinkedHashMap
  • put()调用的HashMap的put方法,newNode是自己重写的

  • 在传入AccessOrder的情况下,使用get方法也是结构性的修改. 最常用的将其放在链表的最后,不常用的放在链表的最前

    LinkedHashMap<Integer,String> accessOrder = new LinkedHashMap(16, 0.75f, true);
    
  • 因为最常被使用的元素再遍历的时候却放在了最后边,在LinkedHashMap中我也没找到对应的方法来进行调用~

  • 一个removeEldestEntry(Map.Entry<K,V> eldest)方法,重写它可以删除最久未被使用的元素

  • 还有一个是afterNodeInsertion(boolean evict)方法,新增时判断是否需要删除最久未被使用的元素

连环问

StringBuffer,StringBuilder区别是啥?

什么是线程安全?

如何保证线程安全?

什么是锁?

死锁?

synchronized的实现原理是什么?

有了synchronized,还要volatile干什么?

synchronized的锁优化是怎么回事?

(锁粗化?锁消除?自旋锁?偏向锁?轻量级锁?)

知道JMM吗?(原子性?可见性?有序性?)

Java并发包了解吗?

那什么是fail-fast?

什么是fail-safe?

什么是CopyOnWrite?

那AQS呢?

那CAS呢?

CAS都知道,那乐观锁一定知道了?

乐观锁悲观锁区别是什么?

数据库如何实现悲观锁和乐观锁?

数据库锁有了解么?

行级锁?

表级锁?

共享锁?

排他锁?

gap锁?

next-key lock?

数据库锁和隔离级别有什么关系?

数据库锁和索引有什么关系?

什么是聚簇索引?

非聚簇索引?

最左前缀是什么?

B+树索引?

联合索引?

回表?

分布式锁有了解吗?

Redis怎么实现分布式锁?

为什么要用Redis?

Redis和memcache区别是什么?

Zookeeper怎么实现分布式锁?

什么是Zookeeper?

什么是CAP?

什么是BASE?和CAP什么区别?

CAP怎么推导?

如何取舍?

分布式系统怎么保证数据一致性?

啥是分布式事务?

分布式事务方案?

那么,最后了,来手写一个线程安全的单例吧?

不用synchronized和lock能实现线程安全的单例吗?

这你都能答上?那好吧,你给我解释下什么是Paxos算法吧?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值