关闭

【Java】HashMap、HashSet、TreeMap、TreeSet判断元素相同(代码整理)

标签: 集合对等map判断是否一样元素对比逻辑判断源码工具
589人阅读 评论(0) 收藏 举报
1.1     HashMap
       先来看一下HashMap里面是怎么存放元素的。Map里面存放的每一个元素都是key-value这样的键值对,而且都是通过put方法进行添加的,而且相同的key在Map中只会有一个与之关联的value存在。put方法在Map中的定义如下。
    V put(K key, V value);

    它用来存放key-value这样的一个键值对,返回值是key在Map中存放的旧value,如果之前不存在则返回null。HashMap的put方法是这样实现的。

 public V put(K key, V value) {

        if (key == null)

            return putForNullKey(value);

        int hash = hash(key);

        int i = indexFor(hash, table.length);

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {

            Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

 

        modCount++;

        addEntry(hash, key, value, i);

        return null;

    }

    从上我们可以看到在添加对应的key-value这样的组合时,如果原本已经存在对应的key,则直接改变对应的value,并返回旧的value,而在判断key是否存在的时候是先比较key的hashCode,再比较相等或equals的。这里可能我们还看不出来,直接从上面代码来看是比较的对应Map.Entry的hashCode和key的hashCode,而实际上在后面我们可以看到Map.Entry的hashCode其实就是其存放key的hashCode。而如果对应的key原本不存在的话将调用addEntry将对应的key-value添加到Map中。addEntry传递的参数hash就是对应key的hashCode。接着我们来看一下addEntry的方法定义。

   void addEntry(int hash, K key, V value, int bucketIndex) {

        if ((size >= threshold) && (null != table[bucketIndex])) {

            resize(2 * table.length);

            hash = (null != key) ? hash(key) : 0;

            bucketIndex = indexFor(hash, table.length);

        }

 

        createEntry(hash, key, value, bucketIndex);

    }

 

    void createEntry(int hash, K key, V value, int bucketIndex) {

        Entry<K,V> e = table[bucketIndex];

        table[bucketIndex] = new Entry<>(hash, key, value, e);

        size++;

    }
   addEntry的核心是调用createEntry来建立表示对应key-value的Map.Entry对象并进行存放,很显然上述table是一个Map.Entry的数组。Map.Entry中用一个属性hash保存了对应key的hashCode。还是来看一下上面调用的Map.Entry的构造方法吧。
   Entry(int h, K k, V v, Entry<K,V> n) {

            value = v;

            next = n;

            key = k;

            hash = h;

        }
 很显然,其内部保存了对应key、value和key对应的hashCode。
       了解了HashMap是怎样存放元素的以后,我们再来看HashMap是怎样存放元素的就比较简单了。HashMap中判断元素是否相同主要有两个方法,一个是判断key是否相同,一个是判断value是否相同。其实在介绍HashMap是怎样存放元素时我们已经介绍了HashMap是怎样判断元素Key是否相同的,那就是首先得hashCode相同,其次是key相等或equals。Map中判断key是否相同是通过containsKey()方法进行的,其在HashMap中的实现如下。

  public boolean containsKey(Object key) {

        return getEntry(key) != null;

    }

 

    final Entry<K,V> getEntry(Object key) {

        int hash = (key == null) ? 0 : hash(key);

        for (Entry<K,V> e = table[indexFor(hash, table.length)];

             e != null;

             e = e.next) {

            Object k;

            if (e.hash == hash &&

                ((k = e.key) == key || (key != null && key.equals(k))))

                return e;

        }

        return null;

    }
 很明显,它就是先判断key的hashCode是否相同,再判断key是否相等或equals的。
 Map中用来判断value是否相同是通过containsValue方法来判断的,其在HashMap中的实现如下。

   public boolean containsValue(Object value) {

        if (value == null)

            return containsNullValue();

 

        Entry[] tab = table;

        for (int i = 0; i < tab.length ; i++)

            for (Entry e = tab[i] ; e != null ; e = e.next)

                if (value.equals(e.value))

                    return true;

        return false;

    }
    很显然,对于非null形式的value是通过value的equals来进行判断的,而null形式的只要相等即可,即保存的元素中有value为null即可。


1.2     HashSet

       知道了HashMap是如何存放元素和判断元素是否相同的方式以后,我们再来看HashSet是如何判断元素是否相同就比较简单了。
       HashSet中的元素其实是通过HashMap来保存的,在每个HashSet对象中都持有一个对应的HashMap对象的引用,在对HashSet进行元素的添加、删除等操作时都是通过其持有的HashMap来进行的。在保存元素时其会将对应的元素作为持有的HashMap的key来进行保存,对应的value是一个常量Object,所以其在保存的时候判断元素是否相同所使用的是HashMap判断key是否相同的逻辑。其在判断是否包含某一元素时也是调用了所持有的HashMap的containsKey方法来进行判断的。

 public boolean contains(Object o) {

        returnmap.containsKey(o);

    }

    有兴趣的朋友可以去看一下HashSet的源码。

1.3     TreeMap

       TreeMap中存放的元素都是有序的,而且是根据key进行排序的。TreeMap在对存放的元素进行排序时有两种方式,一种是通过自身持有的Comparator进行排序,另一种是通过实现了Comparable接口的key进行排序,优先使用第一种方式,当持有的Comparator(默认为null)为null时则采用第二种方式。TreeMap好几个构造方法,可以通过其构造方法来初始化其持有的Comparator。我们还是先来看一下TreeMap是如何保存元素的,其put方法实现如下。

 public V put(K key, V value) {

        Entry<K,V> t = root;

        if (t == null) {

            compare(key, key); // type (and possibly null) check

 

            root = new Entry<>(key, value, null);

            size = 1;

            modCount++;

            return null;

        }

        int cmp;

        Entry<K,V> parent;

        // split comparator and comparable paths

        Comparator<? super K> cpr = comparator;

        if (cpr != null) {

            do {

                parent = t;

                cmp = cpr.compare(key, t.key);

                if (cmp < 0)

                    t = t.left;

                elseif (cmp > 0)

                    t = t.right;

                else

                    return t.setValue(value);

            } while (t != null);

        }

        else {

            if (key == null)

                thrownew NullPointerException();

            Comparable<? super K> k = (Comparable<? super K>) key;

            do {

                parent = t;

                cmp = k.compareTo(t.key);

                if (cmp < 0)

                    t = t.left;

                elseif (cmp > 0)

                    t = t.right;

                else

                    return t.setValue(value);

            } while (t != null);

        }

        Entry<K,V> e = new Entry<>(key, value, parent);

        if (cmp < 0)

            parent.left = e;

        else

            parent.right = e;

        fixAfterInsertion(e);

        size++;

        modCount++;

        return null;

    }
      从上述实现我们可以看到,第一个元素将直接存进去。之后的元素分两种情况进行,一种是持有的Comparator不为空的情况,一种是持有的Comparator为空的情况。Comparator不为空的时候将通过Comparator来确定存放元素的位置,其中有一点很重要的是当通过Comparator比较了现有元素的key与当前存放元素的key的结果为0时,将认为当前存放的元素key在原有Map中已经存在,然后改变原有的key对应的value为新value,然后就直接返回了旧value。当持有的Comparator为空时将通过实现了Comparable接口的key的compareTo方法来决定元素存放的位置,有一点与使用Comparator类似的地方是当原有key作为Comparable与新存入的key进行比较的结果为0时将认为新存入的key在原Map中已经存在,将直接改变对应的原key的value,而不再新存入key-value对。实际上其判断元素是否存在的containsKey方法的主要实现逻辑也是类似的,具体实现如下。

    public boolean containsKey(Object key) {

        return getEntry(key) != null;

    }

 

    final Entry<K,V> getEntry(Object key) {

        // Offload comparator-based version for sake of performance

        if (comparator != null)

            return getEntryUsingComparator(key);

        if (key == null)

            thrownew NullPointerException();

        Comparable<? super K> k = (Comparable<? super K>) key;

        Entry<K,V> p = root;

        while (p != null) {

            int cmp = k.compareTo(p.key);

            if (cmp < 0)

                p = p.left;

            elseif (cmp > 0)

                p = p.right;

            else

                return p;

        }

        return null;

    }

 

   因为TreeMap判断元素是否存在的逻辑是通过判断Comparator或Comparable进行比较后的结果是否为0,所以我们在使用TreeMap希望实现某种类似于元素equals的逻辑时要特别小心。
       TreeMap的containsValue的逻辑还是判断的对应的value是否equals,与HashMap类似,有兴趣的朋友可以查看一下TreeMap的源码。

1.4     TreeSet

       TreeSet也是的Set的一种实现,其存放的元素是不重复的,而且是有序的,默认情况下所存放的元素必须实现Comparable接口,因为默认情况下将把元素当做Comparable对象进行比较。TreeSet也是可以通过Comparator来比较其中存放的元素的,这可以在构造TreeSet的时候通过传入一个Comparator对象或一个持有Comparator对象的TreeMap来实现。TreeSet的实现与HashSet的实现类似,其内部也持有了一个Map的引用,只不过它引用的不是HashMap,而是TreeMap。TreeSet中元素的新增、删除等操作都是由其持有的TreeMap来实现的,所以与HashSet类似,TreeSet中判断元素是否相同的方式与TreeMap是一致的,也是通过Comparator或Comparable来判定的,而不是传统的equals方法。有兴趣的朋友可以去查看一下TreeSet的源码。


 



0
0
查看评论

使用hashmap存储对象 并获取元素 祛除重复元素需要在类对象中重写hashcode和equal方法哦

这是主题部分package com.heima.maptest; import java.util.HashMap; import java.util.Map; import com.heima.Student.Student; /** * 怎么用hashmap存储对象 * * @au...
  • qq_34533413
  • qq_34533413
  • 2016-08-31 17:47
  • 405

java中的几种泛型类——HashSet、HashMap、TreeSet、TreeMap,遍历map,排序,HashTable比较

HashSet HashSet泛型类在数据组织上类似于数学上的集合,可以进行“交”、“并”、“差”等运算。 HashSet泛型类创建的对象称为集合,如:     HashSet set =  HashSet();     那么set就是一个可以...
  • u013159040
  • u013159040
  • 2015-05-08 09:17
  • 1354

Java集合框架(Set与Map,HashSet与HashMap,TreeSet与TreeMap)

定义
  • u013344815
  • u013344815
  • 2015-10-14 17:45
  • 1229

Java集合类4—HashSet、TreeSet、HashMap、TreeMap介绍

在前面我们已经介绍了题目中几个类分别实现了哪个接口。HashSet和TreeSet都直接或者间接的继承了Set接口,所以它存储的元素中不允许出现重复元素。     我们先来介绍一下HashSet。HashSet称为哈希表又称散列表。散列表算法的基本思想是:以结点的关键...
  • mengxiangyue
  • mengxiangyue
  • 2011-10-17 19:55
  • 5906

Java集合中的ArrayList LinkedList HashSet TreeSet HashMap TreeMap 的区别

首先它们6个是分别属于三个类别的 也就是List   Set  Map List与Set都是单独存放对象的,Map是放一个名指对,就是可以通过key找到value 主要区别: List 存放是有顺序的    Set是没有顺序的 List是允许重复存入的...
  • ch751652064
  • ch751652064
  • 2013-01-18 13:02
  • 1006

HashMap、HashSet、TreeMap、TreeSet判断元素相同

HashMap、HashSet、TreeMap、TreeSet判断元素相同 etTreeMapTreeSet元素存在  HashMap、HashSet、TreeMap、TreeSet判断元素相同   目录 1.1     H...
  • dotedy
  • dotedy
  • 2015-10-19 01:33
  • 338

ArrayList、LinkedList、Vector、HashSet、Treeset、HashMap、TreeMap的适用场景

ArrayList(适合查询) ArrayList是实现了基于动态数组的数据结构,地址连续,一旦数据存储好了,查询操作效率会比较高 LinkedList(适合增删) LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除Link...
  • jyxmust
  • jyxmust
  • 2017-04-24 19:47
  • 565

TreeSet、TreeMap、HashSet、HashMap底层实现

TreeMap底层基于红黑树,以下是源代码,先指定一个根节点,若该根节点不存在,则说明TreeMap里面没有元素,将当前准备加入的元素(Entry)加入进去当成根节点。 如果里面已经 存在元素,再判断是否TreeMap是否含有比较器,如果比较器不为空,就按照比较器的比较方法进行比较,如果为空,按照...
  • u013914313
  • u013914313
  • 2015-07-26 09:35
  • 1285

java中的HashSet和TreeSet的区别,以及HashSet导致的内存泄漏详解

Set集合中的元素是无序的,不可重复的。这个接口下有两个常用集合的实现,HashSet和TreeSet。 HashSet HashSet底层用的是哈希表,它把对象根据其哈希值存放到对应的区域里。由于这种特性,两个在不同区域的对象会被认为不相同的。 所以如果对象要存放到Hash集合里面,则需...
  • xingjiyuan26
  • xingjiyuan26
  • 2015-10-30 17:43
  • 7954

HashSet如何判定两个元素相同

在介绍java的集合时,我们提到,set是一个“罐子”。我们可以向其中放入各式各样的元素,这些元素没有顺序,但不能相同。其中,HashSet是最常用的一个实现类。
  • u010066934
  • u010066934
  • 2016-02-19 21:09
  • 2486
    个人资料
    • 访问:1932090次
    • 积分:26258
    • 等级:
    • 排名:第265名
    • 原创:806篇
    • 转载:291篇
    • 译文:20篇
    • 评论:427条
    博客专栏
    文章分类
    最新评论