|
HashMap在初始化后就会构建此Entry 的数组对象table, 我们后面的添加操作也都是往这个table中进行的,接下来我们来看一下它的put方法:
- //新增键值对的方法
- public V put(K key, V value) {
- //如果key为null, 则直接调用putForNullKey(value)方法,该方法直接取table[0]]中保存的Entry,如果当前位置还没有保存值,则将此值放入,并返回null; 如果有值则将旧值返回,新值放入。
- if (key == null)
- return putForNullKey(value);
- //计算key对应的hash值,这也能解释为什么key不能用基本类型了
- int hash = hash(key.hashCode());
- //计算当前hash对应的hash桶的位置
- int i = indexFor(hash, table.length);
-
- //下面的代码是依次从i对应的hash桶中,去查找是否已经有相同hash,相同key的对象保存了
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value; //用新值覆盖原来的值
- e.recordAccess(this);
- return oldValue; //将原来的值返回
- }
- }
-
- //如果执行到此,表示没有在对应的桶中找到相应的Entry,这时调用增加Entry的方法
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
复制代码
我们再来看看addEntry方法:
- void addEntry(int hash, K key, V value, int bucketIndex) {
- // 获取bucketIndex对应的Hash桶中最顶端的元素
- Entry<K,V> e = table[bucketIndex];
- //将当前元素的值存入bucketIndex桶中,并将其与原来的hash桶中的元素相连,此时我们加入的元素已经成为了桶顶元素,而原来的元素则成为了其后续元素。
- table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
- if (size++ >= threshold)
- resize(2 * table.length);
- }
复制代码
通过分析我们可以看出: HashMap处理重复元素的方式是将原来的值返回,新的值加入。HashMap 提供了基于Hash的快速查询机制,我们只需要计算出key的hashCode就可以很容易地得到hash桶编号,继而对桶内元素进行遍历查询,所以说查询性能很高。
进而可以推导出,HashSet的处理方式跟这个其实是一致的,还记得上面的map.put(e, PRESENT)吗?在HashSet的源码中,PRESENT其实就是一个new Object(),没有任何实际意义的。
假设有一个key为1,value为"zhangSan",经过hash之后成为101,那么这个101就作为数组的下标[也即对应的hash桶编号],然后将hash=101、key=1及value="zhangSan"的值封装成实体对象存放到该数组的101下标处, 将原来存放与此的Entry对象作为该Hash桶的第二个元素连接在我们产生的实体对象的尾部(实体对象内部有一个next属性)。因为不同的key会产生不同的hash值,这也是为什么HashMap不排序的原因!
通过key提取值时,当然先要通过该key计算出hash值来,再通过这个hash值作为下标提取出对应的Hash桶中的实体对象(会对该Hash桶中的所有元素遍历),同时加了必要的判断(主要是key的值)来确保提取出正确的数值来。
HashMap迭代的原理:
答: 因为HashMap中所有的数据都存放在table(hash桶 数组)对象中,所以其遍历是通过遍历table中所包括的每一个hash桶,再对每一个桶中的每个元素进行遍历来完成的。 我们在对其中的元素进行遍历的时候应尽量使用:
(1) 使用 public Set<Map.Entry<K,V>> entrySet()方法得到所有的Entry实体数组。
(2) 使用迭代器依次对其中的对象进行迭代操作,这样就可以取得每一个元素的key 和value了.
这里有个误区:很多人在取值的时候采用,先 keyset()得到key的set集,然后对此进行迭代,再通过key来取value,这样的话操作不但重复了上面我们的操作entrySet(), 还进行了查询操作,相当于增加了新的步骤,所以这是一种低效的做法。 |
|