文章目录

  • 概述
  • List
    • ArrayList
    • LinkedList
    • Vector
    • CopyOnWriteArrayList
  • Set
    • HashSet
    • LinkedHashSet
    • TreeSet
    • CopyOnWriteArraySet
  • Queue
  • Map
    • HashMap
    • LinkedHashMap
    • Hashtable
    • TreeMap
    • ConcurrentHashMap
  • 写在后面

概述

说起集合,算是三顾茅庐了,在我初学Java的时候,曾接触过集合,那个时候只会用像Collection接口下的List(ArrayList、Vector、LinkedList)和Set(HashSet、TreeSet)还有Map接口下的HashMap,当时只是会用,第二次学习集合是看到了网上一些大佬的文章,感觉自己差的太多,然后又了解了一点LinkedHashSet、TreeMap、HashTable的知识,直到今天的第三次,我意识到了集合和多线程之间的关系,这篇文章主要是把集合的知识点串起来,并加入并发下的集合问题。

List

List集合的底层是数组和链表,其存储顺序是有序的。

ArrayList

ArrayList是List集合的一个实现类,其底层实现是数组transient Object[] elementData;,数组的查询是直接通过索引,查询速度比较快,时间复杂度是O(1),增删的话,根据增删的位置,时间复杂度有所不同,如果是中间第i个位置,时间复杂度就是O(n-i),简单理解其时间复杂度是O(n),举个例子,一个有8个元素的数组,要在第6个位置插入元素,那么,需要把后2个元素往后移,来保证原来的顺序不变,其空间复杂度的话,为了防止数组越界,尾部会有一部分的空闲空间。

默认初始化容量

当构造方法中没有指定数组的大小时,其默认初始容量是10。

扩容机制

既然默认值是10,那么当超过这个默认值的时候,一定有一个扩容机制,其扩容机制是,当集合中元素的个数大于集合容量的时候,也就是add的时候集合放不下了,就会触发扩容机制,扩容后的新集合容量是旧集合容量的1.5倍,源码:int newCapacity = oldCapacity + (oldCapacity >> 1);

线程问题

ArrayList是线程不安全的,在add()方法的时候,首先会检查一下数组的容量是否够用,如果够用,那么就会执行elementData[size++] = e;方法,该语句执行了两大步,第一大步是,将e放到elementData缓冲区,第二大步是,将size的大小进行加1操作,也就是说,这个操作并非原子性操作。当在并发的情况时,就会出现问题。

import java.util.ArrayList;
import java.util.List;public class Main {public static void main(String[] args) {List<String> list = new ArrayList<>();for (int i = 0; i < 100; i++) {new Thread(()->{for (int j = 0; j < 10; j++) {list.add("执行添加元素操作");}}).start();}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(list.size());}
}

LinkedList

LinkedList也是List的一个实现类,其底层是双向链表,其内部有一个next指针指向下一个节点,一个prev指针,指向上一个节点,由于是链表的数据结构,所以在查询的时候相对就比较慢了,时间复杂度是O(n),因为当我们需要查询某个元素的时候,需要从第一个节点开始遍历,直到查询结束。而他的增删就比较快了,如果增加一个N节点,直接将后一个节点的prev指向N节点,N节点的next指向后一个节点,前一个节点的next指向N节点,N节点的prev指针指向前一个节点即可,时间复杂度为O(1),空间复杂度一般比ArrayList大,因为每个节点都要存储两个指针。

线程问题

LinkedList也是线程不安全的,其添加元素的操作,通过linklast方法在尾部进行添加的,添加完之后,并把size的大小加1。其他的不说,单单一个size++就不是原子性了。

下面一个例子来演示加1操作。

