一、前言

集合是java的基础。
我们有了集合,在我们开发过程中,事半功倍。我们常用的集合有这几类:Array,Map,Set,Queue等,他们每一类在java迭代升级的过程中,也是有不同的升级优化。

二、集合全局观

这个是小编画的一个集合全家福,整体上是 Map和Collection;

collection 包含我们常用的Set + Queue + List
Map 包含常用的HashMap + Hashtable + treeMap

三、依次看一下

List类

我们依次梳理一下,数组中常用的几种:

ArrayList

ArrayList可以说是我们最常用的了,基本写代码都会用到。有几个特点

  • 线程不安全
  • 基于数组,需要连续内存
  • 随机访问快,(根据下标直接访问)
  • 尾部插入、尾部删除性能可以;其他部分插入、删除都会移动数据,因此性能低
  • 可以利用cpu缓存, 局部性原理

扩容机制是什么样的?

数组扩容均是建立一个新的数组,大小会计算好的, 然后把旧数组中的数据copy过去。

  • ArrayList() 会初始化 长度为0的数组

  • ArrayList(int initialCapacity) 会初始化指定容量的数组

  • public ArrayList(Collection<? extends E> c) 会使用c的大小做为容量

  • add方法,默认是尾插法,首次扩容为10,再次扩容为上次的1.5倍,底层是通过x + x>>1 ,(向右移1位,等于x/2)。

比如,当前数组大小是15,再次扩容的时候,会计算增量 15>>1=7,最终扩容到 15+7=22

   /*** Appends the specified element to the end of this list.** @param e element to be appended to this list* @return <tt>true</tt> (as specified by {@link Collection#add})*/public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;}
  • addAll方法,比较 下一次扩容容量 和元素个数较大的。

初始空时,扩容为Math.max(10,实际元素个数);
有元素时,Math.max(原容量1.5倍,实际元素个数);

比如:

当前数组为空,addall了一个6个元素的list,那么此时元素是6个,6<10,数组容量是10个。

再addall 一个6个元素的list, 现在10个装不下,要扩容,下次扩容量为 10+10>>1 = 15,12<15,所以扩容到15.

如果我们插入6个后,我们再插入10个,那么我们就有16个元素,16>15,所以会扩容到16.

fail-fast 和 fail-safe

  • fail-fast
    ArrayList是典型代表,多线程操作,遍历的同时修改数组,立即抛出异常。

过程:在遍历之前,会把当前list的元素个数modCount记录下来,迭代的时候会记录迭代器修改的次数expectedModCount,两者会比较,如果不相等,证明被修改了,抛出ConcurrentModificationException异常。

优化方案:使用juc下的包代替java.util,如CopyOnWriteArrayList。

  • fail-safe
    CopyOnWriteArrayList是典型代表,遍历的同时可以修改,原理是读写分离。
    会copy一份数据,牺牲一致性,让遍历完成。

LinkedList

特点:

  • 基于双向链表,无需连续内存
  • 随机访问慢,(要沿着链表遍历)
  • 头部插入删除性能高
  • 占内存多 (双向链表有更多的成员变量)

Vector

特点:

  • 数组实现,内存连续
  • 线程安全,Synchronized修饰
  • 扩容方式通过扩容因子判断
  • fail-fast

说一说面试题:

1.ArrayList 和 LinkedList 区别
2.ArrayList 和 Vector 区别
3.ArrayList如何扩容的?
。。。。

Map

map也是我们常用的数据结构,也是面试最经常问的,小编理解了Map的设计后,其实也是很佩服这个设计的。而且我们jdk1.7和jdk1.8结构是有所不同的,其中优化的理念,还是值得借鉴的。比如二次hash,链表转红黑树,以及为什么初始大小是16,扩容大小是2^n?

map这里呢,我们也挑选几个典型:

HashMap

HashMap,在1.7 和1.8 结构有所升级。

1.7 中的HashMap:

  • 结构:数组 + 链表
  • 线程不安全
  • 允许 null 作为 key和value

1.8 中的HashMap

  • 结构:数组 + 链表 or 红黑树
  • 线程不安全
  • 允许 null 作为 key和value

提问环节:

  • 1.8 和1.7 的Hashmap有什么区别?

数据结构不一样,1.7 是数组+链表,1.8 是数组+链表 or 红黑树;
链表插入方式:1.7是头插法,1.8是尾插法

  • 什么是红黑树??为什么用红黑树?

红黑树是特殊的平衡二叉树,她比平衡二叉树性能好一些,查询时间复杂度为 O(log2^n)。而链表的查询复杂度是O(1),当链表过长的时候,查询会很慢。
1.8 中转换红黑树的条件是 1.数组长度大于等于64,2.链表长度大于8;
使用红黑树就是为了优化长链表查询慢的问题。

  • 为什么不一上来就用红黑树?

链表短的时候,查询性能很快,没有必要直接用红黑树
存储方面:链表节点是Node,红黑树的节点是treeNode,treeNode成员变量比node多,所以内存会占用多,也没有必要。

  • 1.8转红黑树的阈值为什么是8 ?

