Java集合框架(1)hashset

来源:互联网 发布:vmware mac os 慢 编辑:程序博客网 时间:2024/05/16 09:13

众所周知,hashset里面存储的元素都具有无序性,标识唯一性。但最近仔细研究了一下java里面的hashset,发现hashset里面大多数的内容都是在hashmap的基础上进行修改的。

接下来是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);    }    public Iterator<E> iterator() {        return map.keySet().iterator();    }    public int size() {        return map.size();    }    public boolean isEmpty() {        return map.isEmpty();    }    public boolean contains(Object o) {        return map.containsKey(o);    }    public boolean add(E e) {        return map.put(e, PRESENT)==null;    }    public boolean remove(Object o) {        return map.remove(o)==PRESENT;    }    public void clear() {        map.clear();    }    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();        }    }    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 in HashMap capacity and load factor and create backing HashMap        int capacity = s.readInt();        float loadFactor = s.readFloat();        map = (((HashSet)this) instanceof LinkedHashSet ?               new LinkedHashMap<E,Object>(capacity, loadFactor) :               new HashMap<E,Object>(capacity, loadFactor));        // Read in size        int size = s.readInt();        // Read in all elements in the proper order.        for (int i=0; i<size; i++) {            E e = (E) s.readObject();            map.put(e, PRESENT);        }    }}

静下心来仔细研究了一下,发现hashset是通过将相应的内容存储在了一个hashmap里的key中,然后再去读取的。
在对于hashset进行遍历的时候,主要有两种方式:
第一种是在jdk1.5之前才出现的方式:

import java.util.ArrayList;import java.util.Collection;import java.util.HashMap;import java.util.HashSet;import java.util.Iterator;import java.util.LinkedHashSet;import java.util.Map;import java.util.Map.Entry;import java.util.Set;public class set {        public static void main(String [] args)        {            /*对于hashset里面的排序其实也并不是完全没有顺序的,它实际上是按照hashcode来决定的*/            System.out.println("-----set1 iterator遍历------");            Set<String> set=new HashSet<String>();            set.add("a");            set.add("b");            set.add("c");            set.add("d");            Iterator<String> iterator=set.iterator();            String str=null;            while(iterator.hasNext())            {                str=iterator.next();                System.out.println(str);            }        }}

第二种遍历方式是在jdk1.5以后才出现的foreach循环

package Java_Collection_FrameWork;import java.util.ArrayList;import java.util.Collection;import java.util.HashMap;import java.util.HashSet;import java.util.Iterator;import java.util.LinkedHashSet;import java.util.Map;import java.util.Map.Entry;import java.util.Set;public class set {        public static void main(String [] args)        {            /*对于hashset里面的排序其实也并不是完全没有顺序的,它实际上是按照hashcode来决定的*/            System.out.println("-----set1 iterator遍历------");            Set<String> set=new HashSet<String>();            set.add("a");            set.add("b");            set.add("c");            set.add("d");            for (String string : set) {                System.out.println(string);            }        }}

二者的遍历速度相比而言,foreach循环遍历更加的快。原因也很简单,因为第一种的方式不仅要把hashset先通过迭代器去读取,接着还要从hashmap里面去读取Entry的值。然而在foreach循环里面,对于hashset的遍历则是直接的将hashmap的Entry和value值给读取了出来。
谈到这个时候,也许我们会有些好奇,如果hashset里面将相应的内容都存在了一个hashmap的key里面进行保存,那么hashmap的value又会被用来存储什么呢?
在hashset的源码里面,对于其传过来的数值,会被保存到相应的hashmap的key里面,相对应的value则会用于保存一个叫做PRESENT的静态对象。
在hashset里面,每个被存储进来的数值排序都是按照相对应的hashcode进行一定的划分的。对于hashcode用于存储对象的内容,在这里我不打算细讲,可以推荐各位去阅读下列的相应文章,里面的内容写的相当详细。

http://blog.csdn.net/fenglibing/article/details/8905007

那么在hashset里面存储的对象又是怎么进行判断的呢?
在这里附上一小段代码来演示:

class demo{    public String key;    public String value;    public demo(String key,String val)    {        this.key=key;        this.value=val;    }    public String toString()    {        return "demo[key:"+key+", value:"+value+"]";    }}public class hashset02 {    public static void main(String[] arg)    {        demo d1=new demo("02","haha");        demo d2=new demo("02","haha");        Set<demo> set=new HashSet<demo>();        set.add(d1);        set.add(d2);        System.out.println(d1.hashCode());        System.out.println(d2.hashCode());        System.out.println(set.contains(new demo("02","haha")));    }}

其输出的结果是:

18790965081080497174false

前两行输出的结果不一样还好理解,但是为什么到了第三行会输出一个false呢?其实真正的判断两个对象是否想的的时候,是需要通过hashcode和equals方法进行判断的。于是我对于这个hashcode和equal方法进行了相对应的重写:

class demo{    public String key;    public String value;    public demo(String key,String val)    {        this.key=key;        this.value=val;    }    public boolean equals(Object e)    {        if(e==this)        {            return true;        }        /*判断是否是同一个类*/        if(e.getClass()==demo.class)        {            demo d=(demo) e;            return this.key.equals(d.key)&&this.value.equals(d.value);        }        return false;    }    public int hashCode()    {        return key.hashCode();    }    public String toString()    {        return "demo[key:"+key+", value:"+value+"]";    }}

此时返回的结果就是true了(如下方结果所示)。原因就是,我在demo这个类里面,重写了hashCode和equals方法,使得hashCode返回的哈希值是key的值(当然,每当我们重写一个类的equals方法的时候,最好也去重写它的hashCode方法)

18790965081080497174true

通过这一次研究,我们可以了解到hashset底层的实现原理是通过hashmap来进行编写的,以及hashset里面对于是否包含某一个元素的判断是通过hashmap里面对于key值的内容和key值的hashCode来进行判断的。