91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何用JAVA源碼解析hashcode方法

發布時間:2021-10-23 17:44:24 來源:億速云 閱讀:178 作者:柒染 欄目:大數據

這期內容當中小編將會給大家帶來有關如何用JAVA源碼解析hashcode方法,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

在開發過程中我們可能會經常接觸到hashcode這個方法來生成哈希碼,那么底層是如何實現的?使用時有何注意點呢?

hashcode() 方法底層實現

hashcode()Object的方法:

@HotSpotIntrinsicCandidate
public native int hashCode();

它是一個native的方法,并且被@HotSpotIntrinsicCandidate注解修飾,證明它是一個在HotSpot中有一套高效的實現,該高效實現基于CPU指令。

具體的實現參考源碼synchronizer.cpp

static inline intptr_t get_next_hash(Thread* self, oop obj) {
  intptr_t value = 0;
  if (hashCode == 0) {
    value = os::random();
  } else if (hashCode == 1) {
    intptr_t addr_bits = cast_from_oop<intptr_t>(obj) >> 3;
    value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;
  } else if (hashCode == 2) {
    value = 1;           
  } else if (hashCode == 3) {
    value = ++GVars.hc_sequence;
  } else if (hashCode == 4) {
    value = cast_from_oop<intptr_t>(obj);
  } else {
    unsigned t = self->_hashStateX;
    t ^= (t << 11);
    self->_hashStateX = self->_hashStateY;
    self->_hashStateY = self->_hashStateZ;
    self->_hashStateZ = self->_hashStateW;
    unsigned v = self->_hashStateW;
    v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));
    self->_hashStateW = v;
    value = v;
  }

  value &= markWord::hash_mask;
  if (value == 0) value = 0xBAD;
  assert(value != markWord::no_hash, "invariant");
  return value;
}

可以看出,根據hashcode這個全局變量的取值,決定用何種策略生成哈希值,查看globals.hpp來看是哪一種變量:

 experimental(intx, hashCode, 5, "(Unstable) select hashCode generation algorithm")

發現是一個experimental的 JVM 變量,這樣的話,想要修改,必須添加額外的參數,如下所示:

-XX:+UnlockExperimentalVMOptions -XX:hashCode=2

并且,這個hashCode默認為5。

哈希值是每次hashcode()方法調用重計算么?

對于沒有覆蓋hashcode()方法的類,實例每次調用hashcode()方法,只有第一次計算哈希值,之后哈希值會存儲在對象頭的 標記字(MarkWord) 中。

 

如果進入各種鎖狀態,那么會緩存在其他地方,一般是獲取鎖的線程里面存儲,恢復無鎖(即釋放鎖)會改回原有的哈希值

關于對象頭結構,以及對象存儲結構,感興趣的話,可以參考:Java GC詳解 - 1. 理解Java對象結構

-XX:hashCode=0 利用 Park-Miller 偽隨機數生成器生成哈希值

if (hashCode == 0) {
    value = os::random();
}

調用 os 的 random 方法生成隨機數。這個方法的實現方式是: os.cpp:

//初始seed,默認是1
volatile unsigned int os::_rand_seed = 1;

static int random_helper(unsigned int rand_seed) {
  /* standard, well-known linear congruential random generator with
   * next_rand = (16807*seed) mod (2**31-1)
   * see
   * (1) "Random Number Generators: Good Ones Are Hard to Find",
   *      S.K. Park and K.W. Miller, Communications of the ACM 31:10 (Oct 1988),
   * (2) "Two Fast Implementations of the 'Minimal Standard' Random
   *     Number Generator", David G. Carta, Comm. ACM 33, 1 (Jan 1990), pp. 87-88.
  */
  const unsigned int a = 16807;
  const unsigned int m = 2147483647;
  const int q = m / a;        assert(q == 127773, "weird math");
  const int r = m % a;        assert(r == 2836, "weird math");

  // compute az=2^31p+q
  unsigned int lo = a * (rand_seed & 0xFFFF);
  unsigned int hi = a * (rand_seed >> 16);
  lo += (hi & 0x7FFF) << 16;

  // if q overflowed, ignore the overflow and increment q
  if (lo > m) {
    lo &= m;
    ++lo;
  }
  lo += hi >> 15;

  // if (p+q) overflowed, ignore the overflow and increment (p+q)
  if (lo > m) {
    lo &= m;
    ++lo;
  }
  return lo;
}

int os::random() {
  // Make updating the random seed thread safe.
  while (true) {
    unsigned int seed = _rand_seed;
    unsigned int rand = random_helper(seed);
    //CAS更新
    if (Atomic::cmpxchg(&_rand_seed, seed, rand) == seed) {
      return static_cast<int>(rand);
    }
  }
}

其中,random_helper 就是隨機數的生成公式的實現,公式是: 如何用JAVA源碼解析hashcode方法 這里,a=16807, c=0, m=2^31-1

由于這些隨機數都是采用的同一個生成器,會 CAS 更新同一個 seed,如果有大量的生成的新對象并且都調用hashcode()方法的話,可能會有性能問題。重復調用同一個對象的hashcode()方法不會有問題,因為之前提到了是有緩存的。

