千家信息网

JDK的HashMap怎么使用

发表于:2025-01-20 作者:千家信息网编辑
千家信息网最后更新 2025年01月20日,本篇内容主要讲解"JDK的HashMap怎么使用",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"JDK的HashMap怎么使用"吧!首先先来分析一下Hash
千家信息网最后更新 2025年01月20日JDK的HashMap怎么使用

本篇内容主要讲解"JDK的HashMap怎么使用",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"JDK的HashMap怎么使用"吧!

首先先来分析一下HashMap

static final int DEFAULT_INITIAL_CAPACITY = 16;

最大容量2的15次方+1;
static final int MAXIMUM_CAPACITY = 1 << 30;

默认加载因子:
static final float DEFAULT_LOAD_FACTOR = 0.75f;

内部实现的机制是用具有键值对格式的单个的entry数组实现:

  1. transient Entry[] table;


  2. static class Entry implements Map.Entry {

  3. final K key; //键

  4. V value; // 值

  5. Entry next; //下一个

  6. final int hash; //哈西值


  7. /**

  8. * Creates new entry.

  9. */

  10. Entry(int h, K k, V v, Entry n) {

  11. value = v;

  12. next = n;

  13. key = k;

  14. hash = h;

  15. }


  16. public final K getKey() {

  17. return key;

  18. }


  19. public final V getValue() {

  20. return value;

  21. }


  22. public final V setValue(V newValue) {

  23. V oldValue = value;

  24. value = newValue;

  25. return oldValue;

  26. }


  27. public final boolean equals(Object o) {

  28. if (!(o instanceof Map.Entry))

  29. return false;

  30. Map.Entry e = (Map.Entry)o;

  31. Object k1 = getKey();

  32. Object k2 = e.getKey();

  33. if (k1 == k2 || (k1 != null && k1.equals(k2))) {

  34. Object v1 = getValue();

  35. Object v2 = e.getValue();

  36. if (v1 == v2 || (v1 != null && v1.equals(v2)))

  37. return true;

  38. }

  39. return false;

  40. }


  41. public final int hashCode() {

  42. return (key==null ? 0 : key.hashCode()) ^

  43. (value==null ? 0 : value.hashCode());

  44. }


  45. public final String toString() {

  46. return getKey() + "=" + getValue();

  47. }



  48. void recordAccess(HashMap m) {

  49. }


  50. void recordRemoval(HashMap m) {

  51. }

  52. }

当前的数组大小:
transient int size;

构造函数初始化:
设置初始化的容积和加载因子

  1. public HashMap(int initialCapacity, float loadFactor) {


  2. //初始化容积必须大于0

  3. if (initialCapacity < 0)

  4. throw new IllegalArgumentException("Illegal initial capacity: " +

  5. initialCapacity);

  6. //超过最大容积的时候,那么等于最大容积

  7. if (initialCapacity > MAXIMUM_CAPACITY)

  8. initialCapacity = MAXIMUM_CAPACITY;


  9. //初始化加载因子;

  10. if (loadFactor <= 0 || Float.isNaN(loadFactor))

  11. throw new IllegalArgumentException("Illegal load factor: " +

  12. loadFactor);


  13. //找出一个数的平方大于当前给出的初始化容积,比如是初始化是10的话,那么最终的容积就是16

  14. int capacity = 1;

  15. while (capacity < initialCapacity)

  16. capacity <<= 1;


  17. this.loadFactor = loadFactor;

  18. threshold = (int)(capacity * loadFactor);


  19. //用容积来初始化数组大小;size当前还是为0;

  20. table = new Entry[capacity];


  21. //初始化动作,可以留给子类实现,模板模式的应用

  22. init();

  23. }


这是一个hashcode的转换算法;他能够把对象的hashcode转换成为小于length-1的整数作为数组的下标;那么势必会出现重合

  1. static int hash(int h) {

  2. h ^= (h >>> 20) ^ (h >>> 12);

  3. return h ^ (h >>> 7) ^ (h >>> 4);

  4. }


  5. static int indexFor(int h, int length) {

  6. return h & (length-1);

  7. }



我们先看增加方法:

  1. public V put(K key, V value) {

  2. //判断键值是否为空;

  3. if (key == null)

  4. return putForNullKey(value);


  5. //得到hashcode

  6. int hash = hash(key.hashCode());

  7. //得到一个小于数组长度(取的是与操作)的下标

  8. int i = indexFor(hash, table.length);

  9. //在数组中找到该下标的entry值;

  10. //事实上entry也是一个链表,相对于LinkedList来说,他的entry是单向的

  11. for (Entry e = table[i]; e != null; e = e.next) {

  12. Object k;

  13. //如果存在键值是同一对象的entry,那么用新值覆盖旧值,不存在则再往下找,知道末尾

  14. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

  15. V oldValue = e.value;

  16. e.value = value;

  17. e.recordAccess(this);

  18. return oldValue;

  19. }

  20. }

  21. //增加修改次数

  22. modCount++;

  23. //增加这个entry到该下标列表的首部

  24. addEntry(hash, key, value, i);

  25. return null;

  26. }


  27. void addEntry(int hash, K key, V value, int bucketIndex) {

  28. Entry e = table[bucketIndex];

  29. //可见是放在該下标链表的第一位的

  30. table[bucketIndex] = new Entry(hash, key, value, e);

  31. //在这里增加了size;

  32. if (size++ >= threshold)

  33. resize(2 * table.length);

  34. }

如果key是null的话:
那么他可以不用通过hashcode定位数组中的队列下标-_-||事实上他也没有hashcode;规定在0位存放这个链表的头;可见在HashMap中是可以

存放null的key的;但是正因为其没有hashcode那么就只能存放一个元素,而不是像其他一样能存放多个;但是另外我们可见,你可以考虑把使

用的最多的值的键设置成为null,因为他是找到的最快的;


  1. private V putForNullKey(V value) {

  2. for (Entry e = table[0]; e != null; e = e.next) {

  3. if (e.key == null) {

  4. V oldValue = e.value;

  5. e.value = value;

  6. e.recordAccess(this);

  7. return oldValue;

  8. }

  9. }

  10. modCount++;

  11. addEntry(0, null, value, 0);

  12. return null;

  13. }


上面说了存放了,下面我们再来看看如何取出来把:

  1. final Entry getEntry(Object key) {

  2. //经过算法得到这个key对应的hashcode,可见hashcode是固定的对应,而不是随机的;如果是null的话为0,经过与操作还是0,直接


  3. //定位到table[0]否则查找出下标

  4. int hash = (key == null) ? 0 : hash(key.hashCode());

  5. for (Entry e = table[indexFor(hash, table.length)];

  6. e != null;

  7. e = e.next) {

  8. Object k;

  9. //匹配这个key的hashcode和数值,可见不是同一的值其hashcode是可能一样的,否则如果是一一对应则没必要匹配key了

  10. if (e.hash == hash &&

  11. ((k = e.key) == key || (key != null && key.equals(k))))

  12. return e;

  13. }

  14. //没有找到,为空;

  15. return null;

我们再来看看其容量是如何增长的:

  1. public void putAll(Map m) {

  2. //得到新增加进来的元素的个数

  3. int numKeysToBeAdded = m.size();

  4. if (numKeysToBeAdded == 0)

  5. return;

  6. //如果新增加的元素个数大于原来容积*加载因子;可见我们必须要扩充容量了

  7. if (numKeysToBeAdded > threshold) {

  8. //那么我们增加的容量将是该新增元素个数+预留空间的总数

  9. int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);

  10. if (targetCapacity > MAXIMUM_CAPACITY)

  11. targetCapacity = MAXIMUM_CAPACITY;

  12. int newCapacity = table.length;

  13. //如果我们现在的表的容量小于新增加的容量,那么我们扩展两倍,如果还小,再扩大两倍,直到他的大于当前需要的容量

  14. while (newCapacity < targetCapacity)

  15. newCapacity <<= 1;

  16. if (newCapacity > table.length)

  17. resize(newCapacity);

  18. }


  19. //添置新的"家具"

  20. for (Iterator> i = m.entrySet().iterator(); i.hasNext(); ) {

  21. Map.Entry e = i.next();

  22. put(e.getKey(), e.getValue());

  23. }

  24. }

用新的容积开辟了一块足够大的存储空间,

  1. void resize(int newCapacity) {

  2. Entry[] oldTable = table;

  3. int oldCapacity = oldTable.length;

  4. if (oldCapacity == MAXIMUM_CAPACITY) {

  5. threshold = Integer.MAX_VALUE;

  6. return;

  7. }


  8. Entry[] newTable = new Entry[newCapacity];

  9. transfer(newTable);

  10. table = newTable;

  11. threshold = (int)(newCapacity * loadFactor);

  12. }

  13. 并且把家具从以前的"小房子"帮到了"大房子"

  14. void transfer(Entry[] newTable) {

  15. Entry[] src = table;

  16. int newCapacity = newTable.length;

  17. for (int j = 0; j < src.length; j++) {

  18. //得到各个数组元素的链表的头

  19. Entry e = src[j];

  20. if (e != null) {

  21. src[j] = null; //一定要把原来的"小房子"收拾好,方便垃圾回收;

  22. do {

  23. Entry next = e.next;

  24. int i = indexFor(e.hash, newCapacity);

  25. e.next = newTable[i];

  26. newTable[i] = e;

  27. e = next;

  28. } while (e != null);

  29. }

  30. }

  31. }

新增,查找,都看过了,我们来看一下删除:

  1. public V remove(Object key) {

  2. Entry e = removeEntryForKey(key);

  3. return (e == null ? null : e.value);

  4. }



  5. final Entry removeEntryForKey(Object key) {

  6. //定位查找到链表表头;

  7. int hash = (key == null) ? 0 : hash(key.hashCode());

  8. int i = indexFor(hash, table.length);

  9. Entry prev = table[i];

  10. Entry e = prev;


  11. while (e != null) {

  12. Entry next = e.next;

  13. Object k;

  14. if (e.hash == hash &&

  15. ((k = e.key) == key || (key != null && key.equals(k)))) {

  16. modCount++;

  17. //若能找到,修改前后的链表节点的指向,从中删除;

  18. size--;

  19. if (prev == e)

  20. table[i] = next;

  21. else

  22. prev.next = next;

  23. e.recordRemoval(this);

  24. return e;

  25. }

  26. //方便链表往下传递;保存prev是因为是单向链表,所以一旦找到的目标节点,无法通过该节点得到钱一个节点,就无法更改前


  27. 一个节点的指//向,所以要保存prev

  28. prev = e;

  29. e = next;

  30. }


  31. return e;

  32. }


现在我们再看一下如何单独取到他的键值集合或者值集合:

  1. values实现了一个内部私有类;

  2. public Collection values() {

  3. Collection vs = values;

  4. return (vs != null ? vs : (values = new Values()));

  5. }


  6. //AbstractCollection 把一些基本的curd委托给了iteartor,所以只需重载其获取iteartor的方法;

  7. private final class Values extends AbstractCollection {


  8. //重载了iteartor

  9. public Iterator iterator() {

  10. return newValueIterator();

  11. }

  12. public int size() {

  13. return size;

  14. }

  15. public boolean contains(Object o) {

  16. return containsValue(o);

  17. }

  18. public void clear() {

  19. HashMap.this.clear();

  20. }

  21. }

  22. 下面是该iteartor的获取:其中主要的是看看我们获取的value的collection是如何排序的;


  23. private abstract class HashIterator implements Iterator {

  24. Entry next; // next entry to return

  25. int expectedModCount; // For fast-fail

  26. int index; // current slot

  27. Entry current; // current entry


  28. HashIterator() {

  29. expectedModCount = modCount;

  30. //如果该HashMap有元素

  31. if (size > 0) { // advance to first entry

  32. Entry[] t = table;

  33. 然后把next和index都调至该table数组的链表头中不为空的地方;

  34. while (index < t.length && (next = t[index++]) == null)

  35. ;

  36. }

  37. }


  38. public final boolean hasNext() {

  39. return next != null;

  40. }


  41. final Entry nextEntry() {

  42. if (modCount != expectedModCount)

  43. throw new ConcurrentModificationException();

  44. Entry e = next;

  45. if (e == null)

  46. throw new NoSuchElementException();

  47. //代表这个链表遍历完成了,需要开始遍历下一个table下标的链表了

  48. if ((next = e.next) == null) {

  49. Entry[] t = table;

  50. //找到下一个不为空的table元素

  51. while (index < t.length && (next = t[index++]) == null)

  52. ;

  53. }

  54. current = e;

  55. return e;

  56. }


  57. public void remove() {

  58. if (current == null)

  59. throw new IllegalStateException();

  60. if (modCount != expectedModCount)

  61. throw new ConcurrentModificationException();

  62. Object k = current.key;

  63. current = null;

  64. HashMap.this.removeEntryForKey(k);

  65. expectedModCount = modCount;

  66. }


  67. }



  68. ValueIterator :


  69. private final class ValueIterator extends HashIterator {

  70. public V next() {

  71. return nextEntry().value;

  72. }

  73. }

这是我们会问加入我在这个value的collection中增加元素会出现什么样的情况呢,次序会不会打乱,对不起,AbstractCollection不存在add

的,iteartor也是不允许增加元素的;

对于来说keySet来说是同理的,不过因为key是不存在一样的,所以,我们不会返回collection而返回set,避免了重复key的出现

到此,相信大家对"JDK的HashMap怎么使用"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

0