Java的Set集合,你真的会用吗?HashSet/TreeSet/LinkedHashSet

开发 前端
HashSet、LinkedHashSet、TreeSet,这三个常用的Set集合的共同点是都实现了Set接口,所以使用方式都是一样的,使用add()​方法添加元素,使用remove()​删除元素,使用contains()​方法判断元素是否存在,使用iterator()方法迭代遍历元素,这三个类都可以去除重复元素。

引言

当我们需要对元素去重的时候,会使用Set集合,可选的Set集合有三个,分别是HashSet、LinkedHashSet、TreeSet,这三个常用的Set集合有什么区别呢?底层实现原理是什么样?这篇文章一起来深度剖析。

共同点这三个类都实现了Set接口,所以使用方式都是一样的,使用add()方法添加元素,使用remove()删除元素,使用contains()方法判断元素是否存在,使用iterator()方法迭代遍历元素,这三个类都可以去除重复元素。

特性

  1. HashSet是最基础的Set集合,可以去除重复元素,元素存储是无序的。
  2. LinkedHashSet在HashSet功能基础上,增加了按照元素插入顺序或者访问顺序的迭代方式。
  3. TreeSet在HashSet功能基础上,可以保证按照元素大小顺序排列。

底层实现

  1. HashSet是基于HashMap实现的,使用组合的方式,并非继承。
  2. LinkedHashSet继承自HashSet,而内部则是采用组合LinkedHashMap的方式实现的。[流汗] 就是这么乱,一会儿看一下源码就明白了。
  3. TreeSet是基于TreeMap实现的,采用组合的方式,跟上面两个Set集合没关系。

图片图片

下面详细看一下这三个Set集合源码的底层实现:

HashSet源码实现

类属性

public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable {

    /**
     * 使用HashMap存储数据
     */
    private transient HashMap<E, Object> map;

    /**
     * value的默认值
     */
    private static final Object PRESENT = new Object();

}

可以看出HashSet实现了Set接口,内部采用HashMap存储元素,利用了HashMap的key不能重复的特性,实现元素去重。而value使用默认值,是一个空对象,没有任何作用,纯粹占坑。

初始化

HashSet常用的构造方法有两个,有参构造方法,可以指定初始容量和负载系数。

/**
 * 无参构造方法
 */
HashSet<Integer> hashSet1 = new HashSet<>();

/**
 * 有参构造方法,指定初始容量和负载系数
 */
HashSet<Integer> hashSet = new HashSet<>(16, 0.75f);

再看一下构造方式对应的源码实现:

/**
 * 无参构造方法
 */
public HashSet() {
    map = new HashMap<>();
}

/**
 * 有参构造方法,指定初始容量和负载系数
 */
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}

HashSet的构造方式源码也很简单,都是利用的HashMap的构造方法实现。

常用方法源码

再看一下HashSet常用方法源码实现:

/**
 * 添加元素
 */
public boolean add(E e) {
    return map.put(e, PRESENT) == null;
}

/**
 * 删除元素
 */
public boolean remove(Object o) {
    return map.remove(o) == PRESENT;
}

/**
 * 判断是否包含元素
 */
public boolean contains(Object o) {
    return map.containsKey(o);
}

/**
 * 迭代器
 */
public Iterator<E> iterator() {
    return map.keySet().iterator();
}

HashSet方法源码也很简单,都是利用HashMap的方法实现逻辑。利用HashMap的key不能重复的特性,value使用默认值,contains()方法和iterator()方法也都是针对key进行操作。

LinkedHashSet源码实现

类属性

LinkedHashSet继承自HashSet,没有任何私有的属性。

public class LinkedHashSet<E>
        extends HashSet<E>
        implements Set<E>, Cloneable, java.io.Serializable {
}

初始化

LinkedHashSet常用的构造方法有三个,有参构造方法,可以指定初始容量和负载系数。

/**
 * 无参构造方法
 */
Set<Integer> linkedHashSet1 = new LinkedHashSet<>();

/**
 * 有参构造方法,指定初始容量
 */
Set<Integer> linkedHashSet2 = new LinkedHashSet<>(16);

/**
 * 有参构造方法,指定初始容量和负载系数
 */
Set<Integer> linkedHashSet3 = new LinkedHashSet<>(16, 0.75f);

再看一下构造方法的源码实现:

/**
 * 无参构造方法
 */
public LinkedHashSet() {
    super(16, .75f, true);
}

/**
 * 有参构造方法,指定初始容量
 */
public LinkedHashSet() {
    super(16, .75f, true);
}

/**
 * 有参构造方法,指定初始容量和负载系数
 */
public LinkedHashSet(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor, true);
}

LinkedHashSet的构造方法使用的是父类HashSet的构造方法,而HashSet的构造方法使用的是LinkedHashMap的构造方法,设计的就是这么乱!

public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable {

    /**
     * HashSet的构造方法,底层使用的是LinkedHashMap,专门给LinkedHashSet使用
     *
     * @param initialCapacity 初始容量
     * @param loadFactor      负载系数
     * @param dummy           这个字段没啥用
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

}

LinkedHashSet的其他方法也是使直接用的父类HashSet的方法,就不用看了。

LinkedHashSet额外实现了按照元素的插入顺序或者访问顺序进行迭代的功能,是使用LinkedHashMap的实现,不了解LinkedHashMap的,可以看一下上篇文章对LinkedHashMap的源码解析。

TreeSet源码实现

类属性

public class TreeSet<E> extends AbstractSet<E>
        implements NavigableSet<E>, Cloneable, java.io.Serializable {

    /**
     * 用来存储数据
     */
    private transient NavigableMap<E, Object> m;

    /**
     * value的默认值
     */
    private static final Object PRESENT = new Object();

}

