java常用类一百问

ArrayList和LinkedList

  1. 底层数据结构,分别是动态数组和双向链表。

  2. 内存分配,数组是连续内存空间且需要指定大小。链表是非连续的,且链表空间占用更大。

  3. 插入和删除元素,链表更快,只需改变链表元素指针。数组需要移动复制整个数组。

  4. 随机查找,数组更快,支持索引下标查找。链表需要遍历查找。

  5. 使用场景,数组适合存储大量数据且不需要频繁插入和删除。链表适合需要频繁插入和删除的数据。

  6. 两者都是非线程安全的。

Vector和Stack

  1. Vector 和 ArrayList 的结构基本一致,只是在每个方法上都添加了 synchronized 关键字,但由于序列化安全,性能等原因通常不被使用。

  2. Stack 是栈结构,继承 Vector,同样不推荐使用。

ArrayList 扩容

  1. ArrayList 是动态数组,在没有空间插入新元素时会进行扩容。

  2. 扩容操作是创建一个1.5倍长度的新数组,然后复制原数组元素。

快速失败和安全失败

  1. 快速失败,是指集合在每次迭代时,都会判断集合元素是否发生变化(增加或减少),如果是就会抛出异常,终止迭代。但无法发现aba的问题。todo

  2. foreach 操作是快速失败的,使用 iterator 进行迭代,可以调用其 remove 方法来删除集合元素。todo

  3. 安全失败,是指集合发生改变不会影响集合的迭代,这是因为迭代过程是复制一个原集合对象,在复制集合上操作。

  4. java.util.concurrent 包中集合类都是安全失败的,是线程安全的,缺点是迭代过程无法感知集合的变化。

CopyOnWriteArrayList

  1. CopyOnWriteArrayList 底层数据结构也是动态数组,和 ArrayList 的区别是,所有修改操作都是先对集合加锁,然后拷贝一份集合,之后在拷贝集合上进行修改,完成后再将原引用指向新拷贝的集合,最后释放锁,整个修改都是加锁是为了避免修改丢失。

  2. CopyOnWriteArrayList 的读操作不会加锁,因此修改操作是串行的,读和写之间是可以并行的。

  3. 迭代操作也是安全失败的,因为修改是在拷贝的新数组上。

  4. 缺点是修改需要额外占用内存空间,对于大集合是不友好的,且修改不是实时可见的。

Collections

  1. Collections 是一个工具类,提供了排序、查找、包装线程安全的方法。

  2. Collections.sort() 对集合进行排序(改进的归并排序)。

  3. Collections.binarySearch(list,key) 从排序集合查找元素(二分查找)。

  4. Collections.synchronizedXXX() 包装集合为线程安全对象,通过给每个方法添加 synchronized 关键字。

  5. Collections.unmodifiableXXX() 返回一个不可变集合。

数组

  1. 创建数组时需要指定大小和数据类型,此时整个内存空间已经完成分配,每个数组元素均有默认值。

  2. 数组是引用类型,分配在堆区。

  3. java 中没有多维数组,所谓的多维数组只不过是一维数组的每个元素都是一个新数组,且不要求新数组的长度都相同。

  4. Arrays 是数组工具类,提供了排序,查找,复制等方法,排序要求数据元素实现 comparable 接口(如果是基本类型,采用的是优化后的快排,如果是引用类型,采用的是改进的归并排序)。查找返回数组下标位置,返回负数表示不存在。复制是浅复制,其引用类型仍指向原始堆空间的对象。

复制数组,获取指定的长度 newLength,截取或用 null 填充
T[] Arrays.copyOf(T[] ori,int newLength) 
复制数组,在for循环复制的基础上做了优化
System.arraycopy(src,beginIndex,dest,beginIndex,length) 

HashMap

  1. 底层数据结构,是动态数组 + 链表或红黑树。当链表长度大于8且数组长度大于64时,链表结构转为红黑树,如果红黑树大小缩小到6时,又转为链表结构,这样做都是为了提高查找效率。

  2. 存入元素时,对键值key进行哈希求余得到数组下标,如果该下标处已有元素,遍历查找该下标的链表是否相同的key(equal相等),如果有覆盖该键值对的value,如果没有则在链表尾插入新键值对。

  3. 哈希取余,实际会先对 key.hashCode() 结果高 16 位和低 16 位做异或操作,这样设计混合了哈希值的高位和低位,相比于直接使用哈希值低位值,随机性更大。

  4. 解决哈希冲突有:链表(冲突元素组成链表),开放寻址(直接向后查找位置),再哈希(将冲突元素再次哈希计算位置)和公共溢出区(建立一个公共区域,存放冲突元素)等方式。

  5. hashmap 底层的数组,会始终保持 2 的整数倍,这是为了方便哈希求余,同时也减少了扩容时数组元素的迁移。length为2的倍数时,可以使用 &(length-1) 代替%length,运算效率更高。同时扩容迁移元素时,要么还在原位置,或者原位置加上数组大小的位置。

当 length 是 2 的整数次幂,那么其 2 进制就最高位是 1,其余位都是 0,那么(length-1) 是最高位是0,其余位都是 1,进行 & 运算后就是只保留了低位,去除最高位,这和取余%的效果一致,但效率会高很多。
  1. hashMap 当元素个数大于(数组大小 * 负载因子)时触发扩容,默认负载因子是 0.75,数组大小是 16,那么当加入第13个元素时触发扩容,扩容2倍大小。通过指定合适的负载因子和数组大小,能平衡空间和效率。

  2. jdk8 将链表插入方式从头插法改为尾插法,是因为头插法在多线程扩容情况下可能产生链表环,导致get操作形成死循环。并且jdk8在扩容时元素不再是重新计算哈希位置放入,而是直接放在原来位置或在原来位置的基础上移动扩容的大小。

  3. 非线程安全,多线程 put 元素时可能出现覆盖,多线程 get 和 put 时触发扩容也会造成找不到元素。

HashSet

  1. HashSet 的底层数据结构和HashMap是完全一致的,不同的是其存入元素的键值对中 value 固定是 new Object。

  2. hashSet 添加元素时,如果存在 equals 相等的元素,返回该值,表示添加失败,如果不存在返回null,表示没有相同值,添加成功。

TreeMap 和 TreeSet

  1. TreeMap 底层是红黑树的结构,是有序集合,要求存储元素实现 compare 接口,其查找,插入,删除的时间复杂度都是 O(logN)。

  2. TreeSet 底层和 TreeMap 是同样的数据结构,区别是键值对的 value 固定为 new Object()。

LinkedHashMap

  1. LinkedHashMap 在 HashMap 的基础上, 给每个键值对添加了两个指针,使得可以根据插入顺序或访问顺序(lru)来获取元素,是有序集合。

  2. 在需要键值对有序的场景,选择 LinkedHashMap。

ConcurrentHashMap

  1. 线程安全的 hashmap,jdk7 使用分段锁实现,jdk8 使用 cas+synchronized 实现,性能更高。

  2. cas + synchronized 的方式,其底层数据结构和 hashMap 完全一致。

  3. 插入元素时,如果下标元素 node[index] 为空,采用 cas 写入值,写入失败会自旋尝试若干次。如果仍失败表示 node[index] 有值。

  4. 当下标元素 node[index] 有值时,通过 synchronized 对 node[index] 处的链表加锁,只有获取到锁才能完成插入。这样加锁只发生在哈希冲突的场景,且加锁范围控制在单个数组下标,其他下标元素的操作可以并行。get 操作不需要加锁。

  5. 如果需要扩容,会阻塞所有读写操作,并会让阻塞的线程也参与到扩容操作中。

  6. 分段锁实现,底层是 segment 数组,每个 segment 元素都可以理解为一个小的 hashMap。然后操作时先定位到 segment,然后对segment 加锁,完成操作后释放锁。这样设计对不同 segment 的操作是可以并行的,相同 segment 的操作串行,保证了线程安全同时缩小了加锁范围,并发度更好。其get 操作不加锁,键值对使用 volatile 修饰。

  7. 定位 segment 是通过对键值 key 哈希求余,之后对 segment 的操作和 hashmap 完全一致。segment 个数在创建时已经固定,不能被修改,因此扩容只会增加 segment 中的 entry 数组大小。

queue

  1. 先进先出的队列操作,实现类有 LinkedList(插入顺序),PriorityQueue(优先级队列,按优先级顺序)。

  2. Deque 双向队列,支持队列头尾双向操作,方便实现先进后出的栈结构,实现类有 LinkedList。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值