目录
一、HashSet 介绍
二、HashSet 的应用场景
三、HashSet 源码分析
四、HashSet 没有 get 方法的几大因素
1、官方的 HashSet 介绍链接
2、源码翻译
/**
* This class implements the Set interface, backed by a hash table
* (actually a HashMap instance). It makes no guarantees as to the
* iteration order of the set; in particular, it does not guarantee that the
* order will remain constant over time. This class permits the null
* element.
*
* 翻译:HashSet 实现了 Set 接口,受 HashTable 的支持(实际上是一个 HashMap 实例)。
* 它的迭代顺序不能保证有序,特别是从长时间角度来说。不过它允许存入null元素
*
* This class offers constant time performance for the basic operations
* (add, remove, contains and size),
* assuming the hash function disperses the elements properly among the
* buckets. Iterating over this set requires time proportional to the sum of
* the HashSet instance's size (the number of elements) plus the
* "capacity" of the backing HashMap instance (the number of
* buckets). Thus, it's very important not to set the initial capacity too
* high (or the load factor too low) if iteration performance is important.
*
* 翻译:对于 ARCS( add(),remove(),contains() and size() ) 的基本使用 Set 集合可以表现出持
* 久的性能,前提是假定 hash 函数能够将元素在<桶中>分配的错落有致。对该集合的遍历时间要求与
* 该集合的大小加上底层支持它的 HashMap 容量即 Capacity 成比例。另外,如果注重迭代性能,最好
* 不要将其初始容量即 “initial capacity” 设置的过高或其加载因子即 “Load Factor” 设置的过低
*
*
Note that this implementation is not synchronized.
* If multiple threads access a hash set concurrently, and at least one of
* the threads modifies the set, it must be synchronized externally.
* This is typically accomplished by synchronizing on some object that
* naturally encapsulates the set.
*
* 翻译:注意, HashSet 集合非[线程安全]。如果多个线程并发访问该集合,且至少有一个线程对其元
* 素进行修改,必须通过外部实现达到线程同步。常用的实现就是对某个封装了set集合的对象使用
* sychronized 关键字
*
* If no such object exists, the set should be "wrapped" using the
* {@link Collections#synchronizedSet Collections.synchronizedSet}
* method. This is best done at creation time, to prevent accidental
* unsynchronized access to the set:
* Set s = Collections.synchronizedSet(new HashSet(...));
*
* 翻译:如果没有这种对象存在,应该使用 Collections.sychronizedSet 对其进行同步封装。对于创
* 建期间来说使用该方式避免突发性非线程安全的访问,是一个非常好的做法。
*
* The iterators returned by this class's iterator method are
* fail-fast: if the set is modified at any time after the iterator is
* created, in any way except through the iterator's own remove
* method, the Iterator throws a {@link ConcurrentModificationException}.
* Thus, in the face of concurrent modification, the iterator fails quickly
* and cleanly, rather than risking arbitrary, non-deterministic behavior at
* an undetermined time in the future.
*
* 翻译:HashSet 集合有一个 iterator 方法会返回一个 Iterator 对象,该方法采用了 “fail-fast
* 机制”即“错误机制”:如果在 iterator 被创建之后的任何时间里对该集合进行修改操作,除了使用
* iterator 自己的 remove 方法外,都会抛出一个 “ConcurrentModificationException” 异
* 常。因此,面对并发修改,iterator 会快速失败且不影响数据,
*
*
Note that the fail-fast behavior of an iterator cannot be guaranteed
* as it is, generally speaking, impossible to make any hard guarantees in the
* presence of unsynchronized concurrent modification. Fail-fast iterators
* throw ConcurrentModificationException on a best-effort basis.
* Therefore, it would be wrong to write a program that depended on this
* exception for its correctness: the fail-fast behavior of iterators
* should be used only to detect bugs.
*
* 翻译:注意,iterator 的 fail-fast 行为无法像他说的那样具有保证性,且更不可能在非线程安全
* 的并发修改情况下保证,而抛出 ConcurrentModificationException 异常则是它尽可能发挥作用的
* 基本。因此,依赖该此异常来进行某些程序的维护与矫正会是一个不明智的选择:所以它仅被用于发
* 现某些问题。
*
*
This class is a member of the
*
* Java Collections Framework.
*
* @param the type of elements maintained by this set
*
* @author Josh Bloch
* @author Neal Gafter
* @see Collection
* @see Set
* @see TreeSet
* @see HashMap
* @since 1.2
*/
public class HashSet
extends AbstractSet
implements Set, Cloneable, java.io.Serializable
{
// Some Codes...
}
1、元素去重
/**
* 通过该构造函数创建一个新的HashSet实例,同时传入 Collction 或其子类引用比如List集合,即可将
* 数据去重, 比如 Set aSet = new HashSet<>( aList );
*
*/
public HashSet(Collection extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
2、取出 Map 的所有 Key 时,Map#keySet();
3、HashSet 拥有 Map 与 Collection 两个接口的特性,因为它的父类接口继承了 Collection 接口,同时元素存取又受 HashMap 的支持,所以可以根据项目要求来考量要不要使用 HashSet
首先,HashSet 底层的实现方式前面也讲过是受 HashMap 的支持,那这就奇怪了——Set 不是 KVP(key-value pairs) 数据类型,那么 HashMap 是如何支持这个集合来存储元素并对元素进行相关操作呢?
/**
* Constructs a new, empty set; the backing HashMap instance has
* default initial capacity (16) and load factor (0.75).
* 翻译: 创建一个新且为空的Set实例; 支持该集合的 HashMap 实例拥有默认16的容量以及
* 0.75的加载因子。换句话说就是创建了一个新的 HashMap 集合
*/
public HashSet() {
map = new HashMap<>();
}
/**
* 用来代为存储 HashSet 元素的 map,Key(E)就是我们
* 往Set集合添加的元素,Value(Object)则是它下面那个
* 成员变量,private static final Object PRESENT = new Object();
* 也是“Dummy value”,即“哑值”的意思
*/
private transient HashMap map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// some code
}
public static void main(String[] args) {
HashSet set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c");
set.add("d");
printBackingMapInstanceOfHashSet(set);
}
private static void printBackingMapInstanceOfHashSet(HashSet set) {
Class extends HashSet> aClass = set.getClass();
try {
Field map = aClass.getDeclaredField("map");
map.setAccessible(true);
@SuppressWarnings("unchecked")
HashMap mapField = (HashMap)map.get(set);
Field present = aClass.getDeclaredField("PRESENT");
present.setAccessible(true);
Object presentField = present.get(mapField);
mapField.forEach((k, v) -> {
System.out.println(v + " 是否等于 " + presentField + " ? => " + v.equals(presentField));
});
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
java.lang.Object@6f539caf 是否等于 java.lang.Object@6f539caf ? => true
java.lang.Object@6f539caf 是否等于 java.lang.Object@6f539caf ? => true
java.lang.Object@6f539caf 是否等于 java.lang.Object@6f539caf ? => true
java.lang.Object@6f539caf 是否等于 java.lang.Object@6f539caf ? => true
1、最直接的原因是:
- HashMap 底层数据结构是数组+链表+红黑树(java 1.8版本),如果不做特殊处理,无法通过索引直接获取元素,且索引一般作用于线性搜索,比如数组。
- 如果使用 HashMap 的 #get 方法获取元素,那么 Map#get 方法就失去了它通过 key 来寻找配偶的意义了。
- 问这个问题时,也许应该思考为何不使用 ArrayList 或者 HashMap 呢,偏偏选择这个底层用 HashMap 实现,而自己的父类却继承了 Collection 接口的 “四不像”,当然 HashSet 的设计自然有它的优势。
2、其次:
- 设计 HashSet 是为了过渡 Map 和 Collection,也就是说为了让这两种数据结构有一个更好的交流,同时又兼顾去重的作用,HashSet 才被设计而诞生,这样一来,拥有自己独特的获取元素方式势必要增加额外的代码来对HashMap实例进行操作,比如获取HashMap的 table 数组,然后获取相应的key或value, 。
- 作为一种去重工具而被设计诞生,Map 集合存储相同 key 的键值对时,会把原来的同样 key 的值给覆盖,因此 map 集合的 key 永远都不会重复,后来这一特性就被 java 设计师利用,最终在不耗费额外工程的前提下,HashSet 诞生了。