`
whl717247746
  • 浏览: 16686 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

HashMap的实现过程

阅读更多

 HashMap的实现过程

 

一、从Hash说起

还记得,我们第一次接触Hash这个词,是在学数据结构,讲到查找一节,引入哈希表的时候。

对于像顺序查找、折半查找、二叉排序树查找等查找,关键字在存储结构中的位置是随机的,即关键字与它的存储位置之间存在着不确定性的关系,因而这样的查找方法是建立在“比较”的基础上。而查找的效率也主要依赖于查找过程中所进行比较的次数。

哈希表的引入,提供了有别于上面所说查找的另外一种查找方式。即关键字与它所存储的位置之间有着一个确定的对应关系,这样当我们要查找指定关键字时,只要通过这样的一个映射关系,就可以得到它的存储地址,从而可以很快地找到关键字。

而这种映射关系就是所谓的哈希函数了,哈希函数的作用在于,它提供关键字到其存储地址的一种映射。不同的哈希函数,一般来说,就会有不同的映射结果。对于单值的映射来说,只要哈希函数一确定,输入相同的关键字,就会得到相同的映射结果,而输入不同的关键字,也有可能得到相同的映射结果。不过一旦出现这种现象,可就糟了,因为,如果我们直接将映射结果所指空间作为关键字的存储位置,那这样将会出现两个不同的关键字被存储在同一个存储空间中,这样肯定是不行的,所以我们必须对这样的结果还需进行一个加工处理。对于这样现象,我们叫它冲突Hash冲突),而当冲突出现时,我们需要对它进行处理,以使得最后每个关键字都能够找到它唯一的一个存储空间。

对于冲突的处理有许多方法,在这里,我们用的是挂链法。即,当我们用哈希函数处理不同的关键字,得到相同的映射结果,我们姑且把这样相同的映射结果所对应的空间叫做“第一地址”,然后我们在“第一地址”上挂一个链表(即“第一地址”存储链表的首地址),而链表里的元素就是实实在在所保存的关键字,只要当不同的关键字通过该哈希函数得到相同的映射结果,我们就把该关键字加到“第一地址”下的链表中存储。当我们访问该关键字时,首先通过哈希函数找到“第一地址”,然后遍历链表即可。

我们用哈希这一名词来表示存储这些关键字的结构。在这里,哈希表就是我们常说的“数组+链表”的结构了。数组空间存储就是上面所说的“第一地址”,即数组下标索引指的就是“第一地址”,通过索引,我们可以找到存有关键字的链表,然后再遍历链表就可以找到指定的关键字了。

附“哈希表”的定义(数据结构(严蔚敏版)):

哈希表:根据设定的哈希函数Hkey)和处理冲突的方法,将一组关键字映像到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为哈希表,这一映像过程称为哈希造表散列,所得存储位置称哈希地址散列地址

 



 

针对以上构造的哈希表,当我们存储的元素不是单值元素,而是像Map中键值对的形式的元素时,这时这种结构就是我们谈论的HashMap了,不过实际中的HashMap存储的元素还有hash值,以及指向下一个结点的引用。具体的东西,我们一看源码即可知晓。

对于Map,我们知道,有put(Object key , Object value)get(Object key)remove(Object key)containsKey(Object key)containsValue(Object value)等方法,同样,HashMap中也含有这些方法,下面我们从HashMap的源码中截取这些方法,自定义一个HashMap

二、从源码中截取经典自定义HashMap

1、定义内部类Entry<K,V>,封装所存数据

 

	/**
	 * 内部类:封装数据,作为链表的结点
	 * @author wanghaoliang
	 *
	 * @param <K>
	 * @param <V>
	 */
	static class Entry<K,V>{
		K key;
		V value;
		int hash;//参与h & (length-1)的hash码值
		Entry<K,V> next;
		
		Entry(K k,V v,int h,Entry<K,V> n){
			key=k;
			value=v;
			hash=h;
			next=n;
		}
	}

 

2、自定义带有初始容量和装载因子的构造器

 

	/**
	 * 指定初始容量和装载因子的构造器
	 * @param initialCapacity
	 * @param loadFactor
	 */
	public MyHashMap(int initialCapacity,float loadFactor){
		//IllegalArgumentException非法参数处理
		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);

		//初始化参数
		int capacity=1;
		while(capacity<initialCapacity)
			capacity<<=1;
		
		this.loadFactor=loadFactor;
		//计算阀值
		threshold=(int)(capacity*loadFactor);
		//对hash表初始化
		table=new Entry[capacity];
	}

 

3、定义put(K key,V value)等函数,关联指定的KeyValue,并加入到链表中 

 

	/**
	 * 关联指定的Key和Value,
	 * 如果map中已经有了指定的Key的映射,则新的value会覆盖老的value
	 * 如果map中没有指定的Key,则调用addEntry方法,添加新的mapping
	 * 返回老的value值
	 * 如果返回Null,则说明map中不存在指定
	 * @param key
	 * @param value
	 * @return
	 */
	public V put(K key,V value){
		//HashMap中允许存放null键和null值
		if(key==null)
			return putForNullKey(value);
		int hash = hash(key.hashCode());
		int i= indexFor(hash, table.length);
		//遍历table,查看其中是否已有指定的Key,若有,则用新的Value覆盖旧的Value
		for(Entry<K,V> e=table[i];e!=null;e=e.next){
			Object k=e.key;
			if(e.hash==hash&&(key==k||key.equals(k))){
				V oldValue=e.value;
				e.value=value;
				return oldValue;
			}
		}
		//table中没有指定Key的映射,故加入该映射到链表中
		addEntry(hash,key, value, i);
		return null;
	}

 

4、定义get(K key)等函数,通过指定键获取对应的Value

 

	/**
	 * 返回指定的键(Key)所对应的值(Value)
	 * @param key
	 * @return
	 */
	public V get(K key){
		if(key==null)
			return getForNullKey();
		int hash=hash(key.hashCode());
		int i=indexFor(hash, table.length);
		//遍历指定索引下的链表
		for(Entry<K,V> e=table[i];e!=null;e=e.next){
			Object k=e.key;
			//先比较hash,再比较key值
			if(e.hash==hash&&(key==k)||key.equals(k))
				return e.value;
		}
		return null;
	}

 

5、定义remove(K key)等函数,通过指定键移除对应的映射关系

 

	/**
	 * 移除指定的Key所对应的映射
	 * 返回Entry<K,V>
	 * @param key
	 * @return
	 */
	public Entry<K,V> removeEntryForKey(K key){
		//如果key为null,则对应的hash值为0
		int hash=(key==null)?0:hash(key.hashCode());
		int i=indexFor(hash,table.length);
		Entry<K,V> prev=table[i];
		Entry<K,V> e=prev;
		
		while(e!=null){
			Entry<K,V> next=e.next;
			Object k=e.key;
			//遍历链表,找到指定的结点
			if(e.hash==hash&&(key==k||key.equals(k))){
				size--;
				if(prev==e)//要删除的结点为首结点
					table[i]=next;
				else
					prev.next=next;
				return e;
			}
			prev=e;
			e=next;
		}
		return e;
	}

 

6、定义containsKey(K key)containsValue(V value)函数,判断指定的KV是否存在于HashMap

 

 

	/**
	 * 判断table中是否含有指定的Key
	 * @param key
	 * @return
	 */
	public boolean containsKey(K key){
		return getEntry(key)!=null;
	}

 

	/**
	 * 判断table中是否含有指定的Value
	 * @param value
	 * @return
	 */
	public boolean containsValue(V value){
		if(value==null)
			return containsNullValue();
		Entry[] tab=table;
		for(int i=0;i<tab.length;i++){
			for(Entry e=tab[i];e!=null;e=e.next)
				if(value.equals(e.value))
					return true;
		}
		return false;
	}

 

7、测试

 

三、源码要点分析

 

1、为什么table表的大小为2的幂次方?

 

   /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry[] table;

 

 

我们知道,选择哈希函数构造哈希表时,应尽量避免冲突,即构造一个均匀的哈希表,使得表中的每个索引空间被映射到的概率都是均等的。对于一片有限的连续的存储空间(数组),如果将不同的关键字比较均匀地映射到这一片存储空间呢?是的,我们一般采用“除留取余法”,即拿hash%table.length所得值,作为索引空间。但是计算机对于%(取余运算)较耗时间,而计算机对于&(位与运算),即hash & (table.length-1) 却是大大地节省了时间,而只有当table.length2的幂次方时,上面的hash & (table.length-1)才具有等价的hash%table.length效果。

2、为什么还要再哈希?

即为什么当我们调用key.hashcode()返回一个哈希值时,为什么不直接将key.hashcode()作为最终的hash值,而还要经过如下的这一操作?

 

    /**
     * Applies a supplemental hash function to a given hashCode, which
     * defends against poor quality hash functions.  This is critical
     * because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

 

 

大概看了一下源码中的对此函数的注释可知,原始的key.hashcode()返回得到的hash值,如果参与hash & (table.length-1)运算,在低字节上很容易相同,即就很容易产生冲突,为了使得冲突减少,所以系统增加了上面的这一操作,它是原始hash的补充,旨在使得key的最终的hash码值,在参与hash & (table.length-1)运算能够达到一个分布均匀的结果值,即最大化地减少冲突。

 

附:完整代码

 

package whlHashMap2;

import java.util.Map;

/**
 * 自定义HashMap
 * @author wanghaoliang
 *
 */
public class MyHashMap<K,V> {

	//最大的初始容量,必须<=2的30次方
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //阀值=capacity * load factor
    int threshold;
    //装载因子
    final float loadFactor;
    //hash表
    transient Entry[] table;
    //键值对的个数
    transient int size;

	/**
	 * 指定初始容量和装载因子的构造器
	 * @param initialCapacity
	 * @param loadFactor
	 */
	public MyHashMap(int initialCapacity,float loadFactor){
		//IllegalArgumentException非法参数处理
		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);

		//初始化参数
		int capacity=1;
		while(capacity<initialCapacity)
			capacity<<=1;
		
		this.loadFactor=loadFactor;
		//计算阀值
		threshold=(int)(capacity*loadFactor);
		//对hash表初始化
		table=new Entry[capacity];
	}
	
	/**
	 * 补充性的hash函数
	 * @param h
	 * @return
	 */
	static int hash(int h){
		h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
	}
    /**
     * 返回对应的hashCode在数组中的索引
     * 等价于h%length操作
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
    /**
     * 添加链表结点
     * @param hash:关键字所对应的hash值
     * @param key:键值对中的Key
     * @param value:键值对张的Value
     * @param bucketIndex数组索引
     */
    void addEntry(int hash,K key,V value,int bucketIndex){
    	Entry<K,V> next=table[bucketIndex];
    	table[bucketIndex]=new Entry<K,V>(key,value,hash,next);
    	if(size++ >= threshold)
    		resize(2*table.length);
    }
    /**
     * 改变table的大小
     * @param newCapacity:新的大小容量
     * newCapacity必须满足:不超过MAXIMUM_CAPACITY,并且为2的幂次方
     */
    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];
    	//从oldTable中转移数据到newTable中
    	transfer(newTable);
    	table=newTable;
    	threshold=(int)(newCapacity*loadFactor);
    }
    
    /**
     * 从oldTable中转移数据到newTable中
     * @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;//清空原始Hash表
    			//原始链表数据在新的链表中顺序被倒置了
    			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);
    		}
    	}
    }
	
	/**
	 * 关联指定的Key和Value,
	 * 如果map中已经有了指定的Key的映射,则新的value会覆盖老的value
	 * 如果map中没有指定的Key,则调用addEntry方法,添加新的mapping
	 * 返回老的value值
	 * 如果返回Null,则说明map中不存在指定
	 * @param key
	 * @param value
	 * @return
	 */
	public V put(K key,V value){
		//HashMap中允许存放null键和null值
		if(key==null)
			return putForNullKey(value);
		int hash = hash(key.hashCode());
		int i= indexFor(hash, table.length);
		//遍历table,查看其中是否已有指定的Key,若有,则用新的Value覆盖旧的Value
		for(Entry<K,V> e=table[i];e!=null;e=e.next){
			Object k=e.key;
			if(e.hash==hash&&(key==k||key.equals(k))){
				V oldValue=e.value;
				e.value=value;
				return oldValue;
			}
		}
		//table中没有指定Key的映射,故加入该映射到链表中
		addEntry(hash,key, value, i);
		return null;
	}
	/**
	 * 当Key为Null时的put(K,V)方法处理
	 * @param value
	 * @return
	 */
	private V putForNullKey(V value){
		for(Entry<K,V> e=table[0];e!=null;e=e.next){
			if(e.key==null){
				V oldValue=e.value;
				e.value=value;
				return oldValue;
			}
		}
		addEntry(0, null, value, 0);
		return null;
	}
	/**
	 * 返回指定的键(Key)所对应的值(Value)
	 * @param key
	 * @return
	 */
	public V get(K key){
		if(key==null)
			return getForNullKey();
		int hash=hash(key.hashCode());
		int i=indexFor(hash, table.length);
		//遍历指定索引下的链表
		for(Entry<K,V> e=table[i];e!=null;e=e.next){
			Object k=e.key;
			//先比较hash,再比较key值
			if(e.hash==hash&&(key==k)||key.equals(k))
				return e.value;
		}
		return null;
	}
	/**
	 * 当Key为Null时的get(K)方法处理
	 * @return
	 */
	private V getForNullKey(){
		for(Entry<K,V> e=table[0];e!=null;e=e.next){
			if(e.key==null)
				return e.value;
		}
		return null;
	}
	/**
	 * 判断table中是否含有指定的Key
	 * @param key
	 * @return
	 */
	public boolean containsKey(K key){
		return getEntry(key)!=null;
	}
	/**
	 * 判断table中是否含有指定的Value
	 * @param value
	 * @return
	 */
	public boolean containsValue(V value){
		if(value==null)
			return containsNullValue();
		Entry[] tab=table;
		for(int i=0;i<tab.length;i++){
			for(Entry e=tab[i];e!=null;e=e.next)
				if(value.equals(e.value))
					return true;
		}
		return false;
	}
	/**
	 * 当Value为Null是的containsValue(V)方法处理
	 * @return
	 */
	private boolean containsNullValue(){
		Entry[] tab=table;
		for(int i=0;i<tab.length;i++)
			for(Entry e=tab[i];e!=null;e=e.next)
				if(e.value==null)
					return true;
		return false;
	}
	/**
	 * 返回指定Key所对应的映射
	 * @param key
	 * @return
	 */
	public Entry<K,V> getEntry(K key){
		//若key为null,则对应hash码值为0
		int hash=(key==null)?0:hash(key.hashCode());
		int i=indexFor(hash, table.length);
		for(Entry<K,V> e=table[i];e!=null;e=e.next){
			Object k=e.key;
			if(e.hash==hash&&(key==k||key.equals(k)))
				return e;
		}
		return null;
	}
	/**
	 * 移除指定的Key所对应的映射
	 * 返回Value
	 * @param key
	 * @return
	 */
	public V remove(K key){
		Entry<K,V> e =removeEntryForKey(key);
		return  (e ==null ? null:e.value);
	}
	/**
	 * 移除指定的Key所对应的映射
	 * 返回Entry<K,V>
	 * @param key
	 * @return
	 */
	public Entry<K,V> removeEntryForKey(K key){
		//如果key为null,则对应的hash值为0
		int hash=(key==null)?0:hash(key.hashCode());
		int i=indexFor(hash,table.length);
		Entry<K,V> prev=table[i];
		Entry<K,V> e=prev;
		
		while(e!=null){
			Entry<K,V> next=e.next;
			Object k=e.key;
			//遍历链表,找到指定的结点
			if(e.hash==hash&&(key==k||key.equals(k))){
				size--;
				if(prev==e)//要删除的结点为首结点
					table[i]=next;
				else
					prev.next=next;
				return e;
			}
			prev=e;
			e=next;
		}
		return e;
	}
	/**
	 * 直接移除所指的映射
	 * @param o:被移除的映射对象
	 * @return
	 */
	public Entry<K,V> removeMapping(Object o){
		if(!(o instanceof Map.Entry))
			return null;
		
		Map.Entry<K, V> entry=(Map.Entry<K, V>)o;
		Object key=entry.getKey();
		int hash=(key==null)?0:hash(key.hashCode());
		int i=indexFor(hash, table.length);
		Entry<K,V> prev=table[i];
		Entry<K,V> e=prev;
		
		while(e!=null){
			Entry<K,V> next=e.next;
			Object k=e.key;
			if(e.hash==hash&&(key==k||key.equals(k))){
				size--;
				if(prev==e)
					table[i]=next;
				else
					prev.next=next;
				return e;
			}
			prev=e;
			e=next;
		}
		return e;
	}
	/**
	 * 内部类:封装数据,作为链表的结点
	 * @author wanghaoliang
	 *
	 * @param <K>
	 * @param <V>
	 */
	static class Entry<K,V>{
		K key;
		V value;
		int hash;//参与h & (length-1)的hash码值
		Entry<K,V> next;
		
		Entry(K k,V v,int h,Entry<K,V> n){
			key=k;
			value=v;
			hash=h;
			next=n;
		}
	}
	
}

 

 

<!--EndFragment-->

  • 大小: 7.3 KB
分享到:
评论

相关推荐

    hashmap实现原理

    通过`hashCode()`和`equals()`的合理使用,以及数组和链表的结合,HashMap实现了高效的键值对存储和查找。了解这些实现细节对于优化代码性能和避免潜在问题至关重要。在实际编程中,应充分考虑哈希冲突的处理、负载...

    用hashmap实现词典查询

    总的来说,通过HashMap实现词典查询,我们可以充分利用其高效的查找机制,为用户提供快速的查询服务。同时,理解HashMap的内部机制对于优化查询性能和处理潜在问题至关重要。在实际项目中,还需要考虑如何优雅地处理...

    深入Java集合学习系列:HashMap的实现原理

    在Java编程语言中,集合框架是开发者日常工作中不可或缺的一部分,HashMap作为其中的重要成员,它的实现原理对于理解Java性能优化和数据结构有深远的意义。HashMap是一个基于哈希表的数据结构,它实现了Map接口,...

    Jdk1.8中的HashMap实现原理.docx

    《Jdk1.8中的HashMap实现原理》 HashMap作为Java编程语言中常用的数据结构,它在Jdk1.8中的实现结合了哈希表、链表以及红黑树的特性,提供高效且灵活的键值对存储功能。本文将深入探讨HashMap的内部结构、工作原理...

    HashMap之resize()方法源码解读.docx

    在resize()方法的实现中,我们可以看到HashMap使用了power-of-two expansion机制,即每次扩容时,数组的长度都会翻倍。这使得HashMap的扩容机制更加高效。同时,在resize()方法中,我们还可以看到HashMap使用了 ...

    HashMap的实现1

    HashMap实现了Map接口,提供了所有映射操作。它不保证映射的顺序,尤其在多线程环境下,映射的顺序可能会发生变化。HashMap通过哈希函数来计算键(key)在数组中的位置,以便快速访问。 2. **数据结构**: HashMap...

    HashMap的实现原理

    ### HashMap的实现原理 #### 1. HashMap概述 HashMap 是 Java 集合框架中一个非常重要的类,它实现了 Map 接口,并提供了基于哈希表的存储方式。与其它 Map 实现不同的是,HashMap 允许使用 `null` 键和 `null` 值...

    HashMap源码实现红黑树添加元素和删除元素

    HashMap 源码实现红黑树添加元素和删除元素 HashMap 中使用红黑树来存储元素,是为了提高查找、插入和删除操作的性能。红黑树是一种自平衡二叉树,可以保持二叉树的平衡,从而获得较高的查找性能。 红黑树的创建...

    Jdk1.8 HashMap实现原理详细介绍

    以下是对JDK 1.8 HashMap实现原理的详细解释。 首先,HashMap是一个基于哈希表的数据结构,它实现了Map接口。它不保证映射的顺序,并且是非同步的。这意味着在多线程环境中,如果不进行同步控制,可能会出现并发...

    HashMap底层实现原理共6页.pdf.zip

    《HashMap底层实现原理》 HashMap是Java编程语言中一个非常重要的数据结构,它在实际开发中被广泛应用。HashMap提供了一种高效、灵活的方式来存储和检索键值对数据,它的核心特性是快速查找,通常时间复杂度为O(1)...

    面试必考之HashMap源码分析与实现

    面试中,HashMap的源码分析与实现是一个常见的考察点,因为它涉及到数据结构、并发处理和性能优化等多个核心领域。本篇文章将深入探讨HashMap的内部工作原理、关键特性以及其在实际应用中的考量因素。 HashMap基于...

    HashMap介绍和使用

    HashMap是Java集合框架的一个重要组成部分,它实现了Map接口,能够存储键值对映射。在Java编程语言中,最基本的数据结构有两种:数组和引用(模拟指针)。所有的复杂数据结构都可以基于这两种基本结构构建出来,...

    hashmap中hash函数的构造问题

    ### HashMap中的Hash函数构造问题详解 #### 一、引言 在Java中,`HashMap`是一种非常常用的集合类,它提供了基于键值对的数据存储方式。`HashMap`内部使用哈希表来存储数据,其中最关键的部分就是哈希函数的设计。...

    马士兵老师HashMap学习笔记

    ConcurrentHashMap是线程安全的HashMap实现,它使用分段锁(Segment)来提高并发性能。在put过程中,首先根据键的哈希值确定所在的段,然后在该段上加锁,执行put操作。这样可以避免全局锁,提升并发效率。每个段...

    Java中HashMap详解(通俗易懂).doc

    HashSet是基于HashMap实现的,它不存储值,只存储键。当向HashSet添加元素时,实际上是在HashMap中添加键,而值是默认的null。由于HashSet没有值的概念,所以它不提供键值对的关联操作,仅提供基本的添加、删除和...

    HashMap的数据结构

    这个过程虽然消耗资源,但是能保持HashMap的高效性能。 11. **HashMap的实现细节**:在Java 8之后,为了优化性能,当链表长度达到8时,HashMap会将链表转换为红黑树,这降低了查找、插入和删除的时间复杂度,尤其是...

    Hashmap详解

    下面我们将深入探讨 HashMap 的数据结构、 put 方法的实现细节和 Hash 码的计算过程。 HashMap 的数据结构 HashMap 的数据结构可以分为两部分:数组和链表。数组是 HashMap 的基本结构,链表是数组元素的具体实现...

    HASHMAP缓存.txt

    接下来的部分内容则详细展示了如何利用HashMap实现缓存功能。 ### HashMap作为缓存机制 #### 基本概念与原理 HashMap是Java集合框架的一部分,它实现了Map接口,能够存储键值对数据。其内部使用哈希表来存储数据...

Global site tag (gtag.js) - Google Analytics