赞
踩
HashSet和HashMap是面试中比较常问的一个问题,他的难点主要在于它的底层原理和处理冲突的方式。他们两个是Java中常见的两种集合类型,它们的主要区别在于如何存储和访问元素。接下来我们展开具体讲一讲。
1、什么是HashSet?
HashSet是一种无序集合它实现了Set接口,它不允许集合中有重复的值,当添加元素时,HashSet首先使用哈希函数计算元素的索引,然后将元素添加到该索引对应的链表中。当需要查找元素时,HashSet会使用哈希函数计算元素的索引,并在相应的链表中搜索它。
2、HashSet常见的构造函数:
(1) HashSet()
该构造函数用于构建一个空的HashSet对象,其中默认初始容量为16,默认加载因子为0.75。如果我们希望创建一个名为 hs 的空 HashSet,则可以将其创建为:
HashSet<E> hs = new HashSet<E>();
//其实也可以写成,以下类推
Set<E> hs = new HashSet<E>();
(2) HashSet(int initialCapacity)
initialCapacity就是初始容量,在对象创建时指定initialCapacity。
HashSet<E> hs = new HashSet<E>(int initialCapacity);
(3)HashSet(int initialCapacity, float loadFactor)
该构造函数用于构建一个空的HashSet对象,其中在创建对象时指定了initialCapacity和loadFactor。
HashSet<E> hs = new HashSet<E>(int initialCapacity, float loadFactor);
3、HashSet常用的方法:
(1)添加元素add()
因为HashSet是纯Key模型,所以相同的值不会存进去。
public static void main(String[] args) {
Set<String> set=new HashSet<String>();
set.add("world");
set.add("hello");
set.add("good");
//加入重复元素;
set.add("world");
System.out.println("set中的值为:"+set);
}
输出的结果:
从结果中可以看出,虽然有两个world但是只存了一次。
(2)删除元素remove()
public static void main(String[] args) {
Set<String> set=new HashSet<String>();
set.add("world");
set.add("hello");
set.add("good");
//加入重复元素;
set.add("world");
System.out.println("set中的值为:"+set);
set.remove("hello");
System.out.println("删除后set中的元素为:"+set);
(3)判断是否包含元素contains(), 判断是否为空isEmpty(),获得大小size();
public static void main(String[] args) {
Set<String> set=new HashSet<String>();
set.add("world");
set.add("hello");
set.add("good");
//加入重复元素;
set.add("world");
System.out.println("set中的值为:"+set);
boolean ret=set.contains("China");
System.out.println("是否包含元素:"+ret);
boolean isFull=set.isEmpty();
System.out.println("是否为空:"+isfull);
int len=set.size();
System.out.println("set中值得元素个数:"+len);
}
(4)遍历HashSet:
使用iterator()遍历,也就是迭代器。
还有一种方法是for循环。
public static void main(String[] args) { Set<String> set=new HashSet<String>(); set.add("world"); set.add("hello"); set.add("good"); set.add("中国"); //加入重复元素; set.add("world"); //使用Iterator迭代器遍历; Iterator<String> integer=set.iterator(); System.out.println("使用迭代器遍历:"); while (integer.hasNext()){ System.out.print(integer.next()+","); } System.out.println(); System.out.println("使用for循环遍历"); for (String s:set) { System.out.print(s+","); } }
这里是引用HashSet 操作的时间复杂度:HashSet的底层数据结构是 hashtable。因此,HashSet
的添加、删除和查找(包含方法)时间复杂度需要O(1)时间。
1.什么是HashMap?
2、HashMap的实现原理:
HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
如图:
当链表长度到达8时,升级成红黑树结构。
让我们看看底层实现的代码。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;计算得到哈希值
final K key;
V value;
Node<K,V> next;
}
interface Entry<K, V> {
K getKey();
V getValue();
V setValue(V value);
3、HashMap的扩容机制
1、当前存储的数量大于等于阈值;
2、发生hash碰撞;
特点: 先扩容,再添加(扩容使用的头插法)。
缺点: 头插法会使链表发生反转,多线程环境下可能会死循环。
- JDK.18的扩容条件:
特点: 先插后判断是否需要扩容(扩容时是尾插法)。
缺点: 多线程下,1.8会有数据覆盖。
源码实现:
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()方法把原数组中的值放到新数组中 transfer(newTable, initHashSeedAsNeeded(newCapacity)); //设置hashmap扩容后为新的数组引用 table = newTable; //设置hashmap扩容新的阈值 threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
4、哈希冲突
(1)什么是哈希冲突:
当两个不同的数经过哈希函数计算后得到了同一个结果,即他们会被映射到哈希表的同一个位置时,即称为发生了哈希冲突。简单来说就是哈希函数算出来的地址被别的元素占用了。
(2)解决哈希冲突的方法:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。