你可能不知道但卻很有用的 Java 特性
本文轉(zhuǎn)載自微信公眾號「 crossoverJie」,作者crossoverJie 。轉(zhuǎn)載本文請聯(lián)系 crossoverJie公眾號。
在這篇文章中你將會學(xué)習(xí)到一些你可能沒聽過但有用的 Java 特性,這些是我個人常用的一些特性或者是從其他文章中學(xué)習(xí)到的,重點是關(guān)注 API 而不是語言本身。
延遲隊列
眾所周知,在 Java 中有許多類型的集合可以使用,但你聽說過 DelayQueue 嗎?它是一個特定類型的集合,允許我們基于延時時間對數(shù)據(jù)排序,這是一個非常有意思的類,它實現(xiàn)了 BlockingQueue 接口,只有當數(shù)據(jù)過期后才能從隊列里取出。
使用它的第一步,你的 class 需要實現(xiàn) Delayed 接口中的 getDelay 方法,當然也可以不用聲明一個 class,使用 Record 也是可以的。
這是 Java14 的新特性
- public record DelayedEvent(long startTime, String msg) implements Delayed {
- public long getDelay(TimeUnit unit) {
- long diff = startTime - System.currentTimeMillis();
- return unit.convert(diff, TimeUnit.MILLISECONDS);
- }
- public int compareTo(Delayed o) {
- return (int) (this.startTime - ((DelayedEvent) o).startTime);
- }
- }
假設(shè)我們需要一個延時 10s 取出的數(shù)據(jù),我們只需要放入一個比當前時間多 10s 的任務(wù)即可。
- final DelayQueue<DelayedEvent> delayQueue = new DelayQueue<>();
- final long timeFirst = System.currentTimeMillis() + 10000;
- delayQueue.offer(new DelayedEvent(timeFirst, "1"));
- log.info("Done");
- log.info(delayQueue.take().msg());
最終輸出如下:
時間格式的日期
這個特性可能對大部分人來說沒什么用,但老實說我個人非常喜歡;不管怎么說 Java 8 在時間 API 上改進了許多。從這個版本開始或許你不再需要其他任何擴展庫了。
你能想到嘛,從 Java 16 中你甚至可以用標準庫表示一天內(nèi)的日期了,比如 “in the morning” “in the afternoon” ,這是一個新的格式語句 B。
- String s = DateTimeFormatter
- .ofPattern("B")
- .format(LocalDateTime.now());
- System.out.println(s);
以下是我的輸出,具體和你當前時間有關(guān)。
你可能會想為什么會是調(diào)用 “B” 呢,這確實看起來不太直觀,通過下表也許能解答疑惑:
Stamped Lock
在我看來,并發(fā)包是 Java 中最有意思的包之一,同時又很少被開發(fā)者熟練掌握,特別是長期使用 web 開發(fā)框架的開發(fā)者。
有多少人曾經(jīng)使用過 Lock 呢?相對于 synchronized 來說這是一種更靈活的線程同步機制。
從 Java8 開始你可以使用一種新的鎖:StampedLock.StampedLock,能夠替代 ReadWriteLock。
假設(shè)現(xiàn)在有兩個線程,一個線程更新金額、一個線程讀取余額;更新余額的線程首先需要讀取金額,再多線程的情況下需要某種同步機制(不然更新數(shù)據(jù)會發(fā)生錯誤),第二個線程用樂觀鎖的方式讀取余額。
- StampedLock lock = new StampedLock();
- Balance b = new Balance(10000);
- Runnable w = () -> {
- long stamp = lock.writeLock();
- b.setAmount(b.getAmount() + 1000);
- System.out.println("Write: " + b.getAmount());
- lock.unlockWrite(stamp);
- };
- Runnable r = () -> {
- long stamp = lock.tryOptimisticRead();
- if (!lock.validate(stamp)) {
- stamp = lock.readLock();
- try {
- System.out.println("Read: " + b.getAmount());
- } finally {
- lock.unlockRead(stamp);
- }
- } else {
- System.out.println("Optimistic read fails");
- }
- };
現(xiàn)在更新和讀取的都用 50 個線程來進行測試,最終的余額將會等于 60000.
- ExecutorService executor = Executors.newFixedThreadPool(10);
- for (int i = 0; i < 50; i++) {
- executor.submit(w);
- executor.submit(r);
- }
并發(fā)累加器
鎖并并不是并發(fā)包中唯一有意思的特性,并發(fā)累加器也同樣有趣;它可以根據(jù)我們提供的函數(shù)更新數(shù)據(jù);再多線程更新數(shù)據(jù)的場景下,LongAccumulator 是比 AtomicLong 更優(yōu)的選擇。
現(xiàn)在讓我們來看看具體如何使用,我們需要兩個參數(shù)進行初始化;第一個是用于累加計算的函數(shù),通常是一個 sum 函數(shù),第二個參數(shù)則是累加計算的初始化值。
接下來我們用 10000 作為初始值來創(chuàng)建一個 LongAccumulator,最終結(jié)果是多少?其實結(jié)果與上文相同,都是 60000,但這次我們并沒有使用鎖。
- LongAccumulator balance = new LongAccumulator(Long::sum, 10000L);
- Runnable w = () -> balance.accumulate(1000L);
- ExecutorService executor = Executors.newFixedThreadPool(50);
- for (int i = 0; i < 50; i++) {
- executor.submit(w);
- }
- executor.shutdown();
- if (executor.awaitTermination(1000L, TimeUnit.MILLISECONDS))
- System.out.println("Balance: " + balance.get());
- assert balance.get() == 60000L;
數(shù)組的二分查找
假設(shè)我們想在一個排序列表中插入一個新元素,可以使用 Arrays.binarySearch() 函數(shù),當這個 key 存在時將會返回 key 所在的索引,如果不存在時將會返回插入的位置-(insertion point)-1。
binarySearch 是 Java 中非常簡單且有效的查詢方法。
下面的這個例子中,對返回結(jié)果取反便能的到索引位置。
- int[] t = new int[] {1, 2, 4, 5};
- int x = Arrays.binarySearch(t, 3);
- assert ~x == 2;
負數(shù)的二進制是以正數(shù)的補碼表示,對一個數(shù)取反+1 就等于補碼,所以這里直接取反就等于 Arrays.binarySearch() 不存在時的返回值了。
Bit Set
如果你需要對二進制數(shù)組進行操作你會怎么做?用 boolean[] 布爾數(shù)組?
有一種更高效又更省內(nèi)存的方式,那就是 BitSet。它允許我們存儲和操作 bit 數(shù)組,與 boolean[] 相比可省 8 倍的內(nèi)存;也可以使用 and/or/xor 等邏輯操作。
假設(shè)我們現(xiàn)在有兩個 bit 數(shù)組,我們需要對他們進行 xor 運算;我們需要創(chuàng)建兩個 BitSet 實例,然后調(diào)用 xor 函數(shù)。
- BitSet bs1 = new BitSet();
- bs1.set(0);
- bs1.set(2);
- bs1.set(4);
- System.out.println("bs1 : " + bs1);
- BitSet bs2 = new BitSet();
- bs2.set(1);
- bs2.set(2);
- bs2.set(3);
- System.out.println("bs2 : " + bs2);
- bs2.xor(bs1);
- System.out.println("xor: " + bs2);
最終的輸出結(jié)果如下: