5.1、对于HashMap需要掌握以下几点

  • Map的创建:HashMap()
  • 往Map中添加键值对:即put(Object key, Object value)方法
  • 获取Map中的单个对象:即get(Object key)方法
  • 删除Map中的对象:即remove(Object key)方法
  • 判断对象是否存在于Map中:containsKey(Object key)
  • 遍历Map中的对象:即keySet(),在实际中更常用的是增强型的for循环去做遍历
  • Map中对象的排序:主要取决于所采取的排序算法

5.2、构建HashMap

 源代码:

一些属性:

  1. static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认的初始化容量(必须是2的多少次方)
  2. static final int MAXIMUM_CAPACITY = 1 << 30; // 最大指定容量为2的30次方
  3. static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认的加载因子(用于resize)
  4.  
  5. transient Entry[] table;// Entry数组(数组容量必须是2的多少次方,若有必要会扩容resize)--这就是HashMap的底层数据结构
  6.  
  7. transient int size; // 该map中存放的key-value对个数,该个数决定了数组的扩容(而非table中的所占用的桶的个数来决定是否扩容)
  8. // 扩容resize的条件:eg.capacity=16,load_factor=0.75,threshold=capacity*load_factor=12,即当该map中存放的key-value对个数size>=12时,就resize)
  9. int threshold;
  10. final float loadFactor; // 负载因子(用于resize)
  11.  
  12. transient volatile int modCount;// 标志位,用于标识并发问题,主要用于迭代的快速失败(在迭代过程中,如果发生了put(添加而不是更新的时候)、remove操作,该值发生变化,快速失败)

注意:

  • map中存放的key-value对个数size,该个数决定了数组的扩容(size>=threshold时,扩容),而非table中的所占用的桶的个数来决定是否扩容
  • 标志位modCount采用volatile实现该变量的线程可见性(之后会在"Java并发"章节中去讲)
  • 数组中的桶,指的就是table[i]
  • threshold默认为0.75,这是综合时间和空间的利用率来考虑的,通常不要变,如果该值过大,可能会造成链表太长,导致get、put等操作缓慢;如果太小,空间利用率不足。

无参构造器(也是当下最常用的构造器)

  1. /**
  2. * 初始化一个负载因子、resize条件和Entry数组
  3. */
  4. public HashMap() {
  5. this.loadFactor = DEFAULT_LOAD_FACTOR;// 负载因子:0.75
  6. threshold = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//当该map中存放的key-value对个数size>=12时,就resize
  7. table = new Entry[DEFAULT_INITIAL_CAPACITY];// 设置Entry数组容量为16
  8. init();
  9. }

注意:

  • init()为空方法

对于hashmap而言,还有两个比较常用的构造器,一个双参,一个单参。

  1. /**
  2. * 指定初始容量和负载因子
  3. */
  4. public HashMap(int initialCapacity, float loadFactor) {
  5. if (initialCapacity < 0)
  6. throw new IllegalArgumentException("Illegal initial capacity:"+initialCapacity);
  7. if (initialCapacity > MAXIMUM_CAPACITY)
  8. initialCapacity = MAXIMUM_CAPACITY;
  9. if (loadFactor <= 0 || Float.isNaN(loadFactor))//loadFactor<0或者不是一个值
  10. throw new IllegalArgumentException("Illegal load factor:"+loadFactor);
  11.  
  12. /*
  13. * 下边的逻辑是找一个2的几次方的数,该数刚刚大于initialCapacity
  14. * eg.当指定initialCapacity为17,capacity就是32(2的五次方),而2的四次方(16)正好小于17
  15. */
  16. int capacity = 1;
  17. while (capacity < initialCapacity)
  18. capacity <<= 1;// capacity = capacity<<1
  19.  
  20. this.loadFactor = loadFactor;
  21. threshold = (int)(capacity * loadFactor);
  22. table = new Entry[capacity];
  23. init();
  24. }
  25.  
  26. /**
  27. * 指定初始容量
  28. */
  29. public HashMap(int initialCapacity) {
  30. this(initialCapacity, DEFAULT_LOAD_FACTOR);//调用上边的双参构造器
  31. }

