一下是从底层(为什么)分析各个容器。有兴趣的可以看看
ArrayList
LinkedList
HashSet
HashMap
TreeMap
LinkedHashMap
目录
简介
如果一个程序只包含固定数量的且其声明周期都是已知的对象,那么这是一个非常简单的程序
通常,一个程序总是根据运行时才知道的某些条件去创建对象。在此之前不会知道所需对象的数量,甚至不知道确切的类型。为了解决这个问题,需要在任意时刻和任意位置创建任意数量的对象。
这时候就引出来了容器这一个概念
泛型和类型安全的容器
java SE5之前编译器是运行你向容器中插入不正确的类型;这不用说现在这么做肯定错误
于是出现泛型,现在使用<>阔起来来规定该容器运行添加的类型比如:
List<Interger> l=ArrayList<Integer>();
通过使用泛型,就可以在编译器防止将错误类型的对象放置到容器中。
基本概念
java容器类类库其用途“保存对象”
java容器分为了以下两类:
-
Collection:存的Value值,代表了一个独立元素的序列,这些元素都服从一条或多条规则。
比如:
List:必须按照插入的顺序保存元素。
Set:不能有重复的元素。
Queue:只能从一端插入元素,或者只能从一端去除元素,也就是说,所有的操作都在"端"上,而不必遍历这个容器的其它位置。 -
Map:存储的Key-Value值,代表了一组成对的"键值对"对象,允许你使用键来查找值。
比如:HashMap;TreeMap;LinkedHashMap这些后续再细讲
记住Map是强大的编程工具,它也称为“字典”,就像名字一样我们可以根据Key可以找到Value
这里我创建容器对象的时候尽量与接口打交道,创建容器对象的时候将其向上转型为其接口,这是有好处的(其余的代码中都能使用该接口)
//创建ArrayList对象,看源码可以知道ArrayList实现了List
//我们通常都使用这种方式,向上转型为List
List<Apple> apples = new Arraylist<Apple>();
添加一组元素
如果你经常使用容器,那么你一定会常常用到两个工具类:java.util包中的Arrays和Collections,这两个类中有许多实用的方法:
Arrays.asList()接受一个数组或者用逗号隔开的元素列表,将其转为一个List对象
Collections.addAll()方法接受一个Collection对象,以及一个数组或是一个用逗号分隔的列表,将元素添加到Collection中。
容器的打印
如果你是想打印数组那么Arrays.toString()是一个好方法,但是容器则不需要这些帮助:
// 容器的打印
public class PrintingContainers{
static Collection fill(Collection<String> collection){
collection.add("rat");
collection.add("cat");
collection.add("dog");
collection.add("dog");
return collection;
}
static Map fill(Map<String,String> map){
map.put("rat","Fuzzy");
map.put("cat","Rags");
map.put("dog","Bosco");
map.put("dog","Spot");
return map;
}
public static void main(String[] args){
System.out.println(fill(new ArrayList<String>()));
System.out.println(fill(new LinkedList<String>()));
System.out.println(fill(new HashSet<String>()));
System.out.println(fill(new TreeSet<String>()));
System.out.println(fill(new LinkedHashSet<String>()));
System.out.println(fill(new HashMap<String,String>()));
System.out.println(fill(new TreeMap<String,String>()));
System.out.println(fill(new LinkedHashMap<String,String>()));
}
}
//Output
//[rat,cat,dog,dog]
//[rat,cat,dog,dog]
//[dog,cat,rat]
//[cat,dog,rat]
//[rat,cat,dog]
//{dog=Spot,cat=Rags,rat=Fuzzy}
//{cat=Rags,dog=Spot,rat=Fuzzy}
//{rat=Fuzzy,cat=Rags,dog=Spot}
上面代码看见容器也没有使用toString方法呀,但是为什么它们可以打印出类容来?原因是它们本身已经重写了toString()方法
本例使用了各种容器,我们来比较以下
List:ArrayList,LinkedList:看得出来它们都是顺序输出,这两者主要的区别是性能的问题
Set:HashSet,TreeSet,LinkedList:这里看得出来基本它们的答案都不同,这里HashSet底层实现的HashMap,存储的元素是无序的。TreeSet,是通过比较来存储对象。LinkedList则是按照添加的元素顺序保存对象。这里只简单说一下
Map:HashMap,TreeMap,LinkedHashMap:这里HashMap提供最快的查找技术,但是没有按照任何有规律的顺序保存元素(或者说规律是Hashcode?),TreeMap按照比较结果的升序保存键,LinkedHashMap则按照插入顺序保存键,同时还有HashMap的查询速度(但也有缺点)
List
List承诺可以将元素维护再特定的序列中,List接口再Collection的基础上添加了大量的方法,使得可以在List中间插入和移除。
List可以分为两大类:
- ArrayList:它常用于随机访问元素,但是在List中插入删除元素比较慢
- LinkedList:它的一个双向链表,便于插入和删除和添加,提供了顺序访问,但是其访问元素比较慢。
这里更细的知识(底层源码),建议大家看一下,看了基本就了解两个的区别
ArrayList
LinkedList
迭代器
迭代器是一个对象,它的工作是遍历并选择序列中的对象,常常用于遍历容器,而客户端程序员不必知道或关心该序列底层的结构。此外迭代器通常被称为轻量级对象:创建它的代价小。因此它也有些限制
例如:java中Iterator只能单向移动,这个Iterator只能用来:
- 使用方法Iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
- 使用next()获得序列中的下一个元素。
- 使用hasNext()检查序列中是否还有元素。
- 使用remove()将迭代器新近返回的元素删除。
举个例子
public class SimpleIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(12);
Iterator<Pet> it = pets.iterator();
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
// A simpler approach, when possible:
for(Pet p : pets)
System.out.print(p.id() + ":" + p + " ");
System.out.println();
// An Iterator can also remove elements:
it = pets.iterator();
for(int i = 0; i < 6; i++) {
it.next();
it.remove();
}
System.out.println(pets);
}
} /* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
[Pug, Manx, Cymric, Rat, EgyptianMau, Hamster]
*///:~
有了Iterator就不必为容器中元素的数量担心了,那是由hasNext()和next()关系的事情
注意:使用remove()之前必须先调用next()。
ListIterator
ListIterator是一个更加功能强大的Iterator的子类型,看名字就知道它只能用于List类的访问,但是它能双向移动,而Iterator只能单向移动。
ListIterator可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素
你可以通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且可以通过listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。
举个例子
public class ListIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(8);
ListIterator<Pet> it = pets.listIterator();
while(it.hasNext())
System.out.print(it.next() + ", " + it.nextIndex() +
", " + it.previousIndex() + "; ");
System.out.println();
// Backwards:
while(it.hasPrevious())
System.out.print(it.previous().id() + " ");
System.out.println();
System.out.println(pets);
it = pets.listIterator(3);
while(it.hasNext()) {
it.next();
it.set(Pets.randomPet());//替换
}
System.out.println(pets);
}
} /* Output:
Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7;
7 6 5 4 3 2 1 0
[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx]
[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau]
*///:~
上面的代码,可以看出ListIterator比Iterator的功能更加完善。
LinkedList
LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间插入和移除)时比ArrayList更高效,但在随机访问的时候却比ArrayList效率低
LinkedList还添加了可以使用其用作栈,队列或双端队列的方法。
其中里面方法,源码请看下面的文章
LinkedList
Stack
Stack被称为“栈”,通常是指后进先出的容器(底层数组)。
但是已经不建议使用
更多请看:
从底层认知Stack
Set和Map
Set
Set不保存重复的元素,这里如果你看里之前的源码,这里就大概会明白。
如果你添加相同的元素到set,则add()方法会返回false;
Set具有与Collection完全一样的接口,因此没有任何额外的功能,实际上Set就是Collection,只是行为不同。
Map
Map保存的是键值对(Map<Interger,String>),它在编程中很重要,常常能解决很多问题。比如存储商品名字和价格,和统计随机数出现的概率等。
常用的实现一般有HashMap,TreeMap,LinkedHashMap;
这里不举例子了,建议大家看看源码。或者后续章节将详讲。
Queue
队列是一个典型的先进先出(FIFO)的容器
在并发编程中队列十分重要,因为它们可以安全地将对象从一个任务传输给另一个任务。
LinkedList提供了方法以支持队列的行为,并且实现了Queue接口,因此LinkedList可作为Queue的一种实现。通过将LinkedList向上转型为Queue
public class QueueDemo {
public static void printQ(Queue queue) {
while(queue.peek() != null)
System.out.print(queue.remove() + " ");
System.out.println();
}
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<Integer>();
Random rand = new Random(47);
for(int i = 0; i < 10; i++)
queue.offer(rand.nextInt(i + 10));
printQ(queue);
Queue<Character> qc = new LinkedList<Character>();
for(char c : "Brontosaurus".toCharArray())
qc.offer(c);
printQ(qc);
}
} /* Output:
8 1 1 1 5 14 3 1 0 1
B r o n t o s a u r u s
*///:~
PriorityQueue(优先级队列)
优先级队列声明下一个弹出元素是最需要的元素(具有最高级优先级)。
Foreach与迭代器
foreach语法主要用于数组,但是它也可以用于任何Collection对象。
之所以能用于Collection是因为,javaSE5引入了新的被称为Iterable的接口,该接口包含一个能够产生Iterator的interator()方法,并且Iterator接口被foreach用来在序列中移动。因此如果你创建了任何实现Iterable的类,都可以将它用于foreach语句中
可以想而知Collection类也实现了Iterable接口
public class IterableClass implements Iterable<String> {
protected String[] words = ("And that is how " +
"we know the Earth to be banana-shaped.").split(" ");
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;
public boolean hasNext() {
return index < words.length;
}
public String next() { return words[index++]; }
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for(String s : new IterableClass())
System.out.print(s + " ");
}
} /* Output:
And that is how we know the Earth to be banana-shaped.
*///:~
在java SE5中,大量的类都是Iterable类型,主要包括Collection(不包括Map)
总结
java提供了大量持有对象的方式:
- 数组将数字和对象联系起来,它保存类型明确的对象,查询对象时,不需要对结果做类型转换,数组时可以多维的,可以保存基本类型的数据。但是,数组一旦生成,其容量就不能改变。
- Collection保存单一的元素,而Map保存相关联的键值对。java的泛型规定容器里放置的对象类型,防止我们将错误的类型放到容器中,在容器中获取元素时不必进行类型转换。各种Collection和Map都可以在向容器中添加元素的时候自动扩容。容器不能持有基本类型的数据。
- 像数组一样,List也建立数字索引与对象的联系,因此数组和List都是排好序的容器。List能够自动扩容。
- 如果要进行大量的随机访问,应该使用ArrayList;如果要经常从表中间插入和删除元素,则应该使用LinkedList。
- 各种Queue及栈的行为,由LinkedList提供支持。
- Map是一种将对象(而非数字)与对象相关联的设计。HashMap设计用来快速访问;而TreeMap保持“键”始终处于排序状态,所以它没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
- Set不接受重复元素。HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态,LinkedHashSet以插入顺序保存元素。
- 新程序中不应该使用Vector、Hashtable和Stack。