工作中常见到的Java集合类有哪些

感谢参考原文-http://bjbsair.com/2020-04-01/tech-info/18590.html

作为一个新人,最关心的其实有一点:这个技术在工作中是怎么用的。换个说法:“工作中常用到的Java集合有哪些,应用场景是什么”

工作中常用到的Java集合类有哪些?

如何入门Java集合以及每个常用的子类我在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接口

工作中常用到的Java集合类有哪些?

如果考虑线程安全的问题,可以看看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更多的可能用在刷算法的时候。

工作中常用到的Java集合类有哪些?

Map集合

Map集合最常见的子类也有三个:HashMap、LinkedHashMap、TreeMap

如果考虑线程安全问题,应该想到的是ConcurrentHashMap,当然了Hashtable也要有一定的了解,因为面试实在是问得太多太多了。

HashMap在实际开发中用得也非常多,只要是key-value结构的,一般我们就用HashMap。LinkedHashMap和TreeMap用的不多,原因跟HashSet和TreeSet一样。

ConcurrentHashMap在实际开发中也用得挺多,我们很多时候把ConcurrentHashMap用于本地缓存,不想每次都网络请求数据,在本地做本地缓存。监听数据的变化,如果数据有变动了,就把ConcurrentHashMap对应的值给更新了。

工作中常用到的Java集合类有哪些?

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锁吗?应该没有吧?

因为我们的操作的对象往往是无状态的。没有共享变量被多个线程访问,自然就没有线程安全问题了

工作中常用到的Java集合类有哪些?

SpringMVC是单例的,但SpringMVC都是在方法内操作数据的,每个线程进入方法都会生成栈帧,每个栈帧的数据都是线程独有的,如果不设定共享变量,不会有线程安全问题。

上面只是简单举了SpringMVC的例子(只是为了更好的理解);

一句话总结:只要涉及到多个线程操作一个共享变量的时候,就要考虑是不是要用线程安全的集合类

最后

还是想强调一下,Java集合虽然在工作中不是每个都经常用得到,但是还是得重点学习学习。

如果你学习到了源码,可能你在创建集合的时候就会指定了集合的大小(即便我们知道它能动态扩容)

如果你想要去面试,Java集合是肯定少不了的,必问的一个知识点,你学会了就是送分题

现在已经工作有一段时间了,为什么还来写Java集合呢,原因有以下几个:

  • 我是一个对排版有追求的人,如果早期关注我的同学可能会发现,我的GitHub、文章导航的read.me会经常更换。现在的GitHub导航也不合我心意了(太长了),并且早期的文章,说实话排版也不太行,我决定重新搞一波。
  • 我的文章会分发好几个平台,但文章发完了可能就没人看了,并且图床很可能因为平台的防盗链就挂掉了。又因为有很多的读者问我:”你能不能把你的文章转成PDF啊?“
  • 我写过很多系列级的文章,这些文章就几乎不会有太大的改动了,就非常适合把它们给”持久化“。感谢参考原文-http://bjbsair.com/2020-04-01/tech-info/18590.html

作为一个新人,最关心的其实有一点:这个技术在工作中是怎么用的。换个说法:“工作中常用到的Java集合有哪些,应用场景是什么”

工作中常用到的Java集合类有哪些?

如何入门Java集合以及每个常用的子类我在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接口

工作中常用到的Java集合类有哪些?

如果考虑线程安全的问题,可以看看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更多的可能用在刷算法的时候。

工作中常用到的Java集合类有哪些?

Map集合

Map集合最常见的子类也有三个:HashMap、LinkedHashMap、TreeMap

如果考虑线程安全问题,应该想到的是ConcurrentHashMap,当然了Hashtable也要有一定的了解,因为面试实在是问得太多太多了。

HashMap在实际开发中用得也非常多,只要是key-value结构的,一般我们就用HashMap。LinkedHashMap和TreeMap用的不多,原因跟HashSet和TreeSet一样。

