现在的位置: 首页 > 综合 > 正文

java集合的总结!

2014年10月08日 ⁄ 综合 ⁄ 共 6672字 ⁄ 字号 评论关闭

    java集合大体可分为三类,分别是Set、List和Map,它们都继承了基类接口Collection,Collection接口定义了众多操作集合的基本方法,如下:

    为了访问Collection集合,不得不去了解Iterator接口。该接口很简单,主要用于定义访问集合的方法,如下:

    所以上述的三大类子集合必定都继承了上面2个接口。其中Set集合要求元素不重复,且内部无序,所以访问时只能根据元素值来访问;List内部为动态数组,支持有序,元素也可重复,所以往往有index;Map所代表的集合是具有Key-Value的映射关系的集合,如哈希表。

1.Set

 

1.1Set不可添加相同元素

import java.util.Collection;
import java.util.Iterator;
import java.util.HashSet;
public class TestSet{
	public static void main(String[] args){	
		Collection c1=new HashSet();
		Person p=new Person();
		c1.add(p);
		c1.add(p);
		System.out.println(c1);
		
		Collection c2=new HashSet();
		String str1=new String("123");
		String str2=new String("123");
		c2.add(str1);
		c2.add(str2);
		System.out.println(c2);		
		}
}
class Person{
	public Person(){}
	public Person(String name){this.name=name;}
        public String name;
}

输出:

[Person@c17164]
[123]

    第一次添加了俩次p对象,集合不会重复添加,所以输出了[Person@c17164],这很合理。但是第二次明明new了两个字符串,str1和str2的引用肯定是不同的,那为什么程序还是会认为是相同的元素呢。查找add(E e)的源码,找到了其中的关键部分,如下

 public boolean add(E e) {
	return map.put(e, PRESENT)==null;
    }

 public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        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;
    }

这一句,

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

    表明,当两个对象的哈希值相等并且对象的equals方法返回真时,则认为两个对象是相同的,并不会进行后面的addEntry操作,即不会添加至集合。

    这也就难怪String str1=new String("123")和String str2=new String("123");被认为是同一个对象了,因为String在做equals的时候恰好很特殊,只要值相等,则euqals就返回真。

    为了测试源码是否真的是这么执行的,改写程序如下:

import java.util.Collection;
import java.util.Iterator;
import java.util.HashSet;
public class TestSet{
	public static void main(String[] args){		
		Collection c1=new HashSet();
		c1.add(new A());
		c1.add(new A());
		c1.add(new B());
		c1.add(new B());
		c1.add(new C());
		c1.add(new C());	
		System.out.println(c1);
	}	
}
class A{
	@Override
	public boolean equals(Object obj){
		return true;
	}
	@Override
	public int hashCode(){
		return 1;
	}
}
class B{
	@Override
	public int hashCode(){
		return 1;
	}
}
class C{
	@Override
	public boolean equals(Object obj){
		return true;
	}
}

输出:

[B@1, B@1, A@1, C@c17164, C@de6ced]

    可以看到,B和C的对象都没有被集合认为是同一个对象,而A类中重写的哈希值和equals永远相等,导致A类new出的匿名对象也是相等的,故只添加了一个。

1.2Set不可修改元素的值

import java.util.Collection;
import java.util.Iterator;
import java.util.HashSet;
public class TestSet{
	public static void main(String[] args){
		
		Collection coll=new HashSet();
		coll.add(new Person("f"));
		coll.add(new Person("l"));
		coll.add(new Person("y"));
		System.out.println(coll);//a
		
		Iterator it=coll.iterator();//b
		while(it.hasNext()){
			Person p=(Person)it.next();
			if(p.name.equals("f")){
				p=new Person();//c
			}
		}
		Iterator it1=coll.iterator();//d
	        while(it1.hasNext()){
			Person p=(Person)it1.next();
			System.out.println(p.name);
		}
		System.out.println(coll);
	}
}
class Person{
	public Person(){}
	public Person(String name){this.name=name;}
        public String name;
}

输出:

[Person@1fb8ee3, Person@c17164, Person@61de33]
l
f
y
[Person@1fb8ee3, Person@c17164,
Person@61de33
]

    代码输出表明,HashSet集合的元素并不是有序的,另外在代码c处取出了元素后,为该元素重新赋值,而后输出发现集合并没有改变,这说明iterator迭代器在提供next的方法里应该是类似于copy的技术,目的就是防止在遍历set集合的时候元素被改变。

 

2.List

 

    List作为Collection的子接口,自然可以调用父接口的基本方法,但由于List集合元素是有序的,所以List接口在父接口的基础上又增加了些方法。这些方法的作用与类父接口类似,只是都会增加一个index参数做为索引。

    List中最常用的就是ArrayList,它在Vector的基础上做了许多改进,下面代码将展示List的基本操作用法:

import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
public class TestList{
	public static void main(String[] args){
        //向list中添加不同类型的元素,会自动装箱
	List list=new ArrayList();
	list.add(1);
	list.add("123");
	list.add(3.14f);
	//列表元素:[1, 123, 3.14]
	System.out.println("列表元素:"+list);
		
	  //清除列表
	  list.clear();
	  
	  list.add("我");
	  list.add("们");
	  list.add("交");
	  list.add("个");
	  list.add("朋");
	  list.add("友");
	  list.add("吧");
	  
	  //列表元素:[我, 们, 交, 个, 朋, 友, 吧]
	  System.out.println("列表元素:"+list);
	  
	  List sub1=list.subList(0,list.size()/2);
	  //子列表元素:[我, 们, 交]
	  System.out.println("子列表元素:"+sub1);

	  //从list中删除sub
	  sub1.removeAll(list);
	  
	  //列表元素:[个, 朋, 友, 吧]
	  System.out.println("列表元素:"+list);
	  
	  //添加至头
	  List sub2=new ArrayList();
	  sub2.add("我");
	  sub2.add("们");
	  sub2.add("交");
	  System.out.println("子列表元素:"+sub2);
	  //在list中添加sub2
	  list.addAll(0,sub2);
	  System.out.println("列表元素:"+list);
	  
	  //遍历操作
	  ListIterator iter=list.listIterator();
	  System.out.println("--正向遍历--");
	  while(iter.hasNext()){
	  	System.out.println(iter.next());
	  }
	  System.out.println("--反向遍历--");
	  while(iter.hasPrevious()){
	  	System.out.println(iter.previous());
	  }

	}

}

输出:

列表元素:[1, 123, 3.14]
列表元素:[我, 们, 交, 个, 朋, 友, 吧]
子列表元素:[我, 们, 交]
列表元素:[个, 朋, 友, 吧]
子列表元素:[我, 们, 交]
列表元素:[我, 们, 交, 个, 朋, 友, 吧]
--正向遍历--







--反向遍历--







    List就像是一个动态且元素类型可不一的数组,它不仅具有iterator迭代器,而且还有listIterator,后者就像数组一样,支持正向和反向遍历。
 

3.Map

 

      Map是具有映射关系的集合,key做为主键,可以索引到唯一的value,key和value都可以是对象。如果单独取出Map里的所有值的话,Map看起来就像是Set,而又由于它较之Set又具有索引功能,所以又似乎有些List的影子。实际上,Map的key必须实现equals和hashCode方法,这也就解释了为什么可以将一个对象的引用做为key了(实际上是计算这个对象的hashCode做为主键),因此不能将同一个对象的引用存入某一个Map中。HashSet实现了Set接口,ArrayList实现了List接口,那么单从命名上就能得知,HashMap肯定实现了Map接口,Map接口的功能如下,

    在HashSet和ArrayList都有一个访问迭代器的方法iterator(),在Set接口中却没有,毕竟Set是key-value组合,取而代之的是一个keySet()方法,用以返回一个实现了Set接口的对象,从而又可以进行iterator的操作。

基本操作如下,

import java.util.HashMap;
import java.util.Iterator;
public class TestMap{
	public static void main(String[] args){	
		HashMap hash=new HashMap();
		hash.put("1","我");
		hash.put("2","们");
		//主键可为null,但只能有一个null值的主键
		hash.put(null,null);
		//值可以为null,可以有很多个值为null
		hash.put("3",null);
		hash.put("4",null);
		
		System.out.println("直接遍历:"+hash);
		System.out.println("----keySey遍历----:");
		for(Object key :hash.keySet()){
	           System.out.println("key:"+key+" value:"+hash.get(key));
	         }
	         System.out.println("----iterator遍历----:");
	         Iterator iter=hash.keySet().iterator();
	         while(iter.hasNext()){
	  	  String key=(String)iter.next();
		  System.out.println("key:"+key+" value:"+hash.get(key));
	         }
                }
}

输出:

直接遍历:{3=null, null=null, 2=们, 1=我, 4=null}
----keySey遍历----:
key:3 value:null
key:null value:null
key:2 value:们
key:1 value:我
key:4 value:null
----iterator遍历----:
key:3 value:null
key:null value:null
key:2 value:们
key:1 value:我
key:4 value:null

    HashMap可以有空key,但是只能有一个,,这符合唯一主键的原则,并且若主键重复了,则会覆盖之前的相同主键。而值却没有限制,有多少个null都可以。此外,在使用HashMap的时候还需要注意下面两点:

1.HashMap是非线程安全的,而Hashtable是线程安全的。

    对于各种集合的各种操作,其实可以依赖于Collections类,该类提供了许多静态操作集合的方法,其中就可以将一个普通集合封装为线程安全的集合,如下

    Collection c=Collections.synchronized(new ArrayList());

2.了解HashMap的性能

    HashMap利用每一个key的哈希值,去为value找寻存储位置。这个存储位置往往被称为“桶”,当哈希值唯一时,那么一个桶中就只有一个对象,这时情况最理想,然而若非正常情况下(比如重写hashCode强制返回相等),那么一个桶能就有放多个对象,这时性能最差。

    上面说道,HashMap与Set、List在某方面都很相似,做为一个强大的集合,它的内部自然也有会动态开辟内存的操作。所有就有了下面几个参数,

    capacity(容量):在初始化HashMap时将会有一个默认值(好象是10吧),随着集合的大小也会自身调整。

    size(元素个数):有多少个元素size就是多少。

    load factor(负载因子):load factor=size/capacity,取值0~1。

    当负载因子很大时,如有90个元素,而集合的容量为100,因子就是0.9,这样情况非常不利于查询操作,因为put和get操作会遍历大量的元素,时间复杂度无形就会增加,但在内存开销上确实是比较节省的,因为集合不会反复的创建,因为每一次扩充集合的操作,就意味着要将原始元素重新插入到新的集合中去,性能开销是很大的。

    而当负载因子很小时,查询效率将会非常高(因为遍历少),但是却在内部进行了许多次开辟内存的操作。

    因此,在系统中,要根据实际需求正确把握HashMap的用法,如一开始建立集合的时候就知道这个集合非常大,那么就有必要在初始化的时候就指明capacity,不应该使用默认值,这样效率能高点;相反只有少量集合元素时,不应该在创建的时候指定很大的capacity,这明显是在浪费内存。 

 

 

 

 

 

 

 

 

 

 

 

 

抱歉!评论已关闭.