集合类
(Collection, List, Set, Map)
u Collection – 对象之间没有指定的顺序,允许重复元素。
u Set – 对象之间没有指定的顺序,不允许重复元素
u List– 对象之间有指定的顺序,允许重复元素,并引入位置下标。
u Map – 接口用于保存关键字(Key)和数值(Value)的集合,集合中的每个对象加入时都提供数值和关键字。Map 接口既不继承 Set 也不继承 Collection。
List、Set、Map共同的实现基础是Object数组
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
除了四个历史集合类外,还引入了六个集合实现,如下表所示。
接口 | 实现 | 历史集合类 |
Set | HashSet |
|
| TreeSet |
|
List | ArrayList | Vector |
| LinkedList | Stack |
Map | HashMap | Hashtable |
| TreeMap | Properties |
List,Set,Map将持有对象一律视为Object型别。
Collection、List、Set、Map都是接口,不能实例化。
1.2 Collection
集合必须只有对象,集合中的元素不能是基本数据类型。
Collection接口支持如添加和除去等基本操作。设法除去一个元素时,如果这个元素存在,除去的仅仅是集合中此元素的一个实例。
u booleanadd(Object element)
u booleanremove(Object element)
Collection接口还支持查询操作:
u int size()
u booleanisEmpty()
u booleancontains(Object element)
u Iteratoriterator()
组操作 :Collection 接口支持的其它操作,要么是作用于元素组的任务,要么是同时作用于整个集合的任务。
u boolean containsAll(Collection collection)
u boolean addAll(Collection collection)
u void clear()
u void removeAll(Collection collection)
u void retainAll(Collection collection)
containsAll() 方法允许您查找当前集合是否包含了另一个集合的所有元素,即另一个集合是否是当前集合的子集。其余方法是可选的,因为特定的集合可能不支持集合更改。 addAll() 方法确保另一个集合中的所有元素都被添加到当前的集合中,通常称为并。 clear() 方法从当前集合中除去所有元素。 removeAll() 方法类似于 clear() ,但只除去了元素的一个子集。 retainAll() 方法类似于 removeAll() 方法,不过可能感到它所做的与前面正好相反:它从当前集合中除去不属于另一个集合的元素,即交。
我们看一个简单的例子,来了解一下集合类的基本方法的使用:
import java.util.*;
public class CollectionToArray {
public static voidmain(String[] args) {
Collection collection1=newArrayList();//创建一个集合对象
collection1.add("000");//添加对象到Collection集合中
collection1.add("111");
collection1.add("222");
System.out.println("集合collection1的大小:"+collection1.size());
System.out.println("集合collection1的内容:"+collection1);
collection1.remove("000");//从集合collection1中移除掉 "000" 这个对象
System.out.println("集合collection1移除 000 后的内容:"+collection1);
System.out.println("集合collection1中是否包含000 :"+collection1.contains("000"));
System.out.println("集合collection1中是否包含111 :"+collection1.contains("111"));
Collection collection2=newArrayList();
collection2.addAll(collection1);//将collection1 集合中的元素全部都加到collection2中
System.out.println("集合collection2的内容:"+collection2);
collection2.clear();//清空集合 collection1 中的元素
System.out.println("集合collection2是否为空 :"+collection2.isEmpty());
//将集合collection1转化为数组
Object s[]= collection1.toArray();
for(inti=0;i<s.length;i++){
System.out.println(s[i]);
}
}
}
运行结果为:
集合collection1的大小:3
集合collection1的内容:[000, 111, 222]
集合collection1移除 000 后的内容:[111,222]
集合collection1中是否包含000 :false
集合collection1中是否包含111 :true
集合collection2的内容:[111, 222]
集合collection2是否为空 :true
111
222
1.2.2 迭代器
迭代器(Iterator)的概念,也是出于一种设计模式就是为达成此目的而形成的。所以Collection不提供get()方法。如果要遍历Collectin中的元素,就必须用Iterator。
下面,我们看一个对于迭代器的简单使用:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add("s1");
collection.add("s2");
collection.add("s3");
Iterator iterator =collection.iterator();//得到一个迭代器
while (iterator.hasNext()) {//遍历
Object element =iterator.next();
System.out.println("iterator = " + element);
}
if(collection.isEmpty())
System.out.println("collection is Empty!");
else
System.out.println("collection is not Empty! size="+collection.size());
Iterator iterator2 =collection.iterator();
while (iterator2.hasNext()) {//移除元素
Object element = iterator2.next();
System.out.println("remove: "+element);
iterator2.remove();
}
Iterator iterator3 =collection.iterator();
if (!iterator3.hasNext()){//察看是否还有元素
System.out.println("没有元素");
}
if(collection.isEmpty())
System.out.println("collection is Empty!");
//使用collection.isEmpty()方法来判断
}
}
程序的运行结果为:
iterator = s1
iterator = s2
iterator = s3
collection is not Empty! size=3
remove: s1
remove: s2
remove: s3
没有元素
collection is Empty!
可以看到,Java的Collection的Iterator 能够用来,:
1) 使用方法 iterator() 要求容器返回一个Iterator .第一次调用Iterator 的next() 方法时,它返回集合序列的第一个元素。
2) 使用next() 获得集合序列的中的下一个元素。
3) 使用hasNext()检查序列中是否元素。
4) 使用remove()将迭代器新返回的元素删除。
需要注意的是:方法删除由next方法返回的最后一个元素,在每次调用next时,remove方法只能被调用一次 。
大家看,Java 实现的这个迭代器的使用就是如此的简单。Iterator(跌代器)虽然功能简单,但仍然可以帮助我们解决许多问题,同时针对List 还有一个更复杂更高级的ListIterator。
1.3 List
List就是列表的意思,它是Collection 的一种,即
继承了 Collection
接口,以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。List是按对象的进入顺序进行保存对象,而不做排序或编辑操作。它除了拥有Collection接口的所有的方法外还拥有一些其他的方法。
面向位置的操作包括插入某个元素或 Collection 的功能,还包括获取、除去或更改元素的功能。在 List 中搜索元素可以从列表的头部或尾部开始,如果找到元素,还将报告元素所在的位置。
u void add(intindex, Object element) :添加对象element到位置index上
u booleanaddAll(int index, Collection collection) :在index位置后添加容器collection中所有的元素
u Object get(intindex) :取出下标为index的位置的元素
u intindexOf(Object element) :查找对象element 在List中第一次出现的位置
u intlastIndexOf(Object element) :查找对象element 在List中最后出现的位置
u Objectremove(int index) :删除index位置上的元素
u Object set(intindex, Object element) :将index位置上的对象替换为element 并返回老的元素。
先看一下下面表格:
在“集合框架”中有两种常规的List实现:ArrayList和LinkedList。使用两种 List实现的哪一种取决于您特定的需要。如果要支持随机访问,而不必在除尾部的任何位置插入或除去元素,那么,ArrayList提供了可选的集合。但如果,您要频繁的从列表的中间位置添加和除去元素,而只要顺序的访问列表元素,那么,LinkedList实现更好。
而LinkedList添加了一些处理列表两端元素的方法,使用这些新方法,您就可以轻松的把 LinkedList 当作一个堆栈、队列或其它面向端点的数据结构。
我们再来看另外一个使用LinkedList 来实现一个简单的队列的例子:
import java.util.*;
public class ListExample {
public static void main(String args[]) {
LinkedList queue = new LinkedList();
queue.addFirst("Bernadine");
queue.addFirst("Elizabeth");
queue.addFirst("Gene");
queue.addFirst("Elizabeth");
queue.addFirst("Clara");
System.out.println(queue);
queue.removeLast();
queue.removeLast();
System.out.println(queue);
}
}
运行程序产生了以下输出。请注意,与Set不同的是List允许重复。
[Clara, Elizabeth, Gene,Elizabeth, Bernadine]
[Clara, Elizabeth, Gene]
ListIterator 接口
ListIterator
接口继承Iterator
接口以支持添加或更改底层集合中的元素,还支持双向访问。
我们看一个List的例子:
import java.util.*;
public class ListIteratorTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
System.out.println("下标0开始:"+list.listIterator(0).next());//next()
System.out.println("下标1开始:"+list.listIterator(1).next());
System.out.println("子List 1-3:"+list.subList(1,3));//子列表
ListIterator it =list.listIterator();//默认从下标0开始
//隐式光标属性add操作 ,插入到当前的下标的前面
it.add("sss");
while(it.hasNext()){
System.out.println("next Index="+it.nextIndex()+",Object="+it.next());
}
//set属性
ListIterator it1 =list.listIterator();
it1.next();
it1.set("ooo");
ListIterator it2 =list.listIterator(list.size());//下标
while(it2.hasPrevious()){
System.out.println("previous Index="+it2.previousIndex()+",Object="+it2.previous());
}
}
}
程序的执行结果为:
下标0开始:aaa
下标1开始:bbb
子List 1-3:[bbb, ccc]
next Index=1,Object=aaa
next Index=2,Object=bbb
next Index=3,Object=ccc
next Index=4,Object=ddd
previous Index=4,Object=ddd
previous Index=3,Object=ccc
previous Index=2,Object=bbb
previous Index=1,Object=aaa
previous Index=0,Object=ooo
1.4 Map
Map
接口不是Collection
接口的继承。而是从自己的用于维护键-值关联的接口层次结构入手。按定义,该接口描述了从不重复的键到值的映射。
我们可以把这个接口方法分成三组操作:改变、查询和提供可选视图。
改变操作允许您从映射中添加和除去键-值对。键和值都可以为 null。但是,您不能把 Map 作为一个键或值添加给自身。
u
Object put(Object key,Object value)
:用来存放一个键
-
值对
Map
中
u
Object remove(Object key)
:根据
key(
键
)
,移除一个键
-
值对,并将值返回
u
voidputAll(Map mapping)
:将另外一个
Map
中的元素存入当前的
Map
中
u
void clear()
:清空当前
Map
中的元素
查询操作允许您检查映射内容:
u
Object get(Object key)
:根据
key(
键
)
取得对应的值
u
boolean containsKey(Object key)
:判断
Map
中是否存在某键(
key
)
u
boolean containsValue(Object value):
判断
Map
中是否存在某值
(value)
u
int size():
返回
Map
中
键-值对的个数
u
boolean isEmpty()
:判断当前
Map
是否为空
最后一组方法允许您把键或值的组作为集合来处理。
u
publicSet keySet()
:返回所有的键(
key
),并使用
Set
容器存放
u
public Collection values()
:返回所有的值(
Value
),并使用
Collection
存放
u
publicSet entrySet()
:
返回一个实现Map.Entry接口的元素Set
因为映射中键的集合必须是唯一的,就使用 Set 来支持。因为映射中值的集合可能不唯一,就使用 Collection 来支持。最后一个方法返回一个实现 Map.Entry 接口的元素 Set。
下面我们看一个简单的例子:
import java.util.*;
public class MapTest {
public static void main(String[] args) {
Map map1 = new HashMap();
Map map2 = new HashMap();
map1.put("1","aaa1");
map1.put("2","bbb2");
map2.put("10","aaaa10");
map2.put("11","bbbb11");
//根据键 "1" 取得值:"aaa1"
System.out.println("map1.get(\"1\")="+map1.get("1"));
// 根据键 "1" 移除键值对"1"-"aaa1"
System.out.println("map1.remove(\"1\")="+map1.remove("1"));
System.out.println("map1.get(\"1\")="+map1.get("1"));
map1.putAll(map2);//将map2全部元素放入map1中
map2.clear();//清空map2
System.out.println("map1 IsEmpty?="+map1.isEmpty());
System.out.println("map2 IsEmpty?="+map2.isEmpty());
System.out.println("map1 中的键值对的个数size = "+map1.size());
System.out.println("KeySet="+map1.keySet());//set
System.out.println("values="+map1.values());//Collection
System.out.println("entrySet="+map1.entrySet());
System.out.println("map1 是否包含键:11 = "+map1.containsKey("11"));
System.out.println("map1 是否包含值:aaa1 = "+map1.containsValue("aaa1"));
}
}
运行输出结果为:
map1.get("1")=aaa1
map1.remove("1")=aaa1
map1.get("1")=null
map1 IsEmpty?=false
map2 IsEmpty?=true
map1 中的键值对的个数size = 3
KeySet=[10, 2, 11]
values=[aaaa10, bbb2, bbbb11]
entrySet=[10=aaaa10, 2=bbb2, 11=bbbb11]
map1 是否包含键:11 = true
map1 是否包含值:aaa1 = false
在该例子中,我们创建一个HashMap,并使用了一下Map接口中的各个方法。
其中Map中的entrySet()方法返回一个实现 Map.Entry 接口的对象集合。集合中每个对象都是底层Map 中一个特定的键-值对。
我们再看看排序的Map是如何使用:
import java.util.*;
public class MapSortExample {
public static void main(String args[]) {
Map map1 = new HashMap();
Map map2 = new LinkedHashMap();
for(int i=0;i<10;i++){
double s=Math.random()*100;//产生一个随机数,并将其放入Map中
map1.put(new Integer((int) s),"第 "+i+" 个放入的元素:"+s+"\n");
map2.put(new Integer((int) s),"第 "+i+" 个放入的元素:"+s+"\n");
}
System.out.println("未排序前HashMap:"+map1);
System.out.println("未排序前LinkedHashMap:"+map2);
//使用TreeMap来对另外的Map进行重构和排序
Map sortedMap = new TreeMap(map1);
System.out.println("排序后:"+sortedMap);
System.out.println("排序后:"+new TreeMap(map2));
}
}
该程序的一次运行结果为:
未排序前HashMap:{64=第 1 个放入的元素:64.05341725531845
, 15=第 9 个放入的元素:15.249165766266382
, 2=第 4 个放入的元素:2.66794706854534
, 77=第 0 个放入的元素:77.28814965781416
, 97=第 5 个放入的元素:97.32893518378948
, 99=第 2 个放入的元素:99.99412014935982
, 60=第 8 个放入的元素:60.91451419025399
, 6=第 3 个放入的元素:6.286974058646977
, 1=第 7 个放入的元素:1.8261658496439903
, 48=第 6 个放入的元素:48.736039522423106
}
未排序前LinkedHashMap:{77=第 0 个放入的元素:77.28814965781416
, 64=第 1 个放入的元素:64.05341725531845
, 99=第 2 个放入的元素:99.99412014935982
, 6=第 3 个放入的元素:6.286974058646977
, 2=第 4 个放入的元素:2.66794706854534
, 97=第 5 个放入的元素:97.32893518378948
, 48=第 6 个放入的元素:48.736039522423106
, 1=第 7 个放入的元素:1.8261658496439903
, 60=第 8 个放入的元素:60.91451419025399
, 15=第 9 个放入的元素:15.249165766266382
}
排序后:{1=第 7 个放入的元素:1.8261658496439903
, 2=第 4 个放入的元素:2.66794706854534
, 6=第 3 个放入的元素:6.286974058646977
, 15=第 9 个放入的元素:15.249165766266382
, 48=第 6 个放入的元素:48.736039522423106
, 60=第 8 个放入的元素:60.91451419025399
, 64=第 1 个放入的元素:64.05341725531845
, 77=第 0 个放入的元素:77.28814965781416
, 97=第 5 个放入的元素:97.32893518378948
, 99=第 2 个放入的元素:99.99412014935982
}
排序后:{1=第 7 个放入的元素:1.8261658496439903
, 2=第 4 个放入的元素:2.66794706854534
, 6=第 3 个放入的元素:6.286974058646977
, 15=第 9 个放入的元素:15.249165766266382
, 48=第 6 个放入的元素:48.736039522423106
, 60=第 8 个放入的元素:60.91451419025399
, 64=第 1 个放入的元素:64.05341725531845
, 77=第 0 个放入的元素:77.28814965781416
, 97=第 5 个放入的元素:97.32893518378948
, 99=第 2 个放入的元素:99.99412014935982
}
从运行结果,我们可以看出,HashMap的存入顺序和输出顺序无关。而LinkedHashMap 则保留了键值对的存入顺序。TreeMap则是对Map中的元素进行排序。在实际的使用中我们也经常这样做:使用HashMap或者LinkedHashMap 来存放元素,当所有的元素都存放完成后,如果使用则是需要一个经过排序的Map的话,我们再使用TreeMap来重构原来的Map对象。这样做的好处是:因为HashMap和LinkedHashMap 存储数据的速度比直接使用TreeMap 要快,存取效率要高。当完成了所有的元素的存放后,我们再对整个的Map中的元素进行排序。这样可以提高整个程序的运行的效率,缩短执行时间。
这里需要注意的是,TreeMap中是根据键(Key)进行排序的。而如果我们要使用TreeMap来进行正常的排序的话,Key 中存放的对象必须实现Comparable 接口。
我们再回到Map中来,Java提高的API中除了上面介绍的几种Map比较常用以为还有一些Map,大家可以了解一下:
u WeakHashMap: WeakHashMap 是 Map 的一个特殊实现,它只用于存储对键的弱引用。当映射的某个键在 WeakHashMap 的外部不再被引用时,就允许垃圾收集器收集映射中相应的键值对。使用 WeakHashMap 有益于保持类似注册表的数据结构,其中条目的键不再能被任何线程访问时,此条目就没用了。
u IdentifyHashMap: Map的一种特性实现,关键属性的hash码不是由hashCode()方法计算,而是由System.identityHashCode 方法计算,使用==进行比较而不是equals()方法。
通过简单的对与Map中各个常用实现类的使用,为了更好的理解Map,下面我们再来了解一下Map的实现原理。
下面我们以HashMap为例,对Map的实现机制作一下更加深入一点的理解。Hash,一般翻译做“散列”,也有直接音译为"哈希"的,我们建立一个HashTable(哈希表),该表的长度为N,然后我们分别在该表中的格子中存放不同的元素。每个格子下面存放的元素又是以链表的方式存放元素。哈希码就是将对象的信息经过一些转变形成一个独一无二的int值,这个值存储在一个array中。我们都知道所有存储结构中,array查找速度是最快的。所以,可以加速查找。发生冲突时,让array指向多个values。即,数组每个位置上又生成一个梿表。
使用keySet()抽取key序列,将map中的所有keys生成一个Set。
使用values()抽取value序列,将map中的所有values生成一个Collection。
为什么一个生成Set,一个生成Collection?因为key总是独一无二的,value允许重复。
u 当添加一个新的元素Entry的时候,首先我们通过一个Hash函数计算出这个Entry元素的Hash值hashcode。通过该hashcode值,就可以直接定位出我们应该把这个Entry元素存入到Hash表的哪个格子中,如果该格子中已经存在元素了,那么只要把新的Entry元存放到这个链表中即可。
u 如果要查找一个元素Entry的时候,也同样的方式,通过Hash函数计算出这个Entry元素的Hash值hashcode。然后通过该hashcode值,就可以直接找到这个Entry是存放到哪个格子中的。接下来就对该格子存放的链表元素进行逐个的比较查找就可以了。
举一个比较简单的例子来说明这个算法的运算方式:
假定我们有一个长度为8的Hash表(可以理解为一个长度为8的数组)。在这个Hash表中存放数字:如下表
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
假定我们的Hash函数为:
Hashcode = X%8 -------- 对8 取余数
其中X就是我们需要放入Hash表中的数字,而这个函数返回的Hashcode就是Hash码。
假定我们有下面10个数字需要依次存入到这个Hash表中:
11 , 23 , 44 , 9 , 6 , 32 , 12 , 45 , 57 , 89
通过上面的Hash函数,我们可以得到分别对应的Hash码:
11――3 ; 23――7 ;44――4 ;9――1;6――6;32――0;12――4;45――5;57――1;89――1;
计算出来的Hash码分别代表,该数字应该存放到Hash表中的哪个对应数字的格子中。如果改格子中已经有数字存在了,那么就以链表的方式将数字依次存放在该格子中,如下表:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
32 | 9 |
| 11 | 44 | 45 | 6 | 23 |
| 57 |
|
| 12 |
|
|
|
| 89 |
|
|
|
|
|
|
Hash表和Hash算法的特点就是它的存取速度比数组差一些,但是比起单纯的链表,在查找和存储方面却要好很多。
1.4.5 重写hashCode()
我们看一个简单的例子,就能更加清楚的理解上面的意思。假定我们写了一个类:Person (人),我们判断一个对象“人”是否指向同一个人,只要知道这个人的身份证号一直就可以了。
先看我们没有实现hashCode的情况:
import java.util.*;
//身份证类
class Code{
final int id;//身份证号码已经确认,不能改变
Code(int i){
id=i;
}
//身份号号码相同,则身份证相同
public boolean equals(Object anObject) {
if (anObject instanceof Code){
Code other=(Code) anObject;
return this.id==other.id;
}
return false;
}
public String toString() {
return "身份证:"+id;
}
}
//人员信息类
class Person {
Code id;// 身份证
String name;// 姓名
public Person(String name, Code id) {
this.id=id;
this.name=name;
}
//如果身份证号相同,就表示两个人是同一个人
public boolean equals(Object anObject) {
if (anObject instanceof Person){
Person other=(Person)anObject;
return this.id.equals(other.id);
}
return false;
}
public String toString() {
return "姓名:"+name+" 身份证:"+id.id+"\n";
}
}
public class HashCodeEx {
public static void main(String[] args) {
HashMap map=new HashMap();
Person p1=new Person("张三",new Code(123));
map.put(p1.id,p1);//我们根据身份证来作为key值存放到Map中
Person p2=new Person("李四",new Code(456));
map.put(p2.id,p2);
Person p3=new Person("王二",new Code(789));
map.put(p3.id,p3);
System.out.println("HashMap 中存放的人员信息:\n"+map);
// 张三改名为:张山但是还是同一个人。
Person p4=new Person("张山",new Code(123));
map.put(p4.id,p4);
System.out.println("张三改名后 HashMap 中存放的人员信息:\n"+map);
//查找身份证为:123 的人员信息
System.out.println("查找身份证为:123 的人员信息:"+map.get(new Code(123)));
}
}
运行结果为:
HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:123=姓名:张三身份证:123
, 身份证:789=姓名:王二 身份证:789
}
张三改名后 HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:123=姓名:张三身份证:123
, 身份证:123=姓名:张山身份证:123
, 身份证:789=姓名:王二 身份证:789
}
查找身份证为:123 的人员信息:null
上面的例子的演示的是,我们在一个HashMap中存放了一些人员的信息。并以这些人员的身份证最为人员的“键”。当有的人员的姓名修改了的情况下,我们需要更新这个HashMap。同时假如我们知道某个身份证号,想了解这个身份证号对应的人员信息如何,我们也可以根据这个身份证号在HashMap中得到对应的信息。
而例子的输出结果表示,我们所做的更新和查找操作都失败了。失败的原因就是我们的身份证类:Code 没有覆写hashCode()方法。这个时候,当查找一样的身份证号码的键值对的时候,使用的是默认的对象的内存地址来进行定位。这样,后面的所有的身份证号对象new Code(123) 产生的hashCode()值都是不一样的。所以导致操作失败。
下面,我们给Code类加上hashCode()方法,然后再运行一下程序看看:
//身份证类
class Code{
final int id;//身份证号码已经确认,不能改变
Code(int i){
id=i;
}
//身份号号码相同,则身份证相同
public boolean equals(Object anObject) {
if (anObject instanceof Code){
Code other=(Code) anObject;
return this.id==other.id;
}
return false;
}
public String toString() {
return "身份证:"+id;
}
//覆写hashCode方法,并使用身份证号作为hash值
public int hashCode(){
return id;
}
}
再次执行上面的HashCodeEx 的结果就为:
HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:789=姓名:王二 身份证:789
, 身份证:123=姓名:张三身份证:123
}
张三改名后 HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:789=姓名:王二 身份证:789
, 身份证:123=姓名:张山身份证:123
}
查找身份证为:123 的人员信息:姓名:张山身份证:123
这个时候,我们发现。我们想要做的更新和查找操作都成功了。
对于Map部分的使用和实现,主要就是需要注意存放“键值对”中的对象的equals()方法和hashCode()方法的覆写。如果需要使用到排序的话,那么还需要实现Comparable 接口中的compareTo()方法。我们需要注意Map中的“键”是不能重复的,而是否重复的判断,是通过调用“键”对象的equals()方法来决定的。而在HashMap中查找和存取“键值对”是同时使用hashCode()方法和equals()方法来决定的。
1.5 Set
按照定义,Set
接口继承 Collection
接口,而且它不允许集合中存在重复项。所有原始方法都是现成的,没有引入新方法。具体的 Set
实现类依赖添加的对象的 equals()
方法来检查等同性。
我们简单的描述一下各个方法的作用:
u public int size() :返回set中元素的数目,如果set包含的元素数大于Integer.MAX_VALUE,返回Integer.MAX_VALUE
u public boolean isEmpty() :如果set中不含元素,返回true
u public booleancontains(Object o) :如果set包含指定元素,返回true
u public Iterator iterator()
l 返回set中元素的迭代器
l 元素返回没有特定的顺序,除非set是提高了该保证的某些类的实例
u public Object[] toArray() :返回包含set中所有元素的数组
u public Object[] toArray(Object[] a):返回包含set中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型
u public boolean add(Object o) :如果set中不存在指定元素,则向set加入
u public boolean remove(Object o) :如果set中存在指定元素,则从set中删除
u public booleanremoveAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素
u public booleancontainsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一个set,只有是当前set的子集时,方法返回true
u public booleanaddAll(Collection c) :如果set中中不存在指定集合的元素,则向set中加入所有元素
u public booleanretainAll(Collection c) :只保留set中所含的指定集合的元素(可选操作)。换言之,从set中删除所有指定集合不包含的元素。 如果指定集合也是一个set,那么该操作修改set的效果是使它的值为两个set的交集
u public booleanremoveAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素
u public void clear() :从set中删除所有元素
“集合框架” 支持 Set 接口两种普通的实现:HashSet 和 TreeSet以及LinkedHashSet。下表中是Set的常用实现类的描述:
在更多情况下,您会使用 HashSet 存储重复自由的集合。同时HashSet中也是采用了Hash算法的方式进行存取对象元素的。所以添加到 HashSet 的对象对应的类也需要采用恰当方式来实现 hashCode() 方法。虽然大多数系统类覆盖了 Object 中缺省的 hashCode() 实现,但创建您自己的要添加到 HashSet 的类时,别忘了覆盖 hashCode()。
对于Set的使用,我们先以一个简单的例子来说明:
import java.util.*;
public class HashSetDemo {
public static void main(String[] args) {
Set set1 = new HashSet();
if (set1.add("a")) {//添加成功
System.out.println("1 add true");
}
if (set1.add("a")) {//添加失败
System.out.println("2 add true");
}
set1.add("000");//添加对象到Set集合中
set1.add("111");
set1.add("222");
System.out.println("集合set1的大小:"+set1.size());
System.out.println("集合set1的内容:"+set1);
set1.remove("000");//从集合set1中移除掉 "000" 这个对象
System.out.println("集合set1移除 000 后的内容:"+set1);
System.out.println("集合set1中是否包含000 :"+set1.contains("000"));
System.out.println("集合set1中是否包含111 :"+set1.contains("111"));
Set set2=new HashSet();
set2.add("111");
set2.addAll(set1);//将set1 集合中的元素全部都加到set2中
System.out.println("集合set2的内容:"+set2);
set2.clear();//清空集合 set1 中的元素
System.out.println("集合set2是否为空:"+set2.isEmpty());
Iterator iterator =set1.iterator();//得到一个迭代器
while (iterator.hasNext()) {//遍历
Object element =iterator.next();
System.out.println("iterator = " + element);
}
//将集合set1转化为数组
Object s[]= set1.toArray();
for(int i=0;i<s.length;i++){
System.out.println(s[i]);
}
}
}
程序执行的结果为:
1 add true
集合set1的大小:4
集合set1的内容:[222, a, 000, 111]
集合set1移除 000 后的内容:[222, a, 111]
集合set1中是否包含000 :false
集合set1中是否包含111 :true
集合set2的内容:[222, a, 111]
集合set2是否为空 :true
iterator = 222
iterator = a
iterator = 111
222
a
111
从上面的这个简单的例子中,我们可以发现,Set中的方法与直接使用Collection中的方法一样。唯一需要注意的就是Set中存放的元素不能重复。
我们再看一个例子,来了解一下其它的Set的实现类的特性:
import java.util.*;
public class SetSortExample {
public static void main(String args[]) {
Set set1 = new HashSet();
Set set2 = new LinkedHashSet();
for(int i=0;i<5;i++){
//产生一个随机数,并将其放入Set中
int s=(int) (Math.random()*100);
set1.add(new Integer( s));
set2.add(new Integer( s));
System.out.println("第 "+i+" 次随机数产生为:"+s);
}
System.out.println("未排序前HashSet:"+set1);
System.out.println("未排序前LinkedHashSet:"+set2);
//使用TreeSet来对另外的Set进行重构和排序
Set sortedSet = new TreeSet(set1);
System.out.println("排序后 TreeSet :"+sortedSet);
}
}
该程序的一次执行结果为:
第 0 次随机数产生为:96
第 1 次随机数产生为:64
第 2 次随机数产生为:14
第 3 次随机数产生为:95
第 4 次随机数产生为:57
未排序前HashSet:[64, 96, 95, 57, 14]
未排序前LinkedHashSet:[96, 64, 14, 95, 57]
排序后 TreeSet :[14, 57, 64, 95, 96]
从这个例子中,我们可以知道HashSet的元素存放顺序和我们添加进去时候的顺序没有任何关系,而LinkedHashSet 则保持元素的添加顺序。TreeSet则是对我们的Set中的元素进行排序存放。
一般来说,当您要从集合中以有序的方式抽取元素时,TreeSet 实现就会有用处。为了能顺利进行,添加到 TreeSet 的元素必须是可排序的。 而您同样需要对添加到TreeSet中的类对象实现 Comparable 接口的支持。对于Comparable接口的实现,在前一小节的Map中已经简单的介绍了一下。我们暂且假定一棵树知道如何保持 java.lang 包装程序器类元素的有序状态。一般说来,先把元素添加到 HashSet,再把集合转换为 TreeSet 来进行有序遍历会更快。这点和HashMap的使用非常的类似。
1.6 总结:集合框架中常用类比较
1、数组
效率高,但容量固定且无法动态改变。
array还有一个缺点是,无法判断其中实际存有多少元素,length只是告诉我们array的容量。
2、Java中有一个Arrays类,专门用来操作array。Arrays中拥有一组static函数,
equals():比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。
fill():将值填入array中。
sort():用来对array进行排序。
binarySearch():在排好序的array中寻找元素。
System.arraycopy():array的复制。
若写程序时不知道究竟需要多少对象,需要在空间不足时自动扩增容量,则需要使用容器类库,array不适用。
2.List的功能方法
ArrayList:由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。
LinkedList:由列表实现的List。对顺序访问进行了优化,向List中间插入与删除得开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
在各种Lists中,最好的做法是以ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList();Vector此类是实现同步的,总是比ArrayList慢,所以要尽量避免使用。
在各种Sets中,HashSet通常优于HashTree(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet。
HashMap 类 (除了不同步和允许使用 null 键/值之外,与 Hashtable 大致相同.)
Hashtable类此类是实现同步的,不允许使用 null 键值
当元素个数固定,用Array,因为Array效率是最高的。
Collections是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。
相当于对Array进行类似操作的类——Arrays。
如,Collections.max(Collectioncoll); 取coll中最大的元素。
Collections.sort(List list); 对list中元素排序
集合类
(Collection, List, Set, Map)
u Collection – 对象之间没有指定的顺序,允许重复元素。
u Set – 对象之间没有指定的顺序,不允许重复元素
u List– 对象之间有指定的顺序,允许重复元素,并引入位置下标。
u Map – 接口用于保存关键字(Key)和数值(Value)的集合,集合中的每个对象加入时都提供数值和关键字。Map 接口既不继承 Set 也不继承 Collection。
List、Set、Map共同的实现基础是Object数组
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
除了四个历史集合类外,还引入了六个集合实现,如下表所示。
接口 | 实现 | 历史集合类 |
Set | HashSet |
|
| TreeSet |
|
List | ArrayList | Vector |
| LinkedList | Stack |
Map | HashMap | Hashtable |
| TreeMap | Properties |
List,Set,Map将持有对象一律视为Object型别。
Collection、List、Set、Map都是接口,不能实例化。
1.2 Collection
集合必须只有对象,集合中的元素不能是基本数据类型。
Collection接口支持如添加和除去等基本操作。设法除去一个元素时,如果这个元素存在,除去的仅仅是集合中此元素的一个实例。
u booleanadd(Object element)
u booleanremove(Object element)
Collection接口还支持查询操作:
u int size()
u booleanisEmpty()
u booleancontains(Object element)
u Iteratoriterator()
组操作 :Collection 接口支持的其它操作,要么是作用于元素组的任务,要么是同时作用于整个集合的任务。
u boolean containsAll(Collection collection)
u boolean addAll(Collection collection)
u void clear()
u void removeAll(Collection collection)
u void retainAll(Collection collection)
containsAll() 方法允许您查找当前集合是否包含了另一个集合的所有元素,即另一个集合是否是当前集合的子集。其余方法是可选的,因为特定的集合可能不支持集合更改。 addAll() 方法确保另一个集合中的所有元素都被添加到当前的集合中,通常称为并。 clear() 方法从当前集合中除去所有元素。 removeAll() 方法类似于 clear() ,但只除去了元素的一个子集。 retainAll() 方法类似于 removeAll() 方法,不过可能感到它所做的与前面正好相反:它从当前集合中除去不属于另一个集合的元素,即交。
我们看一个简单的例子,来了解一下集合类的基本方法的使用:
import java.util.*;
public class CollectionToArray {
public static voidmain(String[] args) {
Collection collection1=newArrayList();//创建一个集合对象
collection1.add("000");//添加对象到Collection集合中
collection1.add("111");
collection1.add("222");
System.out.println("集合collection1的大小:"+collection1.size());
System.out.println("集合collection1的内容:"+collection1);
collection1.remove("000");//从集合collection1中移除掉 "000" 这个对象
System.out.println("集合collection1移除 000 后的内容:"+collection1);
System.out.println("集合collection1中是否包含000 :"+collection1.contains("000"));
System.out.println("集合collection1中是否包含111 :"+collection1.contains("111"));
Collection collection2=newArrayList();
collection2.addAll(collection1);//将collection1 集合中的元素全部都加到collection2中
System.out.println("集合collection2的内容:"+collection2);
collection2.clear();//清空集合 collection1 中的元素
System.out.println("集合collection2是否为空 :"+collection2.isEmpty());
//将集合collection1转化为数组
Object s[]= collection1.toArray();
for(inti=0;i<s.length;i++){
System.out.println(s[i]);
}
}
}
运行结果为:
集合collection1的大小:3
集合collection1的内容:[000, 111, 222]
集合collection1移除 000 后的内容:[111,222]
集合collection1中是否包含000 :false
集合collection1中是否包含111 :true
集合collection2的内容:[111, 222]
集合collection2是否为空 :true
111
222
1.2.2 迭代器
迭代器(Iterator)的概念,也是出于一种设计模式就是为达成此目的而形成的。所以Collection不提供get()方法。如果要遍历Collectin中的元素,就必须用Iterator。
下面,我们看一个对于迭代器的简单使用:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add("s1");
collection.add("s2");
collection.add("s3");
Iterator iterator =collection.iterator();//得到一个迭代器
while (iterator.hasNext()) {//遍历
Object element =iterator.next();
System.out.println("iterator = " + element);
}
if(collection.isEmpty())
System.out.println("collection is Empty!");
else
System.out.println("collection is not Empty! size="+collection.size());
Iterator iterator2 =collection.iterator();
while (iterator2.hasNext()) {//移除元素
Object element = iterator2.next();
System.out.println("remove: "+element);
iterator2.remove();
}
Iterator iterator3 =collection.iterator();
if (!iterator3.hasNext()){//察看是否还有元素
System.out.println("没有元素");
}
if(collection.isEmpty())
System.out.println("collection is Empty!");
//使用collection.isEmpty()方法来判断
}
}
程序的运行结果为:
iterator = s1
iterator = s2
iterator = s3
collection is not Empty! size=3
remove: s1
remove: s2
remove: s3
没有元素
collection is Empty!
可以看到,Java的Collection的Iterator 能够用来,:
1) 使用方法 iterator() 要求容器返回一个Iterator .第一次调用Iterator 的next() 方法时,它返回集合序列的第一个元素。
2) 使用next() 获得集合序列的中的下一个元素。
3) 使用hasNext()检查序列中是否元素。
4) 使用remove()将迭代器新返回的元素删除。
需要注意的是:方法删除由next方法返回的最后一个元素,在每次调用next时,remove方法只能被调用一次 。
大家看,Java 实现的这个迭代器的使用就是如此的简单。Iterator(跌代器)虽然功能简单,但仍然可以帮助我们解决许多问题,同时针对List 还有一个更复杂更高级的ListIterator。
1.3 List
List就是列表的意思,它是Collection 的一种,即
继承了 Collection
接口,以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。List是按对象的进入顺序进行保存对象,而不做排序或编辑操作。它除了拥有Collection接口的所有的方法外还拥有一些其他的方法。
面向位置的操作包括插入某个元素或 Collection 的功能,还包括获取、除去或更改元素的功能。在 List 中搜索元素可以从列表的头部或尾部开始,如果找到元素,还将报告元素所在的位置。
u void add(intindex, Object element) :添加对象element到位置index上
u booleanaddAll(int index, Collection collection) :在index位置后添加容器collection中所有的元素
u Object get(intindex) :取出下标为index的位置的元素
u intindexOf(Object element) :查找对象element 在List中第一次出现的位置
u intlastIndexOf(Object element) :查找对象element 在List中最后出现的位置
u Objectremove(int index) :删除index位置上的元素
u Object set(intindex, Object element) :将index位置上的对象替换为element 并返回老的元素。
先看一下下面表格:
在“集合框架”中有两种常规的List实现:ArrayList和LinkedList。使用两种 List实现的哪一种取决于您特定的需要。如果要支持随机访问,而不必在除尾部的任何位置插入或除去元素,那么,ArrayList提供了可选的集合。但如果,您要频繁的从列表的中间位置添加和除去元素,而只要顺序的访问列表元素,那么,LinkedList实现更好。
而LinkedList添加了一些处理列表两端元素的方法,使用这些新方法,您就可以轻松的把 LinkedList 当作一个堆栈、队列或其它面向端点的数据结构。
我们再来看另外一个使用LinkedList 来实现一个简单的队列的例子:
import java.util.*;
public class ListExample {
public static void main(String args[]) {
LinkedList queue = new LinkedList();
queue.addFirst("Bernadine");
queue.addFirst("Elizabeth");
queue.addFirst("Gene");
queue.addFirst("Elizabeth");
queue.addFirst("Clara");
System.out.println(queue);
queue.removeLast();
queue.removeLast();
System.out.println(queue);
}
}
运行程序产生了以下输出。请注意,与Set不同的是List允许重复。
[Clara, Elizabeth, Gene,Elizabeth, Bernadine]
[Clara, Elizabeth, Gene]
ListIterator 接口
ListIterator
接口继承Iterator
接口以支持添加或更改底层集合中的元素,还支持双向访问。
我们看一个List的例子:
import java.util.*;
public class ListIteratorTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
System.out.println("下标0开始:"+list.listIterator(0).next());//next()
System.out.println("下标1开始:"+list.listIterator(1).next());
System.out.println("子List 1-3:"+list.subList(1,3));//子列表
ListIterator it =list.listIterator();//默认从下标0开始
//隐式光标属性add操作 ,插入到当前的下标的前面
it.add("sss");
while(it.hasNext()){
System.out.println("next Index="+it.nextIndex()+",Object="+it.next());
}
//set属性
ListIterator it1 =list.listIterator();
it1.next();
it1.set("ooo");
ListIterator it2 =list.listIterator(list.size());//下标
while(it2.hasPrevious()){
System.out.println("previous Index="+it2.previousIndex()+",Object="+it2.previous());
}
}
}
程序的执行结果为:
下标0开始:aaa
下标1开始:bbb
子List 1-3:[bbb, ccc]
next Index=1,Object=aaa
next Index=2,Object=bbb
next Index=3,Object=ccc
next Index=4,Object=ddd
previous Index=4,Object=ddd
previous Index=3,Object=ccc
previous Index=2,Object=bbb
previous Index=1,Object=aaa
previous Index=0,Object=ooo
1.4 Map
Map
接口不是Collection
接口的继承。而是从自己的用于维护键-值关联的接口层次结构入手。按定义,该接口描述了从不重复的键到值的映射。
我们可以把这个接口方法分成三组操作:改变、查询和提供可选视图。
改变操作允许您从映射中添加和除去键-值对。键和值都可以为 null。但是,您不能把 Map 作为一个键或值添加给自身。
u
Object put(Object key,Object value)
:用来存放一个键
-
值对
Map
中
u
Object remove(Object key)
:根据
key(
键
)
,移除一个键
-
值对,并将值返回
u
voidputAll(Map mapping)
:将另外一个
Map
中的元素存入当前的
Map
中
u
void clear()
:清空当前
Map
中的元素
查询操作允许您检查映射内容:
u
Object get(Object key)
:根据
key(
键
)
取得对应的值
u
boolean containsKey(Object key)
:判断
Map
中是否存在某键(
key
)
u
boolean containsValue(Object value):
判断
Map
中是否存在某值
(value)
u
int size():
返回
Map
中
键-值对的个数
u
boolean isEmpty()
:判断当前
Map
是否为空
最后一组方法允许您把键或值的组作为集合来处理。
u
publicSet keySet()
:返回所有的键(
key
),并使用
Set
容器存放
u
public Collection values()
:返回所有的值(
Value
),并使用
Collection
存放
u
publicSet entrySet()
:
返回一个实现Map.Entry接口的元素Set
因为映射中键的集合必须是唯一的,就使用 Set 来支持。因为映射中值的集合可能不唯一,就使用 Collection 来支持。最后一个方法返回一个实现 Map.Entry 接口的元素 Set。
下面我们看一个简单的例子:
import java.util.*;
public class MapTest {
public static void main(String[] args) {
Map map1 = new HashMap();
Map map2 = new HashMap();
map1.put("1","aaa1");
map1.put("2","bbb2");
map2.put("10","aaaa10");
map2.put("11","bbbb11");
//根据键 "1" 取得值:"aaa1"
System.out.println("map1.get(\"1\")="+map1.get("1"));
// 根据键 "1" 移除键值对"1"-"aaa1"
System.out.println("map1.remove(\"1\")="+map1.remove("1"));
System.out.println("map1.get(\"1\")="+map1.get("1"));
map1.putAll(map2);//将map2全部元素放入map1中
map2.clear();//清空map2
System.out.println("map1 IsEmpty?="+map1.isEmpty());
System.out.println("map2 IsEmpty?="+map2.isEmpty());
System.out.println("map1 中的键值对的个数size = "+map1.size());
System.out.println("KeySet="+map1.keySet());//set
System.out.println("values="+map1.values());//Collection
System.out.println("entrySet="+map1.entrySet());
System.out.println("map1 是否包含键:11 = "+map1.containsKey("11"));
System.out.println("map1 是否包含值:aaa1 = "+map1.containsValue("aaa1"));
}
}
运行输出结果为:
map1.get("1")=aaa1
map1.remove("1")=aaa1
map1.get("1")=null
map1 IsEmpty?=false
map2 IsEmpty?=true
map1 中的键值对的个数size = 3
KeySet=[10, 2, 11]
values=[aaaa10, bbb2, bbbb11]
entrySet=[10=aaaa10, 2=bbb2, 11=bbbb11]
map1 是否包含键:11 = true
map1 是否包含值:aaa1 = false
在该例子中,我们创建一个HashMap,并使用了一下Map接口中的各个方法。
其中Map中的entrySet()方法返回一个实现 Map.Entry 接口的对象集合。集合中每个对象都是底层Map 中一个特定的键-值对。
我们再看看排序的Map是如何使用:
import java.util.*;
public class MapSortExample {
public static void main(String args[]) {
Map map1 = new HashMap();
Map map2 = new LinkedHashMap();
for(int i=0;i<10;i++){
double s=Math.random()*100;//产生一个随机数,并将其放入Map中
map1.put(new Integer((int) s),"第 "+i+" 个放入的元素:"+s+"\n");
map2.put(new Integer((int) s),"第 "+i+" 个放入的元素:"+s+"\n");
}
System.out.println("未排序前HashMap:"+map1);
System.out.println("未排序前LinkedHashMap:"+map2);
//使用TreeMap来对另外的Map进行重构和排序
Map sortedMap = new TreeMap(map1);
System.out.println("排序后:"+sortedMap);
System.out.println("排序后:"+new TreeMap(map2));
}
}
该程序的一次运行结果为:
未排序前HashMap:{64=第 1 个放入的元素:64.05341725531845
, 15=第 9 个放入的元素:15.249165766266382
, 2=第 4 个放入的元素:2.66794706854534
, 77=第 0 个放入的元素:77.28814965781416
, 97=第 5 个放入的元素:97.32893518378948
, 99=第 2 个放入的元素:99.99412014935982
, 60=第 8 个放入的元素:60.91451419025399
, 6=第 3 个放入的元素:6.286974058646977
, 1=第 7 个放入的元素:1.8261658496439903
, 48=第 6 个放入的元素:48.736039522423106
}
未排序前LinkedHashMap:{77=第 0 个放入的元素:77.28814965781416
, 64=第 1 个放入的元素:64.05341725531845
, 99=第 2 个放入的元素:99.99412014935982
, 6=第 3 个放入的元素:6.286974058646977
, 2=第 4 个放入的元素:2.66794706854534
, 97=第 5 个放入的元素:97.32893518378948
, 48=第 6 个放入的元素:48.736039522423106
, 1=第 7 个放入的元素:1.8261658496439903
, 60=第 8 个放入的元素:60.91451419025399
, 15=第 9 个放入的元素:15.249165766266382
}
排序后:{1=第 7 个放入的元素:1.8261658496439903
, 2=第 4 个放入的元素:2.66794706854534
, 6=第 3 个放入的元素:6.286974058646977
, 15=第 9 个放入的元素:15.249165766266382
, 48=第 6 个放入的元素:48.736039522423106
, 60=第 8 个放入的元素:60.91451419025399
, 64=第 1 个放入的元素:64.05341725531845
, 77=第 0 个放入的元素:77.28814965781416
, 97=第 5 个放入的元素:97.32893518378948
, 99=第 2 个放入的元素:99.99412014935982
}
排序后:{1=第 7 个放入的元素:1.8261658496439903
, 2=第 4 个放入的元素:2.66794706854534
, 6=第 3 个放入的元素:6.286974058646977
, 15=第 9 个放入的元素:15.249165766266382
, 48=第 6 个放入的元素:48.736039522423106
, 60=第 8 个放入的元素:60.91451419025399
, 64=第 1 个放入的元素:64.05341725531845
, 77=第 0 个放入的元素:77.28814965781416
, 97=第 5 个放入的元素:97.32893518378948
, 99=第 2 个放入的元素:99.99412014935982
}
从运行结果,我们可以看出,HashMap的存入顺序和输出顺序无关。而LinkedHashMap 则保留了键值对的存入顺序。TreeMap则是对Map中的元素进行排序。在实际的使用中我们也经常这样做:使用HashMap或者LinkedHashMap 来存放元素,当所有的元素都存放完成后,如果使用则是需要一个经过排序的Map的话,我们再使用TreeMap来重构原来的Map对象。这样做的好处是:因为HashMap和LinkedHashMap 存储数据的速度比直接使用TreeMap 要快,存取效率要高。当完成了所有的元素的存放后,我们再对整个的Map中的元素进行排序。这样可以提高整个程序的运行的效率,缩短执行时间。
这里需要注意的是,TreeMap中是根据键(Key)进行排序的。而如果我们要使用TreeMap来进行正常的排序的话,Key 中存放的对象必须实现Comparable 接口。
我们再回到Map中来,Java提高的API中除了上面介绍的几种Map比较常用以为还有一些Map,大家可以了解一下:
u WeakHashMap: WeakHashMap 是 Map 的一个特殊实现,它只用于存储对键的弱引用。当映射的某个键在 WeakHashMap 的外部不再被引用时,就允许垃圾收集器收集映射中相应的键值对。使用 WeakHashMap 有益于保持类似注册表的数据结构,其中条目的键不再能被任何线程访问时,此条目就没用了。
u IdentifyHashMap: Map的一种特性实现,关键属性的hash码不是由hashCode()方法计算,而是由System.identityHashCode 方法计算,使用==进行比较而不是equals()方法。
通过简单的对与Map中各个常用实现类的使用,为了更好的理解Map,下面我们再来了解一下Map的实现原理。
下面我们以HashMap为例,对Map的实现机制作一下更加深入一点的理解。Hash,一般翻译做“散列”,也有直接音译为"哈希"的,我们建立一个HashTable(哈希表),该表的长度为N,然后我们分别在该表中的格子中存放不同的元素。每个格子下面存放的元素又是以链表的方式存放元素。哈希码就是将对象的信息经过一些转变形成一个独一无二的int值,这个值存储在一个array中。我们都知道所有存储结构中,array查找速度是最快的。所以,可以加速查找。发生冲突时,让array指向多个values。即,数组每个位置上又生成一个梿表。
使用keySet()抽取key序列,将map中的所有keys生成一个Set。
使用values()抽取value序列,将map中的所有values生成一个Collection。
为什么一个生成Set,一个生成Collection?因为key总是独一无二的,value允许重复。
u 当添加一个新的元素Entry的时候,首先我们通过一个Hash函数计算出这个Entry元素的Hash值hashcode。通过该hashcode值,就可以直接定位出我们应该把这个Entry元素存入到Hash表的哪个格子中,如果该格子中已经存在元素了,那么只要把新的Entry元存放到这个链表中即可。
u 如果要查找一个元素Entry的时候,也同样的方式,通过Hash函数计算出这个Entry元素的Hash值hashcode。然后通过该hashcode值,就可以直接找到这个Entry是存放到哪个格子中的。接下来就对该格子存放的链表元素进行逐个的比较查找就可以了。
举一个比较简单的例子来说明这个算法的运算方式:
假定我们有一个长度为8的Hash表(可以理解为一个长度为8的数组)。在这个Hash表中存放数字:如下表
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
假定我们的Hash函数为:
Hashcode = X%8 -------- 对8 取余数
其中X就是我们需要放入Hash表中的数字,而这个函数返回的Hashcode就是Hash码。
假定我们有下面10个数字需要依次存入到这个Hash表中:
11 , 23 , 44 , 9 , 6 , 32 , 12 , 45 , 57 , 89
通过上面的Hash函数,我们可以得到分别对应的Hash码:
11――3 ; 23――7 ;44――4 ;9――1;6――6;32――0;12――4;45――5;57――1;89――1;
计算出来的Hash码分别代表,该数字应该存放到Hash表中的哪个对应数字的格子中。如果改格子中已经有数字存在了,那么就以链表的方式将数字依次存放在该格子中,如下表:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
32 | 9 |
| 11 | 44 | 45 | 6 | 23 |
| 57 |
|
| 12 |
|
|
|
| 89 |
|
|
|
|
|
|
Hash表和Hash算法的特点就是它的存取速度比数组差一些,但是比起单纯的链表,在查找和存储方面却要好很多。
1.4.5 重写hashCode()
我们看一个简单的例子,就能更加清楚的理解上面的意思。假定我们写了一个类:Person (人),我们判断一个对象“人”是否指向同一个人,只要知道这个人的身份证号一直就可以了。
先看我们没有实现hashCode的情况:
import java.util.*;
//身份证类
class Code{
final int id;//身份证号码已经确认,不能改变
Code(int i){
id=i;
}
//身份号号码相同,则身份证相同
public boolean equals(Object anObject) {
if (anObject instanceof Code){
Code other=(Code) anObject;
return this.id==other.id;
}
return false;
}
public String toString() {
return "身份证:"+id;
}
}
//人员信息类
class Person {
Code id;// 身份证
String name;// 姓名
public Person(String name, Code id) {
this.id=id;
this.name=name;
}
//如果身份证号相同,就表示两个人是同一个人
public boolean equals(Object anObject) {
if (anObject instanceof Person){
Person other=(Person)anObject;
return this.id.equals(other.id);
}
return false;
}
public String toString() {
return "姓名:"+name+" 身份证:"+id.id+"\n";
}
}
public class HashCodeEx {
public static void main(String[] args) {
HashMap map=new HashMap();
Person p1=new Person("张三",new Code(123));
map.put(p1.id,p1);//我们根据身份证来作为key值存放到Map中
Person p2=new Person("李四",new Code(456));
map.put(p2.id,p2);
Person p3=new Person("王二",new Code(789));
map.put(p3.id,p3);
System.out.println("HashMap 中存放的人员信息:\n"+map);
// 张三改名为:张山但是还是同一个人。
Person p4=new Person("张山",new Code(123));
map.put(p4.id,p4);
System.out.println("张三改名后 HashMap 中存放的人员信息:\n"+map);
//查找身份证为:123 的人员信息
System.out.println("查找身份证为:123 的人员信息:"+map.get(new Code(123)));
}
}
运行结果为:
HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:123=姓名:张三身份证:123
, 身份证:789=姓名:王二 身份证:789
}
张三改名后 HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:123=姓名:张三身份证:123
, 身份证:123=姓名:张山身份证:123
, 身份证:789=姓名:王二 身份证:789
}
查找身份证为:123 的人员信息:null
上面的例子的演示的是,我们在一个HashMap中存放了一些人员的信息。并以这些人员的身份证最为人员的“键”。当有的人员的姓名修改了的情况下,我们需要更新这个HashMap。同时假如我们知道某个身份证号,想了解这个身份证号对应的人员信息如何,我们也可以根据这个身份证号在HashMap中得到对应的信息。
而例子的输出结果表示,我们所做的更新和查找操作都失败了。失败的原因就是我们的身份证类:Code 没有覆写hashCode()方法。这个时候,当查找一样的身份证号码的键值对的时候,使用的是默认的对象的内存地址来进行定位。这样,后面的所有的身份证号对象new Code(123) 产生的hashCode()值都是不一样的。所以导致操作失败。
下面,我们给Code类加上hashCode()方法,然后再运行一下程序看看:
//身份证类
class Code{
final int id;//身份证号码已经确认,不能改变
Code(int i){
id=i;
}
//身份号号码相同,则身份证相同
public boolean equals(Object anObject) {
if (anObject instanceof Code){
Code other=(Code) anObject;
return this.id==other.id;
}
return false;
}
public String toString() {
return "身份证:"+id;
}
//覆写hashCode方法,并使用身份证号作为hash值
public int hashCode(){
return id;
}
}
再次执行上面的HashCodeEx 的结果就为:
HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:789=姓名:王二 身份证:789
, 身份证:123=姓名:张三身份证:123
}
张三改名后 HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:789=姓名:王二 身份证:789
, 身份证:123=姓名:张山身份证:123
}
查找身份证为:123 的人员信息:姓名:张山身份证:123
这个时候,我们发现。我们想要做的更新和查找操作都成功了。
对于Map部分的使用和实现,主要就是需要注意存放“键值对”中的对象的equals()方法和hashCode()方法的覆写。如果需要使用到排序的话,那么还需要实现Comparable 接口中的compareTo()方法。我们需要注意Map中的“键”是不能重复的,而是否重复的判断,是通过调用“键”对象的equals()方法来决定的。而在HashMap中查找和存取“键值对”是同时使用hashCode()方法和equals()方法来决定的。
1.5 Set
按照定义,Set
接口继承 Collection
接口,而且它不允许集合中存在重复项。所有原始方法都是现成的,没有引入新方法。具体的 Set
实现类依赖添加的对象的 equals()
方法来检查等同性。
我们简单的描述一下各个方法的作用:
u public int size() :返回set中元素的数目,如果set包含的元素数大于Integer.MAX_VALUE,返回Integer.MAX_VALUE
u public boolean isEmpty() :如果set中不含元素,返回true
u public booleancontains(Object o) :如果set包含指定元素,返回true
u public Iterator iterator()
l 返回set中元素的迭代器
l 元素返回没有特定的顺序,除非set是提高了该保证的某些类的实例
u public Object[] toArray() :返回包含set中所有元素的数组
u public Object[] toArray(Object[] a):返回包含set中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型
u public boolean add(Object o) :如果set中不存在指定元素,则向set加入
u public boolean remove(Object o) :如果set中存在指定元素,则从set中删除
u public booleanremoveAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素
u public booleancontainsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一个set,只有是当前set的子集时,方法返回true
u public booleanaddAll(Collection c) :如果set中中不存在指定集合的元素,则向set中加入所有元素
u public booleanretainAll(Collection c) :只保留set中所含的指定集合的元素(可选操作)。换言之,从set中删除所有指定集合不包含的元素。 如果指定集合也是一个set,那么该操作修改set的效果是使它的值为两个set的交集
u public booleanremoveAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素
u public void clear() :从set中删除所有元素
“集合框架” 支持 Set 接口两种普通的实现:HashSet 和 TreeSet以及LinkedHashSet。下表中是Set的常用实现类的描述:
在更多情况下,您会使用 HashSet 存储重复自由的集合。同时HashSet中也是采用了Hash算法的方式进行存取对象元素的。所以添加到 HashSet 的对象对应的类也需要采用恰当方式来实现 hashCode() 方法。虽然大多数系统类覆盖了 Object 中缺省的 hashCode() 实现,但创建您自己的要添加到 HashSet 的类时,别忘了覆盖 hashCode()。
对于Set的使用,我们先以一个简单的例子来说明:
import java.util.*;
public class HashSetDemo {
public static void main(String[] args) {
Set set1 = new HashSet();
if (set1.add("a")) {//添加成功
System.out.println("1 add true");
}
if (set1.add("a")) {//添加失败
System.out.println("2 add true");
}
set1.add("000");//添加对象到Set集合中
set1.add("111");
set1.add("222");
System.out.println("集合set1的大小:"+set1.size());
System.out.println("集合set1的内容:"+set1);
set1.remove("000");//从集合set1中移除掉 "000" 这个对象
System.out.println("集合set1移除 000 后的内容:"+set1);
System.out.println("集合set1中是否包含000 :"+set1.contains("000"));
System.out.println("集合set1中是否包含111 :"+set1.contains("111"));
Set set2=new HashSet();
set2.add("111");
set2.addAll(set1);//将set1 集合中的元素全部都加到set2中
System.out.println("集合set2的内容:"+set2);
set2.clear();//清空集合 set1 中的元素
System.out.println("集合set2是否为空:"+set2.isEmpty());
Iterator iterator =set1.iterator();//得到一个迭代器
while (iterator.hasNext()) {//遍历
Object element =iterator.next();
System.out.println("iterator = " + element);
}
//将集合set1转化为数组
Object s[]= set1.toArray();
for(int i=0;i<s.length;i++){
System.out.println(s[i]);
}
}
}
程序执行的结果为:
1 add true
集合set1的大小:4
集合set1的内容:[222, a, 000, 111]
集合set1移除 000 后的内容:[222, a, 111]
集合set1中是否包含000 :false
集合set1中是否包含111 :true
集合set2的内容:[222, a, 111]
集合set2是否为空 :true
iterator = 222
iterator = a
iterator = 111
222
a
111
从上面的这个简单的例子中,我们可以发现,Set中的方法与直接使用Collection中的方法一样。唯一需要注意的就是Set中存放的元素不能重复。
我们再看一个例子,来了解一下其它的Set的实现类的特性:
import java.util.*;
public class SetSortExample {
public static void main(String args[]) {
Set set1 = new HashSet();
Set set2 = new LinkedHashSet();
for(int i=0;i<5;i++){
//产生一个随机数,并将其放入Set中
int s=(int) (Math.random()*100);
set1.add(new Integer( s));
set2.add(new Integer( s));
System.out.println("第 "+i+" 次随机数产生为:"+s);
}
System.out.println("未排序前HashSet:"+set1);
System.out.println("未排序前LinkedHashSet:"+set2);
//使用TreeSet来对另外的Set进行重构和排序
Set sortedSet = new TreeSet(set1);
System.out.println("排序后 TreeSet :"+sortedSet);
}
}
该程序的一次执行结果为:
第 0 次随机数产生为:96
第 1 次随机数产生为:64
第 2 次随机数产生为:14
第 3 次随机数产生为:95
第 4 次随机数产生为:57
未排序前HashSet:[64, 96, 95, 57, 14]
未排序前LinkedHashSet:[96, 64, 14, 95, 57]
排序后 TreeSet :[14, 57, 64, 95, 96]
从这个例子中,我们可以知道HashSet的元素存放顺序和我们添加进去时候的顺序没有任何关系,而LinkedHashSet 则保持元素的添加顺序。TreeSet则是对我们的Set中的元素进行排序存放。
一般来说,当您要从集合中以有序的方式抽取元素时,TreeSet 实现就会有用处。为了能顺利进行,添加到 TreeSet 的元素必须是可排序的。 而您同样需要对添加到TreeSet中的类对象实现 Comparable 接口的支持。对于Comparable接口的实现,在前一小节的Map中已经简单的介绍了一下。我们暂且假定一棵树知道如何保持 java.lang 包装程序器类元素的有序状态。一般说来,先把元素添加到 HashSet,再把集合转换为 TreeSet 来进行有序遍历会更快。这点和HashMap的使用非常的类似。
1.6 总结:集合框架中常用类比较
1、数组
效率高,但容量固定且无法动态改变。
array还有一个缺点是,无法判断其中实际存有多少元素,length只是告诉我们array的容量。
2、Java中有一个Arrays类,专门用来操作array。Arrays中拥有一组static函数,
equals():比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。
fill():将值填入array中。
sort():用来对array进行排序。
binarySearch():在排好序的array中寻找元素。
System.arraycopy():array的复制。
若写程序时不知道究竟需要多少对象,需要在空间不足时自动扩增容量,则需要使用容器类库,array不适用。
2.List的功能方法
ArrayList:由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。
LinkedList:由列表实现的List。对顺序访问进行了优化,向List中间插入与删除得开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
在各种Lists中,最好的做法是以ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList();Vector此类是实现同步的,总是比ArrayList慢,所以要尽量避免使用。
在各种Sets中,HashSet通常优于HashTree(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet。
HashMap 类 (除了不同步和允许使用 null 键/值之外,与 Hashtable 大致相同.)
Hashtable类此类是实现同步的,不允许使用 null 键值
当元素个数固定,用Array,因为Array效率是最高的。
Collections是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。
相当于对Array进行类似操作的类——Arrays。
如,Collections.max(Collectioncoll); 取coll中最大的元素。
Collections.sort(List list); 对list中元素排序
此文章来自百度文库。