注意:

  • 利用上述两个构造器构造出的数组容量不一定是指定的初始化容量,而是一个刚刚大于指定初始化容量的2的几次方的一个值。
  • 在实际使用中,若我们能预判所要存储的元素的多少,最好使用上述的单参构造器来指定初始容量,这样的话,就可以避免就来扩容时带来的消耗(这一点与ArrayList一样

HashMap的底层数据结构是一个Entry[],Entry是HashMap的一个内部类,源代码如下:

  1. static class Entry<K, V> implements Map.Entry<K, V> {
  2. final K key; // 该Entry的key
  3. V value; // 该Entry的value
  4. Entry<K, V> next; // 该Entry的下一个Entry(hash冲突时,形成链表)
  5. final int hash; // 该Entry的hash值
  6.  
  7. /**
  8. * Creates new entry.
  9. */
  10. Entry(int h, K k, V v, Entry<K, V> n) {
  11. value = v;
  12. next = n;
  13. key = k;
  14. hash = h;
  15. }
  16.  
  17. public final K getKey() {
  18. return key;
  19. }
  20.  
  21. public final V getValue() {
  22. return value;
  23. }
  24.  
  25. //为Entry设置新的value
  26. public final V setValue(V newValue) {
  27. V oldValue = value;
  28. value = newValue;
  29. return oldValue;
  30. }
  31.  
  32. public final boolean equals(Object o) {
  33. if (!(o instanceof Map.Entry))
  34. return false;
  35. Map.Entry e = (Map.Entry) o;
  36. Object k1 = getKey();
  37. Object k2 = e.getKey();
  38. //在hashmap中可以存放null键和null值
  39. if (k1 == k2 || (k1 != null && k1.equals(k2))) {
  40. Object v1 = getValue();
  41. Object v2 = e.getValue();
  42. if (v1 == v2 || (v1 != null && v1.equals(v2)))
  43. return true;
  44. }
  45. return false;
  46. }
  47.  
  48. public final int hashCode() {
  49. return (key == null ? 0 : key.hashCode())^(value == null ? 0 : value.hashCode());
  50. }
  51.  
  52. public final String toString() {
  53. return getKey() + "=" + getValue();
  54. }
  55. }

注:这里我去掉了两个空方法。

  • Entry是一个节点,在其中还保存了下一个Entry的引用(用来解决put时的hash冲突问题),这样的话,我们可以把hashmap看作是"一个链表数组"
  • Entry类中的equals()方法会在get(Object key)中使用

5.3、put(Object key, Object value)

源代码:

put(Object key, Object value)

  1. /**
  2. * 向map中添加新Entry
  3. * 步骤:
  4. * 1)HashMap可以添加null的key,key==null的Entry只会放在table[0]中,但是table[0]不仅仅可以存放key==null的Entry
  5. * 1.1、遍历table[0]中的Entry链,若有key==null的值就用新值覆盖旧值,并返回旧值value,
  6. * 1.2、若无,执行addEntry方法,用新的Entry替换掉原来旧的Entry赋值给table[0],而旧的Entry作为新的Entry的next,执行结束后,返回null
  7. * 2)添加key!=null的Entry时,
  8. * 2.1、先计算key.hashCode()的hash值,
  9. * 2.2、然后计算出将要放入的table的下标i,
  10. * 2.3、之后遍历table[i]中的Entry链,若有相同key的值就用新值覆盖旧值,并返回旧值value,
  11. * 2.4、若无,执行addEntry方法,用新的Entry替换掉原来旧的Entry赋值给table[i],而旧的Entry作为新的Entry的next,执行结束后,返回null
  12. */
  13. public V put(K key, V value) {
  14. /******************key==null******************/
  15. if (key == null)
  16. return putForNullKey(value); //将空key的Entry加入到table[0]中
  17. /******************key!=null******************/
  18. int hash = hash(key.hashCode()); //计算key.hashcode()的hash值,hash函数由hashmap自己实现
  19. int i = indexFor(hash, table.length);//获取将要存放的数组下标
  20. /*
  21. * for中的代码用于:当hash值相同且key相同的情况下,使用新值覆盖旧值(其实就是修改功能)
  22. */
  23. for (Entry<K, V> e = table[i]; e != null; e = e.next) {//注意:for循环在第一次执行时就会先判断条件
  24. Object k;
  25. //hash值相同且key相同的情况下,使用新值覆盖旧值
  26. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  27. V oldValue = e.value;
  28. e.value = value;
  29. //e.recordAccess(this);
  30. return oldValue;//返回旧值
  31. }
  32. }
  33.  
  34. modCount++;
  35. addEntry(hash, key, value, i);//增加一个新的Entry到table[i]
  36. return null;//如果没有与传入的key相等的Entry,就返回null
  37. }