TreeSet内部使用NavigableMap存储数据,而NavigableMap是TreeMap的父类,后面在初始化NavigableMap的时候,会用TreeMap进行替换。而value使用默认空对象,与HashSet类似。

初始化

TreeSet有两个构造方法,有参构造方法,可以指定排序方式,默认是升序。

/**
 * 无参构造方法
 */
TreeSet<Integer> treeSet1 = new TreeSet<>();

/**
 * 有参构造方法,传入排序方式,默认升序,这里传入倒序
 */
TreeSet<Integer> treeSet2 = new TreeSet<>(Collections.reverseOrder());

再看一下构造方法的源码实现:

TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}

/**
 * 无参构造方法
 */
public TreeSet() {
    this(new TreeMap<E, Object>());
}

/**
 * 有参构造方法,传入排序方式,默认升序,这里传入倒序
 */
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

TreeSet的构造方法内部是直接使用的TreeMap的构造方法,是基于TreeMap实现的。

常用方法源码

/**
 * 添加元素
 */
public boolean add(E e) {
    return m.put(e, PRESENT) == null;
}

/**
 * 删除元素
 */
public boolean remove(Object o) {
    return m.remove(o) == PRESENT;
}

/**
 * 判断是否包含元素
 */
public boolean contains(Object o) {
    return m.containsKey(o);
}

/**
 * 迭代器
 */
public Iterator<E> iterator() {
    return m.navigableKeySet().iterator();
}

TreeSet常用方法的底层实现都是使用的TreeMap的方法逻辑,就是这么偷懒。

TreeSet可以按元素大小顺序排列的功能,也是使用TreeMap实现的,感兴趣的可以看一下上篇文章讲的TreeMap源码。由于TreeSet可以元素大小排列,所以跟其他Set集合相比,增加了一些按照元素大小范围查询的方法。

其他方法列表:

作用

方法签名

获取第一个元素

E first()

获取最后一个元素

E last()

获取大于指定键的最小键

E higher(E e)

获取小于指定键的最大元素

E lower(E e)

获取大于等于指定键的最小键

E ceiling(E e)

获取小于等于指定键的最大键

E floor(E e)

获取并删除第一个元素

E pollFirst()

获取并删除最后一个元素

E pollLast()

获取前几个元素(inclusive表示是否包含当前元素)

NavigableSetheadSet(E toElement, boolean inclusive)

获取后几个元素(inclusive表示是否包含当前元素)

NavigableSettailSet(E fromElement, boolean inclusive)

获取其中一段元素集合(inclusive表示是否包含当前元素)

NavigableSetsubSet(E fromElement, boolean fromInclusive, E toElement,   boolean toInclusive)

获取其中一段元素集合(左开右开)

SortedSetsubSet(E fromElement, E toElement)

获取前几个元素(不包含当前元素)

SortedSetheadSet(E toElement)

获取后几个元素(不包含当前元素)

SortedSettailSet(E fromElement)

总结

HashSet、LinkedHashSet、TreeSet,这三个常用的Set集合的共同点是都实现了Set接口,所以使用方式都是一样的,使用add()方法添加元素,使用remove()删除元素,使用contains()方法判断元素是否存在,使用iterator()方法迭代遍历元素,这三个类都可以去除重复元素。

不同点是:HashSet的关键特性:

  1. 是最基础的Set集合,可以去除重复元素。
  2. HashSet是基于HashMap实现的,使用组合的方式,并非继承。
  3. 利用了HashMap的key不重复的特性,而value是一个默认空对象,其他方法也都是使用HashMap实现。

LinkedHashSet的关键特性:

  1. LinkedHashSet继承自HashSet,而内部则是采用组合LinkedHashMap的方式实现的。
  2. LinkedHashSet在HashSet功能基础上,增加了按照元素插入顺序或者访问顺序的迭代方式,代价是额外增加一倍的存储空间。
  3. 方法内部都是使用LinkedHashMap实现的。

TreeSet的关键特性:

  1. TreeSet是基于TreeMap实现的,也是采用组合的方式。
  2. TreeSet在HashSet功能基础上,可以保证按照元素大小顺序排列,代价是查询、插入、删除接口的时间复杂度从O(1)退化到O(log n)。
  3. 方法内部都是使用TreeMap实现的。
责任编辑:武晓燕 来源: 一灯架构
相关推荐

2018-09-29 15:34:34

JavaList接口

2020-06-04 14:15:55

Java中BigDecimal函数

2016-05-04 10:36:42

iossdwebimage开发

2024-01-25 10:14:09

HashSetHashMapJava

2020-12-18 08:59:51

苹果iCloud储存照片

2019-07-25 12:46:32

Java高并发编程语言

2023-11-01 13:48:00

反射java

2012-05-02 10:47:51

JavaJava集合框架

2021-05-21 12:36:16

限流代码Java

2024-03-06 08:15:03

@Autowired注入方式Spring

2022-01-17 07:32:34

Java参数方法

2020-09-02 14:20:21

JavaHashSetTreeSet

2021-09-06 10:42:18

Linux命令服务器

2016-06-01 15:42:58

Hadoop数据管理分布式

2021-08-11 10:00:51

缓存MyBatis管理

2014-04-17 16:42:03

DevOps

2020-04-17 14:25:22

Kubernetes应用程序软件开发

2022-07-26 00:00:22

HTAP系统数据库

2018-11-20 09:37:19

Java内存模型

2023-05-10 11:07:18

点赞
收藏

51CTO技术栈公众号