前言
书接上文,上一篇中对链表最终实现类 LinkedList 做了分析,至此 Java 8 所有业界最佳实现的链表最终实现类都以分析完毕,本篇开始将对 Set 数据结构进行分析。
还是先来看下数据结构总继承关系图与 Set 部分的继承关系图:
其中 LinkedEntrySet 与 LinkedKeySet 是 HashMap 中的子类,由于也是继承自 AbstractSet,所以放在这里一并介绍。
Set
/**
* 一种不包含重复元素的数据结构。更准确的说,套数据结构中不会包含 e1.equals(e2) 的元素,并且至多有一个
* null 元素。正如它名字中隐含的意思,这个接口的抽象了数学概念中“套”的模型。
*
* 套接口定义了附加的预定,超过哪些继承自 Collection 的接口,在所有构造器的约定以及 add 方法,equals 方法
* 和 hashCode 方法的约定上。方便起见,其他继承方法的定义同样被包含在这里。(这些定义的特定完成被定制到了套
* 接口,但是它们不包含任何附加的约定。)
*
* 构造器的附加约定是,不出意料的,所有的构造器必须创建一个包含不重复元素的套(就像上面定义的)。
*
* 注意:需要着重注意如果可变的对象用作套元素的情况。当元素在套中时,如果一个可变对象的值被通过会影响 equals
* 比较结果的方式修改了,套的行为不会被详细说明。
*
* 一些套的实现类限制了它们可以包含的元素。比如,一些实现类禁止 null 元素,还有一些限制了它们元素的类型。尝试
* 添加一个不合规的元素将抛出一个未检查异常,通常是 NullPointerException 或者 ClassCastException。尝试
* 访问一个不合规的元素将抛出一个异常,或者将简单的返回 false,一些实现类将表现出前者行为,而一些将是后者。更
* 普遍的说,尝试在一个不会熬制掺入结果的不合规的元素上的操作将抛出一个异常,或者它会成功,取决于实现类的选
* 择。这种异常在接口定义中被标记为”可选择的“
*/
public interface Set<E> extends Collection<E> {
// 查询操作
/**返回这个套中的元素个数(它的基数)。如果这个套包含大于 Integer.MAX_VALUE 个元素,返回 Integer.MAX_VALUE**/
int size();
/**判断是否套中不包含元素**/
boolean isEmpty();
/**判断套中是否包含与指定参数相同的元素**/
boolean contains(Object o);
/**返回一个涵盖所有套中元素的串行迭代器。元素将不以任何顺序返回(除非这个套是一个一些提供了这种保证的类的实例),注意这里就和链表不同,链表中的迭代器都是保证顺序的,套中的迭代器不保证顺序**/
Iterator<E> iterator();
/**返回一个涵盖套中所有元素的数组。如果这个套保证了任何顺序,将通过它的迭代器返回,这个方法必须以相同的顺序返回元素,显然无论是 ArrayList 的 arraycopy 还是 LinkedList 的 Node next 都不再适合**/
Object[] toArray();
/**
* 返回一个涵盖套中所有元素的数组,返回的运行时类型与参数数组的范型类型相同。如果数组大小合适,直接在其中 * 返回。否则,一个新的与运行时类型相同的数组将被分配,长度为这个套的长度。
*
* 如果这个套适合指定的数组参数的空间(比如,数组有大于这个套的元素个数的元素),数组中的元素在套之后的元 * 素将被置为 null。(这在调用者知道这个套不包含 null 元素的时候定义这个套的长度的时候是有用的。)
*
* 如果这个套做了任何它的迭代器返回元素的顺序保证,这个方法将必须以相同的顺序返回元素。
*
* 就像 toArray() 方法,这个方法以类似基于属于和基于数据结构之间的 API 的方式运行。并且,这个方法允许
* 对于返回数组的运行时类型的精确掌控,而且或许,在某些情况下,被用来存放分配花费。
*
* 试想 x 是一个只能用来存放 String 的套。以下的代码可以用来抛弃套并将它的元素重新分配到一个 String
* 数组:
* <pre>
* String[] y = x.toArray(new String[0]);</pre>
*
* 注意 toArray(new Object[0]) 与 toArray() 是完全一样的
*/
<T> T[] toArray(T[] a);
// 修改操作
/**
* 向套中添加一个指定元素,如果它还不存在(可选操作)。进一步说,如果套中不包含 e.eqauls(e2) 的指定元
* 素,那么就添加,如果这个套已经包含了这种元素,调用将不对套做任何改动并且返回 false。作为限制与约定的
* 组合时,这保证了套永远不会包含重复的元素。
*
* 以上约定不暗示套必须接收所有元素,套可能拒绝添加任何不寻常的元素,包括 null,并且抛出一个异常,就是
* Collection#add 中定义的那样。独立的套实现类应该明确指出任何它们可以包含的元素的限制
*/
boolean add(E e);
/**从套中移除与指定元素相同的元素,如果它存在的话(可选操作)**/
boolean remove(Object o);
// 扩展操作
/**如果套包含指定组数据结构元素中的所有原色,则返回 true**/
boolean containsAll(Collection<?> c);
/**向套添加指定组数据结构中的所有元素,如果它们并没有已经存在(可选操作)。如果指定元素也是一个套,addAll 操作的高效修改这个套让它的值是两个套中唯一的。如果这个指定容器在操作过程中被修改了,那么这个行为是不可定义的**/
boolean addAll(Collection<? extends E> c);
/**只保留在指定容器中存在的套中的元素(可选操作)。换句话说,提出所有不包含在指定容器中的参数。如果指定容器也是一个套,这个操作将将高效的修改这个套使它的值是这两个套的交集**/
boolean retainAll(Collection<?> c);
/**在这个套中移除指定容器中的所有元素(可选操作)。如果指定容器也是一个套,这个操作将高效的修改这个套使它的值是是两个套的不对等不同(asymmetric set difference 不清楚如何翻译。。)**/
boolean removeAll(Collection<?> c);
/**移除套中的所有元素(可选操作)。调用完成后这个套将为空**/
void clear();
// 比较和哈希
/**Set 对 equals 方法的重写,比较指定参数与当前套是否相等。如果指定参数也是一个套,两个套的长度相等,并且每个指定套中的元素被包含在当前套中(或者等于,每个当前套中的元素都包含在指定套中)时,返回 true。这个定义保证了 equals 方法在不同的 Set 实现类中能正常工作**/
boolean equals(Object o);
/**Set 对 hashCode 方法的重写,返回这个套的哈希值。一个套的哈希值是由其中的元素的哈希值之和定义的,一个为 null 的元素的哈希值定义为 0。这保证了对于任何两个套, s1.equals(s2) 隐式的包含了 s1.hashCode() 等于 s2.hashCode 被用作一般的 Object#hashCode 的约定**/
int hashCode();
/**创建一个涵盖套中所有元素的并写迭代器**/
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT);
}
}
可以看到这个接口与 List 接口基本相同,甚至还简单了很多,List 中因为要提供链表通过下标访问的特性所以还额外提供了许多通过下标进行操作的方法,这些方法在 Set 中是没有的。
然后再来看下 Set 接口的抽象实现类 AbstractSet
/**
* 这个类提供了一个 Set 接口的实现框架来最小化实现这个类所需要的时间
*
* 通过继承这个类来实现一个 Set 的步骤与通过继承 AbtractCollection 实现一个一个
* Collection 是相同的,除了在这个类的子类中所有方法和构造器必须遵守额外的 Set接口强制
* 的规约(对于实例,add 方法必须不容许多个相同对象实例加入到一个套中)。
*
* 注意这个类不重写任何 AbstractCollection 类中的实现。它仅仅是添加了 equals 和
* hashCode 方法的实现
*/
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
/**
* 唯一构造器(通常是隐式的被被子类构造器调用。)
*/
protected AbstractSet() {
}
// 比较和哈希
/**
* 比较指定对于与这个套的相等行。如果银锭对象也是一个套,两个套的长度相同,并且每个给
* 定套中的元素被包含在这个套中,就返回 true。者保证了在不同的 Set 接口实现类中
* equals 方法都能正常运作。
*
* 这个实现第一次不检查是否指定的对象在这个套中,如果是就返回 true。然后它将产是否指
* 定的对象是一个与当前套长度相等的套。如果是,他返回 containAll((Collection) o)
*/
public boolean equals(Object o) {
if (o == this) //如果 o 就是 当前套
return true;
if (!(o instanceof Set)) //如果 o 不是套的实例
return false;
Collection<?> c = (Collection<?>) o; //强转 o,缓存给 c
if (c.size() != size()) //如果 c 的长度不等于当前套长度
return false;
try {
return containsAll(c); //调用 containsAll 方法
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
/**
* 返回这个套的 hash 值。一个套的 hash 值被定义为套中元素的 hash 值之和,而一个 null 元素的 hash code 被定义为 0。
*
* 这保证了对于任意两个套 s1 与 s2, s1.equals(s2) 隐式包含了 s1.hashCode()
* == s2.hashCode(),就如 Object#hashCode 需要的一半规约。
*
* 这个实现类迭代套,在套中的每个元素上调用 hashCode 方法,并且累加得到结果。
*/
public int hashCode() {
int h = 0;
Iterator<E> i = iterator(); //获得迭代器
while (i.hasNext()) { //判断游标之后是否还有值
E obj = i.next(); //获得游标后面位置的值,累加游标
if (obj != null)
h += obj.hashCode(); //累加元素的 hash code
}
return h;
}
/**
* 从当前套中移除指定 collection 中包含的所有的元素(可选操作)。如果指定
* collection 也是一个套,这个操作高效地修改了这个 set,所以它的值是这两个套的不对
* 称集差。
*
* 这中实现决定了这个套与指定套中哪个是更小的。如果这个套中元素更少,那么实现类迭代这
* 个套,依次检查每个迭代器返回的元素来查看它是否包含在指定套中。如果它套喊了,那么它
* 就被通过当前迭代器的 remove 方法移除。如果指定的 collection 元素更少,那么实现
* 类就迭代指定套,从当前套中移除每个迭代器返回的元素,使用当前套的 remove 方法。
*
* 注意如果 iterator 方法返回的迭代器没有实现 remove 方法的话,这个实现将会抛出一
* 个 UnsupportedOperationException
*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false; //更改标志位缓存为 false
if (size() > c.size()) { //如果当前套的长度大于参数
/**获取 c 的迭代器,步移判断游标之后是否还有值**/
for (Iterator<?> i = c.iterator(); i.hasNext(); )
modified |= remove(i.next()); //如果有,获取游标后面的元素,累加游标,调用当前套的移除方法,与 modified 取或后赋值给 modified
} else { //如果指定套的长度大于当前套
/**获取当前套的迭代器,步移判断游标之后是否还有值**/
for (Iterator<?> i = iterator(); i.hasNext(); ) {
if (c.contains(i.next())) { //如果有,获取游标后面的元素,累加游标,调用指定套的 contains 方法判断指定套中是否包含元素
i.remove(); //调用当前套迭代器的移除方法
modified = true; //赋值 modified 为 true
}
}
}
return modified; //返回 modified
}
}
从 AbstractSet 的源码中可以看到,它中重写了 equals,hashCode 与 removeAll 三个方法,前两个方法是为了保证 Object#euqals 在套中的相同语义,第三个方法就有点奇妙了,与 ArrayList 实现的 removeAll 方法完全不同,首先判断两个套谁更长,如果当前套更长,那么获取指定套的迭代器,最终调用的是 当前套的 remove 方法,如果指定套更长,那么获取当前套的迭代器,最终爹奥用的是***当前套迭代器的 remove 方法***。
以上就是对 Set 与 AbstractSet 的介绍与分析。下一篇中将对 HashSet 进行介绍与分析。