ConcurrentHashMap在实际开发中也用得挺多,我们很多时候把ConcurrentHashMap用于本地缓存,不想每次都网络请求数据,在本地做本地缓存。监听数据的变化,如果数据有变动了,就把ConcurrentHashMap对应的值给更新了。

工作中常用到的Java集合类有哪些?

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锁吗?应该没有吧?

因为我们的操作的对象往往是无状态的。没有共享变量被多个线程访问,自然就没有线程安全问题了

工作中常用到的Java集合类有哪些?

SpringMVC是单例的,但SpringMVC都是在方法内操作数据的,每个线程进入方法都会生成栈帧,每个栈帧的数据都是线程独有的,如果不设定共享变量,不会有线程安全问题。

上面只是简单举了SpringMVC的例子(只是为了更好的理解);

一句话总结:只要涉及到多个线程操作一个共享变量的时候,就要考虑是不是要用线程安全的集合类

最后

还是想强调一下,Java集合虽然在工作中不是每个都经常用得到,但是还是得重点学习学习。

如果你学习到了源码,可能你在创建集合的时候就会指定了集合的大小(即便我们知道它能动态扩容)

如果你想要去面试,Java集合是肯定少不了的,必问的一个知识点,你学会了就是送分题

现在已经工作有一段时间了,为什么还来写Java集合呢,原因有以下几个:

  • 我是一个对排版有追求的人,如果早期关注我的同学可能会发现,我的GitHub、文章导航的read.me会经常更换。现在的GitHub导航也不合我心意了(太长了),并且早期的文章,说实话排版也不太行,我决定重新搞一波。
  • 我的文章会分发好几个平台,但文章发完了可能就没人看了,并且图床很可能因为平台的防盗链就挂掉了。又因为有很多的读者问我:”你能不能把你的文章转成PDF啊?“
  • 我写过很多系列级的文章,这些文章就几乎不会有太大的改动了,就非常适合把它们给”持久化“。感谢参考原文-http://bjbsair.com/2020-04-01/tech-info/18590.html

作为一个新人,最关心的其实有一点:这个技术在工作中是怎么用的。换个说法:“工作中常用到的Java集合有哪些,应用场景是什么”

工作中常用到的Java集合类有哪些?

如何入门Java集合以及每个常用的子类我在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接口

工作中常用到的Java集合类有哪些?

如果考虑线程安全的问题,可以看看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更多的可能用在刷算法的时候。

工作中常用到的Java集合类有哪些?

Map集合

Map集合最常见的子类也有三个:HashMap、LinkedHashMap、TreeMap

如果考虑线程安全问题,应该想到的是ConcurrentHashMap,当然了Hashtable也要有一定的了解,因为面试实在是问得太多太多了。

HashMap在实际开发中用得也非常多,只要是key-value结构的,一般我们就用HashMap。LinkedHashMap和TreeMap用的不多,原因跟HashSet和TreeSet一样。

ConcurrentHashMap在实际开发中也用得挺多,我们很多时候把ConcurrentHashMap用于本地缓存,不想每次都网络请求数据,在本地做本地缓存。监听数据的变化,如果数据有变动了,就把ConcurrentHashMap对应的值给更新了。

工作中常用到的Java集合类有哪些?

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锁吗?应该没有吧?

因为我们的操作的对象往往是无状态的。没有共享变量被多个线程访问,自然就没有线程安全问题了

工作中常用到的Java集合类有哪些?

SpringMVC是单例的,但SpringMVC都是在方法内操作数据的,每个线程进入方法都会生成栈帧,每个栈帧的数据都是线程独有的,如果不设定共享变量,不会有线程安全问题。

上面只是简单举了SpringMVC的例子(只是为了更好的理解);

一句话总结:只要涉及到多个线程操作一个共享变量的时候,就要考虑是不是要用线程安全的集合类

最后

还是想强调一下,Java集合虽然在工作中不是每个都经常用得到,但是还是得重点学习学习。

