网站建设+荆州,重庆沙坪坝有什么好玩的,青岛高端网站开发公司,wordpress云主机模板1 HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储#xff0c;但这两者基本上是两个极端。 数组 数组存储区间是连续的#xff0c;占用内存严重#xff0c;故空间复杂的很大。但数组的二分查找时间复杂度小#xff0c;为O(1)#xff1b;数组的特点是#xf…1 HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储但这两者基本上是两个极端。 数组 数组存储区间是连续的占用内存严重故空间复杂的很大。但数组的二分查找时间复杂度小为O(1)数组的特点是寻址容易插入和删除困难 链表 链表存储区间离散占用内存比较宽松故空间复杂度很小但时间复杂度很大达ON。链表的特点是寻址困难插入和删除容易。 哈希表 那么我们能不能综合两者的特性做出一种寻址容易插入删除也容易的数据结构答案是肯定的这就是我们要提起的哈希表。哈希表(Hash table既满足了数据的查找方便同时不占用太多的内容空间使用也十分方便。 从上图我们可以发现哈希表是由数组链表组成的一个长度为16的数组中每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中12%1612,28%1612,108%1612,140%1612。所以12、28、108以及140都存储在数组下标为12的位置。 HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解一个线性的数组怎么实现按键值对来存取数据呢这里HashMap有做一些处理。 首先HashMap里面实现一个静态内部类Entry其重要的属性有 key , value, next从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean我们上面说到HashMap的基础就是一个线性数组这个数组就是Entry[]Map里面的内容都保存在Entry[]里面。 /** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry[] table; 2. HashMap的存取实现 既然是线性数组为什么能随机存取这里HashMap用了一个小算法大致是这样实现 1 // 存储时:
2 int hash key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
3 int index hash % Entry[].length;
4 Entry[index] value;
5
6 // 取值时:
7 int hash key.hashCode();
8 int index hash % Entry[].length;
9 return Entry[index]; 1put 疑问如果两个key通过hash%Entry[].length得到的index相同会不会有覆盖的危险 这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性作用是指向下一个Entry。打个比方 第一个键值对A进来通过计算其key的hash得到的index0记做:Entry[0] A。一会后又进来一个键值对B通过计算其index也等于0现在怎么办HashMap会这样做:B.next A,Entry[0] B,如果又进来C,index也等于0,那么C.next B,Entry[0] C这样我们发现index0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止HashMap的大致实现我们应该已经清楚了。 public V put(K key, V value) { if (key null) return putForNullKey(value); //null总是放在数组的第一个链表中 int hash hash(key.hashCode()); int i indexFor(hash, table.length); //遍历链表 for (EntryK,V e table[i]; e ! null; e e.next) { Object k; //如果key在链表中已存在则替换为新value if (e.hash hash ((k e.key) key || key.equals(k))) { V oldValue e.value; e.value value; e.recordAccess(this); return oldValue; } } modCount; addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { EntryK,V e table[bucketIndex]; table[bucketIndex] new EntryK,V(hash, key, value, e); //参数e, 是Entry.next //如果size超过threshold则扩充table大小。再散列 if (size threshold) resize(2 * table.length); } 当然HashMap里面也包含一些优化方面的实现这里也说一下。比如Entry[]的长度一定后随着map里面数据的越来越长这样同一个index的链就会很长会不会影响性能HashMap里面设置一个因子随着map的size越来越大Entry[]会以一定的规则加长长度。 2get public V get(Object key) { if (key null) return getForNullKey(); int hash hash(key.hashCode()); //先定位到数组元素再遍历该元素处的链表 for (EntryK,V e table[indexFor(hash, table.length)]; e ! null; e e.next) { Object k; if (e.hash hash ((k e.key) key || key.equals(k))) return e.value; } return null; } 3null key的存取 null key总是存放在Entry[]数组的第一个元素。 private V putForNullKey(V value) { for (EntryK,V e table[0]; e ! null; e e.next) { if (e.key null) { V oldValue e.value; e.value value; e.recordAccess(this); return oldValue; } } modCount; addEntry(0, null, value, 0); return null; } private V getForNullKey() { for (EntryK,V e table[0]; e ! null; e e.next) { if (e.key null) return e.value; } return null; } 4确定数组indexhashcode % table.length取模 HashMap存取时都需要计算当前key应该对应Entry[]数组哪个元素即计算数组下标算法如下 /** * Returns index for hash code h. */ static int indexFor(int h, int length) { return h (length-1); } 按位取并作用上相当于取模mod或者取余%。 这意味着数组下标相同并不表示hashCode相同。 5table初始大小 public HashMap(int initialCapacity, float loadFactor) { ..... // Find a power of 2 initialCapacity int capacity 1; while (capacity initialCapacity) capacity 1; this.loadFactor loadFactor; threshold (int)(capacity * loadFactor); table new Entry[capacity]; init(); } 注意table初始大小并不是构造函数中的initialCapacity 而是 initialCapacity的2的n次幂 ————为什么这么设计呢—— 3. 解决hash冲突的办法 开放定址法线性探测再散列二次探测再散列伪随机探测再散列再哈希法链地址法建立一个公共溢出区Java中hashmap的解决办法就是采用的链地址法。 4. 再散列rehash过程 当哈希表的容量超过默认容量时必须调整table的大小。当容量已经达到最大可能值时那么该方法就将容量调整到Integer.MAX_VALUE返回这时需要创建一张新表将原表的映射到新表中。 /** * Rehashes the contents of this map into a new array with a * larger capacity. This method is called automatically when the * number of keys in this map reaches its threshold. * * If current capacity is MAXIMUM_CAPACITY, this method does not * resize the map, but sets threshold to Integer.MAX_VALUE. * This has the effect of preventing future calls. * * param newCapacity the new capacity, MUST be a power of two; * must be greater than current capacity unless current * capacity is MAXIMUM_CAPACITY (in which case value * is irrelevant). */ void resize(int newCapacity) { Entry[] oldTable table; int oldCapacity oldTable.length; if (oldCapacity MAXIMUM_CAPACITY) { threshold Integer.MAX_VALUE; return; } Entry[] newTable new Entry[newCapacity]; transfer(newTable); table newTable; threshold (int)(newCapacity * loadFactor); } /** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable) { Entry[] src table; int newCapacity newTable.length; for (int j 0; j src.length; j) { EntryK,V e src[j]; if (e ! null) { src[j] null; do { EntryK,V next e.next; //重新计算index int i indexFor(e.hash, newCapacity); e.next newTable[i]; newTable[i] e; e next; } while (e ! null); } } } 转载于:https://www.cnblogs.com/betterboyz/p/8556954.html