JDK源碼中實(shí)用的小技巧有哪些

這篇文章給大家分享的是有關(guān)JDK源碼中實(shí)用的小技巧有哪些的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。

創(chuàng)新互聯(lián)公司長(zhǎng)期為超過(guò)千家客戶(hù)提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開(kāi)放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為射陽(yáng)企業(yè)提供專(zhuān)業(yè)的成都網(wǎng)站建設(shè)、成都網(wǎng)站制作,射陽(yáng)網(wǎng)站改版等技術(shù)服務(wù)。擁有10年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開(kāi)發(fā)。

1 i++ vs i--

String源碼的第985行,equals方法中

 while (n--!= 0) {
  if (v1[i] != v2[i])
   return false;
  i++;   
 }

這段代碼是用于判斷字符串是否相等,但有個(gè)奇怪地方是用了i--!=0來(lái)做判斷,我們通常不是用i++么?為什么用i--呢?而且循環(huán)次數(shù)相同。原因在于編譯后會(huì)多一條指令:

i-- 操作本身會(huì)影響CPSR(當(dāng)前程序狀態(tài)寄存器),CPSR常見(jiàn)的標(biāo)志有N(結(jié)果為負(fù)), Z(結(jié)果為0),C(有進(jìn)位),O(有溢出)。i > 0,可以直接通過(guò)Z標(biāo)志判斷出來(lái)。

i++操作也會(huì)影響CPSR(當(dāng)前程序狀態(tài)寄存器),但只影響O(有溢出)標(biāo)志,這對(duì)于i < n的判斷沒(méi)有任何幫助。所以還需要一條額外的比較指令,也就是說(shuō)每個(gè)循環(huán)要多執(zhí)行一條指令。

簡(jiǎn)單來(lái)說(shuō),跟0比較會(huì)少一條指令。所以,循環(huán)使用i--,高端大氣上檔次。

2 成員變量 vs 局部變量

JDK源碼在任何方法中幾乎都會(huì)用一個(gè)局部變量來(lái)接受成員變量,比如

public int compareTo(String anotherString) {
  int len1 = value.length;
  int len2 = anotherString.value.length;

因?yàn)榫植孔兞砍跏蓟笫窃谠摲椒ň€(xiàn)程棧中,而成員變量初始化是在堆內(nèi)存中,顯然前者更快,所以,我們?cè)诜椒ㄖ斜M量避免直接使用成員變量,而是使用局部變量。

3 刻意加載到寄存器 && 將耗時(shí)操作放到鎖外部

在ConcurrentHashMap中,鎖segment的操作很有意思,它不是直接鎖,而是類(lèi)似于自旋鎖,反復(fù)嘗試獲取鎖,并且在獲取鎖的過(guò)程中,會(huì)遍歷鏈表,從而將數(shù)據(jù)先加載到寄存器中緩存中,避免在鎖的過(guò)程中在便利,同時(shí),生成新對(duì)象的操作也是放到鎖的外部來(lái)做,避免在鎖中的耗時(shí)操作

  final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    /** 在往該 segment 寫(xiě)入前,需要先獲取該 segment 的獨(dú)占鎖
      不是強(qiáng)制lock(),而是進(jìn)行嘗試 */
    HashEntry<K,V> node = tryLock() ? null :
      scanAndLockForPut(key, hash, value);

scanAndLockForPut()源碼

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
  HashEntry<K,V> first = entryForHash(this, hash);
  HashEntry<K,V> e = first;
  HashEntry<K,V> node = null;
  int retries = -1; // negative while locating node

  // 循環(huán)獲取鎖
  while (!tryLock()) {
    HashEntry<K,V> f; // to recheck first below
    if (retries < 0) {
      if (e == null) {
        if (node == null) // speculatively create node
          //該hash位無(wú)值,新建對(duì)象,而不用再到put()方法的鎖中再新建
          node = new HashEntry<K,V>(hash, key, value, null);
        retries = 0;
      }
      //該hash位置key也相同,退化成自旋鎖
      else if (key.equals(e.key))
        retries = 0;
      else
        // 循環(huán)鏈表,cpu能自動(dòng)將鏈表讀入緩存
        e = e.next;
    }
    // retries>0時(shí)就變成自旋鎖。當(dāng)然,如果重試次數(shù)如果超過(guò) MAX_SCAN_RETRIES(單核1多核64),那么不搶了,進(jìn)入到阻塞隊(duì)列等待鎖
    //  lock() 是阻塞方法,直到獲取鎖后返回,否則掛起
    else if (++retries > MAX_SCAN_RETRIES) {
      lock();
      break;
    }
    else if ((retries & 1) == 0 &&
         // 這個(gè)時(shí)候是有大問(wèn)題了,那就是有新的元素進(jìn)到了鏈表,成為了新的表頭
         //   所以這邊的策略是,相當(dāng)于重新走一遍這個(gè) scanAndLockForPut 方法
         (f = entryForHash(this, hash)) != first) {
      e = first = f; // re-traverse if entry changed
      retries = -1;
    }
  }
  return node;
}

4 判斷對(duì)象相等可先用==

在判斷對(duì)象是否相等時(shí),可先用==,因?yàn)?=直接比較地址,非常快,而equals的話(huà)會(huì)最對(duì)象值的比較,相對(duì)較慢,所以有可能的話(huà),可以用a==b || a.equals(b)來(lái)比較對(duì)象是否相等

5 關(guān)于transient

transient是用來(lái)阻止序列化的,但HashMap源碼中內(nèi)部數(shù)組是定義為transient的

 /**
   * The table, resized as necessary. Length MUST Always be a power of two.
   */
  transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

那豈不里面的鍵值對(duì)都無(wú)法序列化了么,網(wǎng)絡(luò)中用hashmap來(lái)傳輸豈不是無(wú)法傳輸,其實(shí)不然。

Effective Java 2nd, Item75, Joshua大神提到:

For example, consider the case of a hash table. The physical
representation is a sequence of hash buckets containing key-value
entries. The bucket that an entry resides in is a function of the hash
code of its key, which is not, in general, guaranteed to be the same
from JVM implementation to JVM implementation. In fact, it isn't even
guaranteed to be the same from run to run. Therefore, accepting the
default serialized form for a hash table would constitute a serious
bug. Serializing and deserializing the hash table could yield an
object whose invariants were seriously corrupt.

怎么理解? 看一下HashMap.get()/put()知道, 讀寫(xiě)Map是根據(jù)Object.hashcode()來(lái)確定從哪個(gè)bucket讀/寫(xiě). 而Object.hashcode()是native方法, 不同的JVM里可能是不一樣的.

打個(gè)比方說(shuō), 向HashMap存一個(gè)entry, key為 字符串"STRING", 在第一個(gè)java程序里, "STRING"的hashcode()為1, 存入第1號(hào)bucket; 在第二個(gè)java程序里, "STRING"的hashcode()有可能就是2, 存入第2號(hào)bucket. 如果用默認(rèn)的串行化(Entry[] table不用transient), 那么這個(gè)HashMap從第一個(gè)java程序里通過(guò)串行化導(dǎo)入第二個(gè)java程序后, 其內(nèi)存分布是一樣的, 這就不對(duì)了.

舉個(gè)例子,比如向HashMap存一個(gè)鍵值對(duì)entry, key="方老司", 在第一個(gè)java程序里, "方老司"的hashcode()為1, 存入table[1],好,現(xiàn)在傳到另一個(gè)在JVM程序里, "方老司" 的hashcode()有可能就是2, 于是到table[2]去取,結(jié)果值不存在。

HashMap現(xiàn)在的readObject和writeObject是把內(nèi)容 輸出/輸入, 把HashMap重新生成出來(lái).

6 不要用char

char在Java中utf-16編碼,是2個(gè)字節(jié),而2個(gè)字節(jié)是無(wú)法表示全部字符的。2個(gè)字節(jié)表示的稱(chēng)為 BMP,另外的作為high surrogate和 low surrogate 拼接組成由4字節(jié)表示的字符。比如String源碼中的indexOf:

 //這里用int來(lái)接受一個(gè)char,方便判斷范圍
 public int indexOf(int ch, int fromIndex) {
    final int max = value.length;
    if (fromIndex < 0) {
      fromIndex = 0;
    } else if (fromIndex >= max) {
      // Note: fromIndex might be near -1>>>1.
      return -1;
    }
    //在Bmp范圍
    if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
      // handle most cases here (ch is a BMP code point or a
      // negative value (invalid code point))
      final char[] value = this.value;
      for (int i = fromIndex; i < max; i++) {
        if (value[i] == ch) {
          return i;
        }
      }
      return -1;
    } else {
      //否則轉(zhuǎn)到四個(gè)字節(jié)的判斷方式
      return indexOfSupplementary(ch, fromIndex);
    }
  }

所以Java的char只能表示utf&shy;16中的bmp部分字符。對(duì)于CJK(中日韓統(tǒng)一表意文字)部分?jǐn)U展字符集則無(wú)法表示。

例如,下圖中除Ext-A部分,char均無(wú)法表示。

JDK源碼中實(shí)用的小技巧有哪些

此外還有一種說(shuō)法是要用char,密碼別用String,String是常量(即創(chuàng)建之后就無(wú)法更改),會(huì)保存到常量池中,如果有其他進(jìn)程可以dump這個(gè)進(jìn)程的內(nèi)存,那么密碼就會(huì)隨著常量池被dump出去從而泄露,而char[]可以寫(xiě)入其他的信息從而改變,即是被dump了也會(huì)減少泄露密碼的風(fēng)險(xiǎn)。

但個(gè)人認(rèn)為你都能dump內(nèi)存了難道是一個(gè)char能夠防范的住的?除非是String在常量池中未被回收,而被其它線(xiàn)程直接從常量池中讀取,但恐怕也是非常罕見(jiàn)的吧。

感謝各位的閱讀!關(guān)于“JDK源碼中實(shí)用的小技巧有哪些”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

分享名稱(chēng):JDK源碼中實(shí)用的小技巧有哪些
網(wǎng)址分享:http://muchs.cn/article28/jsdocp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供標(biāo)簽優(yōu)化、定制網(wǎng)站搜索引擎優(yōu)化、自適應(yīng)網(wǎng)站、App設(shè)計(jì)、微信小程序

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁(yè)設(shè)計(jì)公司