注意:该方法头部的注释写明了整个put(Object key, Object value)的流程,非常重要

putForNullKey(V value)

  1. /**
  2. * 增加null的key到table[0]
  3. */
  4. private V putForNullKey(V value) {
  5. //遍历第一个数组元素table[0]中的所有Entry节点
  6. for (Entry<K, V> e = table[0]; e != null; e = e.next) {
  7. if (e.key == null) {//用新值覆盖旧值
  8. V oldValue = e.value;
  9. e.value = value;
  10. //e.recordAccess(this);
  11. return oldValue;
  12. }
  13. }
  14. modCount++;
  15. addEntry(0, null, value, 0);//将新节点Entry加入到Entry[]中
  16. return null;
  17. }

addEntry(int hash, K key, V value, int bucketIndex)

  1. /**
  2. * 添加新的Entry到table[bucketIndex]
  3. */
  4. void addEntry(int hash, K key, V value, int bucketIndex) {
  5. /*
  6. * 这里可以看出,
  7. * 1)新加入的Entry会放入链头,也就是说将来遍历的时候,最先加入map的反而是最后被遍历到的
  8. * 2)采用的是Entry替换的方式
  9. * 2.1、当添加第一个Entry1时,table[bucketIndex]==null,也就是说Entry1的下一个Entry为null(链尾),之后把table[bucketIndex] = Entry1
  10. * 2.2、当添加第二个Entry2时,table[bucketIndex]==Entry1,也就是说Entry2的下一个Entry为Entry1,之后把table[bucketIndex] = Entry2
  11. * 2.3、当添加第三个Entry3时,table[bucketIndex]==Entry2,也就是说Entry3的下一个Entry为Entry2,之后把table[bucketIndex] = Entry3
  12. */
  13. Entry<K, V> e = table[bucketIndex];//新节点的下一个节点(当第一次在相应的数组位置放置元素时,table[bucketIndex]==null)
  14. table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
  15. if (size++ >= threshold)//key-value对个数大于等于threshold
  16. resize(2 * table.length);//扩容
  17. }

注意:该方法头部的注释写明了该方法的流程示例,可以自己画个图对比着理解

hash(int h)

  1. /**
  2. * hash函数,用于计算key.hashCode()的hash值
  3. * Note: null的key的hash为0,放在table[0].
  4. */
  5. static int hash(int h) {
  6. //这样的hash函数应该可以尽量将hash值打散
  7. h ^= (h >>> 20) ^ (h >>> 12);
  8. return h ^ (h >>> 7) ^ (h >>> 4);
  9. }

注意:在我们实际使用hashmap时,最好的情况是将key的hash值打散,使插入的这些Entry尽量落在不同的桶上(这样做的主要目的是提高查询效率),以上这个hash函数应该就是实现了这样的功能,但是为什么这样的hash函数可以将hash值打散,求大神指点!!!

indexFor(int h, int length)

  1. /**
  2. * "按位与"来获取数组下标
  3. */
  4. static int indexFor(int h, int length) {
  5. return h & (length - 1);
  6. }

注意:hashmap始终将自己的桶保持在2的n次方,这是为什么?indexFor这个方法解释了这个问题。“这个方法非常巧妙,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率”--http://tech.meituan.com/java-hashmap.html

说明:在上述的addEntry(int hash, K key, V value, int bucketIndex)方法中,我们可以看到,当为把新的Entry赋值给table[i]后,会判断map中的key-value对是不是已经大于等于扩容条件值threshold了,若是,则需要调用resize函数,对Entry数组进行扩容,扩为原来二倍。

resize(int newCapacity)

  1. /**
  2. * 扩容步骤:
  3. * 1)数组扩容为原来容量(eg.16)的二倍
  4. * 2)将旧数组中的所有Entry重新计算索引,加入新数组
  5. * 3)将新数组的引用赋给旧数组
  6. * 4)重新计算扩容临界值threshold
  7. */
  8. void resize(int newCapacity) {
  9. Entry[] oldTable = table;
  10. int oldCapacity = oldTable.length;
  11. //如果旧的数组的容量为2的30次方(这种情况,不考虑了,如果真达到这样的情况,性能下降的就不像话了)
  12. if (oldCapacity == MAXIMUM_CAPACITY) {
  13. threshold = Integer.MAX_VALUE;
  14. return;
  15. }
  16.  
  17. Entry[] newTable = new Entry[newCapacity];//newCapacity==2*oldCapacity
  18. transfer(newTable);//将旧数组中的所有Entry重新计算索引,加入新数组
  19. table = newTable;//将新数组赋给就数组
  20. threshold = (int) (newCapacity * loadFactor);//重新计算threshold
  21. }

transfer(Entry[] newTable)

jdk中的实现:

  1. /**
  2. * 将所有旧的数组中的所有Entry移动到新数组中去
  3. */
  4. void transfer(Entry[] newTable) {
  5. Entry[] src = table;
  6. int newCapacity = newTable.length;
  7. for (int j = 0; j < src.length; j++) {//遍历旧数组
  8. Entry<K, V> e = src[j];//获得头节点
  9. if (e != null) {
  10. /*
  11. * 这样写,若同时有其他线程还在访问这个元素,则访问不到了,这里这样写,是考虑到多线程情况下,我们一般不会会用HashMap
  12. * (查看ConcurrentHashMap并未将旧数组的值置为null)
  13. * 这里将其置为null就方便gc回收
  14. * 当然为了减小以上所说的影响,建议将src[j] = null;放在while循环结束后
  15. */
  16. src[j] = null;
  17. do {
  18. Entry<K, V> next = e.next;
  19. int i = indexFor(e.hash, newCapacity);
  20. e.next = newTable[i];//把之前已经存在的newTable[i]的元素赋给当前节点的下一个节点
  21. newTable[i] = e;//把当前节点赋给newTable[i]
  22. e = next;
  23. } while (e != null);//遍历链表
  24. }
  25. }
  26. }

我的修改:(注意:这是一个错误的修改,错误的根源在下边我会给出

  1. /**
  2. * 将所有旧的数组中的所有Entry移动到新数组中去
  3. */
  4. void transfer(Entry[] newTable) {
  5. Entry[] src = table; //旧数组
  6. int newCapacity = newTable.length; //新数组容量
  7.  
  8. for (int j = 0; j < src.length; j++) {
  9. Entry<K, V> e = src[j];//获取旧数组中的头节点Entry
  10. if (e != null) {
  11. src[j] = null;//将旧数组置空,让gc回收注意:这个时候table的桶并没有置空
  12. /*
  13. * 根据旧的hash值与新的容量值进行重新定位(注意:并没有重新计算hash值)
  14. * 1、那么假设之前table[1]中存放的是Entry3,Entry3.next是Entry2,Entry2.next是Entry1,Entry1.next是null
  15. * 那么假设重新计算后的i=3,那么Entry3-->Entry2-->Entry1依旧会在一起,都放入newTable[3],这样的话,我们只需要将链头的Entry3赋值给newTable[3]即可
  16. * 2、既然通过indexFor(e.hash, newCapacity)不能把同一个桶下的Entry打散,为什么还要用呢?
  17. * 主要是扩容后,若不用newCapacity去计算下标的话,那么扩容后,map中的Entry就都集中在了新数组的前半部分,这样就不够散了
  18. */
  19. int i = indexFor(e.hash, newCapacity);
  20. newTable[i] = e;//将Entry3赋值给newTable[3]
  21. }
  22. }
  23. }

注意:

  • 在这个方法中,并没有重新计算hash值,只是重新计算了下标索引。
  • 错误根源在于认为同一个桶下的所有Entry的hash值相同,事实上不相同,只是hash&(table.length-1)的结果相同,

    所以当table.length发生变化时,同一个桶下各个Entry算出来的index会不同(即Entry3、Entry2、Entry1可能会落在新数组的不同的桶上

5.4、get(Object key)

源代码:

get(Object key)

  1. /**
  2. * 查找指定key的value值
  3. * 1、若key==null
  4. * 遍历table[0],找出key==null的value,若没找到,返回null
  5. * 2、若key!=null
  6. * 1)计算key.hashCode()的hash值
  7. * 2)根据计算出的hash值和数组容量,调用indexFor方法,获得table的下标i,进而获得桶table[i]
  8. * 3)遍历该桶中的每一个Entry,找出key相等(==或equals)的Entry,获取此Entry的value即可
  9. * 4)最后,若没有找到,返回null即可
  10. */
  11. public V get(Object key) {
  12. /****************查找key==null的value****************/
  13. if (key == null)
  14. return getForNullKey();
  15. /****************查找key!=null的value****************/
  16. int hash = hash(key.hashCode());//获取key.hashCode()的hash值
  17.  
  18. for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
  19. Object k;
  20. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  21. return e.value;
  22. }
  23. return null;//若没有指定key的Entry,则直接返回null
  24. }

注意:查看代码头部的注释,表明了get的整个步骤

getForNullKey()

  1. /**
  2. * 在table[0]中查询key==null
  3. */
  4. private V getForNullKey() {
  5. for (Entry<K, V> e = table[0]; e != null; e = e.next) {
  6. if (e.key == null)
  7. return e.value;
  8. }
  9. return null;//找不到的话就返回null
  10. }

5.5、remove(Object key)

源代码:

  1. /**
  2. * 删除指定key的Entry
  3. */
  4. public V remove(Object key) {
  5. Entry<K, V> e = removeEntryForKey(key);
  6. return (e == null ? null : e.value);//返回删除的节点(e为null的话,表示所给出的key不存在)
  7. }
  8. /**
  9. * 删除指定key的Entry
  10. * 1)若删除的是头节点,例如Entry3,只需将Entry2赋值给table[i]即可
  11. * 2)若删除的是中间节点,例如Entry2,只需将Entry3.next指向Entry2.next(即Entry1)即可
  12. * 3)若删除的是尾节点,例如Entry1,只需将Entry2.next指向Entry1.next(即null)即可
  13. */
  14. final Entry<K, V> removeEntryForKey(Object key) {
  15. int hash = (key == null) ? 0 : hash(key.hashCode());//计算hash值
  16. int i = indexFor(hash, table.length);//按位与计算下标
  17. Entry<K, V> prev = table[i];//获取桶
  18. Entry<K, V> e = prev;
  19.  
  20. while (e != null) {
  21. Entry<K, V> next = e.next;
  22. Object k;
  23. if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
  24. modCount++;
  25. size--;//size-1
  26. if (prev == e)//删除头节点,即示例中的Entry3
  27. table[i] = next;
  28. else//删除除了头节点外的其他节点
  29. prev.next = next;
  30. //e.recordRemoval(this);
  31. return e;
  32. }
  33. prev = e;
  34. e = next;
  35. }
  36. return e;//返回删除的节点(e为null的话,表示所给出的key不存在)
  37. }

注:看注释即可,最好用示例去套一下代码。

  • 若删除的key不存在于map中,返回null,不会抛异常。

5.6、containsKey(Object key)

源代码:

  1. /**
  2. * 判断map是否包含指定可以的Entry
  3. */
  4. public boolean containsKey(Object key) {
  5. return getEntry(key) != null;
  6. }
  7. /**
  8. * 判断map是否包含指定可以的Entry,与get(Object key)基本相同(只是这里将key==null与key!=null的情况写在了一起,get(Object key)也可以这样去做)
  9. * 1)计算key.hashCode()的hash值
  10. * 2)根据计算出的hash值和数组容量,调用indexFor方法,获得table的下标i,进而获得桶table[i]
  11. * 3)遍历该桶中的每一个Entry,找出key相等(==或equals)的Entry,获取此Entry,并返回此Entry
  12. * 4)最后,若没有找到,返回null即可
  13. */
  14. final Entry<K, V> getEntry(Object key) {
  15. int hash = (key == null) ? 0 : hash(key.hashCode());//计算hash值
  16. for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
  17. Object k;
  18. if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
  19. return e;
  20. }
  21. return null;
  22. }

注意:此方法与get(Object key)基本相同,只是只是这里将key==null与key!=null的情况写在了一起,get(Object key)也可以这样去做来减少代码

5.7、keySet()

遍历所有Entry链表,获取每一个Entry的key,在整个过程中,如果发生了增删操作,抛出ConcurrentModificationException。

  1. final Entry<K, V> nextEntry() {
  2. if (modCount != expectedModCount)
  3. throw new ConcurrentModificationException();
  4. Entry<K, V> e = next;
  5. if (e == null)
  6. throw new NoSuchElementException();
  7.  
  8. if ((next = e.next) == null) {
  9. Entry[] t = table;
  10. while (index < t.length && (next = t[index++]) == null)
  11. ;
  12. }
  13. current = e;
  14. return e;
  15. }

总结:

  • HashMap底层就是一个Entry数组,Entry又包含next,事实上,可以看成是一个"链表数组"
  • 扩容:map中存放的key-value对个数size,该个数决定了数组的扩容(size>=threshold时,扩容),而非table中的所占用的桶的个数来决定是否扩容
  • 扩容过程,不会重新计算hash值,只会重新按位与
  • 在实际使用中,若我们能预判所要存储的元素的多少,最好使用上述的单参构造器来指定初始容量
  • HashMap可以插入null的key和value
  • remove(Object key):若删除的key不存在于map中,返回null,不会抛异常。
  • HashMap线程不安全,若想要线程安全,最好使用ConcurrentHashMap

疑问:

在我们实际使用hashmap时,最好的情况是将key的hash值打散,使插入的这些Entry尽量落在不同的桶上(这样做的主要目的是提高查询效率),以下这个hash函数应该就是实现了这样的功能,但是为什么这样的hash函数可以将hash值打散,求大神指点!!!

  1. static int hash(int h) {
  2. //这样的hash函数应该可以尽量将hash值打散
  3. h ^= (h >>> 20) ^ (h >>> 12);
  4. return h ^ (h >>> 7) ^ (h >>> 4);
  5. }

jdk1.8对hashmap进行了改造,1.7中的hashmap最大的问题就是当链表比较长时,查询效率急剧下降;所以在1.8中,当链表长度>=8是,链表转为红黑树,提高查询效率。

