JAVA HashSet和TreeSet 保证存入元素不会重复的操作

Set是一种数据集合。它与List同样继承与Collection接口。

它与Collection接口中的方法基本一致,并没有对Collection接口进行功能进行功能上的扩充,只是比Collection接口更严格了。与List不同的是,Set中的元素是无无需的,并且都以某种规则保证存入的元素不会出现重复。

它的特点也就是:

1. 元素不会出现重复。

2. 元素是无序的。(存取无序)

3. 元素可以为空。

每种类型的Set所使用的避免元素重复的规则都是不同的,今天我们主要还是看HashSet和TreeSet:

第一种是HashSet:

HashSet

我们先来看看HashSet的构造器是怎么样的:

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();

  /**
   * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
   * default initial capacity (16) and load factor (0.75).
   */
  public HashSet() {
    map = new HashMap<>();
  }

令人惊讶的是HashSet的结构里实际上就包含了一个HashMap,而初始化HashSet就是给这个对象的Map赋值一个空HashMap对象。

再让我们来看一看插入操作:

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

add操作实际上是向map中插入了一条记录,是以我们所要存的元素为key,以一个空对象为value的记录。

到了这不实际上我们已经能明白,set里的元素是不可能重复的,因为我们对hashMap同一个key进行put,并不会生成新的记录,而是对上一条记录进行覆盖而已。但是hashMap是如何判断Key是否是同一个的呢?让我们来看以下代码

public class SetTest {

 public class Obj {
 public String name;
 public Obj(String name) {
  this.name=name;
 }
 }

 public static void main(String[] args) {
 Set<String> strSet = new HashSet<String>();
 String str1 = new String("123");
 String str2 = new String("123");
 strSet.add(str1);
 strSet.add(str2);
 System.out.println(str1 == str2);
 for(String str : strSet) {
  System.out.println(str);
 }
 Set<Obj> objSet = new HashSet<Obj>();
 Obj o1 = new SetTest().new Obj("1");
 Obj o2 = new SetTest().new Obj("1");
 objSet.add(o1);
 objSet.add(o2);
 for(Obj str : objSet) {
  System.out.println(str.name);
 }
 }
}

结果为:

false
123
1
1

那让我们继续看看,在put方法中java代码又干了什么呢?(汗,感觉我从Set讲到HashMap去了)

 public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
  }

在下一层的代码里,先对key本身进行了一个转化hash(key),这个方法的源码是:

 static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  }

判断key是否为空,如果为空就返回0,不然就对key值取hashCode并与h>>>16的值做异或操作,异或是一种位运算,在此就不做解释了。而>>>是一种位移操作, 在这个hash()方法里,实际上是生成了这个key值对应的hash值。这里做了什么计算,我准备放到另一篇博客里进行讨论,无论怎么样,我们都知道对hashmap put相同的key值,不会重复的,这个是由HashMap的机制由hashCode也就是Hash码解决的,关于HashMap的结构和具体方法,我会在另外一篇博客中单独列出。

TreeSet

当我们new 一个TreeSet的时候,实际上是创建了一个TreeMap,并将这个TreeMap赋值给了TreeSet对象的m.

 /**
   * The backing map.
   */
  private transient NavigableMap<E,Object> m;

  // Dummy value to associate with an Object in the backing Map
  private static final Object PRESENT = new Object();

  /**
   * Constructs a set backed by the specified navigable map.
   */
  TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
  }
/**
   * Constructs a new, empty tree set, sorted according to the
   * natural ordering of its elements. All elements inserted into
   * the set must implement the {@link Comparable} interface.
   * Furthermore, all such elements must be <i>mutually
   * comparable</i>: {@code e1.compareTo(e2)} must not throw a
   * {@code ClassCastException} for any elements {@code e1} and
   * {@code e2} in the set. If the user attempts to add an element
   * to the set that violates this constraint (for example, the user
   * attempts to add a string element to a set whose elements are
   * integers), the {@code add} call will throw a
   * {@code ClassCastException}.
   */
  public TreeSet() {
    this(new TreeMap<E,Object>()); // 将一个新生成的TreeMap空对象赋值给m,也就是上一方法
  }

而用这个构造器定义的TreeMap是没有指定对比器的:

 public TreeMap() {
    comparator = null;
  }

让我们来看一下TreeSet的add方法的全过程:

public boolean add(E e) {
    return m.put(e, PRESENT)==null; // 如果返回值为空则表示我们插入了一个新的元素,如果返回值为非空,则表明我们插入的元素已经存在。
  }