如果你学习到了源码,可能你在创建集合的时候就会指定了集合的大小(即便我们知道它能动态扩容)

如果你想要去面试,Java集合是肯定少不了的,必问的一个知识点,你学会了就是送分题

现在已经工作有一段时间了,为什么还来写Java集合呢,原因有以下几个:

  • 我是一个对排版有追求的人,如果早期关注我的同学可能会发现,我的GitHub、文章导航的read.me会经常更换。现在的GitHub导航也不合我心意了(太长了),并且早期的文章,说实话排版也不太行,我决定重新搞一波。
  • 我的文章会分发好几个平台,但文章发完了可能就没人看了,并且图床很可能因为平台的防盗链就挂掉了。又因为有很多的读者问我:”你能不能把你的文章转成PDF啊?“
  • 我写过很多系列级的文章,这些文章就几乎不会有太大的改动了,就非常适合把它们给”持久化“。感谢参考原文-http://bjbsair.com/2020-04-01/tech-info/18590.html

作为一个新人,最关心的其实有一点:这个技术在工作中是怎么用的。换个说法:“工作中常用到的Java集合有哪些,应用场景是什么”

工作中常用到的Java集合类有哪些?

如何入门Java集合以及每个常用的子类我在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接口

工作中常用到的Java集合类有哪些?

如果考虑线程安全的问题,可以看看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更多的可能用在刷算法的时候。

工作中常用到的Java集合类有哪些?

Map集合

Map集合最常见的子类也有三个:HashMap、LinkedHashMap、TreeMap

如果考虑线程安全问题,应该想到的是ConcurrentHashMap,当然了Hashtable也要有一定的了解,因为面试实在是问得太多太多了。

HashMap在实际开发中用得也非常多,只要是key-value结构的,一般我们就用HashMap。LinkedHashMap和TreeMap用的不多,原因跟HashSet和TreeSet一样。

ConcurrentHashMap在实际开发中也用得挺多,我们很多时候把ConcurrentHashMap用于本地缓存,不想每次都网络请求数据,在本地做本地缓存。监听数据的变化,如果数据有变动了,就把ConcurrentHashMap对应的值给更新了。

工作中常用到的Java集合类有哪些?

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锁吗?应该没有吧?

因为我们的操作的对象往往是无状态的。没有共享变量被多个线程访问,自然就没有线程安全问题了

工作中常用到的Java集合类有哪些?

SpringMVC是单例的,但SpringMVC都是在方法内操作数据的,每个线程进入方法都会生成栈帧,每个栈帧的数据都是线程独有的,如果不设定共享变量,不会有线程安全问题。

上面只是简单举了SpringMVC的例子(只是为了更好的理解);

一句话总结:只要涉及到多个线程操作一个共享变量的时候,就要考虑是不是要用线程安全的集合类

最后

还是想强调一下,Java集合虽然在工作中不是每个都经常用得到,但是还是得重点学习学习。

如果你学习到了源码,可能你在创建集合的时候就会指定了集合的大小(即便我们知道它能动态扩容)

如果你想要去面试,Java集合是肯定少不了的,必问的一个知识点,你学会了就是送分题

现在已经工作有一段时间了,为什么还来写Java集合呢,原因有以下几个:

  • 我是一个对排版有追求的人,如果早期关注我的同学可能会发现,我的GitHub、文章导航的read.me会经常更换。现在的GitHub导航也不合我心意了(太长了),并且早期的文章,说实话排版也不太行,我决定重新搞一波。
  • 我的文章会分发好几个平台,但文章发完了可能就没人看了,并且图床很可能因为平台的防盗链就挂掉了。又因为有很多的读者问我:”你能不能把你的文章转成PDF啊?“
  • 我写过很多系列级的文章,这些文章就几乎不会有太大的改动了,就非常适合把它们给”持久化“。
发布了0 篇原创文章 · 获赞 0 · 访问量 2019

猜你喜欢

转载自blog.csdn.net/zxjoke/article/details/105262732