HashMap是Java中一个经典的数据结构,它实现了Map接口,提供了一种键值对的映射关系。因为它的高效性和易用性,HashMap在Java编程中被广泛使用。
一、HashMap的基本概念
HashMap是一个散列表,它存储的是键值对(key-value)映射的数据。
在HashMap中,每个键(key)都是唯一的,对应着一个值(value)。你可以通过键来访问对应的值,类似于字典。在Java中,键和值都可以是任何对象。
二、HashMap的构造方法
HashMap类有多个构造方法,其中比较常用的是以下两个:
/** * 创建一个空的HashMap */ HashMap() /** * 创建一个具有指定初始容量和默认负载因子(0.75)的HashMap * * @param initialCapacity 初始容量 */ HashMap(int initialCapacity)
第一个构造方法用来创建一个空的HashMap。第二个构造方法用来创建一个具有指定初始容量(initialCapacity)的HashMap。初始容量指的是HashMap能够容纳键值对的数量,当HashMap中存储的键值对数量超过了这个值,HashMap会自动进行扩容。
三、HashMap的常用方法
HashMap提供了许多常用的方法,下面介绍一些比较重要的方法:
1、put()
put()方法用来将键值对插入到HashMap中。如果给定的键已经存在了,则它对应的值会被更新为新的值。
/** * 将指定的键和值插入到HashMap中,如果键已经存在,则更新对应的值 * @param key 键 * @param value 值 * @return 如果之前存在对应的值,则返回旧的值,否则返回null */ V put(K key, V value)
2、get()
get()方法用来根据键来获取对应的值。
/** * 根据键来获取对应的值 * @param key 键 * @return 值 */ V get(Object key)
下面是一个简单的示例:
HashMap<String, Integer> map = new HashMap<>(); map.put("apple", 1); map.put("banana", 2); map.put("orange", 3); System.out.println(map.get("apple")); System.out.println(map.get("banana")); System.out.println(map.get("orange"));
输出结果:
1 2 3
3、containsKey()
containsKey()方法用来检查HashMap中是否包含指定的键。
/** * 检查HashMap中是否包含指定的键 * @param key 键 * @return 如果包含指定的键,则返回true,否则返回false */ boolean containsKey(Object key)
4、containsValue()
containsValue()方法用来检查HashMap中是否包含指定的值。
/** * 检查HashMap中是否包含指定的值 * @param value 值 * @return 如果包含指定的值,则返回true,否则返回false */ boolean containsValue(Object value)
5、remove()
remove()方法用来根据指定的键来删除对应的键值对。
/** * 根据指定的键来删除对应的键值对 * @param key 键 * @return 如果存在对应的值,则返回被删除的值,否则返回null */ V remove(Object key)
6、keySet()
keySet()方法用来获取HashMap中所有键的集合。
/** * 获取HashMap中所有键的集合 * @return 键的集合 */ Set<K> keySet()
下面是一个简单的示例:
HashMap<String, Integer> map = new HashMap<>(); map.put("apple", 1); map.put("banana", 2); map.put("orange", 3); Set<String> keys = map.keySet(); for (String key : keys) { System.out.println(key + " -> " + map.get(key)); }
输出结果:
banana -> 2 orange -> 3 apple -> 1
四、HashMap的扩容机制
HashMap的扩容机制非常重要。当HashMap中的键值对数量达到一定程度时,HashMap会自动进行扩容,这是为了保证HashMap的高效性。HashMap的扩容机制遵循以下两个原则:
1、容量(capacity)总是2的幂次方
HashMap的容量总是2的幂次方,这是为了方便计算哈希值。当你向HashMap中插入一个键值对时,HashMap会根据键的哈希值来计算该键值对应该放在哪个位置。如果HashMap的容量不是2的幂次方,那么计算哈希值的时候需要进行额外的处理,这会降低HashMap的效率。
2、负载因子(load factor)默认为0.75
负载因子决定了HashMap何时需要进行扩容。负载因子默认为0.75,这意味着当HashMap中存储的键值对数量达到总容量的75%时,HashMap就会进行扩容。这样做的好处是可以保证HashMap的效率,即当HashMap中存储的键值对数量不多时,HashMap的容量也不会过大,从而提高HashMap的检索效率。
下面是一个HashMap扩容的示例:
HashMap<Integer, Integer> map = new HashMap<>(); System.out.println(map.size()); for (int i = 0; i < 30; i++) { map.put(i, i); System.out.println("put:" + i); } System.out.println(map.size());
输出结果:
0 put:0 put:1 put:2 put:3 put:4 put:5 put:6 put:7 put:8 put:9 put:10 put:11 put:12 put:13 put:14 put:15 put:16 put:17 put:18 put:19 put:20 put:21 put:22 put:23 put:24 put:25 put:26 put:27 put:28 put:29 32
从上面的代码可以看出,初始容量为16的HashMap存储了30个键值对后,容量自动扩容为32。
五、HashMap和线程安全性
HashMap并不是线程安全的。如果多个线程同时读写一个HashMap,有可能会出现数据不一致的情况。如果需要在多线程环境中使用HashMap,可以考虑使用ConcurrentHashMap。
下面是一个对HashMap进行读写操作的示例:
HashMap<Integer, Integer> map = new HashMap<>(); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 1000; i++) { executorService.submit(() -> { for (int j = 0; j < 1000; j++) { map.put(j, j); System.out.println(map.get(j)); } }); } executorService.shutdown();
输出结果中可能会存在重复的数字,这是因为多个线程同时进行读写操作导致的。
下面是对ConcurrentHashMap进行读写操作的示例:
ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>(); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 1000; i++) { executorService.submit(() -> { for (int j = 0; j < 1000; j++) { map.put(j, j); System.out.println(map.get(j)); } }); } executorService.shutdown();
输出结果中不会存在重复的数字,因为ConcurrentHashMap是线程安全的。
六、HashMap的性能分析
HashMap的性能非常优秀,它的常见操作的时间复杂度为O(1)。但是在实际使用中,我们需要注意其扩容机制可能会带来的性能影响。
下面是一个对HashMap进行性能测试的示例:
long start, end; HashMap<Integer, Integer> map = new HashMap<>(); Random rand = new Random(); start = System.nanoTime(); for (int i = 0; i < 100000; i++) { map.put(i, rand.nextInt()); } end = System.nanoTime(); System.out.println("插入100000个键值对,耗时:" + (end - start) + "ns"); start = System.nanoTime(); for (int i = 0; i < 100000; i++) { map.get(rand.nextInt(100000)); } end = System.nanoTime(); System.out.println("随机访问100000个键值对,耗时:" + (end - start) + "ns");
在我的电脑上,上面的代码的输出结果大概如下:
插入100000个键值对,耗时:10503887ns 随机访问100000个键值对,耗时:68013ns
从上面的代码可以看出,HashMap插入100000个键值对的时间大约为10503887纳秒,随机访问100000个键值对的时间大约为68013纳秒。
七、总结
本文从HashMap的基本概念、构造方法、常用方法、扩容机制、线程安全性和性能分析等多个方面对HashMap进行了详细的介绍。HashMap是Java中一个经典的数据结构,它的高效性和易用性使得它在Java编程中被广泛使用。在使用HashMap时,我们需要注意其扩容机制可能会带来的性能影响,并且需要考虑线程安全性问题。