精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-12-28
最后修改:2012-12-28
上一节我们讲到了如何用散列和链表实现HashMap,其中有一个疑问今天已经有些答案了,为什么要用链表而不是数组 链表的作用有如下两点好处 1. remove操作时效率高,只维护指针的变化即可,无需进行移位操作 2. 重新散列时,原来散落在同一个槽中的元素可能会被散落在不同的地方,对于数组需要进行移位操作,而链表只需维护指针
今天研究下数组长度不够时的处理办法 table为散列数组 1. 首先定义一个不可修改的静态变量存储table的初始大小 DEFAULT_INITIAL_CAPACITY 2. 定义一个全局变量存储table的实际元素长度,size 3. 定义一个全局变量存储临界点,即元素的size>=threshold这个临界点时,扩大table的容量 4. 因为index是根据hash和table的长度计算得到的,所以还需要重新对所有元素进行散列
实现如下:
public class EntryHashMap<K, V> { /** 初始容量 */ static final int DEFAULT_INITIAL_CAPACITY = 16; static final float DEFAULT_LOAD_FACTOR = 0.75f; /** 下次扩容的临界值 */ int threshold; transient int size; final float loadFactor; transient Entry[] table; public EntryHashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; } public V put(K key, V value) { // 计算出新的hash int hash = hash(key.hashCode()); // 计算出数组小标i int i = indexFor(hash, table.length); // 遍历table[i],如果table[i]没有与新加入的key相等的,则新加入 // 一个value到table[i]中的entry,否则将新的value覆盖旧的value并返回旧的value 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; return oldValue; } } addEntry(hash, key, value, i); return null; } public V get(K key) { // 计算出新的hash int hash = hash(key.hashCode()); // 计算出数组小标i int i = indexFor(hash, table.length); 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))) { return e.value; } } return null; } private void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K, V> e = table[bucketIndex]; // 将新的元素插入链表前端 table[bucketIndex] = new Entry<>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); } /** * 扩展散列表的容量 * @param newCapacity */ void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int) (newCapacity * loadFactor); } /** * 重新进行散列 * @param newTable */ void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K, V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K, V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } } /** * 通过hash code 和table的length得到对应的数组下标 * * @param h * @param length * @return */ static int indexFor(int h, int length) { return h & (length - 1); } /** * 通过一定算法计算出新的hash值 * * @param h * @return */ static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } public static void main(String[] args) { EntryHashMap<String, String> hashMap = new EntryHashMap<String, String>(); hashMap.put("key", "value"); System.out.println(hashMap.get("key")); } } 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-12-31
/**
* 通过一定算法计算出新的hash值 * * @param h * @return */ static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 这个算法可以自己随便定义吗 |
|
返回顶楼 | |
发表时间:2012-12-31
青春的、脚步 写道 /**
* 通过一定算法计算出新的hash值 * * @param h * @return */ static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 这个算法可以自己随便定义吗 Of course 不过要注意自己写的hash的质量,写的稍微差点的话,就会产生Very much 的碰撞操作 |
|
返回顶楼 | |
发表时间:2012-12-31
写的不错, 对HASHMAP底层有些了解了, 能不能再来一篇说JDK的HASHMAP的
|
|
返回顶楼 | |
发表时间:2012-12-31
eimhee 写道 写的不错, 对HASHMAP底层有些了解了, 能不能再来一篇说JDK的HASHMAP的
楼猪贴的就是JDK sourcecode啊 |
|
返回顶楼 | |
发表时间:2013-01-02
cectsky 写道 青春的、脚步 写道 /**
* 通过一定算法计算出新的hash值 * * @param h * @return */ static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 这个算法可以自己随便定义吗 Of course 不过要注意自己写的hash的质量,写的稍微差点的话,就会产生Very much 的碰撞操作 这是个static的方法,没法覆盖了,除非把所有使用它的方法都覆盖掉。这说明EntityHashMap的设计者并不希望你在这里使用自己的hash算法。注意一点,这里传进来的h值已经是key的hashCode了,这里是在key的hashCode基础上再进一步散列。 |
|
返回顶楼 | |
发表时间:2013-01-04
最后修改:2013-01-04
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 这个方法可以写一篇论文的。 |
|
返回顶楼 | |
发表时间:2013-01-04
对我有帮助!
|
|
返回顶楼 | |
发表时间:2013-01-10
最后修改:2013-01-10
引用 void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K, V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K, V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } } 这个方法里面的“e.next = newTable[i]”没太懂它的意图,是让“newTable[i]”的下一个节点指向自己,还是赋为空,“newTable[i] = e”这个操作是不是说明了上面那个赋值操作是让“newTable[i]”的下一个节点指向自己,让它的下一个节点指向自己有其他用途吗,求指导~~ |
|
返回顶楼 | |
发表时间:2013-01-11
forchase 写道 引用 void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K, V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K, V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable; newTable[i] = e; e = next; } while (e != null); } } } 这个方法里面的“e.next = newTable[i]”没太懂它的意图,是让“newTable[i]”的下一个节点指向自己,还是赋为空,“newTable[i] = e”这个操作是不是说明了上面那个赋值操作是让“newTable[i]”的下一个节点指向自己,让它的下一个节点指向自己有其他用途吗,求指导~~ 不是让它的下一个节点指向自己 [i] e.next = newTable[i]; newTable[i] = e; e = next; 这段code中,是将原来的Array[j]上的链表移到新申请的数组的某个节点(数组下标有indexFor计算)最好画个图就clear了 |
|
返回顶楼 | |