您的位置:首页 > 编程语言 > Java开发

Java 7之集合类型第6篇 - Set集合的实现

2014-08-21 16:39 423 查看
转载地址:http://blog.csdn.net/mazhimazh/article/details/19030037

Set接口中定义了一些Set常见的操作,与Collection接口中定义的方法差不多。AbstractSet抽象类中只实现了equals()、hashCode()和removeAll()方法,非常简单,有兴趣的读者可以自己去查看。

1、HashSet

HashSet类的特点:能够快速定位集合中的元素、集合中的元素无序(这里所谓的无序并不是完全无序,只是不像List集合按对象的插入顺序保存对象)。

有了HashMap的实现,则HashSet的实现非常简单,只需要将Set中的元素做为Map中的key值来保存即可。

[java] view
plaincopyprint?





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

private transient HashMap<E,Object> map; // 使用map的key来保存Set元素

private static final Object PRESENT = new Object();// 值为一个Object对象

public HashSet() {

map = new HashMap<>();

}

/**

* Constructs a new set containing the elements in the specified

* collection. The HashMap is created with default load factor

* (0.75) and an initial capacity sufficient to contain the elements in

* the specified collection.

*/

public HashSet(Collection<? extends E> c) {

map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));

addAll(c);

}

// 省略其他构造函数

}

[java] view
plaincopyprint?





public Iterator<E> iterator() { // 循环HashMap中的key值

return map.keySet().iterator();

}

public boolean contains(Object o) { // 查找HashMap中的key值

return map.containsKey(o);

}

public boolean add(E e) { // key为Set中要添加的元素,值为一个空的Object对象

return map.put(e, PRESENT)==null;

}

由HashSet类实现的Set集合中的对象必须是惟一的,因此需要添加到由HashSet类实现的Set集合中的对象,需要重新实现equals()方法,从而保证插入集合中对象的标识的惟一性。

由HashSet类实现的Set集合的排列方式为按照哈希码排序,根据对象的哈希码确定对象的存储位置,因此需要添加到由HashSet类实现的Set集合中的对象,还需要重新实现hashCode()方法,从而保证插入集合中的对象能够合理地分布在集合中,以便于快速定位集合中的对象。

[java] view
plaincopyprint?





public class Person{

private String name;

private long id_card;

public Person(String name,long id_card){

this.name = name;

this.id_card = id_card;

}

public long getId_card(){

return id_card;

}

public void setId_card(long id_card){

this.id_card = id_card;

}

public String getName(){

return name;

}

public void setName(String name){

this.name = name;

}

public int hashCode(){//重新实现hashCode()方法

final int PRIME = 31;

int result = 1;

result = PRIME*result+(int)(id_card^(id_card>>>32));

result = PRIME*result+((name ==null)?0:name.hashCode());

return result;

}

public boolean equals(Object obj){//重新实现equals()方法

if(this == obj){

return true;

}

if(obj == null){

return false;

}

if(getClass()!=obj.getClass()){

return false;

}

final Person other = (Person)obj;

if(id_card!=other.id_card){

return false;

}

if(name == null){

if(other.name != null){

return false;

}

}

else if(!name.equals(other.name)){

return false;

}

return true;

}

}

编写测试程序:

[java] view
plaincopyprint?





public class TestSet{

public static void main(String args[]){

Set<Person> hashSet = new HashSet<Person>();

hashSet.add(new Person("马先生",22015));

hashSet.add(new Person("李小姐",22018));

hashSet.add(new Person("李先生",22020));

Iterator<Person> it = hashSet.iterator();

while(it.hasNext()){

Person person = it.next();

System.out.println(person.getName()+" "+person.getId_card());

}

}

}

程序的运行结果如下:

李小姐 22018

李先生 22020

马先生 22015

如果既想保留HashSet类快速定位集合中对象的优点,又想让集合中的对象按插入的顺序保存,可以通过HashSet类的子类LinkedHashSet实现Set集合。

2、LinkedHashSet

LinkedHashSet,顾名思义,就是在Hash的实现上添加了Linked的支持。对于LinkedHashSet,在每个节点上通过一个链表串联起来,这样,就可以保证确定的顺序。对于希望有常量复杂度的高效存取性能要求、同时又要求排序的情况下,可以直接使用LinkedHashSet。

它实现了Set接口。存入Set的每个元素必须是唯一的,因为Set不保存重复元素。但是Set接口不保证维护元素的次序。LinkedHashSet具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的顺序),于是在使用迭代器便利Set时,结果会按元素插入的次序显示。

在HashSet中有一个构造方法,如下:

[java] view
plaincopyprint?





HashSet(int initialCapacity, float loadFactor, boolean dummy) {

map = new LinkedHashMap<>(initialCapacity, loadFactor);

}

这个构造函数是包访问权限,且底层使用LinkedHashMap来实现,所以LinkedHashSet是通过调用这个构造函数来获取实例而保持元素的插入顺序的,源代码如下:

[java] view
plaincopyprint?





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

}

}

继续编写上一个测试程序,将

[java] view
plaincopyprint?





Set<Person> hashSet = new HashSet<Person>();

改为

[java] view
plaincopyprint?





Set<Person> hashSet = new LinkedHashSet<Person>();

后,输出结果发现,和插入的顺序一致。

3、TreeSet

TreeSet类不仅实现了Set接口,还实现了java.util.SortedSet接口,从而保证在遍历集合时按照递增的顺序获得对象。遍历对象时可能是按照自然顺序递增排列,因此存入用TreeSet类实现的Set集合的对象必须实现Comparable接口;也可能是按照指定比较器递增排序,即可以通过比较器对用TreeSet类实现的Set集合中的对象进行排序。

下面先看 TreeSet 类的部分源代码:

[java] view
plaincopyprint?





public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable{

// 使用 NavigableMap 的 key 来保存 Set 集合的元素

private transient NavigableMap<E,Object> m;

// 使用一个 PRESENT 作为 Map 集合的所有 value

private static final Object PRESENT = new Object();

//----------构造函数-------------------------------

// 包访问权限的构造器,以指定的 NavigableMap 对象创建 Set 集合

TreeSet(NavigableMap<E,Object> m) {

this.m = m;

}

// 以自然排序方式创建

public TreeSet() {

this(new TreeMap<E,Object>());

}

// 以指定的排序方式创建

public TreeSet(Comparator<? super E> comparator) {

this(new TreeMap<>(comparator));

}

public TreeSet(Collection<? extends E> c) {

this(); // 调用无参的默认构造器

addAll(c); // 添加Collection中已有的元素

}

// 以SortedSet为基础创建TreeSet

public TreeSet(SortedSet<E> s) {

this(s.comparator());

addAll(s);

}

}

提供了获取部分元素的方法如下:

[java] view
plaincopyprint?





public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,E toElement, boolean toInclusive) {

return new TreeSet<>(m.subMap(fromElement, fromInclusive,toElement, toInclusive));

}

public NavigableSet<E> headSet(E toElement, boolean inclusive) {

return new TreeSet<>(m.headMap(toElement, inclusive));

}

public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {

return new TreeSet<>(m.tailMap(fromElement, inclusive));

}

public SortedSet<E> subSet(E fromElement, E toElement) {

return subSet(fromElement, true, toElement, false);

}

public SortedSet<E> headSet(E toElement) {

return headSet(toElement, false);

}

public SortedSet<E> tailSet(E fromElement) {

return tailSet(fromElement, true);

}

从如上的几个构造器可以看出,其实底层还是通过TreeMap来实现的。

[java] view
plaincopyprint?





public class Person implements Comparable{

private String name;

private long id_card;

public Person(String name,long id_card){

this.name = name;

this.id_card = id_card;

}

public String getName(){

return name;

}

public void setName(String name){

this.name = name;

}

public long getId_card(){

return id_card;

}

public void setId_card(long id_card){

this.id_card = id_card;

}

public int compareTo(Object o){//默认按编号升序排序

Person person = (Person)o;

int result = id_card>person.id_card?1:(id_card==person.id_card?0:-1);

return result;

}

}

[java] view
plaincopyprint?





public class TestSet{

public static void main(String args[]){

TreeSet<Person> treeSet = new TreeSet<Person>();

Person p1 = new Person("马先生",22015);

Person p2 = new Person("李先生",22016);

Person p3 = new Person("王小姐",22018);

Person p4 = new Person("尹先生",22020);

Person p5 = new Person("王先生",22012);

treeSet.add(p1);

treeSet.add(p2);

treeSet.add(p3);

treeSet.add(p4);

treeSet.add(p5);

System.out.println("初始化的集合:");

Iterator<Person> it = treeSet.iterator();

while(it.hasNext()){

Person p = it.next();

System.out.println(p.getId_card()+" "+p.getName());

}

System.out.println("截取前面部分得到的集合:");

it = treeSet.headSet(p1).iterator();

while(it.hasNext()){

Person p = it.next();

System.out.println(p.getId_card()+" "+p.getName());

}

System.out.println("截取中间部分得到的集合:");

it = treeSet.subSet(p1,p3).iterator();

while(it.hasNext()){

Person p = it.next();

System.out.println(p.getId_card()+" "+p.getName());

}

System.out.println("截取后面部分得到的集合:");

it = treeSet.tailSet(p3).iterator();

while(it.hasNext()){

Person p = it.next();

System.out.println(p.getId_card()+" "+p.getName());

}

}

}

