hashmap应用场景_工作中常用到的Java集合有哪些?应用场景是什么?
- 秋招Java面試大綱:Java+并發(fā)+spring+數(shù)據(jù)庫(kù)+Redis+JVM+Netty等
- 疫情期間“閉關(guān)修煉”,吃透這本Java核心知識(shí),跳槽面試不心慌
- Spring全家桶筆記:Spring+Spring Boot+Spring Cloud+Spring MVC
前言
Java集合是我認(rèn)為在Java基礎(chǔ)中最最重要的知識(shí)點(diǎn)了,Java集合是必須掌握的。我在實(shí)習(xí)/秋招面試的時(shí)候,只要是面到Java,那一定是少不了Java集合。
作為一個(gè)新人,最關(guān)心的其實(shí)有一點(diǎn):這個(gè)技術(shù)在工作中是怎么用的。換個(gè)說(shuō)法:“工作中常用到的Java集合有哪些,應(yīng)用場(chǎng)景是什么”
List集合
List集合下最常見(jiàn)的集合類有兩個(gè):ArrayList和LinkedList
在工作中,我都是無(wú)腦用ArrayList。我問(wèn)了兩個(gè)同事:“你們?cè)陧?xiàng)目中用過(guò)LinkedList嗎?”他們都表示沒(méi)有。
眾所周知,ArrayList底層是數(shù)組,LinkedList底層是鏈表。數(shù)組遍歷速度快,LinkedList增刪元素快。
為什么在工作中一般就用ArrayList,而不用LinkedList呢?原因也很簡(jiǎn)單:
- 在工作中,遍歷的需求比增刪多,即便是增加元素往往也只是從尾部插入元素,而ArrayList在尾部插入元素也是O(1)
- ArrayList增刪沒(méi)有想象中慢,ArrayList的增刪底層調(diào)用的copyOf()被優(yōu)化過(guò),加上現(xiàn)代CPU對(duì)內(nèi)存可以塊操作,普通大小的ArrayList增刪比LinkedList更快。
所以,在開(kāi)發(fā)中,想到要用集合來(lái)裝載元素,第一個(gè)想到的就是ArrayList。
那么來(lái)了,LinkedList用在什么地方呢?我們一般用在刷算法題上。把LinkedList當(dāng)做一個(gè)先進(jìn)先出的隊(duì)列,LinkedList本身就實(shí)現(xiàn)了Queue接口
如果考慮線程安全的問(wèn)題,可以看看CopyWriteOnArrayList,實(shí)際開(kāi)發(fā)用得不多,但我覺(jué)得可以了解一下它的思想(CopyWriteOn),這個(gè)思想在Linux/文件系統(tǒng)都有用到。
Set集合
Set集合下最常見(jiàn)的集合類有三個(gè):HashSet、TreeSet、LinkedHashSet
List和Set都是集合,一般來(lái)說(shuō):如果我們需要保證集合的元素是唯一的,就應(yīng)該想到用Set集合
比如說(shuō):現(xiàn)在要發(fā)送一批消息給用戶,我們?yōu)榱藴p少「一次發(fā)送重復(fù)的內(nèi)容給用戶」這樣的錯(cuò)誤,我們就用Set集合來(lái)保存用戶的userId/phone
自然地,首先要保證最上游的那批用戶的userId/phone是沒(méi)有重復(fù)的,而我們用Set集合只是為了做一個(gè)兜底來(lái)盡可能避免重復(fù)發(fā)送的問(wèn)題。
一般我們?cè)陂_(kāi)發(fā)中最多用到的也就是HashSet。TreeSet是可以排序的Set,一般我們需要有序,從數(shù)據(jù)庫(kù)拉出來(lái)的數(shù)據(jù)就是有序的,可能往往寫(xiě)order by id desc比較多。而在開(kāi)發(fā)中也很少管元素插入有序的問(wèn)題,所以LinkedHashSet一般也用不上。
如果考慮線程安全的問(wèn)題,可以考慮CopyOnWriteArraySet,用得就更少了(這是一個(gè)線程安全的Set,底層實(shí)際上就是CopyWriteOnArrayList)
TreeSet和LinkedHashSet更多的可能用在刷算法的時(shí)候。
Map集合
Map集合最常見(jiàn)的子類也有三個(gè):HashMap、LinkedHashMap、TreeMap
如果考慮線程安全問(wèn)題,應(yīng)該想到的是ConcurrentHashMap,當(dāng)然了Hashtable也要有一定的了解,因?yàn)槊嬖噷?shí)在是問(wèn)得太多太多了。
HashMap在實(shí)際開(kāi)發(fā)中用得也非常多,只要是key-value結(jié)構(gòu)的,一般我們就用HashMap。LinkedHashMap和TreeMap用的不多,原因跟HashSet和TreeSet一樣。
ConcurrentHashMap在實(shí)際開(kāi)發(fā)中也用得挺多,我們很多時(shí)候把ConcurrentHashMap用于本地緩存,不想每次都網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù),在本地做本地緩存。監(jiān)聽(tīng)數(shù)據(jù)的變化,如果數(shù)據(jù)有變動(dòng)了,就把ConcurrentHashMap對(duì)應(yīng)的值給更新了。
Queue隊(duì)列
不知道大家有沒(méi)有學(xué)過(guò)生產(chǎn)者和消費(fèi)者模式,秋招面試的時(shí)候可能會(huì)讓你手寫(xiě)一段這樣的代碼。最簡(jiǎn)單的方式就是用阻塞隊(duì)列去寫(xiě)。類似下面:
生產(chǎn)者:
import java.util.Random;import java.util.Vector;import java.util.concurrent.atomic.AtomicInteger;public class Producer implements Runnable { // true--->生產(chǎn)者一直執(zhí)行,false--->停掉生產(chǎn)者 private volatile boolean isRunning = true; // 公共資源 private final Vector sharedQueue; // 公共資源的最大數(shù)量 private final int SIZE; // 生產(chǎn)數(shù)據(jù) 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)); // 當(dāng)隊(duì)列滿時(shí)阻塞等待 while (sharedQueue.size() == SIZE) { synchronized (sharedQueue) { System.out.println("Queue is full, producer " + Thread.currentThread().getId() + " is waiting, size:" + sharedQueue.size()); sharedQueue.wait(); } } // 隊(duì)列不滿時(shí)持續(xù)創(chuàng)造新元素 synchronized (sharedQueue) { // 生產(chǎn)數(shù)據(jù) 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; }}消費(fèi)者:
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)); // 當(dāng)隊(duì)列空時(shí)阻塞等待 while (sharedQueue.isEmpty()) { synchronized (sharedQueue) { System.out.println("Queue is empty, consumer " + Thread.currentThread().getId() + " is waiting, size:" + sharedQueue.size()); sharedQueue.wait(); } } // 隊(duì)列不空時(shí)持續(xù)消費(fèi)元素 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方法測(cè)試:
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.構(gòu)建內(nèi)存緩沖區(qū) 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.睡一會(huì)兒然后嘗試停止生產(chǎn)者(結(jié)束循環(huán)) Thread.sleep(10 * 1000); prodThread1.stop(); prodThread2.stop(); prodThread3.stop(); // 4.再睡一會(huì)兒關(guān)閉線程池 Thread.sleep(3000); // 5.shutdown()等待任務(wù)執(zhí)行完才中斷線程(因?yàn)橄M(fèi)者一直在運(yùn)行的,所以會(huì)發(fā)現(xiàn)程序無(wú)法結(jié)束) service.shutdown(); }}我的項(xiàng)目用阻塞隊(duì)列也挺多的(我覺(jué)得跟個(gè)人編寫(xiě)的代碼風(fēng)格習(xí)慣有關(guān)),類似實(shí)現(xiàn)了上面的生產(chǎn)者和消費(fèi)者模式。
真實(shí)場(chǎng)景例子:
- 運(yùn)營(yíng)要發(fā)一條推送消息,首先需要去用戶畫(huà)像系統(tǒng)圈選一個(gè)人群,填寫(xiě)對(duì)應(yīng)的人群ID和發(fā)送時(shí)間。
- 我通過(guò)時(shí)間調(diào)度,通過(guò)RPC拿到人群的信息。遍歷HDFS得到這個(gè)人群的每個(gè)userId
- 將遍歷的userId放到一個(gè)阻塞隊(duì)列里邊去,用多個(gè)線程while(true)取阻塞隊(duì)列的數(shù)據(jù)
好處是什么?我在取userId的時(shí)候,會(huì)有個(gè)限制:要么超出了指定的時(shí)間,要么達(dá)到BatchSize的值。這樣我就可以將相同內(nèi)容的不同userId組成一個(gè)Task。
本來(lái)100個(gè)userId是100個(gè)Task,現(xiàn)在我將100個(gè)userId放在一個(gè)Task里邊(因?yàn)榘l(fā)送的內(nèi)容是相同的,所以我可以這么干)。這樣再往下游傳的時(shí)候,并發(fā)量就降低了很多。
什么時(shí)候考慮線程安全
什么時(shí)候考慮線程安全的集合類,那當(dāng)然是線程不安全的時(shí)候咯。那什么時(shí)候線程不安全?最常見(jiàn)的是:操作的對(duì)象是有狀態(tài)的
雖然說(shuō),我們經(jīng)常會(huì)聽(tīng)到線程不安全,但在業(yè)務(wù)開(kāi)發(fā)中要我們程序員處理線程不安全的地方少之又少。比如說(shuō):你在寫(xiě)Servlet的時(shí)候,加過(guò)syn/lock鎖嗎?應(yīng)該沒(méi)有吧?
因?yàn)槲覀兊牟僮鞯膶?duì)象往往是無(wú)狀態(tài)的。沒(méi)有共享變量被多個(gè)線程訪問(wèn),自然就沒(méi)有線程安全問(wèn)題了。
SpringMVC是單例的,但SpringMVC都是在方法內(nèi)操作數(shù)據(jù)的,每個(gè)線程進(jìn)入方法都會(huì)生成棧幀,每個(gè)棧幀的數(shù)據(jù)都是線程獨(dú)有的,如果不設(shè)定共享變量,不會(huì)有線程安全問(wèn)題。
上面只是簡(jiǎn)單舉了SpringMVC的例子(只是為了更好的理解);
一句話總結(jié):只要涉及到多個(gè)線程操作一個(gè)共享變量的時(shí)候,就要考慮是不是要用線程安全的集合類。
最后
還是想強(qiáng)調(diào)一下,Java集合雖然在工作中不是每個(gè)都經(jīng)常用得到,但是還是得重點(diǎn)學(xué)習(xí)學(xué)習(xí)。
如果你學(xué)習(xí)到了源碼,可能你在創(chuàng)建集合的時(shí)候就會(huì)指定了集合的大小(即便我們知道它能動(dòng)態(tài)擴(kuò)容)
如果你想要去面試,Java集合是肯定少不了的,必問(wèn)的一個(gè)知識(shí)點(diǎn),你學(xué)會(huì)了就是送分題。
現(xiàn)在已經(jīng)工作有一段時(shí)間了,為什么還來(lái)寫(xiě)Java集合呢,原因有以下幾個(gè):
- 我是一個(gè)對(duì)排版有追求的人,如果早期關(guān)注我的同學(xué)可能會(huì)發(fā)現(xiàn),我的GitHub、文章導(dǎo)航的read.me會(huì)經(jīng)常更換。現(xiàn)在的GitHub導(dǎo)航也不合我心意了(太長(zhǎng)了),并且早期的文章,說(shuō)實(shí)話排版也不太行,我決定重新搞一波。
- 我的文章會(huì)分發(fā)好幾個(gè)平臺(tái),但文章發(fā)完了可能就沒(méi)人看了,并且圖床很可能因?yàn)槠脚_(tái)的防盜鏈就掛掉了。又因?yàn)橛泻芏嗟淖x者問(wèn)我:”你能不能把你的文章轉(zhuǎn)成PDF啊?“
- 我寫(xiě)過(guò)很多系列級(jí)的文章,這些文章就幾乎不會(huì)有太大的改動(dòng)了,就非常適合把它們給”持久化“。
作者:Java3y
原文鏈接:https://juejin.im/post/5e7c05236fb9a009a6764ef9
總結(jié)
以上是生活随笔為你收集整理的hashmap应用场景_工作中常用到的Java集合有哪些?应用场景是什么?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: qmc0转换mp3工具_GoldenRe
- 下一篇: mappedbytebuffer_Jav