//初始化容量 static final int DEFAULT_INITIAL_CAPACITY = 1 <4; // aka 16 //最大的容量 static final int MAXIMUM_CAPACITY = 1 <30; //默认的加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f;
int threshold;
「构造方法」
//自定义初始化容量和加载因子,会进行判断两个值合不合法 // The default initial capacity - MUST be a power of two. 容量必须是2的幂次方 public HashMap(int initialCapacity, float loadFactor)//自定义初始化容量,使用默认的加载因子public HashMap(int initialCapacity) //使用默认的初始化容量和默认的加载因子public HashMap()
//归根结底 最终都是调用了这个构造方法 public HashMap(int initialCapacity, float loadFactor) { //判断初始化容量是否合法 if (initialCapacity 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //当初始化容量大于等于最大容量时,直接赋值为最大容量 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //判断加载因子是否和法 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); //给加载因子赋值 this.loadFactor = loadFactor; //这里留个问题? 这个究竟是干嘛的?为什么要赋值给它。 threshold = initialCapacity; init(); //hashMap中该方法为空,没有实现,在LinkedList才有实现,所以这里不做研究 }
「put方法」
public V put(K key, V value) { //当前table数组为空,则去进行初始化大小,在这里我们就能够知道在构造方法中,为什么要将初始化容量赋值给threshold了,根据我的理解其实就是起到一个懒初始化的效果吧,就是当你去put第一个值的时候,它才去进行初始化数组的大小 if (table == EMPTY_TABLE) { inflateTable(threshold); // 跳到1 } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry 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; } } modCount++; addEntry(hash, key, value, i);return null; }
inflateTable(threshold)
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize 根据注释可以知道是找到一个大于等于toSize的2的幂次方 // 例如toSize=5 时,则capacity此时就会等于8 int capacity = roundUpToPowerOf2(toSize);
private static int roundUpToPowerOf2(int number) { // assert number >= 0 : "number must be non-negative"; // 进来看我们可以发现这是两个三目表达式嵌套 // 又出现了一个我们不认识的函数, 我们继续深究它 return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) <1) : 1; }
我们继续往下点
// 这个函数看上去很是简洁,全是位运算,那么它就是是在干嘛呢? public static int highestOneBit(int i) { // HD, Figure 3-1 i |= (i >> 1); i |= (i >> 2); i |= (i >> 4); i |= (i >> 8); i |= (i >> 16); return i - (i >>> 1); }
private void inflateTable(int toSize) { // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); //这里先标记着,有待研究,因为与后面无关,所以先不探讨(此值与扩容有关,在最后讲) threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); //此时我们就可以看到它给table开辟了一个大小为capacity的数组了。 table = new Entry[capacity]; initHashSeedAsNeeded(capacity); }
好了,来来回回游荡了这么久,我们就要又回到put函数了
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) //key为null时,调用下面的方法去将null-value加进去 //这里可以自己复习的时候点进去看因为里面很简单易懂,这里节省时间不单独 //跟下面的循环一样的 return putForNullKey(value); //跳到下面解释1 int hash = hash(key); int i = indexFor(hash, table.length); //跳到下面解释2 for (Entry 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; } }//当put进去的key算出来的位置此时为null时,则会执行到这里,跳到解释3 modCount++; addEntry(hash, key, value, i); return null; }
int hash = hash(key); //算出key的哈希值 int i = indexFor(hash, table.length); //这里讲一下hashmap的每一个key的存储规则,我们知道1.7它是用链表+数组去实现的,那么每次我们put进去一对k-v //它是怎么去存储的呢? 这两行就是算出该key具体放在数组中哪个位置,它是利用key的哈希值去模上数组的长度, //最后算出来的就是它在数组中存储的下标,indexFor的源码在下面 static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); //为什么是减1呢,因为数组的下标是从0开始的 }
由于hash()函数涉及到哈希种子方面的知识,这里就先不做解释了(因为我还没搞懂,--!)。
//这里的循环其实很容易懂,我们看下面的图就是可以知道 //当put进去的key原来已经存在时,则会替换掉原来的value然后返回旧的值 for (Entry 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); //这里没有实现,在LinkedHashMap有实现,涉及LRU缓存结构return oldValue; } }
❝
需要注意一点的就是,每次put进去的key都是放在链表的头部,也就是将往下挤
❞
image-20201205165507120
在这里,引用一下不知道谁说的一句话 Don't BB,show me the code ,我们写个demo测试一下
public class HashMapTest {
public static void main(String[] args) { HashMap hashMap = new HashMap<>(); System.out.println(hashMap.put(1, 10)); System.out.println(hashMap.put(1, 20)); } }
wp-includes目录1.wp-includes/cache.php2.wp-includes/capabilities.php3.wp-includes/class-IXR.php:Incutio XML-RPC库。包括了 XML RPC支持函数。由http://scripts.incutio.com/xmlrpc/提供支持。4.wp-includes/classes.php:包括了基本的类ÿ…
idea 通过Subversion导入项目,idea项目没有关联到svn环境- IDEA:IntelliJ IDEA 2017.2- svn:1.9.71、检出代码idea Checkout from Subversion导出项目代码:2、提交代码修改某个文件之后,提交代码到svn服务器࿰…
2019独角兽企业重金招聘Python工程师标准>>> 进入vi的命令 vi filename :打开或新建文件,并将光标置于第一行首 vi n filename :打开文件,并将光标置于第n行首 vi filename :打开文件,并将光标置于最后一行…
2021LPL夏季赛FPX vs V5第三场比赛视频2021LPL夏季赛8月4日FPXvsV5视频在哪看,比赛中FPXvsV5比赛中MVP又是谁,2021LPL夏季赛8月4日的比赛中FPXvsV5谁会获胜呢下面一起和小编来看看吧。【2021LPL夏季赛赛程】【2021LPL夏季赛直播】比赛时间:8月…