摘要:哈希表哈希表是根據(jù)關(guān)鍵碼值而直接進(jìn)行訪問的數(shù)據(jù)結(jié)構(gòu)。這樣做的原因是和都是的次冪,并且是的倍,表示轉(zhuǎn)換為二進(jìn)制的唯一一個(gè)向高位移位一次。
一、HashMap簡介
HashMap是基于“拉鏈法”實(shí)現(xiàn)的散列表。一般用于單線程程序中,JDK 1.8對(duì)HashMap進(jìn)行了比較大的優(yōu)化,底層實(shí)現(xiàn)由之前的“數(shù)組+鏈表”改為“數(shù)組+鏈表+紅黑樹”。下面先介紹HashMap中一些關(guān)鍵的知識(shí)點(diǎn)。
1、哈希表哈希表是根據(jù)關(guān)鍵碼值(Key value)而直接進(jìn)行訪問的數(shù)據(jù)結(jié)構(gòu)。也就是說,它通過把關(guān)鍵碼值映射到表中一個(gè)位置來訪問記錄,以加快查找的速度。這個(gè)映射函數(shù)叫做散列函數(shù),存放記錄的數(shù)組叫做散列表。下面是百度百科中的一張哈希表示例:
常用的散列方法有: 直接尋址法:取關(guān)鍵字或關(guān)鍵字的某個(gè)線性函數(shù)值為散列地址、數(shù)字分析法:分析一組數(shù)據(jù),比如一組員工的出生年月日,這時(shí)我們發(fā)現(xiàn)出生年月日的前幾位數(shù)字大體相同、平方取中法:當(dāng)無法確定關(guān)鍵字中哪幾位分布較均勻時(shí),可以先求出關(guān)鍵字的平方值,然后按需要取平方值的中間幾位作為哈希地址。
紅黑樹是一顆自平衡的二叉查找樹,除了符合二叉查找樹的特定,還有一下一些特點(diǎn):
節(jié)點(diǎn)是紅色或黑色。
根節(jié)點(diǎn)是黑色。
每個(gè)葉子節(jié)點(diǎn)都是黑色的空節(jié)點(diǎn)(NIL節(jié)點(diǎn))。
每個(gè)紅色節(jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色。(從每個(gè)葉子到根的所有路徑上不能有兩個(gè)連續(xù)的紅色節(jié)點(diǎn))
從任一節(jié)點(diǎn)到其每個(gè)葉子的所有路徑都包含相同數(shù)目的黑色節(jié)點(diǎn)。
下面是一棵紅黑樹的示例圖:
3、HashMap中的節(jié)點(diǎn)結(jié)構(gòu)HashMap中通過實(shí)現(xiàn)Map.Entry
static class Nodeimplements Map.Entry { //hash值 final int hash; //map中的key final K key; //map中的值 V value; //指向的下一個(gè)節(jié)點(diǎn),用于hash表中的鏈表 Node next; Node(int hash, K key, V value, Node next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry,?> e = (Map.Entry,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
另外,通過定義繼承LinkedHashMap.Entry
HashMap繼承自AbstractMap,并且實(shí)現(xiàn)了Map、Cloneable和Serializable接口,具體的源碼分析如下:
public class HashMap三、使用示例 1、重寫equals時(shí)也要同時(shí)重寫hashCodeextends AbstractMap implements Map , Cloneable, Serializable { /** * 默認(rèn)初始化容量大小,必須是2的次冪 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; /** * 最大的容量 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 默認(rèn)負(fù)載因子. */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 鏈表節(jié)點(diǎn)轉(zhuǎn)紅黑樹節(jié)點(diǎn)的閾值,9個(gè)節(jié)點(diǎn)時(shí)轉(zhuǎn) */ static final int TREEIFY_THRESHOLD = 8; /** * 紅黑樹節(jié)點(diǎn)轉(zhuǎn)為鏈表的閾值,6個(gè)節(jié)點(diǎn)時(shí)轉(zhuǎn) */ static final int UNTREEIFY_THRESHOLD = 6; /** * 鏈表節(jié)點(diǎn)轉(zhuǎn)紅黑樹節(jié)點(diǎn)時(shí),哈希表達(dá)最小節(jié)點(diǎn)為64 */ static final int MIN_TREEIFY_CAPACITY = 64; /** * 鏈表節(jié)點(diǎn)結(jié)構(gòu) */ static class Node implements Map.Entry { final int hash; final K key; V value; Node next; Node(int hash, K key, V value, Node next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry,?> e = (Map.Entry,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } } /* ---------------- 靜態(tài)公用方法 -------------- */ /** * 對(duì)hashCode的hash計(jì)算如總結(jié)中圖所示: * 在設(shè)計(jì)hash函數(shù)時(shí),因?yàn)槟壳暗膖able長度n為2的次冪,所以計(jì)算下標(biāo)的時(shí)候,可使用按位與&代替取模%:(n - 1) & hash。 * 設(shè)計(jì)者想了一個(gè)顧全大局的方法(綜合考慮了速度、作用、質(zhì)量),就是把高16bit和低16bit異或了一下。 * 設(shè)計(jì)者還解釋到因?yàn)楝F(xiàn)在大多數(shù)的hashCode的分布已經(jīng)很不錯(cuò)了,就算是發(fā)生了碰撞也用O(logn)的tree去做了。 * 僅僅異或一下,既減少了系統(tǒng)的開銷,也不會(huì)造成因?yàn)楦呶粵]有參與下標(biāo)的計(jì)算(table長度比較小)時(shí),引起的碰撞。 */ /** *計(jì)算hash值,根據(jù)key的hashCode計(jì)算 */ static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } /** * 如果對(duì)象實(shí)現(xiàn)了Comparable接口,則返回其Class對(duì)象 */ static Class> comparableClassFor(Object x) { if (x instanceof Comparable) { Class> c; Type[] ts, as; Type t; ParameterizedType p; if ((c = x.getClass()) == String.class) // bypass checks return c; if ((ts = c.getGenericInterfaces()) != null) { for (int i = 0; i < ts.length; ++i) { if (((t = ts[i]) instanceof ParameterizedType) && ((p = (ParameterizedType)t).getRawType() == Comparable.class) && (as = p.getActualTypeArguments()) != null && as.length == 1 && as[0] == c) // type arg is c return c; } } } return null; } /** * 如果x和kc匹配,返回k.compareTo(x),否則返回0。 */ @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable static int compareComparables(Class> kc, Object k, Object x) { return (x == null || x.getClass() != kc ? 0 : ((Comparable)k).compareTo(x)); } /** * 根據(jù)給定的容量大小,返回一個(gè)2的次冪大小的值。比如,cap=7,返回8 */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } /* ---------------- 成員變量 -------------- */ /** * 哈希表定義,在第一次使用時(shí)初始化 */ transient Node [] table; /** * 節(jié)點(diǎn)緩存 */ transient Set > entrySet; /** * map中含有key-value的大小 */ transient int size; /** * 修改次數(shù) */ transient int modCount; /** * 下次要調(diào)整容量的大小 (capacity * load factor). */ int threshold; /** * 哈希表的負(fù)載因子 */ final float loadFactor; /* ---------------- 公共操作 -------------- */ /** * 給定初始化容量和負(fù)載因子的構(gòu)造方法 */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } /** * 給定初始化大小的構(gòu)造函數(shù) */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 無參構(gòu)造方法 */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * 通過給定的map構(gòu)造一個(gè)hashmap,負(fù)載因子是0.75 */ public HashMap(Map extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); } /** * 此方法是先構(gòu)造一個(gè)hashMap對(duì)象,調(diào)用putVal方法將m中的元素入新map * */ final void putMapEntries(Map extends K, ? extends V> m, boolean evict) { //m的大小 int s = m.size(); if (s > 0) { //table沒有初始化,先計(jì)算threshold if (table == null) { // pre-size //獲取容量初始大小,+1可以節(jié)省一次resize float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); //計(jì)算threshold if (t > threshold) threshold = tableSizeFor(t); } //如果threshold小于s,調(diào)整大小 else if (s > threshold) resize(); //調(diào)用 putVal將m中的節(jié)點(diǎn)元素入此hashmap for (Map.Entry extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } } /** * 返回map中key-value中的數(shù)量 */ public int size() { return size; } /** * 返回map中key-value中的數(shù)量是否為0 */ public boolean isEmpty() { return size == 0; } /** * 通過key獲取一個(gè)節(jié)點(diǎn)元素,如果不存在,返回null * */ public V get(Object key) { Node e; return (e = getNode(hash(key), key)) == null ? null : e.value; } /** * Map.get的實(shí)現(xiàn)方法 */ final Node getNode(int hash, Object key) { Node [] tab; Node first, e; int n; K k; //如果table不為空,且長度大于0、通過hash能找到第一個(gè)節(jié)點(diǎn) if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //檢查第一個(gè)節(jié)點(diǎn),判斷key是否相等 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; //如果有下一個(gè)節(jié)點(diǎn) if ((e = first.next) != null) { //判斷是否為紅黑樹節(jié)點(diǎn),如果是,調(diào)用getTreeNode方法獲取節(jié)點(diǎn)元素 if (first instanceof TreeNode) return ((TreeNode )first).getTreeNode(hash, key); //循環(huán)取下一個(gè)節(jié)點(diǎn)比較 do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; } /** * 返回map中是否包含key */ public boolean containsKey(Object key) { return getNode(hash(key), key) != null; } /** * 調(diào)用putVal方法,添加一個(gè)節(jié)點(diǎn) */ public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } /** * Map.put的實(shí)現(xiàn)方法 * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent 如果是true,不替換已存在value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node [] tab; Node p; int n, i; //如果table為空,或者大小為0,調(diào)用resize方法 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //hash后,如果此位置的節(jié)點(diǎn)為null,則新建節(jié)點(diǎn),賦值到此位置 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node e; K k; //檢查第一個(gè)節(jié)點(diǎn),如果key一致,替換節(jié)點(diǎn) if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //如果此節(jié)點(diǎn)時(shí)紅黑樹節(jié)點(diǎn),調(diào)用紅黑樹putTreeVal方法添加節(jié)點(diǎn) else if (p instanceof TreeNode) e = ((TreeNode )p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { //到最后一個(gè)節(jié)點(diǎn),新建此節(jié)點(diǎn),并且檢查是否轉(zhuǎn)換為紅黑樹 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //根據(jù)onlyIfAbsent 判斷是否需要替換value if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //size+1 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } /** * 數(shù)組初始化或者加倍 * @return the table */ final Node [] resize() { Node [] oldTab = table; //原數(shù)組的大小 int oldCap = (oldTab == null) ? 0 : oldTab.length; //原數(shù)組的閾值 int oldThr = threshold; //新的大小和閾值 int newCap, newThr = 0; if (oldCap > 0) { //如果原數(shù)組容量已大于等于最大容量,閾值賦值最大整數(shù),返回原數(shù)組 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //如果容量加倍小于最大容量,并且原容量大小大于等于初始默認(rèn)容量,新閾值翻倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } //如果原閾值大于0,則新閾值就是原閾值 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; //否則,新閾值和新容量都默認(rèn) else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //如果新閾值為0,通過加載因子和新容量計(jì)算新閾值 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } //將新閾值賦值threshold threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) //定義新的數(shù)組 Node [] newTab = (Node [])new Node[newCap]; //table指向新的數(shù)組 table = newTab; //如果原數(shù)組為空,直接返回新定義數(shù)組,第一次put時(shí) if (oldTab != null) { //遍歷原數(shù)組 for (int j = 0; j < oldCap; ++j) { Node e; //如果當(dāng)前節(jié)點(diǎn)不為空 if ((e = oldTab[j]) != null) { oldTab[j] = null; //如果下一個(gè)節(jié)點(diǎn)為空 if (e.next == null) //hash到新表 newTab[e.hash & (newCap - 1)] = e; //判斷是否為紅黑樹節(jié)點(diǎn),如果是,調(diào)用split方法處理 else if (e instanceof TreeNode) ((TreeNode )e).split(this, newTab, j, oldCap); else { // preserve order Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; do { next = e.next; /** * 此處關(guān)鍵的是(e.hash & oldCap) == 0,如果這個(gè)表達(dá)式為true,則(e.hash & (oldCap - 1)) * 和(e.hash & (newCap - 1))值是一樣的,說明節(jié)點(diǎn)的位置沒有發(fā)生變化。這樣做的原因是oldCap和newCap都是 * 2的次冪,并且newCap是oldCap的2倍,表示oldCap轉(zhuǎn)換為二進(jìn)制的唯一一個(gè)1向高位移位一次。下面舉例說明: * 比如,oldCap=16,則newCap=32。如果(e.hash & oldCap) == 0, * 因?yàn)閑.hash & 0x10000 == 0, e.hash & 0x100000 == 0,現(xiàn)在e.hash的位置是由e.hash & 0x1111確定, * 則e.hash & 0x11111 的值也是一樣的。根據(jù)這一個(gè)二進(jìn)制位就可以判斷下次hash定位在 * 哪里了。將hash沖突的元素連在兩條鏈表上放在相應(yīng)的位置 */ //將位置不變的節(jié)點(diǎn)放到鏈表loHead if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } //將位置變化的節(jié)點(diǎn),放到鏈表hiHead else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); //將loHead放到新表位置 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } //將hiHead放到新表位置 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } }
在HashMap中,作為key的對(duì)象,如果重寫了equals方法,hashCode也要覆蓋重寫,下面通過一個(gè)例子說明不重寫會(huì)出現(xiàn)什么問題:
public class HashMapTest { public static void main(String[] args) { Dog dog = new Dog("test1", 1); Cat cat = new Cat("test2", 2); Mapmap1 = new HashMap<>(1); map1.put(dog, "測試1"); Map map2 = new HashMap<>(1); map2.put(cat, "測試2"); System.out.println("沒有重寫hashCode方法:" + map1.get(new Dog("test1", 1))); System.out.println("重寫hashCode方法:" + map2.get(new Cat("test2", 2))); } /** * 只重寫了equals方法 */ static class Dog { private String name; private Integer id; public Dog(String name, Integer id){ this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Override public boolean equals(Object obj) { if (this == obj){ return true; } if (obj == null || obj.getClass() != this.getClass()){ return false; } Dog dog = (Dog) obj; return (this.id != null) && (this.id.equals(dog.id)); } } /** * 重寫了equals和hashCode方法 */ static class Cat { private String name; private Integer id; public Cat(String name, Integer id){ this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Override public boolean equals(Object obj) { if (this == obj){ return true; } if (obj == null || obj.getClass() != this.getClass()){ return false; } Cat cat = (Cat) obj; return (this.id != null) && (this.id.equals(cat.id)); } @Override public int hashCode() { if (this.id == null){ return 0; } return this.id; } } }
上述代碼運(yùn)行結(jié)果如下:
可以看到,沒有重寫hashCode方法的對(duì)象作為key,查詢得到的是null,因?yàn)閮蓚€(gè)對(duì)象的hashCode的并不一致,所以導(dǎo)致取到的是null。
2、HashMap的遍歷方式HashMap提供了對(duì)key-value、key、value等多種遍歷方式,下面通過一個(gè)示例演示其用法:
public class HashMapIteratorTest { public static void main(String[] args) { Map四、總結(jié)map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.put(String.valueOf(i), String.valueOf(i)); } entrySetForeach(map); entrySetIterator(map); keySet(map); valueSet(map); foreachJdk8(map); } /** * 獲取Map.Entry,然后遍歷key 和value,通過foreach遍歷 * @param map */ static void entrySetForeach(Map map){ for (Map.Entry entry: map.entrySet() ) { System.out.print("key:" + entry.getKey() + ",value:" + entry.getValue() + "----"); } System.out.println(); } /** * 獲取Map.Entry,然后遍歷key 和value,通過Iterator遍歷 * @param map */ static void entrySetIterator(Map map){ Iterator > iterator = map.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry entry = iterator.next(); System.out.print("key:" + entry.getKey() + ",value:" + entry.getValue() + "----"); } System.out.println(); } /** * 獲取keySet,遍歷key,同樣支持foreach和Iterator遍歷,只實(shí)現(xiàn)foreach * @param map */ static void keySet(Map map){ for (String string: map.keySet() ) { System.out.print("key:" + string + "----"); } System.out.println(); } /** * 獲取values,遍歷value, * @param map */ static void valueSet(Map map){ for (String string: map.values() ) { System.out.print("value:" + string + "----"); } System.out.println(); } static void foreachJdk8(Map map){ map.forEach((k, v)-> System.out.print("key:" + k + ",value:" + v + "----")); System.out.println(); } }
在HashMap的使用中,建議設(shè)置已知的大小,因?yàn)樵跀U(kuò)容的時(shí)候,resize方法要重建hash表,嚴(yán)重影響性能。
HashMap定義和擴(kuò)展中,大小必須為2的次冪,這樣做的原因如下:
a、計(jì)算位置時(shí):(n - 1) & hash可以實(shí)現(xiàn)一個(gè)均勻分布。 b、hash%length==hash&(length-1)的前提是length是2的次冪。length是2次冪時(shí),可以用以為代替取模,提高效率。
擴(kuò)容過程中會(huì)新數(shù)組會(huì)和原來的數(shù)組有指針引用關(guān)系,所以將引起死循環(huán)問題。JDK1.8中已經(jīng)解決這個(gè)問題了。
對(duì)hashCode的hash計(jì)算
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/72079.html
摘要:我的是忙碌的一年,從年初備戰(zhàn)實(shí)習(xí)春招,年三十都在死磕源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實(shí)習(xí)。因?yàn)槲倚睦砗芮宄业哪繕?biāo)是阿里。所以在收到阿里之后的那晚,我重新規(guī)劃了接下來的學(xué)習(xí)計(jì)劃,將我的短期目標(biāo)更新成拿下阿里轉(zhuǎn)正。 我的2017是忙碌的一年,從年初備戰(zhàn)實(shí)習(xí)春招,年三十都在死磕JDK源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實(shí)習(xí)offer。然后五月懷著忐忑的心情開始了螞蟻金...
摘要:大多數(shù)待遇豐厚的開發(fā)職位都要求開發(fā)者精通多線程技術(shù)并且有豐富的程序開發(fā)調(diào)試優(yōu)化經(jīng)驗(yàn),所以線程相關(guān)的問題在面試中經(jīng)常會(huì)被提到。將對(duì)象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對(duì)象稱之為反序列化。 JVM 內(nèi)存溢出實(shí)例 - 實(shí)戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細(xì)介紹 Java 注解的使用,有利于學(xué)習(xí)編譯時(shí)注解 Java 程序員快速上手 Kot...
摘要:基礎(chǔ)知識(shí)復(fù)習(xí)后端掘金的作用表示靜態(tài)修飾符,使用修飾的變量,在中分配內(nèi)存后一直存在,直到程序退出才釋放空間。將對(duì)象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對(duì)象稱之為反序列化。 Java 學(xué)習(xí)過程|完整思維導(dǎo)圖 - 后端 - 掘金JVM 1. 內(nèi)存模型( 內(nèi)存分為幾部分? 堆溢出、棧溢出原因及實(shí)例?線上如何排查?) 2. 類加載機(jī)制 3. 垃圾回收 Java基礎(chǔ) 什么是接口?什么是抽象...
閱讀 2319·2021-10-11 10:59
閱讀 2606·2021-10-11 10:58
閱讀 3311·2021-09-08 09:35
閱讀 3804·2021-09-02 15:21
閱讀 1464·2019-08-30 15:53
閱讀 2616·2019-08-29 14:16
閱讀 2077·2019-08-26 14:00
閱讀 2957·2019-08-26 13:52