黑马程序员——Java集合框架—Set

来源:互联网 发布:淘宝上开店怎么找货源 编辑:程序博客网 时间:2024/06/06 11:47
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


Set概述

Set是对数学上“集”的抽象。

Set集合中的元素是无序的。

Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,添加操作将会失败,add方法返回false。

Set集合判断两个对象是否相同不是使用“==”运算符,而是根据equals方法。也就是说,如果两个对象用equals方法比较返回true,Set集合就会认为这两个对象是相同的,就不会同时存储这两个对象。


Set接口

Set接口是Collection接口的子接口,在《黑马程序员——Java集合框架—概述》中我们展示了Collection接口的源码,下面我们来看一下Set接口的源码:

package java.util;public interface Set<E> extends Collection<E> {    int size();    boolean isEmpty();    boolean contains(Object o);    Iterator<E> iterator();    Object[] toArray();    <T> T[] toArray(T[] a);    boolean add(E e);    boolean remove(Object o);    boolean containsAll(Collection<?> c);    boolean addAll(Collection<? extends E> c);    boolean retainAll(Collection<?> c);    boolean removeAll(Collection<?> c);    void clear();    boolean equals(Object o);    int hashCode();}

我们对比Collection接口和Set接口的源码可以发现,Set接口继承了Collection接口,但是它们的源码是相同的。也就是说,虽然Set是Collection的子接口,但是Set没有添加任何额外的功能。可以认为,Set就是Collection只是行为不同(多态的表现)。


AbstractSet抽象类和SortedSet接口

Set接口有一个抽象子类和一个子接口。


AbstractSet的源码:

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {    protected AbstractSet() {    }    //重写了父类中的equals方法    public boolean equals(Object o) {        if (o == this)            return true;        if (!(o instanceof Set))            return false;        Collection c = (Collection) o;        if (c.size() != size())            return false;        try {            return containsAll(c);        } catch (ClassCastException unused)   {            return false;        } catch (NullPointerException unused) {            return false;        }    }    //重写了父类中的hashCode方法    public int hashCode() {        int h = 0;        Iterator<E> i = iterator();        while (i.hasNext()) {            E obj = i.next();            if (obj != null)                h += obj.hashCode();        }        return h;    }    //重写了父类中的removeAll方法    public boolean removeAll(Collection<?> c) {        boolean modified = false;        if (size() > c.size()) {            for (Iterator<?> i = c.iterator(); i.hasNext(); )                modified |= remove(i.next());        } else {            for (Iterator<?> i = iterator(); i.hasNext(); ) {                if (c.contains(i.next())) {                    i.remove();                    modified = true;                }            }        }        return modified;    }    //抽象方法,没有实现    //public abstract Iterator<E> iterator();    //public abstract int size();}

AbstractCollection抽象类实现了Collection接口,而且Set接口继承了AbstractCollection抽象类,所以AbstractSet抽象类相当于实现了Collection接口(最起码AbstractSet类中包含了Collection接口中的全部方法)。
从《黑马程序员——Java集合框架—概述》中可知,AbstractCollection抽象类中只包含2个抽象方法(iterator、size),AbstractSet中也只包含这2个抽象方法(AbstractSet没有实现这2个方法)。


我们知道Set集合中的元素是无序的,但Java在Set集合中提供了一个SortedSet接口来表示有序Set集合。

下面看一下SortedSet接口的源码:

package java.util;public interface SortedSet<E> extends Set<E> {//返回一个Comparator,代表有序Set集合排序机制    Comparator<? super E> comparator();//取子集,从formElement(包含)到toElement(不含)    SortedSet<E> subSet(E fromElement, E toElement);//取子集,由小于toElement的所有元素组成    SortedSet<E> headSet(E toElement);//取子集,由大于等于fromElement的所有元素组成    SortedSet<E> tailSet(E fromElement);//返回集合中的第一个元素    E first();//返回集合中的最后一个元素    E last();}

因为集合是有序的了,所以集合拥有以上方法是很正常的。


HashSet类

HashSet是AbstractSet抽象类的子类,所以它是无序集合,不能有重复元素。

HashSet是Set接口的典型实现,大多数时候使用Set集合时就是使用这个类,HashSet按Hash算法来存储集合中的元素,因此具有很好的存储和查找性能。

从Set接口的源码可以看到,Set接口中没有包含获取固定集合元素的方法,比如get方法,所以在HashSet中也不包含这种方法,也就是说,如果想要获得HashSet集合中的元素,只能通过遍历该集合。

HashSet具有以下特点:

1、不能保证元素的排列顺序,顺序有可能发生变化(即不保证集合元素迭代的顺序与集合元素插入的顺序相同,集合内部的元素也是无序的)。

2、HashSet不是同步的,如果多个线程同时访问一个HashSet,则必须通过代码来保证其同步。

3、集合元素值可以是null。

HashSet底层是使用HashMap实现的,使用HashMap的key来保存向HashSet中添加的元素,同时使用一个Object对象来充当HashMap的value。

package org.lgy.study.collection;import java.util.Set;import java.util.HashSet;import java.util.Iterator;public class SetTest{public static void main(String[] args){Set s = new HashSet();/*代码片段1:HashSet中可以存放null,但只能存放一个null值(因为set中的元素是不能重复的)。关于为什么HashSet中可以存放null值,请参见HashMap的有关内容。*/SetA sa1 = null, sa2 = null;s.add(sa1);s.add(sa2);System.out.println("s.size() = " + s.size() + "\n");/*代码片段2:向HashSet中添加元素时,系统会调用该元素的hashCode方法计算出哈希值,进而计算出存储该元素的地址值。如果该地址值处没有存储内容,则直接把该元素存储在该位置。如果该地址值处已经存储类内容,则调用待添加元素的equals方法,与该位置已有的元素进行比较,如果有equals方法返回true,则不再存储该元素,add方法返回false;如果所有equals方法都返回false,则根据冲突解决算法存储下该元素。*/s.clear();s.add(new SetA(true, (byte)10, (short)2, 3, 4L, 5.0F, 6.0));s.add(new SetA(true, (byte)20, (short)2, 3, 4L, 5.0F, 6.0));s.add(new SetA(true, (byte)30, (short)2, 3, 4L, 5.0F, 6.0));s.add(new SetA(true, (byte)10, (short)2, 3, 4L, 5.0F, 6.0));//System.out.println(s);Iterator iterator = s.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}System.out.println();/*代码片段3:当向HashSet中添加可变元素时,必须十分小心,如果程序修改了集合中的对象,有可能导致该对象与集合中的其它对象相等,从而导致HashSet无法准确访问该对象。*/s.clear();SetB b1 = new SetB(10);s.add(b1);SetB b2 = new SetB(20);s.add(b2);SetB b3 = new SetB(30);s.add(b3);SetB b4 = new SetB(40);s.add(b4);System.out.println(s);//下面一行代码把b2的count值修改为30,使b2等于b3b2.setCount(30);System.out.println(s);//删除count为30的元素,被删掉的元素为b3,而不是b1System.out.println("s.remove(new SetB(30)) = " + s.remove(new SetB(30)));System.out.println(s);/*再次删除count为30的元素(即打算删除b2),但是无法删除为什么会删除失败呢?当试图删除count为30的b2时,系统会调用其hashCode方法获得其哈希值30,进而求出其存储地址,但是存储b2时,其存储地址是按原来的count=20求得,所以按count=30求到的是b3的地址,因为b3已经被删除了,所以删除失败。*/System.out.println("s.remove(new SetB(30)) = " + s.remove(new SetB(30)));System.out.println(s);/*使用b2原来的count值(即count=20)删除b2,删除失败为什么会删除失败呢?通过count=20求得的地址值确实可以找到存储的b2,当找到b2后,系统会调用集合中已存储对象的equals方法(即把待删除的对象作为参数来调用集合中对象的equals方法),结果没有返回true的对象,所以删除失败。*/System.out.println("s.remove(new SetB(20)) = " + s.remove(new SetB(20)));System.out.println(s);}}/*结果:s.size() = 110 hashCode... ...20 hashCode... ...30 hashCode... ...10 hashCode... ...10 equals 1020 hashCode... ...org.lgy.study.collection.SetA@1118010d30 hashCode... ...org.lgy.study.collection.SetA@1118015310 hashCode... ...org.lgy.study.collection.SetA@111800c7[SetB[20], SetB[40], SetB[10], SetB[30]][SetB[30], SetB[40], SetB[10], SetB[30]]30 equals 30s.remove(new SetB(30)) = true[SetB[30], SetB[40], SetB[10]]s.remove(new SetB(30)) = false[SetB[30], SetB[40], SetB[10]]20 equals 30s.remove(new SetB(20)) = false[SetB[30], SetB[40], SetB[10]]*/class SetB{private int count;public SetB(int count){this.count = count;}public void setCount(int count){this.count = count;}public String toString(){return "SetB[" + this.count + "]";}public int hashCode(){return count;}public boolean equals(Object obj){if(this == obj)return true;if(obj != null && obj instanceof SetB){SetB b = (SetB)obj;System.out.println(this.count + " equals " + b.count);return this.count == b.count;}return false;}}class SetA{private boolean boolField;private byte byteField;private short shortField;private int intField;private long longField;private float floatField;private double doubleField;public SetA(boolean boolField, byte byteField, short shortField, int intField, long longField, float floatField, double doubleField){this.boolField = boolField;this.byteField = byteField;this.shortField = shortField;this.intField = intField;this.longField = longField;this.floatField = floatField;this.doubleField = doubleField;}//重写hashCode方法public int hashCode(){int boolFieldHashCode = (boolField ? 0 : 1) * 3;int byteFieldHashCode = ((int)byteField) * 7;int shortFieldHashCode = ((int)shortField) * 11;int intFieldHashCode = intField * 13; int longFieldHashCode =((int)(longField ^ (longField >>> 32))) * 17;int floatFieldHashCode = Float.floatToIntBits(floatField) * 23;long doubleFieldTemp = Double.doubleToLongBits(doubleField);int doubleFieldHashCode = (int)(doubleFieldTemp ^ (doubleFieldTemp >>> 32)) * 29;int hash = boolFieldHashCode + byteFieldHashCode + shortFieldHashCode + intFieldHashCode + longFieldHashCode + floatFieldHashCode + doubleFieldHashCode;System.out.println(this.byteField + " hashCode... ...");return hash;}//重写equals方法public boolean equals(Object obj){if(this == obj)return true;if(obj != null && obj instanceof  SetA){SetA a = (SetA)obj;System.out.println(this.byteField + " equals " + a.byteField);return this.boolField == a.boolField && this.byteField == a.byteField && this.shortField == a.shortField &&this.intField == a.intField && this.longField ==a.longField &&this.floatField == a.floatField && this.doubleField == a.doubleField;}return false;}}

LinkedHashSet

LinkedHashSet是HashSet的子类,但它同时使用链表来维护元素的次序,这样使得元素是以插入的顺序来保存的,也就是说,当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。

看一下LinkedHashSet的源码:

package java.util;public class LinkedHashSet<E>    extends HashSet<E>    implements Set<E>, Cloneable, java.io.Serializable {private static final long serialVersionUID = -2851667679971038690L;public LinkedHashSet(int initialCapacity, float loadFactor) {super(initialCapacity, loadFactor, true);}public LinkedHashSet(int initialCapacity) {super(initialCapacity, .75f, true);}public LinkedHashSet() {super(16, .75f, true);}public LinkedHashSet(Collection<? extends E> c) {super(Math.max(2*c.size(), 11), .75f, true);addAll(c);}}

LinkedHashSet的源码非常简单,只有4个构造方法,并且这4个构造方法全部是调用父类即HashSet的构造方法来完成初始化。

当调用LinkedHashSet的add、remove等方法时,实际上调用的是HashSet继承的方法。

下面看一个简单的示例:

package org.lgy.study.collection;import java.util.Set;import java.util.HashSet;import java.util.LinkedHashSet;import java.util.Iterator;public class LinkedHashSetTest{public static void main(String[] args){Set s = new LinkedHashSet();s.add("疯狂Java讲义");s.add("疯狂Android讲义");s.add("轻量级Java EE");s.add("经典Java EE");System.out.println("LinkedHashSet: " + s);Iterator it = s.iterator();while(it.hasNext()){System.out.print(it.next() + "  ");}System.out.println("\n");Set s2 = new HashSet();s2.add("疯狂Java讲义");s2.add("疯狂Android讲义");s2.add("轻量级Java EE");s2.add("经典Java EE");System.out.println("HashSet: " + s2);Iterator it2 = s2.iterator();while(it2.hasNext()){System.out.print(it2.next() + "  ");}}}/*结果:LinkedHashSet: [疯狂Java讲义, 疯狂Android讲义, 轻量级Java EE, 经典Java EE]疯狂Java讲义  疯狂Android讲义  轻量级Java EE  经典Java EEHashSet: [经典Java EE, 疯狂Android讲义, 轻量级Java EE, 疯狂Java讲义]经典Java EE  疯狂Android讲义  轻量级Java EE  疯狂Java讲义*/


TreeSet

TreeSet继承了AbstractSet抽象类,并实现了SortedSet接口。TreeSet可以确保集合中的元素处于有序状态,与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构来存储集合元素。TreeSet并不是根据元素的插入顺序进行排序的,而是根据元素实际值的大小进行排序。

TreeSet的基本用法示例:

package org.lgy.study.collection;import java.util.TreeSet;public class TreeSetTest{public static void main(String[] args){TreeSet ts = new TreeSet();ts.add(5);ts.add(2);ts.add(10);ts.add(-9);//输出集合里的元素,虽然添加元素时是无序的,但由于TreeSet是有序的,所以输出时是有序的System.out.println(ts);//输出集合里的第一个元素(默认就是最小的元素)System.out.println("ts.first() = " + ts.first());//输出集合里的最后一个元素(默认就是最大的元素)System.out.println("ts.last() = " + ts.last());//返回小于4的子集,不包含4(做为截取标准的元素 即4 可以是集合之外的元素)System.out.println("ts.headSet(4, false) = " + ts.headSet(4, false));System.out.println("ts.headSet(2, true) = " + ts.headSet(2, true));System.out.println("ts.headSet(2, false) = " + ts.headSet(2, false));//返回大于4的子集,不包含4(作为截取标准的元素 即4 可以是集合之外的元素)System.out.println("ts.tailSet(4, false) = " + ts.tailSet(4, false));System.out.println("ts.tailSet(5, true) = " + ts.tailSet(5, true));System.out.println("ts.tailSet(5, false) = " + ts.tailSet(5, false));//取子集System.out.println("ts.subSet(2, true, 5, true) = " + ts.subSet(2, true, 5, true));System.out.println("ts.subSet(2, false, 5, false) = " + ts.subSet(2,false, 5, false)); }}

下面看一下TreeSet的排序规则。

TreeSet支持2种排序方式:自然排序和定制排序。

自然排序指TreeSet调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合按升序排列。

Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小。当一个对象调用该方法与另一个对象进行比较时,例如obj1.compareTo(obj2),如果返回0,则表明这2个对象相等;如果方法返回一个正整数,则表明obj1大于obj2;如果方法返回一个负整数,则表明obj1小于obj2。

如果试图把一个对象加到TreeSet中,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。

对于TreeSet集合,它判断2个对象是否相等的唯一标准是:2个对象通过compareTo方法比较是否返回0,如果返回0,TreeSet则会认为它们相等,否则就认为它们不相等。

0 0
原创粉丝点击