集合(三):Set

一:java.util.Set(interface)

 Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。

public interface Set extends Collection {
    // Query Operations
}

接下来将简单介绍Set下的几个实现类,如:HashSet,TreeSet,LinkedHashSet。

二:java.util.HashSet(class) 

    对于 HashSet 而言,它是基于 HashMap 实现的HashSet 底层维护了一个HashMap,其采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSet 的源代码,可以看到如下代码: 

public class HashSet extends AbstractSet
	implements Set, Cloneable, java.io.Serializable {   

	// 使用 HashMap 的 key 保存 HashSet 中所有元素  
	private transient HashMap map;   
	// 定义一个虚拟的 Object 对象作为 HashMap 的 value   
	private static final Object PRESENT = new Object();   
	...   
	// 初始化 HashSet,底层会初始化一个 HashMap   
	public HashSet(){   
		map = new HashMap();   
	}   
	// 以指定的 initialCapacity、loadFactor 创建 HashSet   
	// 其实就是以相应的参数创建 HashMap   
	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);   
	}   
	// 调用 map 的 keySet 来返回所有的 key   
	public Iterator iterator(){   
		return map.keySet().iterator();   
	}   
	// 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数  
	public int size(){   
		return map.size();   
	}   
	// 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空,  
	// 当 HashMap 为空时,对应的 HashSet 也为空  
	public boolean isEmpty(){   
		return map.isEmpty();   
	}   
	// 调用 HashMap 的 containsKey 判断是否包含指定 key   
	//HashSet 的所有元素就是通过 HashMap 的 key 来保存的  
	public boolean contains(Object o){   
		return map.containsKey(o);   
	}   
	// 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap   
	public boolean add(E e){   
		return map.put(e, PRESENT) == null;   
	}   
	// 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素  
	public boolean remove(Object o){   
		return map.remove(o)==PRESENT;   
	}   
	// 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素  
	public void clear(){   
		map.clear();   
	}   
	...   
}   

1. 此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。

2. HashSet按Hash算法来存储集合中的元素。因此具有很好的存取和查找性能。

3. HashSet具有以下特点:

    - 不能保证元素的排列(迭代)顺序 即HashSet的元素存放顺序和添加进去时候的顺序没有任何关系;特别是它不保证该顺序恒久不变。

    - HashSet不是线程安全的。

    - 此类允许使用 null 元素,但是不允许出现重复元素。

4. 当想HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode() 方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置。

5. 如果两个元素的equals()方法值返回ture,但它们的hashCode值返回值不相等,hashSet将会把他们存储在不同的位置。

对HashSet类中一些基本方法的使用,参照API:

首先,先定义一个Person类,完成基本的封装操作,并提供hashCode()和equals()方法。

public class Person {

	private String name;
	private int age;
	
	public Person() {
	}
	
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	/**
	 * 为什么在hashCode()方法中有质数的存在。
	 * 
	 * eg:
	 * Person p1: name: 100, age: 22
	 * Person p2: name: 22,	 age: 100  
	 * 如果只是普通的相加,那么上述两个对象的 hashCode值相等,
	 * 	但是通过质数prime * 其中某个数,那么在相加,两者的hashCode值就不相等了。
	 * 
	 * */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	/**
	 * 提供 构造方法,get set方法, equals方法。
	 * */
	
}

在定义一个测试类,完成基本方法的使用:

public class Test1HashSet {

	public static void main(String[] args) {
		
		Set<Person> set = new HashSet<Person>();
		
		Person p1 = new Person("Berg",22);
		Person p2 = new Person("Xujun",22);
		Person p3 = new Person("Berg",22);
		Person p4 = new Person("HashSet",23);
		Person p5 = new Person("SetHash",24);
		
		System.out.println( p1.equals(p3));
		System.out.println( p1.hashCode() + " \t " + p3.hashCode() );
		
		set.add( p1 );
		set.add( p2 );
		set.add( p3 );
		set.add( p4 );
		set.add( p5 );
		
		// 1. 怎么确保不可重复, 重写 HashCode() 和 equals();
		//2.存的顺序与取得顺序不一致。
		//3.迭代输出  循环 的方法 
		//4. 注意输出结果是无序的。
		//5. 如果有序,只需要将 HashSet 改为LinkedHashSet 
		
		System.out.println( " 输出第1种方案 : " + set.size() );
		for( Person s : set){
			System.out.println( s + " " +s.hashCode());      
		} 
		
		System.out.println( " 输出第二种方案 : ");
		Object [] objs = set.toArray();  // toArray() 返回一个包含 set 中所有元素的数组。
		for( Object o : objs){
			System.out.println( o  );   // o.hashCode()
		}
		
		// 迭代 : 
		System.out.println( " 输出第3种方案 : ");
		// 接口不能实例化  ,    
		//返回对此 set 中元素进行迭代的迭代器。
		Iterator<Person> its = set.iterator();
		while(  its.hasNext() == true){ //如果仍有元素可以迭代,则返回 true。
			Person s = its.next();  //返回迭代的下一个元素。
			System.out.println( s );
		}
		
	}
}

三:java.util.LinkedHashSet(class) 

    具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。注意,插入顺序 受在 set 中重新插入的 元素的影响。(如果在 s.contains(e) 返回 true 后立即调用 s.add(e),则元素 e 会被重新插入到 set s 中。)

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {}

1. LinkedHashSet是HashSet的子类。

2. LinkedHashSet集合根据元素的hashCode值来决定元素的存储位置,但他同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。

