Java容器(Java复习一)

集合分类

  • List:有序的,可重复

    • ArrayList:底层数据结构是数组,查询快,增删慢;原因是查询可直接定位到每一元素,而增删可能需要遍历所有元素;扩容相对消耗资源(默认大小为10,每次扩容为原来的1.5倍,int newCapacity = oldCapacity + (oldCapacity >> 1);),原因是,需要创建一个新的数组,并将原有的元素复制到新数组中,故使用ArrayList时最好先确定其容量大小。
    • LinkedList:1.7之前底层数据结构是循环双向链表,查询慢,增删快;原因是链表定位某一元素可能需要遍历所有元素,而增删只需要改变元素的前置和后置应用即可,且无需扩容;1.7之后底层数据结构变成了双向链表,加了first和last节点来标志首尾,原因是更加清晰,且在最后插入元素也无需修改头结点,减少操作
    • Vertor:底层数据结构是数组,查询快,增删慢;原因是底层数据结构是数组;线程安全,效率低,原因是在所有操作数据的方法上加入了synchronized字段;
  • Set:无序的,唯一的

    • HashSet:底层数据结构是HashTable,实际实现是通过HashMap进行实现,值存储在HashMap的Key之中,而HashMap的Value值则是一个常量
    • TreeSet:底层数据结构是红黑树,通过红黑标记使二叉树保持平衡,降低树的高度,提高遍历效率,可通过元素实现Comparable和结合构造器中加入Comparator的方式进行排序

HashMap数据结构

HashMap在JDK1.7的数据结构是Hash表+链表,而在JDK1.8中做了优化,当链表长度达到8时,将链表转换为红黑树进行存储。如下图:
在这里插入图片描述
插入值的操作:首先计算key值的hash值,得到的计算结果为数组下标在HashTable中查找,查看对应位置是否有值,若无值则进行插入,若有值,查看key值是否相等,相等则覆盖,不相等则查看是否为链表,且链表的长度是否小于等于8,未达到则在链表后插入,若达到了则将链表转换为红黑树进行插入。具体查看下图:
在这里插入图片描述

HashMap为何线程不安全

  • jdk1.7中resize方法会造成链表循环,从而get时,会存在死循环;put方法时,两个数据都获取了桶的位置,插入时,导致一个元素被覆盖,从而数据丢失
  • jdk1.8中没有resize方法的问题,但是有put方法导致的问题

如何使Collection线程安全

  1. 通过线程安全的集合,如Vector和HashTable,效率较低,原因是在每个操作数据的方法上加入了synchronized字段,加锁的范围过大导致效率低下
  2. 通过CollectionUtils.synchronizedCollection方法来装饰集合,在操作数据的方法上加入了synchronized字段,加锁的范围过大导致效率低下
  3. 使用JUC包下的线程安全类,缩小加锁的范围,且通过自旋的方式来进行加锁,通过CPU计算来换取阻塞和唤醒的时间

ConcurrentHashMap

  • jdk1.7:采用的是分段锁的形式,将数组分割成多个小的数组来进行加锁,从而不同的段之间有不同的锁,减少了插入时竞争一把锁的情况,提高了效率。在进行Hash值计算时,需要定位两次,首先定位Segment数组,再定位到具体的位置。其中put数据需要加锁,get数据不需要加锁,size方法前三次先不加锁,自旋比较值是否一致,一致就返回,不一致则在每个Segment上加锁计算
  • jdk1.8:采用的是在每个数组节点的头部进行加锁。put方法首先判断有无Hash冲突,没有Hash冲突的情况下通过CAS插入,存在Hash冲突则通过加锁来保证线程安全,相对于jdk1.7加锁的范围更小,效率更高,get数据也不会加锁,而size方法是在操作元素的addCount方法中已经进行了统计
    ConcurrentHashMap原理分析(1.7与1.8)

链表环问题

链表如何判断是否有环

1.使用两个指针,一个步长为1的慢指针,一个步长为2的快指针,从头节点进行遍历,若两个指针相遇,则有环。原因是进入环后,两者步长相差1,所以进入环内后,快指针能够追上慢指针

链表如何计算环大小

  1. 慢指针和快指针第一次相遇的点,慢指针进行遍历,遍历到相遇点后的步长就是环大小
  2. 慢指针和快指针第一次相遇的点,慢指针和快指针同时遍历,下次相遇时走过的步长就是环大小,原因是两者步长差1

链表的入口在哪

  1. 快指针从慢指针和快指针第一次相遇的点以步长为1出发,慢指针从头结点出发,相遇的点就是环的入口,推导过程参看链表找出环的入口

LRU与LFU

LRU

  • 原理:LRU(Least recently used,最近最少使用),淘汰最近不用的数据
  • 实现:
    1. 通过链表存储数据
    2. 新数据或更新数据插入头部
    3. 获取数据时,将获取的数据移至头部
    4. 链表满时,通过尾部进行剔除
  • 优点:实现简单,热点数据命中率较高
  • 缺点:批量或偶发性操作会使命中率急剧下降

LFU

  • 原理:LFU(Least Frequently Used),淘汰最近使用次数最低的
  • 实现:
    1. 通过一个Map缓存数据,一个Map缓存key的操作次数和时间
    2. 新数据或更新数据记录插入Map,并记录操作次数和操作时间
    3. 获取数据时,更新操作记录和时间
    4. 达到容量时,通过Collections.min()方法定位到记录操作次数和时间最小的值,进行剔除
  • 优点:通过加入使用次数,避免了数据污染的情况
  • 缺点:实现较为复杂,访问模式改变后,历史数据会对新的访问模式后的数据有一定影响

两种常见的缓存淘汰算法LFU&LRU
LRU和LFU缓存置换算法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值