import java.util.LinkedList;
import java.util.List;public class Main {static int a = 0;public static void main(String[] args) throws InterruptedException {List<String> list = new LinkedList<>();for (int i = 0; i < 100; i++) {new Thread(()->{for (int j = 0; j < 100; j++) {a ++;}}).start();}Thread.sleep(1000);// 主线程延迟是为了保证那100线程先执行完System.out.println(a);}
}


通过反编译下面的代码,来分析加1操作

public class Main {public static void main(String[] args) {int a = 0;a ++;}
}

简单的加1操作会执行三步:
0:把a的值加载到内存
1:将内存中的值,存储到变量中
2:然后进行加1操作
兜了一大圈子,记住一句话,LinkedList是线程不安全的。

Vector

Vector也是List的一个实现类,其底层也是一个数组protected Object[] elementData;,底层ArrayList差不多,也就是加了synchronized的ArrayList,线程是安全的,效率没有ArrayList高,一般不建议使用。

默认初始化容量

扩容机制

这里的扩容机制是通过capacityIncrement参数来实现的。

线程问题

线程是安全的,因为它用了synchronized关键字,将整个方法进行了包裹,所以效率比较低,不建议使用。

import java.util.Vector;public class Main {public static void main(String[] args) throws InterruptedException {Vector<String> vector = new Vector<>();new Thread(()->{for (int i = 0; i < 100; i++) {for (int j = 0; j < 10; j++) {vector.add("添加元素");}}}).start();Thread.sleep(1000);// 避免主线程先执行输出System.out.println(vector.size());}
}

CopyOnWriteArrayList

既然不建议使用Vector,那么并发的时候怎么办呢,不要忘了,有一个包java.util.concurrent(JUC)包,它下面有一个类CopyOnWriteArrayList也是线程安全的。CopyOnWriteArrayList也是List的一个实现类。

add方法用Lock锁来解决并发问题,其中在进行添加数据的时候,可以看到,用了copyOf方法,也就是复制了一份,然后再set进去。

CopyOnWriteArrayList底层也是用的数组,但是它的数组是用volatile修饰了,主要是保证了数据的可见性。

上图是CopyOnWriteArrayList的get操作,并没有加锁,因为volatile保证了数据的可见性,当数据被修改的时候,读操作能立刻知道。

而vector的get操作加了锁,所以效率肯定就没有CopyOnWriteArrayList高。

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;public class Main {public static void main(String[] args) throws InterruptedException {List<String> list = new CopyOnWriteArrayList<>();new Thread(()->{for (int i = 0; i < 100; i++) {for (int j = 0; j < 10; j++) {list.add("添加元素");}}}).start();Thread.sleep(1000);// 避免主线程先执行输出System.out.println(list.size());}
}

Set

Set集合中的元素是唯一的,不能有重复的元素。

HashSet

HashSet是Set集合的一个实现类,其底层实现是HashMap的key,后面会详细讲解HashMap。不保证数据的存储顺序(即存的顺序和取的顺序不一定一样)。其线程不安全。

LinkedHashSet


LinkedHashSet的初始容量也是16,默认加载因子也是0.75,继承了HashSet,底层基于LinkedHashMap,有一个维护顺序的双向链表,使得LinkedHashSet有序(存取有序)。其线程不安全。

TreeSet

可以看出TreeSet是基于TreeMap的。它是有序的,是数值有顺序,通过树来维护的。其线程不安全。

CopyOnWriteArraySet



import java.util.concurrent.CopyOnWriteArraySet;

这个是并发包下的,线程安全。
说了这么多Set集合,感觉大多都是基于Map的,接下来看看Map到底是怎么实现的!

Queue

import java.util.*;public class Main {public static void main(String[] args) {Queue<String> queue = new LinkedList<>();// 初始化一个队列queue.offer("a");// 添加元素,也可以通过add方法,不推荐queue.offer("b");queue.offer("c");System.out.println(queue.peek());// 访问第一个元素,如果不存在,返回nullSystem.out.println(queue.element());// 访问第一个元素,如果不存在,抛出异常,不推荐使用while (!queue.isEmpty()) {System.out.println(queue.poll());// 访问第一个元素,并删除,如果不存在返回null}System.out.println(queue.peek());System.out.println(queue.poll());System.out.println(queue.element());// 这里由于元素不存在了,所以抛出NoSuchElementException}
}

Map

HashMap

底层数据结构

底层是JDK1.7是通过数组+链表JDK1.8是通过数组+链表+红黑树组成。所有的数据都是通过一个Node节点进行封装,其中Node节点中封装了hash值,key,value,和next指针。hash是通过key计算出的hashCode值进行对数组容量减一求余得到的(官方的求余方式是通过&运算进行的)。不同的key计算出来的hash值可能相同,解决冲突是通过拉链法(链表和红黑树)进行处理。正是因为这种存储形势,所以HashMap的存取顺序是无序的。


HashMap的底层是通过Node节点来维护的。
加载机制

懒加载机制,在put值的时候会判断数组是否为空,如果是就初始化数组,而不是new的时候就初始化。

默认初始化容量

HashMap是Map的一个实现类,其默认初始化容量大小是16。
扩容机制

扩容机制是根据扩容因子来扩容的,当容量的使用量达到总容量的0.75时,就会触发扩容,举例说就是,当总容量是16时,使用量达到12,就会触发扩容机制。


扩容的新空间大小时就空间的2倍。

扩容操作最消耗性能的地方就是,原数组中的数据要重新存放,也就是resize(),即重新计算索引,重新分配。

处理冲突

当我们put一个值的时候,通过key来计算出hash值,计算出来的hash值做为数组的索引,Node节点中封装了hash值,key,value和next。


当链表的长度小于8的时候,处理冲突的方式是链表。大于等于8的时候,就会触发红黑树方式存储。


当元素个数小于等于6的时候,会触发红黑树转化为链表的形式,为什么不是小于等于7,是因为给一个过度,也就是防止添加一个刚好为8,删除一个刚好为7,这样来回转化。

线程问题
HashMap是线程不安全的。

LinkedHashMap


LinkedHashMap继承了HashMap

LinkedHashMap解决了HashMap不能保证存取顺序的问题。内部增加了一个链表用于维护元素存取顺序。

Hashtable


Hashtable也是Map的一个实现类。

Hashtable的初始默认容量是11,扩容阈值也是0.75。

该类的出现主要是解决了HashMap的线程安全问题,直接用了synchronized锁,所以效率上不高,不建议使用(发现JDK1.0的线程问题,解决都很暴力)。

TreeMap


TreeMap也是Map的一个实现类

其底层是通过红黑树实现的,所以有序,这里的有序不是指存取顺序,而是数据大小的顺序。

ConcurrentHashMap

ConcurrentHashMap是java.util.concurrent包下的,并发包下的。他就是对HashMap进行了一个扩展,也就是解决了他的线程安全问题。

然后是它用了大量的CAS来进行优化,上图中的U是private static final sun.misc.Unsafe U;,属于Unsafe下的。

这里是节点上锁,这里的节点可以理解为hash值相同组成的链表(红黑树)的头节点,锁的粒度为头节点。可以看到,锁的力度明显比Hashtable的小。

写在后面

如果感觉这篇文章对你有帮助,欢迎点赞和在看,文章持续输出,关注我,第一时间阅读原创作品!

为了在简历上写掌握【Java集合】,做了万字总结相关推荐

  1. 看到网上有人说软件测试面试要会吹牛,我就在简历上写满了技能精通,拥有工作经验的我被面试官问到窒息...

    前言 如果有真才实学,写个精通可以让面试官眼前一亮! 如果是瞎写?基本就要被狠狠地虐一把里! 最近在面试,我现在十分后悔在简历上写了"精通"二字- 先给大家看看我简历上的技能列表: ...

  2. mysql查询前段时间_没想到!我在简历上写了“精通MySQL”,阿里面试官跟我死磕后就给我发了高薪offer...

    事情是这样的 前段时间面试了阿里,大家也都清楚,如果你在简历上面写着你精通XX技术,那面试官就会跟你死磕到底. 我就是在自己的简历上写了精通MySQL,然后就开启了和阿里面试官的死磕之路,结果就是拿到 ...

  3. 一条mysql语句是事务吗_没想到!我在简历上写了“精通MySQL”,阿里面试官跟我死磕后就给我发了高薪offer...

    事情是这样的 前段时间面试了阿里,大家也都清楚,如果你在简历上面写着你精通XX技术,那面试官就会跟你死磕到底. 我就是在自己的简历上写了精通MySQL,然后就开启了和阿里面试官的死磕之路,结果就是拿到 ...

  4. 简历上写CV开源项目,有用吗?

    常有粉丝留言问我:简历上写CV的开源项目有用吗? 答案很简单,没什么用. 以"实时目标检测"这一自动驾驶.机器人等行业的重要任务来举例,尽管你可以获得一些开源代码,但几乎没有任何开 ...

  5. 阿里第二代微服务强势崛起,你还敢往简历上写“精通”吗?

    总算迎来金三银四...但简历千万别瞎写! 前几天,刚面试完一小伙,简历上写了"精通微服务",进来时表现得非常自信,所以我很期待他接下来的表现...... 也就随便问了几个微服务相关 ...

  6. 在简历上写了“精通”后,拥有工作经验的我被面试官问到窒息

    前言 如果有真才实学,写个精通可以让面试官眼前一亮! 如果是瞎写?基本就要被狠狠地虐一把里! 最近在面试,我现在十分后悔在简历上写了"精通"二字- 先给大家看看我简历上的技能列表: ...

  7. 金三银四,简历上写CV开源项目,有用吗?

    春招在即,常有粉丝留言问我:简历上写CV的开源项目有用吗? 答案很简单,没什么用. 以"实时目标检测"这一自动驾驶.机器人等行业的重要任务来举例,尽管你可以获得一些开源代码,但几乎 ...

  8. 为了在简历上写掌握【Java中IO流】,做了该篇总结

    文章目录 聊聊文件 字节输入流 字节输出流 字符输入流 字符输出流 字节缓冲输入流 字节缓冲输出流 字符缓冲输入流 字符缓冲输出流 流的关闭 对IO流的学习,我记得还是初学Java基础的时候,后来找工 ...

  9. 为了在简历上写掌握【Java多线程和并发编程】,做了两万字总结

    文章目录 概述 继承Thread类 实现Runnable接口 实现Callable接口 线程池 线程的五大状态 多线程买票案例 死锁 Lock锁 生产者消费者问题 八锁问题 volatile 写在后面 ...

最新文章

  1. HDU 4826 Labyrinth(DP解法)
  2. Android 8.0 linux内核,在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序---Android8.0版本实现-对照老罗版本...
  3. 探索大神科比,30000多次投篮数据,有好玩的发现
  4. 结合AlphaGo算法和大数据的量化基本面分析法探讨
  5. 2022中元节前后几天不出门?前三天后三天不能出门是真的吗?
  6. 十大宽带共享组网方式(转)
  7. 如何将MAC设成wifi热点
  8. 多开分身苹果版_苹果手机最新微信分身怎么下载?微信多开地址分享
  9. 汉王人脸通登陆美国“中国创造”出海
  10. QT5打开图片并显示
  11. 我的世界服务器背景音乐修改,我的世界怎么自定义背景音乐教程攻略
  12. SpringBoot+Es7.6.1+Jsoup+Vue+Docker打造古诗词实时搜索功能
  13. QTableView 去除Item选中的虚线框
  14. java计算机毕业设计智能医技预约系统源码+mysql数据库+系统+部署+lw文档
  15. Java 求100以内的质数
  16. 【自然语言处理(NLP)】基于SQuAD的机器阅读理解
  17. 做项目经理太累太辛苦,每天开不完的会议,还不如纯粹编写代码
  18. 【Kettle从零开始】第二弹之Kettle文件夹与界面介绍
  19. bypass csp学习
  20. java中如何输入乘号_乘号在JAVA编成中应怎样直接读取? 它应属于什么类型的?...

热门文章

  1. 波斯语网站步百度后尘糟伊朗网军出击
  2. 21 世纪最需要的 7 种人才素质 - 李开复
  3. django之路由分组,路由分发,FBV,CBV,ORM框架
  4. react-native无法在react-native-gesture-handler中解析符号android.support.v4.util.Pools解决方案...
  5. 【Django】ImportError: cannot import name 'execute_manager'
  6. python正则表达式03--字符串中匹配数字
  7. wap手机网页html5通过特殊链接移动设备:打电话,发短信,发邮件详细教程
  8. php获取对象属性值
  9. COJ-1271 Brackets Sequence
  10. 对Spring Boot还陌生吗?