Java 集合框架系列(三):Set 接口及其实现类

Set 接口

Set,数学意义上的集合,指的是结构数据唯一、数据存储无序、没有跟索引相关的方法,不能用普通的for循环遍历,必须使用迭代器遍历或者增强for循环遍历的数据结构。

 public interface Set<E> extends Collection<E> {
     // 集合中添加元素
     boolean add(E e);
     
     // 移除集合中的元素
     boolean remove(Object o);
     // 清空集合
     void clear();
     
     // 是否包含某个元素
     boolean contains(Object o);
     // 是否为空
     boolean isEmpty();
     
     // 返回遍历集合的迭代器
     Iterator<E> iterator();
     
     // 集合中元素个数
     int size();
     
 }

Set 接口实现类

HashSet

使用 HashMap 作为底层数据结构存放数据,HashSet 中将要存放的数据作为 HashMap 的 Key,HashMap 中的 value 为一个在 HashSet 中定义的所有 value 共享的 Object 对象。
存放数据的过程需要先计算 hash 值,在根据 hash 值和一个表达式计算元素在数组中存放的位置,这和HashMap 存放数据的过程是一致的。保存在 HashSet 中的对象一定要重写 hashcode 和 equals 方法。

  1. 类的声明和重要的字段
public class HashSet<E> extends AbstractSet<E>
     implements Set<E>, Cloneable, java.io.Serializable {
     // HashMap 保存数据
     private transient HashMap<E,Object> map;
     // HashMap 中 Entry 共享的 value 对象
     private static final Object PRESENT = new Object();
     
     // ---------------------构造方法------------------------
     public HashSet() {
         // 初始化容量为16,负载因子为0.75
         map = new HashMap<>();
     }
     public HashSet(int initialCapacity, float loadFactor) {
         // 指定初始化容量和负载因子
         map = new HashMap<>(initialCapacity, loadFactor);
     }
     // 使用 LinkedHashMap 作为实现类
     // dummy 仅仅只是来作为和其他构造方法的重载区别,没有其他意义 - 官方说明
     // 因此可以看成有 dummy 参数的就是使用 LinkedHashMap 作为底层的数据结构存储数据
     HashSet(int initialCapacity, float loadFactor, boolean dummy) {
         map = new LinkedHashMap<>(initialCapacity, loadFactor);
     }
 }
  1. 常用操作
 // 添加操作
 public boolean add(E e) {
     // HashMap第一次添加的时候返回null,如果是返回null则证明是添加成功的操作
     return map.put(e, PRESENT)==null;
 }
 // 移除操作,
 public boolean remove(Object o) {
     return map.remove(o)==PRESENT;
 }
 // HashSet 中没有获取元素的操作,只能通过迭代的方式来遍历数据
 public Iterator<E> iterator() {
     return map.keySet().iterator();
 }

HashSet 中的操作基本都是通过 HashMap 来完成的。

TreeSet

使用 TreeSet 可以保存数据唯一、并且按照升序进行排序,对 TreeSet 进行遍历也是按照排序后的顺序进行遍历的。由于其需要对数据进行排序,因此对于自定义的引用类型需要实现内部比较器或者外部比较器。否则会在编译阶段就抛出错误。
底层使用两种不同类型的数据结构作为存储结构

  • 无参构造或者比较器构造,底层使用 TreeMap,使用共享的 PRESENT 作为 value 值。
  • 传入 NavigableMap 对象,底层使用 NavigableMap。
  • 传入 SortedSet 对象,底层使用 TreeMap。
  1. 类的定义和关键字段
public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    /**
     * 可导航的 Map 结构
     */
    private transient NavigableMap<E,Object> m;

    // 共享的 value 结构
    private static final Object PRESENT = new Object();

    /**
     * 基于可导航 map 的构造方法
     */
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

    // 对于自定义的引用类型,使用 TreeMap 时要么实现内部比较器
    // 要么传入外部比较器
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

    // 仍然使用 TreeMap 数据结构作为底层的数据存储结构
    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }
}
  1. 添加元素操作
public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}

往集合中添加一个元素,添加成功返回 true,元素已经存在了返回 false。

  1. 移除操作
// 对象存在返回 true,对象不存在返回 false
public boolean remove(Object o) {
    return m.remove(o)==PRESENT;
}

/**
 * 清空 set 集合
 */
public void clear() {
    m.clear();
}
  1. 其他方法
// 返回迭代器
public Iterator<E> iterator() {
    return m.navigableKeySet().iterator();
}

// 集合大小
public int size() {
    return m.size();
}

// 集合是否为空
public boolean isEmpty() {
    return m.isEmpty();
}

// 集合是否包含某个元素
public boolean contains(Object o) {
    return m.containsKey(o);
}

LinkedHashSet

JDK 1.4 引入,和 HashSet 一个主要的区别就是存储的元素可以按照输入顺序进行输出。其实现是在 HashSet 的基础上多了一个总链表,将放入的元素串在一起,方便有序的遍历。

  1. 类的声明及关键字段
public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }

    public LinkedHashSet(int initialCapacity) {
        // 传入 true 就是让 HashSet 底层使用 LinkedHashMap
        super(initialCapacity, .75f, true);
    }
    
    // 默认的负载因子为 0.75
    public LinkedHashSet() {
        super(16, .75f, true);
    }
}

底层是使用 HashSet,LinkedHashSet 的实现并不是基于 HashSet 的包装,而是直接继承自 HashSet。同时注意到构造方法的第三个参数都是有传递参数,因此更进一步的是使用 LinkedHashMap 作为底层数据的存储结构。
由于是继承自 HashSet,相关的增删查改操作都在 HashSet 中定义了,因此源码也比较短。

总结

  1. Set 一般用来保存唯一的数据,Set 集合中的元素本身的遍历是无序的。
  2. 为了能够实现对集合本身有序遍历、排序遍历,因此分别在 Set 的基础上实现 LinkedHashSet、TreeSet。
  3. Set 结构的底层都是基于 Map 结构来实现的,要保存到 Set 中的数据结构就是作为 Map 的 key 来进行保存的,同时其 value 是共享的一个 Object 对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值