为了在简历上写掌握【Java集合】,做了万字总结
文章目录
- 概述
- 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集合】,做了万字总结相关推荐
- 看到网上有人说软件测试面试要会吹牛,我就在简历上写满了技能精通,拥有工作经验的我被面试官问到窒息...
前言 如果有真才实学,写个精通可以让面试官眼前一亮! 如果是瞎写?基本就要被狠狠地虐一把里! 最近在面试,我现在十分后悔在简历上写了"精通"二字- 先给大家看看我简历上的技能列表: ...
- mysql查询前段时间_没想到!我在简历上写了“精通MySQL”,阿里面试官跟我死磕后就给我发了高薪offer...
事情是这样的 前段时间面试了阿里,大家也都清楚,如果你在简历上面写着你精通XX技术,那面试官就会跟你死磕到底. 我就是在自己的简历上写了精通MySQL,然后就开启了和阿里面试官的死磕之路,结果就是拿到 ...
- 一条mysql语句是事务吗_没想到!我在简历上写了“精通MySQL”,阿里面试官跟我死磕后就给我发了高薪offer...
事情是这样的 前段时间面试了阿里,大家也都清楚,如果你在简历上面写着你精通XX技术,那面试官就会跟你死磕到底. 我就是在自己的简历上写了精通MySQL,然后就开启了和阿里面试官的死磕之路,结果就是拿到 ...
- 简历上写CV开源项目,有用吗?
常有粉丝留言问我:简历上写CV的开源项目有用吗? 答案很简单,没什么用. 以"实时目标检测"这一自动驾驶.机器人等行业的重要任务来举例,尽管你可以获得一些开源代码,但几乎没有任何开 ...
- 阿里第二代微服务强势崛起,你还敢往简历上写“精通”吗?
总算迎来金三银四...但简历千万别瞎写! 前几天,刚面试完一小伙,简历上写了"精通微服务",进来时表现得非常自信,所以我很期待他接下来的表现...... 也就随便问了几个微服务相关 ...
- 在简历上写了“精通”后,拥有工作经验的我被面试官问到窒息
前言 如果有真才实学,写个精通可以让面试官眼前一亮! 如果是瞎写?基本就要被狠狠地虐一把里! 最近在面试,我现在十分后悔在简历上写了"精通"二字- 先给大家看看我简历上的技能列表: ...
- 金三银四,简历上写CV开源项目,有用吗?
春招在即,常有粉丝留言问我:简历上写CV的开源项目有用吗? 答案很简单,没什么用. 以"实时目标检测"这一自动驾驶.机器人等行业的重要任务来举例,尽管你可以获得一些开源代码,但几乎 ...
- 为了在简历上写掌握【Java中IO流】,做了该篇总结
文章目录 聊聊文件 字节输入流 字节输出流 字符输入流 字符输出流 字节缓冲输入流 字节缓冲输出流 字符缓冲输入流 字符缓冲输出流 流的关闭 对IO流的学习,我记得还是初学Java基础的时候,后来找工 ...
- 为了在简历上写掌握【Java多线程和并发编程】,做了两万字总结
文章目录 概述 继承Thread类 实现Runnable接口 实现Callable接口 线程池 线程的五大状态 多线程买票案例 死锁 Lock锁 生产者消费者问题 八锁问题 volatile 写在后面 ...
最新文章
- HDU 4826 Labyrinth(DP解法)
- Android 8.0 linux内核,在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序---Android8.0版本实现-对照老罗版本...
- 探索大神科比,30000多次投篮数据,有好玩的发现
- 结合AlphaGo算法和大数据的量化基本面分析法探讨
- 2022中元节前后几天不出门?前三天后三天不能出门是真的吗?
- 十大宽带共享组网方式(转)
- 如何将MAC设成wifi热点
- 多开分身苹果版_苹果手机最新微信分身怎么下载?微信多开地址分享
- 汉王人脸通登陆美国“中国创造”出海
- QT5打开图片并显示
- 我的世界服务器背景音乐修改,我的世界怎么自定义背景音乐教程攻略
- SpringBoot+Es7.6.1+Jsoup+Vue+Docker打造古诗词实时搜索功能
- QTableView 去除Item选中的虚线框
- java计算机毕业设计智能医技预约系统源码+mysql数据库+系统+部署+lw文档
- Java 求100以内的质数
- 【自然语言处理(NLP)】基于SQuAD的机器阅读理解
- 做项目经理太累太辛苦,每天开不完的会议,还不如纯粹编写代码
- 【Kettle从零开始】第二弹之Kettle文件夹与界面介绍
- bypass csp学习
- java中如何输入乘号_乘号在JAVA编成中应怎样直接读取? 它应属于什么类型的?...
热门文章
- 波斯语网站步百度后尘糟伊朗网军出击
- 21 世纪最需要的 7 种人才素质 - 李开复
- django之路由分组,路由分发,FBV,CBV,ORM框架
- react-native无法在react-native-gesture-handler中解析符号android.support.v4.util.Pools解决方案...
- 【Django】ImportError: cannot import name 'execute_manager'
- python正则表达式03--字符串中匹配数字
- wap手机网页html5通过特殊链接移动设备:打电话,发短信,发邮件详细教程
- php获取对象属性值
- COJ-1271 Brackets Sequence
- 对Spring Boot还陌生吗?