程序的运行结果如下:

初始化的集合:

22012 王先生

22015 马先生

22016 李先生

22018 王小姐

22020 尹先生

截取前面部分得到的集合:

22012 王先生

截取中间部分得到的集合:

22015 马先生

22016 李先生

截取后面部分得到的集合:

22018 王小姐

22020 尹先生

在使用由TreeSet类实现的Set集合时,也可以通过单独的比较器对集合中的对象进行排序。

比较器既可以作为一个单独的类,也可以作为对应类的内部类,本例中移内部类的形式实现比较器。

[java] view
plaincopyprint?





import java.util.Comparator;

public class Person implements Comparable{

private String name;

private long id_card;

public Person(String name,long id_card){

this.name = name;

this.id_card = id_card;

}

public String getName(){

return name;

}

public void setName(String name){

this.name = name;

}

public long getId_card(){

return id_card;

}

public void setId_card(long id_card){

this.id_card = id_card;

}

public int compareTo(Object o){//默认按编号升序排序

Person person = (Person)o;

int result = id_card>person.id_card?1:(id_card==person.id_card?0:-1);

return result;

}

static class PersonComparator implements Comparator{

public static final int NAME = 1;

public static final int ID_CARD = 2;

private int orderByColumn = 1;//默认为按姓名排序

public static final boolean ASC = true;

public static final boolean DESC = false;

private boolean orderByMode = true;//默认为按升序排序

public int compare(Object o1,Object o2){//实现Comparator接口的方法

Person p1 = (Person)o1;

Person p2 = (Person)o2;

int result = 0;//默认的判断结果为两个对象相等

switch(orderByColumn){//判断排序条件

case 1:

String s1 = CnToSpell.getFullSpell(p1.getName());

String s2 = CnToSpell.getFullSpell(p2.getName());

if(orderByMode){//升序

result = s1.compareTo(s2);

}

else{//降序

result = s2.compareTo(s1);

}

break;

case 2:

if(orderByMode){//升序

result = (int)(p1.getId_card()-p2.getId_card());

}

else{//降序

result = (int)(p2.getId_card()-p1.getId_card());

}

break;

}

return result;

}

public void orderByColumn(int orderByColumn){//用来设置排序条件

this.orderByColumn = orderByColumn;

}

public void orderByMode(boolean orderByMode){//用来设置排序方式

this.orderByMode = orderByMode;

}

}

}

[java] view
plaincopyprint?





import java.util.*;

public class TestSet{

public static void main(String args[]){

TreeSet<Person> treeSet1 = new TreeSet<Person>();

Person p1 = new Person("马先生",22015);

Person p2 = new Person("李先生",22016);

Person p3 = new Person("王小姐",22018);

treeSet1.add(p1);

treeSet1.add(p2);

treeSet1.add(p3);

System.out.println("客户化排序前,默认按编号升序排序:");

//新创建一个Set集合,不进行客户化排序,默认按编号升序排序

TreeSet<Person> treeSet2 = new TreeSet<Person>(treeSet1);//通过构造函数初始化集合

Iterator<Person> it1 = treeSet2.iterator();

while(it1.hasNext()){

Person p = it1.next();

System.out.println(p.getId_card()+" "+p.getName());

}

System.out.println("客户化排序后,按编号降序排序:");

//新创建一个Set集合,进行客户化排序,客户化排序方式为按编号降序排序

Person.PersonComparator pc = new Person.PersonComparator();//创建比较器(内部类)的实例

pc.orderByColumn(Person.PersonComparator.ID_CARD);//设置排序依据的属性

pc.orderByMode(Person.PersonComparator.DESC);//设置排序方式

TreeSet<Person> treeSet3 = new TreeSet<Person>(pc);//必须通过构造函数设置比较器

treeSet3.addAll(treeSet1);//初始化集合

Iterator<Person> it2 = treeSet3.iterator();

while(it2.hasNext()){

Person p = it2.next();

System.out.println(p.getId_card()+" "+p.getName());

}

}

}

程序的运行结果如下:

客户化排序前,默认按编号升序排序:

22015 马先生

22016 李先生

22018 王小姐

客户化排序后,按编号降序排序:

22018 王小姐

22016 李先生

22015 马先生

用LinkedHashSet取出ArrayList中的重复数据

因为LinkedHashSet是有序和不能重复的

public static void listTest1(){
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("a");
for(String s:list){
//			System.out.println(s);
}

list = function(list);

for(String s :list){
System.out.println(s);
}
}

public static <E> List<E> function(List<E> list){
return new ArrayList<E>(new LinkedHashSet<E>(list));
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: