Java 容器类源码详解 Set

前言

Set 表示由无重复对象组成的集合,也是集合框架中重要的一种集合类型,直接扩展自 Collection 接口。在一个 Set 中,不能有两个引用指向同一个对象,或两个指向 null 的引用。如果对象 a 和 b 的引用满足条件 a.equals(b),那么这两个对象也不能同时出现在集合中。

通常 Set 是不要求元素有序的,但也有一些有序的实现,如 SortedMap 接口、LinkedHashSet 接口等。

概述

Set 的具体实现通常都是基于 Map 的。因为 Map 中键是唯一的,因而在基于 Map 实现 Set 时,只需要关心 Map 中的键,和键关联的值不需要有意义,使用一个任意的对象“占位”即可。我们在前面分析 Map 中的迭代器时,KeySet() 方法得到的就是一个 Set。

前面我们分析过 Map 接口的几个具体实现,通用的实现 HahsMap ,插入或访问序的 LinkedHashMap , 按照键升序的 TreeMap。同样,在 Set 的具体实现中,也有 HashSet 、 LinkedHashSet 和 TreeSet 等,分别和 Map 一一对应,它们的特性对应着相应的 Map 实现的特性。下面基于 HashSet 的实现做一个简略的介绍。

HashSet 的实现

public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
}

从成员变量和构造方法可以清楚地看到,内部使用了一个 HahsMap,同时定义了一个无意义的空的静态 Object 对象(占用8byte) PRESENT。既然 map 中和键关联的值没有意义,为什么不干脆使用 null 呢?我们看一下 add() 方法:

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

Map 的 put() 方法在添加一个新的键时会返回 null,在更新一个已经存在的键关联的值时会返回旧值。因而 Set 中的 add() 方法可以据此判断新加入的元素是否改变了集合,如果改变了就返回 true。因而 PRESENT 不可以使用 null 。

其它的方法这里简单地列一下,都是基于 map 实现的:

public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public void clear() {
map.clear();
}
@SuppressWarnings("unchecked")
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
//序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());
// Write out size
s.writeInt(map.size());
// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}
// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}
// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}
// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

小结

Set 的内部通常是基于 Map 来实现的,Map 中的 Key 构成了 Set,而 Value 全部使用一个无意义的 Object 。

Set 的特征与其内部的 Set 的特征是一致的。基于 HashMap 的 HashSet 是无序时的最佳通用实现,基于 LinkedHashMap 的 LinkedHashSet 保留插入或访问的顺序,基于 TreeMap 的 TreeSet 可以按照元素升序排列,要求元素实现 Comaprable 接口或自定义比较器。

HashSet , LinkedHashSet, TreeSet 都不是线程安全的,在多线程环境下使用时要注意同步问题。

CopyOnWriteArraySet 是一个线程安全的实现,但是并不是基于 Map 实现的,而是通过 CopyOnWriteArrayList 实现的。使用 addIfAbsent() 方法进行去重,性能比较一般。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • spring boot基于Java的容器配置讲解

    spring容器是负责实例化.配置.组装组件的容器. 容器的配置有很多,常用的是xml.Java注解和Java代码. 在spring中Ioc容器相关部分是context和beans中.其中context-support保存着许多线程的容器实现.比如AnnotationConfigApplicationContext或者ClassPathXmlApplicationContext.两者只有接收的目标不同,前者接收Java类后者接收Xml文件.但作为spring容器的不同实现殊途同归. 下面我通过s

  • 基于spring-boot和docker-java实现对docker容器的动态管理和监控功能[附完整源码下载]

    docker简介 Docker 是一个开源的应用容器引擎,和传统的虚拟机技术相比,Docker 容器性能开销极低,因此也广受开发者喜爱.随着基于docker的开发者越来越多,docker的镜像也原来越丰富,未来各种企业级的完整解决方案都可以直接通过下载镜像拿来即用.因此docker变得越来越重要. 本文目的 本文通过一个项目实例来介绍如果通过docker对外接口来实现对docker容器的管理和监控. 应用场景: 对服务器资源池通过docker进行统一管理,按需分配资源和创建容器,达到资源最大化利

  • Java从同步容器到并发容器的操作过程

    引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线程安全的,即在多线程的环境下,都需要其他额外的手段来保证数据的正确性,最简单的就是通过synchronized关键字将所有使用到非线程安全的容器代码全部同步执行.这种方式虽然可以达到线程安全的目的,但存在几个明显的问题:首先编码上存在一定的复杂性,相关的代码段都需要添加锁.其次这种一刀切的做法在高并发情况下性

  • Java容器ArrayList知识点总结

    ArrayList 底层实现是数组,访问元素效率高 (查询快,插入.修改.删除元素慢) 与LinkedList相比,它效率高,但线程不安全. ArrayList数组是一个可变数组,可以存取包括null在内的所有元素 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小 随着向ArrayList中不断增加元素,其容量自动增长 在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这样可以减少递增式再分配的数量. 所以如果我

  • Java容器类源码详解 Deque与ArrayDeque

    前言 Queue 也是 Java 集合框架中定义的一种接口,直接继承自 Collection 接口.除了基本的 Collection 接口规定测操作外,Queue 接口还定义一组针对队列的特殊操作.通常来说,Queue 是按照先进先出(FIFO)的方式来管理其中的元素的,但是优先队列是一个例外. Deque 接口继承自 Queue接口,但 Deque 支持同时从两端添加或移除元素,因此又被成为双端队列.鉴于此,Deque 接口的实现可以被当作 FIFO队列使用,也可以当作LIFO队列(栈)来使用

  • 详解JavaFX桌面应用开发-Group(容器组)

    1:Group的功能 Group可以管理一组节点 Group可以对管理的节点进行增删改查的操作 Group可以管理节点的属性 1.2:看看JDKSE1.9的API Group类有下列可以调用的方法 2:Group的使用 代码如下: package application; import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.

  • Java同步容器和并发容器详解

    同步容器 在 Java 中,同步容器主要包括 2 类: Vector.Stack.HashTableCollections 类中提供的静态工厂方法创建的类(由 Collections.synchronizedXxxx 等方法) Collections类中提供的静态工厂方法创建的类 Vector 实现了 List 接口,Vector 实际上就是一个数组,和 ArrayList 类似,但是Vector 中的方法都是 synchronized 方法,即进行了同步措施. Stack 也是一个同步容器,它

  • Java 容器类源码详解 Set

    前言 Set 表示由无重复对象组成的集合,也是集合框架中重要的一种集合类型,直接扩展自 Collection 接口.在一个 Set 中,不能有两个引用指向同一个对象,或两个指向 null 的引用.如果对象 a 和 b 的引用满足条件 a.equals(b),那么这两个对象也不能同时出现在集合中. 通常 Set 是不要求元素有序的,但也有一些有序的实现,如 SortedMap 接口.LinkedHashSet 接口等. 概述 Set 的具体实现通常都是基于 Map 的.因为 Map 中键是唯一的,

  • java LinkedList源码详解及实例

    一.LinkedList概述: LinkedList与ArrayList一样,是实现了List接口.由于LinkedList是基于链表实现的,所以它执行插入和删除操作时比ArrayList更高效,而随机访问的性能要比ArrayList低. 二.LinkedList的实现: 1.构造方法 //构造一个空的LinkedList public LinkedList() { } //接收一个Collection参数c,默认构造方法构造一个空的链表,并通过addAll将c中的元素全部添加到链表中. pub

  • java  LinkedList源码详解及实例

    一.LinkedList概述: LinkedList与ArrayList一样,是实现了List接口.由于LinkedList是基于链表实现的,所以它执行插入和删除操作时比ArrayList更高效,而随机访问的性能要比ArrayList低. 二.LinkedList的实现: 1.构造方法 //构造一个空的LinkedList public LinkedList() { } //接收一个Collection参数c,默认构造方法构造一个空的链表,并通过addAll将c中的元素全部添加到链表中. pub

  • Java并发编程之ConcurrentLinkedQueue源码详解

    一.ConcurrentLinkedQueue介绍 并编程中,一般需要用到安全的队列,如果要自己实现安全队列,可以使用2种方式: 方式1:加锁,这种实现方式就是我们常说的阻塞队列. 方式2:使用循环CAS算法实现,这种方式实现队列称之为非阻塞队列. 从点到面, 下面我们来看下非阻塞队列经典实现类:ConcurrentLinkedQueue (JDK1.8版) ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全的队列.当我们添加一个元素的时候,它会添加到队列的尾部,当我们

  • Android实现屏幕锁定源码详解

    最近有朋友问屏幕锁定的问题,自己也在学习,网上找了下也没太详细的例子,看的资料书上也没有有关屏幕锁定程序的介绍,下个小决心,自己照着官方文档学习下,现在做好了,废话不多说,先发下截图,看下效果,需要注意的地方会加注释,有问题的朋友可以直接留言,我们共同学习交流,共同提高进步!直接看效果图: 一:未设置密码时进入系统设置的效果图如下: 二:设置密码方式预览: 三:密码解密效果图 四:九宫格解密时的效果图 下面来简单的看下源码吧,此处讲下,这个小DEMO也是临时学习下的,有讲的不明白的地方请朋友直接

  • Android线程间通信Handler源码详解

    目录 前言 01. 用法 02.源码 03.结语 前言 在[Android]线程间通信 - Handler之使用篇主要讲了 Handler 的创建,发送消息,处理消息 三个步骤.那么接下来,我们也按照这三个步骤,从源码中去探析一下它们具体是如何实现的.本篇是关于创建源码的分析. 01. 用法 先回顾一下,在主线程和非主线程是如何创建 Handler 的. //主线程 private val mHandler: Handler = object : Handler(Looper.getMainLo

  • Android开发数据结构算法ArrayList源码详解

    目录 简介 ArrayList源码讲解 初始化 扩容 增加元素 一个元素 一堆元素 删除元素 一个元素 一堆元素 修改元素 查询元素 总结 ArrayList优点 ArrayList的缺点 简介 ArrayList是List接口的一个实现类,它是一个集合容器,我们通常会通过指定泛型来存储同一类数据,ArrayList默认容器大小为10,自身可以自动扩容,当容量不足时,扩大为原来的1.5倍,和上篇文章的Vector的最大区别应该就是线程安全了,ArrayList不能保证线程安全,但我们也可以通过其

  • Spring AOP底层源码详解

    ProxyFactory的工作原理 ProxyFactory是一个代理对象生产工厂,在生成代理对象之前需要对代理工厂进行配置.ProxyFactory在生成代理对象之前需要决定到底是使用JDK动态代理还是CGLIB技术. // config就是ProxyFactory对象 // optimize为true,或proxyTargetClass为true,或用户没有给ProxyFactory对象添加interface if (config.isOptimize() || config.isProxy

  • Java8中AbstractExecutorService与FutureTask源码详解

    目录 前言 一.AbstractExecutorService 1.定义 2.submit 3.invokeAll 4.invokeAny 二.FutureTask 1.定义 2.构造方法 3.get 4.run/ runAndReset 5. cancel 三.ExecutorCompletionService 1.定义 2.submit 3.take/ poll 总结 前言 本篇博客重点讲解ThreadPoolExecutor的三个基础设施类AbstractExecutorService.F

随机推荐