实际上也就是向TreeMap以你的要放入的元素为key, 空对象为value做一次put。

 public V put(K key, V value) {
    Entry<K,V> t = root; // 定义t为根节点
    if (t == null) { // 如果根节点为空
      compare(key, key); // type (and possibly null) check // 对自身做对比,如果有对比器就用对比器的规则进行对比,如果没有,就用元素自身对比的规则进行对比。为0则相等。我觉得这波其实没有意义,就是一个空的对比。

      root = new Entry<>(key, value, null); // 新建一个空的根节点
      size = 1; // 设置大小为1
      modCount++; //对0做+1
      return null; // 返回空值,表示插入成功。
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator; // 用本treeMap的对比器对cpr赋值
    if (cpr != null) { // 如果定义的对比器不为空(在TreeSet里是为空的,我们之间说到过)
      do {
        parent = t;
        cmp = cpr.compare(key, t.key);
        if (cmp < 0)
          t = t.left;
        else if (cmp > 0)
          t = t.right;
        else
          return t.setValue(value);
      } while (t != null);
    }
    else { // 如果对比器为空(在这种情况下是为空的)
      if (key == null) // 如果key为空就抛出错误
        throw new NullPointerException();
      @SuppressWarnings("unchecked")
      Comparable<? super K> k = (Comparable<? super K>) key;// 生成可比较的对象Comparable
      do {
        parent = t; 将父节点(最初是根节点)赋值给parent
        cmp = k.compareTo(t.key); //对我们要插入的key与根节点的keyj进行对比
        if (cmp < 0) // 对比后值小于0,则表示我们插入的key小于根节点的key,就让父节点往左走,并循环直至命中
          t = t.left;
        else if (cmp > 0) // 对比后值大于0,则表示我们插入的key小于根节点的key,就让父节点往右走,并循环直至命中
          t = t.right;
        else //当命中,用我们的值替换原有的值一次保证不插入重复的key,并返回替换后的对象
          return t.setValue(value);
      } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent); // 如果没有在树中命中,则新生成一个树节点此时parent的父节点已经遍历到了某个叶子节点。
    if (cmp < 0) // 如果你的这个值是小于叶子节点的,则插入左边,大于则插入右边
      parent.left = e;
    else
      parent.right = e;
    fixAfterInsertion(e); // 对整棵树做平衡修正
    size++; // size值加1表示我们插入了一个值
    modCount++; // modCount也加1
    return null;
  }

整个过程就是:

1. 先查看根节点是否存在,如果不存在就直接吧这个节点放在根节点上。

2. 如果根节点存在就依顺序向下查找,如果找到对应的节点,就把该节点的值替换。

3. 如果遍历到了叶子节点仍然没有命中,那么就向叶子节点插入一个子节点,小就在左边大就在右边。

因为TreeSet插入的值都是空对象,只有key是有效的,key又是相等就覆盖,所以不会重复

以上这篇JAVA HashSet和TreeSet 保证存入元素不会重复的操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java HashSet集合存储遍历学生对象代码实例

    由于Set集合是不存储重复元素的,所以在做此案例时,如果我正常添加一个重复元素是什么结果呢? public class HashSetDemo { public static void main(String[] args) { //创建HashSet集合对象 HashSet<Student> hashSet = new HashSet<Student>(); //创建学生对象 Student s1 = new Student("爱学习", 21); Stude

  • java 中HashMap、HashSet、TreeMap、TreeSet判断元素相同的几种方法比较

    java 中HashMap.HashSet.TreeMap.TreeSet判断元素相同的几种方法比较 1.1     HashMap 先来看一下HashMap里面是怎么存放元素的.Map里面存放的每一个元素都是key-value这样的键值对,而且都是通过put方法进行添加的,而且相同的key在Map中只会有一个与之关联的value存在.put方法在Map中的定义如下. V put(K key, V value); 它用来存放key-value这样的一个键值对,返回值是key在Map中存放的旧va

  • Java面试题之HashSet的实现原理

    HashSet 的实现原理? 首先,我们需要知道它是Set的一个实现,所以保证了当中没有重复的元素. 一方面Set中最重要的一个操作就是查找.而且通常我们会选择 HashSet来实现,因为它专门对快速查找进行了优化. HashSet使用的是散列函数,那么它当中的元素也就无序可寻.当中是允许元素为null的. 先对实现原理进行一个总结: (1)基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap.封装了一个 HashMap 对象来存储所有的集合元素,

  • 详解Java中HashSet和TreeSet的区别

    详解Java中HashSet和TreeSet的区别 1. HashSet HashSet有以下特点: 不能保证元素的排列顺序,顺序有可能发生变化 不是同步的 集合元素可以是null,但只能放入一个null 当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置. 简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个

  • 实例讲解Java HashSet

    HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合. HashSet 允许有 null 值. HashSet 是无序的,即不会记录插入的顺序. HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的. 您必须在多线程访问时显式同步对 HashSet 的并发访问. HashSet 实现来 Set 接口. HashSet 中的元素实际上是对象,一些常见的基本类型可以使用它的包装类. 基本类型对应的包装类表如下: 基本类型 引用类型

  • JAVA HashSet和TreeSet 保证存入元素不会重复的操作

    Set是一种数据集合.它与List同样继承与Collection接口. 它与Collection接口中的方法基本一致,并没有对Collection接口进行功能进行功能上的扩充,只是比Collection接口更严格了.与List不同的是,Set中的元素是无无需的,并且都以某种规则保证存入的元素不会出现重复. 它的特点也就是: 1. 元素不会出现重复. 2. 元素是无序的.(存取无序) 3. 元素可以为空. 每种类型的Set所使用的避免元素重复的规则都是不同的,今天我们主要还是看HashSet和Tr

  • Java HashSet添加 遍历元素源码分析

    目录 HashSet 类图 HashSet 简单说明 HashSet 底层机制说明 模拟数组+链表的结构 HashSet 添加元素底层机制 HashSet 添加元素的底层实现 HashSet 扩容机制 HashSet 添加元素源码 HashSet 遍历元素底层机制 HashSet 遍历元素底层机制 HashSet 遍历元素源码 HashSet 类图 HashSet 简单说明 1.HashSet 实现了 Set 接口 2.HashSet 底层实际上是由 HashMap 实现的 public Has

  • Java HashSet(散列集),HashMap(散列映射)的简单介绍

    简介 本篇将简单讲解Java集合框架中的HashSet与HashMap. 散列集(HashSet) 快速入门 底层原理:动态数组加单向链表或红黑树.JDK 1.8之后,当链表长度超过阈值8时,链表将转换为红黑树. 查阅HashSet的源码,可以看到HashSet的底层是HashMap,HashSet相当于只用了HashMap键Key的部分,当需要进行添加元素操作时,其值Value始终为常量PRESENT = new Object().以下为HashSet的代码片段: private transi

  • Java集合类之TreeSet的用法详解

    目录 上节回顾 TreeSet集合概述和特点 构造方法 方法摘要 Demo 自然排序Comparable的使用 比较器排序Comparator的使用 上节回顾 LinkedHashSet集合概述及特点 LinkedHashSet集合特点 哈希表和链表实现Set接口,具有可预测的迭代次序 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的 由哈希表保证元素唯一,也就是说没有重复元素 LinkedHashSet集合的储存和遍历: import java.util.LinkedHashSet;

  • HashSet和TreeSet使用方法的区别解析

    一.问题 1.HashSet,TreeSet是如何使用hashCode()和equal()方法的 2.TreeMap,TreeSet中的对象何时以及为何要实现Comparable接口? 二.回答: 1.HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的,只不过Set用的只是Map的key. 2.Map的key和Set都有一个共同的特性就是集合的唯一性.TreeMap更是多了一个有序性. 3.hashCode和equal()是HashMap用的,因为无需排序所以只需

  • Java HashSet的Removals()方法注意事项

    目录 前言 那么如何解决? 前言 我有一个集合,实际上是一个HashSet.我想从中删除一些item…其中许多item可能不存在.事实上,在我们的测试用例中,“removals”集合中的所有项都不在原始集合中.这听起来——实际上也是——非常容易编码.毕竟,我们已经准备好了.removeAll来帮助我们,对吗? 让我们把它变成一个小测试.我们在命令行上指定“source”set的大小和“removals”集合的大小,并构建它们.source set合只包含非负整数:删除集仅包含负整数.我们使用系统

  • java中volatile不能保证线程安全(实例讲解)

    今天打了打代码研究了一下java的volatile关键字到底能不能保证线程安全,经过实践,volatile是不能保证线程安全的,它只是保证了数据的可见性,不会再缓存,每个线程都是从主存中读到的数据,而不是从缓存中读取的数据,附上代码如下,当synchronized去掉的时候,每个线程的结果是乱的,加上的时候结果才是正确的. /** * * 类简要描述 * * <p> * 类详细描述 * </p> * * @author think * */ public class Volatil

  • java selenium处理Iframe中的元素示例

    java selenium  处理Iframe 中的元素 有时候我们定位元素的时候,发现怎么都定位不了. 这时候你需要查一查你要定位的元素是否在iframe里面 阅读目录 什么是iframe iframe 就是HTML 中,用于网页嵌套网页的. 一个网页可以嵌套到另一个网页中,可以嵌套很多层. selenium 中提供了进入iframe 的方法 // 进入 id 叫frameA 的 iframe dr.switchTo().frame("frameA"); // 回到主窗口 dr.sw

随机推荐