第五章 HashMap源码解析的更多相关文章

  1. 第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()

    最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...

  2. 第六章 ReentrantLock源码解析2--释放锁unlock()

    最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...

  3. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  4. 第二章 ConcurrentHashMap源码解析

    注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http://www.cnblogs.com/java-zhao/p/5106189.html 1.对于 ...

  5. 第十四章 Executors源码解析

    前边两章介绍了基础线程池ThreadPoolExecutor的使用方式.工作机理.参数详细介绍以及核心源码解析. 具体的介绍请参照: 第十二章 ThreadPoolExecutor使用与工作机理 第十 ...

  6. 第九章 LinkedBlockingQueue源码解析

    1.对于LinkedBlockingQueue需要掌握以下几点 创建 入队(添加元素) 出队(删除元素) 2.创建 Node节点内部类与LinkedBlockingQueue的一些属性 static ...

  7. 第六章 HashSet源码解析

    6.1.对于HashSet需要掌握以下几点 HashSet的创建:HashSet() 往HashSet中添加单个对象:即add(E)方法 删除HashSet中的对象:即remove(Object ke ...

  8. HashMap源码解析 非原创

    Stack过时的类,使用Deque重新实现. HashCode和equals的关系 HashCode为hash码,用于散列数组中的存储时HashMap进行散列映射. equals方法适用于比较两个对象 ...

  9. Java中的容器(集合)之HashMap源码解析

    1.HashMap源码解析(JDK8) 基础原理: 对比上一篇<Java中的容器(集合)之ArrayList源码解析>而言,本篇只解析HashMap常用的核心方法的源码. HashMap是 ...

随机推荐

  1. 转 git push 提示 Everything up-to-date

    git 还没有分支,需要指定一个($ git remote -v),就可以push了 第一步:$ git remote -v 第二步:$ git branch 转载链接: http://blog.cs ...

  2. yaf框架安装配置

    YAF中文文档:http://www.laruence.com/manual/index.html 1 YAF框架是用C开发的,属于PHP的扩展框架: 2 YAF的性能相对于源生PHP,性能只降低不到 ...

  3. 结构体的sort【防止遗忘w】

    #include<iostream> #include<algorithm> using namespace std; int n; struct jie { int num; ...

  4. Spring Boot 简单的请求示例(包括请求体验证)

    1.先做个最简单的Get请求 新建一个Controller , 并给他添加注解@RestController 它是@Controller和@ResponseBody的组合注解,告诉Spring我是一个 ...

  5. 2018秋季c语言基础课第一次作业

    1)大学和高中最大的不同是没有人天天看着你,请看大学理想的师生关系是?有何感想? 答:邹欣老师提到了很多种关系,不外呼就是两种:平等或者不平等.平等的师生关系与陌生人无异,而自古以来尊师重道却被世人所 ...

  6. list集合如何对里面的元素进行排序

    Collections 是集合的公共类,提供各种工具,其中提供了排序方法. Collections.sort(),方法两个参数,1,要排序的集合,2.排序方式 下面是匿名内部类,实现了排序借口,你也可 ...

  7. Windows 下安装mysql总结

    1.配置环境变量 将安装目录添加到系统路径 我的电脑->属性->高级->环境变量->path 2.修改my.ini 位于解压安装目录下 在其中修改或添加配置: [mysqld] ...

  8. IntelliJ IDEA 2017版 SpringBoot的web项目补充

    一.注解        @SpringBootApplication:Spring Boot项目的核心注解,主要目的是开启自动配置.        @Configuration:这是一个配置Sprin ...

  9. HDU 2393 Higher Math (判断直角三角形)

    题意:给定三个边,判断是不是直角三角形. 析:水题,勾股定理... 代码如下: #include <iostream> #include <cstdio> #include & ...

  10. wadl 的自动生成(cxf版本2.7.6)

    参考文档 http://cxf.apache.org/docs/jaxrs-services-description.html 获取项目 git@github.com:witaste/cxf-2.7. ...