Java集合——知识总结

Java集合主要用来存储数值数据和引用数据

Java集合框架图

  • 圆点为接口
  • 方框为具体实现类
  • Collections和Arrays是集合工具类类
  • 集合的顶层都是接口,Collection是单列集合的顶层接口,Map是双列集合的顶层接口
  • Collection下的集合类都包含(add,remove,contains,isEmpty)等方法,也都包括Iterator方法,可以使用具体对象.iterator()方法获取迭代器,其中List接口继承了ListIterator接口,可以在使用迭代器进行遍历的过程中,添加删除元素
  • SortedSet和SortedMap是可指定排序的接口,TreeSet、SortedMap继承,可以实现自定义排序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NSY6du6b-1628996028592)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e7a566c5-4a25-48a6-b376-a1c099162a2a/Untitled.png)]

List/Set/Map之间的基本区别

  • List:有序的,可重复的
  • Set:无序的,不可重复的
  • Map:使用K-V结构存储的,Key是无序的、不可重复的,Value是无序的、可重复的

List/Set/Map之间的区别不绝对

  • 我们常说的集合有序无序是指的是“插入顺序”,是指在插入的时候,插入的顺序是否保持,当遍历集合时是否会按照插入的顺序展示,像TreeSet和TreeMap这种集合底层是红黑树,可以通过构造方法加入比较器进行排序,也就是实现自己的排序,跟插入的顺序无关,所以它不一定是集合的有序无序

  • 我们常见的集合中(有无序指插入时的顺序在遍历时是否可以保持):

    • 有序的包括:(ArrayList、LinkedList、LinkedHashSet、LinkedHashMap)
    • 无序的包括:(HashSet、HashMap、HashTable、TreeSet、TreeMap)
    • 可以自定义排序的包括:(TreeMap、TreeSet)

上述的基本区别是指比较笼统的,实际上在Java集合类中不然

  • 比如(LinkedHashSet和LinkedHashMap)底层是通过双向链表维持插入顺序,所以说它们是有序的,这里说的是Set的值和Map的Key值是有序的

底层构造

  • ArrayList:动态数组
  • LinkedList:双向链表
  • HashSet:HashMap
  • LinkedHashSet: HashSet+双向链表 / LinkedHashMap
  • HashMap:数组+链表+红黑树
  • LinkedHashMap:HashMap+双向链表
  • TreeSet:红黑树
  • TreeMap:红黑树

ArrayList:

  • 底层是一个动态数组 Object[],默认不指定数组大小为10,在add数据的时候才会开创数组的真正内存空间,并且每次add会判断是否达到数组的最大值,比如10,当添加到10后,想再添加数据,发现添加后长度为11,大于数组空间的长度10,这个时候就会扩容,扩容为当前容量的1.5倍。ArrayList是线程不安全的容器。每次add都是添加到数组的末尾,尾插法

LinkedList:

  • 底层是一个双向链表,只能顺序访问数据,可以快速的增加或删除元素,没有初始大小,也没有扩容机制,就是在前面或者后面新增或者删除就好
  • 同样长度的ArrayList和LinkedList进行比较,LinkedList占用的内存空间会更多,因为ArrayList是顺序分配内存,LinkedList是随机分配内存,并且每一个节点多占用了指向前后节点的指针。而ArrayList的空间浪费主要体现在Object数组的结尾会预留一定的容量空间

ArrayList和Vector的区别:

  • 都是动态数组,ArrayList线程不安全,Vector线程安全

HashMap:

  • 1.7之前底层是数组+链表,1.8之后是数组+链表+红黑树。链表主要是为了解决哈希冲突,红黑树主要是为了提高查询的效率
  • HashMap是通过Key值的hashcode()值经过“扰动函数”的处理后得到的hash值,然后通过(length-1)& hash判断当前元素所在的位置,如果发生碰撞,进行equals比较,如果Key值是一样的,则直接覆盖,否则加在桶链表的后面,“扰动函数”主要是为了减少碰撞
  • “扰动函数”其实就是HashMap的hash方法,是对hashcode()值进行再次hash()。此hash()是对hashcode值与高16位做异或运算,得到最后的hash值,然后通过(length-1)& hash计算出该key所在的index位置
  • HashMap是由Entry数组实现,Entry数组本质是一个Hash表,Entry数组中的每个元素称为Bucket桶,桶有自己的索引,所以可以是链表也可以是红黑树
  • HashMap的put()过程,先对key的hashcode()取hash后计算下标,看看对应的桶中是否有元素,如果没有元素直接添加,如果有元素,先比较key值,如果key值相同(key为基本数据类型 / 引用数据类型,先比较hashcode,随后用==运算符和equals()来判断该key是否相同)则覆盖value值,若key值不同则插在此桶链表的后面。如果当链表长度大于8并且Entry数组大于等于64则转换为红黑树。如果Entry数组不够64,先resize()对Entry数组扩容。如果红黑树大小为6,会退化成链表
  • HashMap resize()扩容,默认的Entry数组长度为16,负载因子为0.75,可以通过构造函数指定。如果Entry数组达到了 16 x 0.75 = 12,此时达到阈值,进行resize()扩容到原先Entry数组长度的2倍。扩容会打乱原本K-V值在Entry数组的位置,resize() 在1.7会重新计算hash,1.8进行了优化,不需要全部重新计算hash
  • HashMap Entry数组的长度只能为2次幂的原因,是因为只有大小取2次幂时,才能合理运用「位运算」替代取模(扰动函数:通常使用mod取模计算)
  • 链表、红黑树比较:
    • 链表:
      • 查询速度:o(n)
      • 插入速度:o(1)
    • 红黑树:
      • 查询速度:o(log(n))
      • 插入速度:o(log(n))
  • HashMap线程不安全可能出现的问题i:
    • 线程A put()进去,线程B get()不出来
    • 线程A put()修改,线程B put()修改,线程A修改的数据丢失
    • 达到resize()阈值时,线程A和线程B同时 put()会造成同时resize(),HashMap可能会变成环