阈值为8 ,也是综合考虑的。是为了尽量不要转成红黑树,除非链表真的很长了。
官方的一个demo:如果hash值够随机,在hash表内按泊松分布,在负载因子为0.75的情况下,统计了长度超过8的链表出现的概率是0.00000006 (亿分之6),选8,就是为了让树化 的概率足够小。链表转树,树转链表的开销也很大。

  • 红黑树如何退化为链表?

1.当链表长度小于等于6
2.红黑树的节点,如果 删除节点前,红黑树的 根节点,根的左节点,根的右节点,根的左孙子,有一个为null,就会转为链表。

如何防止hash碰撞?hash如何计算的?

hashMap 做了二次hash操作
第一次,根据key获取到对应的hashCode值;
第二次,根据hashCode 进行二次hash;
最后,用二次hash的值与数组容量进行取余运算。

根据key计算了hashcode,为什么还要进行二次hash?

保证hash结果更加的均匀,防止长链表产生。
二次hash是通过 hashcode ^ (hashcode>>>16) 计算的,目的是为了分配更加的均匀,防止链表过长

从这个图可以看到,二次hash结果分布更加均匀。

数组容量为什么是2^n?

为了提高整体性能,两个地方用到了这个数据
1.取余计算桶的时候,如果数组长度是2^n,那么可以直接用位运算取代取模
eg:
97 % 16 =1
97 & (16-1) = 1
2.方便扩容移动数据
扩容移动数据的时候,根据扩容后桶的长度,计算每个key对应的新桶的位置 A,
如果 A & oldCap == 0, 那就留在原位,否则,新位置 = 旧位置 + oldCap;可以直接移动。

不用2^n 可以吗?

可以,但是综合考虑性能
hashtable 就不是用的2^n

get的流程是什么样的?

首先根据 key 获取hashcode
再 根据hashcode进行二次hash
最后 按位与得到桶的位置
如果桶的位置没有数据,就直接返回null。
如果桶有数据,就依次遍历链表,通过equals()判断key是否相等,相等的话返回对应的value,没有的话返回null。

put流程是什么样的?1.7和1.8 区别?

首先 根据key 获取hashcode
在根据hashcode 进行二次hash
最后 按位与得到桶的位置
如果桶没有数据,就做成node节点,插入
如果桶有数据,判断数据是否存在?通过equals判断存在
存在,更新数据
不存在,插入数据
====如果是treeNode,走红黑树添加逻辑
====如果是普通node,走链表添加逻辑,1.7 头部插入node节点,1.8尾部插入Node节点
添加完后判断是否转红黑树
返回前检查容量是否超过阈值,一旦超过,进行扩容。(先插入,再判断树化,再判断扩容)

1.7并发扩容死链问题?死循环问题?

首先,hashmap是线程不安全的,所以在高并发的时候,会有问题。
其次,1.7是通过头插法完成的,当扩容的时候,a–b,会变为 b–a。node节点还是node节点,只是改变了前后的链接。

比如当前有两个线程来操作

其中一个线程2,已经完成了上面的扩容。现在链表的顺序是b–a。

线程1开始迁移数据,第一轮循环,把a先迁移,然后e的指针指到b,next的指针指到null。

第二轮循环,next指针,指向b的下一个,是a。e把b迁移走,e指向next,指到a。


第三次循环,next指向null,a要用头插法插到头部,就形成了 第一个node是a,a的next是b,b的next又是a,这样就出现了死链。


负载因子为什么是0.75?

综合条件考虑的,从空间和时间考虑
大于这个值,空间节省了,但是链表会变长,影响效率。
小于这个值,扩容次数多了,hash冲突少了,空间多了。

hashMap出现HashDos问题,如何解决的?

通过红黑树解决,防止链表太长,性能急剧下降。

ConcurrentHashMap

ConcurrentHashMap ,在1.7 和1.8 结构有所升级

1.7的ConcurrentHashMap

  • 结构 : segment + 数组 + 链表
  • 线程安全,使用ReentrantLock ,用自旋锁来保证线程安全

1.8的ConcurrentHashMap

  • 结构 : 数组 + 链表 or 红黑树
  • 线程安全,使用 CAS + Synchronized保证线程安全

提问时间:
1.7和1.8 ConcurrentHashMap 有什么区别?

1.数据结构不一样
====1.7 segment + 数组 + 链表
====1.8 数组 + 链表or红黑树
2.初始化时机不一样
====1.7,饿汉式初始化,初始化的时候,就初始化好segment,以及segment0的数组,数组大小根据容量和并发度来计算。

====1.8,懒汉式初始化,真正put数据的时候创建

3. 插入方式不同,1.7头插法,1.8尾插法。
4.扩容时机不同
====1.7 当超过 容量负载因子大小,才扩容
====1.8 当 >= 容量
负载因子,就扩容,eg 12 个就扩容了
5.锁的对象不一样
====1.7锁的是segment

1.8 锁的是链表的第一个Node

ConcurrentHashMap如何保证线程安全的?
ConcurrentHashMap 和 hashMap的区别?
ConcurrentHashMap 和 hashtable的区别?

HashTable

  • 结构 : 数组 + 链表 or 红黑树
  • 线程安全,所有方法通过 Synchronized修饰
  • 不允许 null 作为 key和value,否则报空指针错误

四、小结

集合这个还是很值得研究的。包括里的设计思想。

一篇带你搞懂 java 集合相关推荐

  1. 面试:一文搞懂Java集合

    前言 Java集合就像一个容器,可以存储任何类型的数据,也可以结合泛型来存储具体的类型对象.在程序运行时,Java集合可以动态的进行扩展,随着元素的增加而扩大.在Java中,集合类通常存在于java. ...

  2. 300 行代码带你搞懂 Java 多线程!

    线程 线程的概念,百度是这样解释的: 线程(英语:Thread)是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可 ...

  3. 三篇文章彻底搞懂Java面向对象之一

    写在前面,Java基础系列文章都是作者基于b站尚硅谷的Java基础视频所做的笔记,没有时间的同学可以认真看看,如果有时间的同学,还是建议看看视频,毕竟笔记说到底还是自己的东西,每个人的习惯也是不一样的 ...

  4. 一文带你搞懂Java的四大引用:强引用,软引用,弱引用以及虚引用

    Java中的引用 强引用Reference Reference类以及继承派生的类. 当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收. 这样定义的默 ...

  5. [C++] 一篇带你搞懂引用()-- C++入门(3)

    问题引入 在我们日常的生活中每个人都或多或少存在一个"外号",例如<西游记>中孙悟空就有诸多外号:美猴王,孙行者,齐天大圣等等.那么在C++中,也可以给一个已经存在的变 ...

  6. 网络二层技术——VLAN三种接口Access、Trunk、Hybrid(从原理到配置一篇带你搞懂)

    目录 前言 传统以太网 VLAN 技术 VLAN帧格式 链路类型 PVID 端口类型 Access 端口 Trunk 端口 Hybrid 端口 VLAN 划分方法 VLAN 配置方法 VLAN配置 配 ...

  7. Android --- 一篇带你搞懂CTS

    ·什么是CTS CTS全称Compatibility Test Suite兼容性测试工具,为了保证开发的应用在所有兼容Android的设备上正常运行,并保证一致的用户体验,Google制定了CTS来确 ...

  8. JVM学习笔记(Ⅰ):Class类文件结构解析(带你读懂Java字节码,这一篇就够了)

    JVM学习笔记(Ⅰ):Class类文件结构解析,带你读懂Java字节码 前言:本文属于博主个人的学习笔记,博主也是小白.如果有不对的地方希望各位帮忙指出.本文主要还是我的学习总结,因为网上的一些知识分 ...

  9. 一篇文章带你搞懂网络层(网际层)-- 地址篇

    网络层(Network Layer)是OSI模型中的第三层(TCP/IP模型中的网际层),提供路由和寻址的功能,使两终端系统能够互连且决定最佳路径,并具有一定的拥塞控制和流量控制的能力.相当于发送邮件 ...

最新文章

  1. ICA算法处理后,ICA成分识别
  2. OpenGL ES 2兼容函数列表
  3. mysql语句在node.js中的写法
  4. 扩展源_瑞萨电子推出具备反向充电WattShare TRx模式的 15W无线充电电源P9415R接收器,扩展无线电源产品线...
  5. Django根据现有数据库建立/更新model
  6. 深度克隆对象【前端每日一题-19】
  7. hihocoder 1449 : 后缀自动机三·重复旋律6(后缀自动机)
  8. 程序员鼓励师是什么?
  9. Weblogic常见故障常 JDBC Connection Pools
  10. web元素定位之------日历控件的定位
  11. 已知两点坐标求水平距离_知道两个点的坐标X,Y,如何计算出两点间的距离以及角度,公式是什么...
  12. 微信小程序轮播图swiper使用
  13. regester正则用法_Regester(正则表达式测试器)
  14. java 场景面试题_Java面试场景整理收录
  15. 国产系统为什么用linux,为什么国产操作系统不用Unix,而是集体用Linux
  16. C语言学习(十)C语言中的小数
  17. 乐行科技获1.08亿元A轮融资,并推出艾特好车 1
  18. SPI Flash/Nor Flash/Nand Flash
  19. IndexedDB 学习笔记
  20. 第二关,KPM算法和next函数值

热门文章

  1. 整顿职场,从 ROC 曲线开始
  2. 小白必看!渗透测试的8个步骤
  3. 2019年,ISV该如何抉择?
  4. 详解最大似然估计(MLE)、最大后验概率估计(MAP),以及贝叶斯公式的理解
  5. android 6无法连电脑,vivo手机无法连接电脑怎么办?vivo手机无法连接电脑的解决方法...
  6. 梅科尔工作室-赵一帆-鸿蒙笔记1
  7. 群晖笔记一:使用Hyper Backup在多个硬盘间备份重要资料
  8. 电子器件系列40:高压放电电阻(绕线电阻)
  9. 上海理工大学宣布利用人工智能实现了三维矢量全息新技术
  10. 常见图片格式jpg、jpeg、png、gif等之间的区别