3. LinkedHashSet 插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。

4.LinkedHashSet不允许集合元素重复。

5.此实现也不同步,即线程不安全的。

eg: 将上述HashSet代码实现中 注释部分的第五点 要求满足即可,即:

         将HashSet 更改为 LinkedHashSet即可看到迭代输出结果是有序的,且元素不重复。

 

四:java.util.TreeSet<E>

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    //...
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }

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

        基于TreeMap的NavigableSet实现。使用元素的自然顺序对元素进行排序。当然也可以自己定制排序方法。   

4.1 自然排序:

在原有的Person类中,让其实现 Comparable 接口,具体实现 compareTo()方法。

package com.berg.se.bean;

public class Person implements Comparable<Person> {

	private String name;
	private int age;
	
	//.....省略 
	@Override
	public int compareTo(Person p) {
		if(p instanceof Person ){
			// return this.name.compareTo( p.name);  按升序排序
			return p.name.compareTo( this.name );  //  按降序排序 根据名字
		}else{
			throw new ClassCastException("非Person类型。");
		}
	}
	
}

注意:

public int compareTo( T o):
若返回0,代表两个元素相等,若返回正数,代表当前元素大,若返回负数,代表当前元素小

TreeSet会调用每个元素的compareTo()方法去和集合中的每个已经存在的元素去比较,进而决定当前元素在集合中的位置。

 

然后在看看具体的TreeSet操作:

public class Test3TreeSet {

	public static void main(String[] args) {

		TreeSet<Person> ts = new TreeSet<Person>();
		
		Person p1 =  new Person("AA", 19);
		Person p2 =  new Person("BB", 20);
		Person p3 =  new Person("CC", 21);
		Person p4 =  new Person("EE", 23);
		Person p5 =  new Person("DD", 19);
		Person p6 =  new Person("AA", 19);
		
		ts.add(p1);
		ts.add(p5);
		ts.add(p4);
		ts.add(p3);
		ts.add(p2);
		ts.add(p6);

		Iterator<Person> iterator = ts.iterator();

		while (iterator.hasNext()) {
			Person p = iterator.next();
			System.out.println(p);
		}
		
		//返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。
		System.out.println( " \n*************************部分视图: \n");
		SortedSet<Person> ss = ts.subSet(p4,p2);
		
		Iterator<Person> iterator1 = ss.iterator();
		while (iterator1.hasNext()) {
			Person p = iterator1.next();
			System.out.println(p);
		}
	}
}

 

4.2 定制排序:

        不让其Person类实现Comparable接口。而是将Comparator这个对象当做参数传递给TreeSet,让其元素实现排序效果。可以降低耦合。

        Person2 并没有实现 Comparable接口。

public class Test3TreeSet2 {

	public static void main(String[] args) {

		Comparator<Object> comparator = new Comparator<Object>() {

			@Override
			public int compare(Object o1, Object o2) {
				if( o1 instanceof Person2  && o2 instanceof Person2){
					Person2 p1 = (Person2) o1;
					Person2 p2 = (Person2) o2;
					return p2.getAge() - p1.getAge();
				}else{
					throw new ClassCastException("非Person2类型。");
				}
			}
		};
		TreeSet<Person2> ts = new TreeSet<Person2>(comparator);
		
		Person2 p1 =  new Person2("AA", 19);
		Person2 p2 =  new Person2("BB", 20);
		Person2 p3 =  new Person2("CC", 21);
		Person2 p4 =  new Person2("EE", 23);
		Person2 p5 =  new Person2("DD", 19);
		Person2 p6 =  new Person2("AA", 19);
		
		ts.add(p1);
		ts.add(p5);
		ts.add(p4);
		ts.add(p3);
		ts.add(p2);
		ts.add(p6);

		Iterator<Person2> iterator = ts.iterator();

		while (iterator.hasNext()) {
			Person2 p = iterator.next();
			System.out.println(p);
		}
	}
}

 

再来了解下TreeSet与TreeMap的区别于联系:

    TreeMap 和 TreeSet 是 Java Collection Framework 的两个重要成员,其中 TreeMap 是 Map 接口的常用实现类,而 TreeSet 是 Set 接口的常用实现类。虽然 TreeMap 和TreeSet 实现的接口规范不同,但 TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现的一样),因此二者的实现方式完全一样。而 TreeMap 的实现就是红黑树算法。

1. TreeSet和TreeMap的关系

与HashSet完全类似,TreeSet里面绝大部分方法都市直接调用TreeMap方法来实现的。

相同点:

TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是排好序的。

TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步

运行速度都要比Hash集合慢,他们内部对元素的操作时间复杂度为O(logN),而HashMap/HashSet则为O(1)。

不同点:

最主要的区别就是TreeSet和TreeMap分别实现Set和Map接口

TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)

TreeSet中不能有重复对象,而TreeMap中可以存在

TreeMap的底层采用红黑树的实现,完成数据有序的插入,排序。

 

********************************************************************************************************

注意hashCode方法:

1. HashSet集合判断两个元素相等的标准:两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等,

2. 如果两个对象通过equals()方法返回true,这两个对象的hashCode()值也应该相等。

3. 重写hashCode()方法时基本原则:

        - 同一个对象多次调用hashCode()方法应该返回相同的值。

        - 当两个对象的equals()方法比较返回true时,这两个对象的hashCode()方法的返回值也应该相等。

        - 对象中用作equals()方法比较的字段,都应该用来计算hashCode值。

转载于:https://my.oschina.net/gently/blog/688806

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值