工作中常用到的Java集合类有哪些?
前言
只有光头才能变强。
文本已收录至我的github精选文章,欢迎star:https://github.com/zhongfucheng3y/3y
java集合是我认为在java基础中最最重要的知识点了,java集合是必须掌握的。我在实习/秋招面试的时候,只要是面到java,那一定是少不了java集合。
作为一个新人,最关心的其实有一点:这个技术在工作中是怎么用的。换个说法:“工作中常用到的java集合有哪些,应用场景是什么”
如何入门java集合以及每个常用的子类我在pdf整理好了,这就不粘贴过来了,有需要的就在pdf查看就好了。这份pdf绝对令你满意。
list集合
list集合下最常见的集合类有两个:arraylist和linkedlist
在工作中,我都是无脑用arraylist。我问了两个同事:“你们在项目中用过linkedlist吗?”他们都表示没有。
众所周知,arraylist底层是数组,linkedlist底层是链表。数组遍历速度快,linkedlist增删元素快。
为什么在工作中一般就用arraylist,而不用linkedlist呢?原因也很简单:
- 在工作中,遍历的需求比增删多,即便是增加元素往往也只是从尾部插入元素,而arraylist在尾部插入元素也是o(1)
- arraylist增删没有想象中慢,arraylist的增删底层调用的
copyof()
被优化过,加上现代cpu对内存可以块操作,普通大小的arraylist增删比linkedlist更快。
所以,在开发中,想到要用集合来装载元素,第一个想到的就是arraylist。
那么来了,linkedlist用在什么地方呢?我们一般用在刷算法题上。把linkedlist当做一个先进先出的队列,linkedlist本身就实现了queue接口
如果考虑线程安全的问题,可以看看copywriteonarraylist,实际开发用得不多,但我觉得可以了解一下它的思想(copywriteon),这个思想在linux/文件系统都有用到。
set集合
set集合下最常见的集合类有三个:hashset、treeset、linkedhashset
list和set都是集合,一般来说:如果我们需要保证集合的元素是唯一的,就应该想到用set集合
比如说:现在要发送一批消息给用户,我们为了减少「一次发送重复的内容给用户」这样的错误,我们就用set集合来保存用户的userid/phone
自然地,首先要保证最上游的那批用户的userid/phone
是没有重复的,而我们用set集合只是为了做一个兜底来尽可能避免重复发送的问题。
一般我们在开发中最多用到的也就是hashset。treeset是可以排序的set,一般我们需要有序,从数据库拉出来的数据就是有序的,可能往往写order by id desc
比较多。而在开发中也很少管元素插入有序的问题,所以linkedhashset一般也用不上。
如果考虑线程安全的问题,可以考虑copyonwritearrayset,用得就更少了(这是一个线程安全的set,底层实际上就是copywriteonarraylist)
treeset和linkedhashset更多的可能用在刷算法的时候。
map集合
map集合最常见的子类也有三个:hashmap、linkedhashmap、treemap
如果考虑线程安全问题,应该想到的是concurrenthashmap,当然了hashtable也要有一定的了解,因为面试实在是问得太多太多了。
hashmap在实际开发中用得也非常多,只要是key-value
结构的,一般我们就用hashmap
。linkedhashmap和treemap用的不多,原因跟hashset和treeset一样。
concurrenthashmap在实际开发中也用得挺多,我们很多时候把concurrenthashmap用于本地缓存,不想每次都网络请求数据,在本地做本地缓存。监听数据的变化,如果数据有变动了,就把concurrenthashmap对应的值给更新了。
queue队列
不知道大家有没有学过生产者和消费者模式,秋招面试的时候可能会让你手写一段这样的代码。最简单的方式就是用阻塞队列去写。类似下面:
生产者:
import java.util.random; import java.util.vector; import java.util.concurrent.atomic.atomicinteger; public class producer implements runnable { // true--->生产者一直执行,false--->停掉生产者 private volatile boolean isrunning = true; // 公共资源 private final vector sharedqueue; // 公共资源的最大数量 private final int size; // 生产数据 private static atomicinteger count = new atomicinteger(); public producer(vector sharedqueue, int size) { this.sharedqueue = sharedqueue; this.size = size; } @override public void run() { int data; random r = new random(); system.out.println("start producer id = " + thread.currentthread().getid()); try { while (isrunning) { // 模拟延迟 thread.sleep(r.nextint(1000)); // 当队列满时阻塞等待 while (sharedqueue.size() == size) { synchronized (sharedqueue) { system.out.println("queue is full, producer " + thread.currentthread().getid() + " is waiting, size:" + sharedqueue.size()); sharedqueue.wait(); } } // 队列不满时持续创造新元素 synchronized (sharedqueue) { // 生产数据 data = count.incrementandget(); sharedqueue.add(data); system.out.println("producer create data:" + data + ", size:" + sharedqueue.size()); sharedqueue.notifyall(); } } } catch (interruptedexception e) { e.printstacktrace(); thread.currentthread().interrupted(); } } public void stop() { isrunning = false; } }
消费者:
import java.util.random; import java.util.vector; public class consumer implements runnable { // 公共资源 private final vector sharedqueue; public consumer(vector sharedqueue) { this.sharedqueue = sharedqueue; } @override public void run() { random r = new random(); system.out.println("start consumer id = " + thread.currentthread().getid()); try { while (true) { // 模拟延迟 thread.sleep(r.nextint(1000)); // 当队列空时阻塞等待 while (sharedqueue.isempty()) { synchronized (sharedqueue) { system.out.println("queue is empty, consumer " + thread.currentthread().getid() + " is waiting, size:" + sharedqueue.size()); sharedqueue.wait(); } } // 队列不空时持续消费元素 synchronized (sharedqueue) { system.out.println("consumer consume data:" + sharedqueue.remove(0) + ", size:" + sharedqueue.size()); sharedqueue.notifyall(); } } } catch (interruptedexception e) { e.printstacktrace(); thread.currentthread().interrupt(); } } }
main方法测试:
import java.util.vector; import java.util.concurrent.executorservice; import java.util.concurrent.executors; public class test2 { public static void main(string[] args) throws interruptedexception { // 1.构建内存缓冲区 vector sharedqueue = new vector(); int size = 4; // 2.建立线程池和线程 executorservice service = executors.newcachedthreadpool(); producer prodthread1 = new producer(sharedqueue, size); producer prodthread2 = new producer(sharedqueue, size); producer prodthread3 = new producer(sharedqueue, size); consumer consthread1 = new consumer(sharedqueue); consumer consthread2 = new consumer(sharedqueue); consumer consthread3 = new consumer(sharedqueue); service.execute(prodthread1); service.execute(prodthread2); service.execute(prodthread3); service.execute(consthread1); service.execute(consthread2); service.execute(consthread3); // 3.睡一会儿然后尝试停止生产者(结束循环) thread.sleep(10 * 1000); prodthread1.stop(); prodthread2.stop(); prodthread3.stop(); // 4.再睡一会儿关闭线程池 thread.sleep(3000); // 5.shutdown()等待任务执行完才中断线程(因为消费者一直在运行的,所以会发现程序无法结束) service.shutdown(); } }
我的项目用阻塞队列也挺多的(我觉得跟个人编写的代码风格习惯有关),类似实现了上面的生产者和消费者模式。
真实场景例子:
- 运营要发一条推送消息,首先需要去用户画像系统圈选一个人群,填写对应的人群id和发送时间。
- 我通过时间调度,通过rpc拿到人群的信息。遍历hdfs得到这个人群的每个userid
- 将遍历的userid放到一个阻塞队列里边去,用多个线程while(true)取阻塞队列的数据
好处是什么?我在取userid的时候,会有个限制:要么超出了指定的时间,要么达到batchsize的值。这样我就可以将相同内容的不同userid组成一个task。
本来100个userid是100个task,现在我将100个userid放在一个task里边(因为发送的内容是相同的,所以我可以这么干)。这样再往下游传的时候,并发量就降低了很多。
什么时候考虑线程安全
什么时候考虑线程安全的集合类,那当然是线程不安全的时候咯。那什么时候线程不安全?最常见的是:操作的对象是有状态的
虽然说,我们经常会听到线程不安全,但在业务开发中要我们程序员处理线程不安全的地方少之又少。比如说:你在写servlet的时候,加过syn/lock
锁吗?应该没有吧?
因为我们的操作的对象往往是无状态的。没有共享变量被多个线程访问,自然就没有线程安全问题了。
springmvc是单例的,但springmvc都是在方法内操作数据的,每个线程进入方法都会生成栈帧,每个栈帧的数据都是线程独有的,如果不设定共享变量,不会有线程安全问题。
上面只是简单举了springmvc的例子(只是为了更好的理解);
一句话总结:只要涉及到多个线程操作一个共享变量的时候,就要考虑是不是要用线程安全的集合类。
更多的细节,等我写java多线程总结的时候再说了
最后
还是想强调一下,java集合虽然在工作中不是每个都经常用得到,但是还是得重点学习学习。
如果你学习到了源码,可能你在创建集合的时候就会指定了集合的大小(即便我们知道它能动态扩容)
如果你想要去面试,java集合是肯定少不了的,必问的一个知识点,你学会了就是送分题。
现在已经工作有一段时间了,为什么还来写java集合
呢,原因有以下几个:
- 我是一个对排版有追求的人,如果早期关注我的同学可能会发现,我的github、文章导航的
read.me
会经常更换。现在的github导航也不合我心意了(太长了),并且早期的文章,说实话排版也不太行,我决定重新搞一波。 - 我的文章会分发好几个平台,但文章发完了可能就没人看了,并且图床很可能因为平台的防盗链就挂掉了。又因为有很多的读者问我:”你能不能把你的文章转成pdf啊?“
- 我写过很多系列级的文章,这些文章就几乎不会有太大的改动了,就非常适合把它们给”持久化“。
基于上面的原因,我决定把我的系列文章汇总成一个pdf/html/word
文档。说实话,打造这么一个文档花了我不少的时间。为了防止白嫖,关注我的公众号回复「888」即可获取。
pdf的内容非常非常长,干货非常非常的硬,有兴趣的同学可以「白嫖」一波。记住:java集合在java的知识处于一个非常重要的知识点,建议掌握!
文档的内容均为手打,有任何的不懂都可以直接来问我(公众号有我的联系方式)。
上一期的「jsp」的pdf在公众号差点意思,目标是180个在看,虽然没达到,但我还是带着黑眼圈来了。
jsp的pdf有人会说:『大人,时代变了』,我就不信java集合还有人对我说『大人,时代变了』
如果这次在看超过200,那下周再肝一个系列出来。想要看什么,可以留言告诉我
涵盖java后端所有知识点的开源项目(已有6 k star):https://github.com/zhongfucheng3y/3y
如果大家想要实时关注我更新的文章以及分享的干货的话,微信搜索java3y。
pdf文档的内容均为手打,有任何的不懂都可以直接来问我(公众号有我的联系方式)。
上一篇: Spring管理事务的几种方式
下一篇: 根据传入字符串和截取个数分割字符串