LinkedHashMap:

  • 底层结构:HashMap+双向链表
  • 继承了HashMap,同时多维护了一个双向链表,实现了插入有序
  • 遍历时通过双向链表进行遍历,所以LinkedHashMap的大小不会影响到遍历的性能

TreeMap:

  • 底层结构:红黑树
  • 通过Comparator实现key的自定义排序,如果TreeMap不指定Comparator进行排序,则会使用自然排序
  • 节点的key不能为null,如果为null则无法排序

红黑树特点:

  • 红黑树并不追求完美的平衡,也就是叶子结点之间可能高度大于2,为了防止二叉查找树退化成链表,而使用的红黑树
  • 红黑树降低了旋转的需求,AVL树基本上每次插入都需要旋转,红黑树最多旋转三次 O(1)
  • 搜索的次数 > 插入和删除 选择AVL
  • 搜索的次数 < 插入和删除 选择红黑树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-czKrfNiO-1628996028594)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bc9c4f8b-3861-49da-ada8-ad330b662937/Untitled.png)]

HashMap和HashTable的区别:

  • HashMap可以存储Null key和Null value,key只能出现一次,value可以出现多次,而HashTable的key和value都不可以存储Null
  • HashMap是非线程安全的,HashTable是线程安全的。
  • HashMap初始值为16,2倍扩容,HashTable初始值为11,2n+1倍扩容
  • HashMap若给定容量初始值,会将其扩充到2的幂次方,而HashTable直接使用给定的初始值
  • HashMap底层数据结构为数组+链表+红黑树,HashTable只是数组+链表,因为HashTable不再更新了,两者都为Map接口子类

HashMap和HashSet的区别:

  • HashSet底层就是HashMap,在HashMap上源码改动的比较少
  • HashMap是K-V结构,而HashSet是Set结构,HashSet底层的Set数据其实是存储在HashMap的Key上的,而HashSet底层的HashMap上的Value存储的是PRESENT常量值
  • HashSet在add方法时会调用hashcode()和equals()方法进行HashMap上的Key的比较,如果相同则覆盖,不相同产生哈希冲突加在链表后面或红黑树

ConcurrentHashMap与HashTable的区别:

  • 两者都是线程安全的,只不过实现线程安全的方法不同
  • 底层数据结构:
    • 1.7 ConcurrentHashMap采用数组+链表 1.8 采用数组+链表+红黑树
    • 1.7 HashTable采用数组+链表 1.7 还是使用 数组+链表
  • 实现线程安全的方式:
    • ConcurrentHashMap:
      • 1.7: 分段的数组+链表实现,所以采用了分段锁对Entry数组进行分段(Segment),每一把锁只能锁住一段数据
        • Segment 数组 + HashEntry 数组 + 链表实现
        • Segment实现了ReentrantLock,是一种可重入锁,在需要修改Segment中的数组时,必须先获取Segment的锁
      • 1.8: 抛弃了分段锁,转而使用数组+链表+红黑树进行实现,使用Sychronized + CAS进行实现
        • Node 数组 + 链表 / 红黑树
        • Sychronized只锁住了当前链表和红黑树的首节点,所以只要hash不冲突,就不会产生锁冲突,效率提升非常多
    • HashTable:
      • 全表锁,使用Synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低
  • 线程安全的粒度:
    • ConcurrentHashMap(多个线程put()可能会触发锁,hash不冲突就不会触发锁,多线程读时无锁,Node的val和next使用volatile修饰,读写线程对该变量互相可见):
      • 1.7:分段锁(锁住一个Segment的数据)
      • 1.8:Sychronized + CAS(锁住链表或红黑树头节点)
    • HashTable(多个线程get()、put()都会触发锁):
      • 全表锁(所有方法Synchronized)

线程安全/不安全:

  • 线程安全:
    • Vector(对应ArrayList)
    • HashTable(对应HashMap)
    • JUC下集合类(CopyOnWriteArrayLIst(对于add()操作使用ReentrantLock进行加锁)、ConcurrentHashMap)
  • 线程不安全;
    • 其余都为线程不安全
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值