-XX:hashCode=1或者4 基于對象指針 OOPs

OOPs(Ordinary Object Pointers)對象指針是對象頭的一部分。關于對象頭結構,以及對象存儲結構,感興趣的話,可以參考:Java GC詳解 - 1. 理解Java對象結構。可以簡單理解為對象在內存中的地址的描述。

else if (hashCode == 1) {
    // This variation has the property of being stable (idempotent)
    // between STW operations.  This can be useful in some of the 1-0
    // synchronization schemes.
    intptr_t addr_bits = cast_from_oop<intptr_t>(obj) >> 3;
    value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;
}
else if (hashCode == 4) {
    value = cast_from_oop<intptr_t>(obj);
}

cast_from_oop很簡單,就是獲取oop的實現基類oopDesc的指向地址(oopDesc描述了OOP的基本組成,感興趣可以參考:Java GC詳解 - 1. 理解Java對象結構):

template <class T> inline T cast_from_oop(oop o) {
  return (T)(CHECK_UNHANDLED_OOPS_ONLY((oopDesc*))o);
}

-XX:hashCode=4,直接用oop的地址作為哈希值。-XX:hashCode=1則是經過變換的,每次發生 Stop The World (STW)stw_random會發生改變,通過這個addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random變換減少哈希碰撞,讓哈希值更散列化。

想更深入了解 Stop the world,可以參考:JVM相關 - SafePoint 與 Stop The World 全解(基于OpenJDK 11版本)

-XX:hashCode=2 敏感測試,恒定為1

else if (hashCode == 2) {
    value = 1;            // for sensitivity testing
}

主要用于測試某些集合是否對于哈希值敏感。

-XX:hashCode=3 自增序列

else if (hashCode == 3) {
    value = ++GVars.hc_sequence;
}

struct SharedGlobals {
  // omitted
  DEFINE_PAD_MINUS_SIZE(1, DEFAULT_CACHE_LINE_SIZE, sizeof(volatile int) * 2);
  // Hot RW variable -- Sequester to avoid false-sharing
  volatile int hc_sequence;
  DEFINE_PAD_MINUS_SIZE(2, DEFAULT_CACHE_LINE_SIZE, sizeof(volatile int));
};
static SharedGlobals GVars;

每創建一個新對象,調用哈希值,這個自增數+1,可以看出,散列性極差,很容易哈希碰撞。

-XX:hashCode=5 默認實現

else {
    // Marsaglia's xor-shift scheme with thread-specific state
    // This is probably the best overall implementation -- we'll
    // likely make this the default in future releases.
    unsigned t = self->_hashStateX;
    t ^= (t << 11);
    self->_hashStateX = self->_hashStateY;
    self->_hashStateY = self->_hashStateZ;
    self->_hashStateZ = self->_hashStateW;
    unsigned v = self->_hashStateW;
    v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));
    self->_hashStateW = v;
    value = v;
}

采用的算法是 Marsaglia's xor-shift 隨機數生成法。主要是這篇論文提出的一種快速并且散列性好的哈希算法。

特殊的哈希值導致某些場景的問題

我們經常使用某個對象或者某個字段的哈希值,通過對于某個數組長度取模,獲取到下標,取出數組對應下標的對象,進行進一步處理。這在負載均衡,任務調度,線程分配很常見。那下面這段代碼是否有問題呢?

//獲取userId這個字符串的哈希值的絕對值
int index = Math.abs(userId.hashCode());
//返回哈希值取模之后的下標的對象
return userAvatarList.get(index % userAvatarList.size()).getUrl();

通常大多數情況下,是沒有問題的,但是假設userId是這幾個哈希值為Integer.MIN_VALUE的字符串:

System.out.println("polygenelubricants".hashCode());
System.out.println("GydZG_".hashCode());
System.out.println("DESIGNING WORKHOUSES".hashCode());

輸出:

-2147483648
-2147483648
-2147483648

對于這些值,如果你用Math.abs()取絕對值的話,我們知道Math.abs(Integer.MIN_VALUE)還是等于Integer.MIN_VALUE,這是因為底層實現:

public static int abs(int a) {
    return (a < 0) ? -a : a;
}

-Integer.MIN_VALUEInteger.MIN_VALUE是相等的。Integer.MIN_VALUE取模還是負數,這樣取下標對應的對象的時候,就會報異常

所以,需要修改為:

int index = Math.abs(userId.hashCode() % userAvatarList.size());
return userAvatarList.get(index).getUrl();

上述就是小編為大家分享的如何用JAVA源碼解析hashcode方法了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

灵寿县| 枞阳县| 宁武县| 聂拉木县| 房山区| 隆德县| 钦州市| 济阳县| 兴海县| 尼木县| 五大连池市| 潮州市| 宜兰县| 商河县| 广东省| 博客| 黄浦区| 渝北区| 平利县| 沈阳市| 红安县| 平顶山市| 咸阳市| 弥渡县| 张北县| 博野县| 岫岩| 米脂县| 大悟县| 界首市| 葫芦岛市| 星座| 乐陵市| 丰城市| 马边| 冷水江市| 大洼县| 玉林市| 延寿县| 册亨县| 凌海市|