栈
“栈”通常是表示“后进先出”的容器。就好比在盘子里叠入披萨,最后放进去的总是在最上面。
ListkedList具有能直接实现栈的所有功能的方法,因此可以直接将LinkedList当作栈使用。
不过,有时候一个真正的“栈”更能把事情说明白:
import java.util.LinkedList;
public class Stack<T>{
private LinkedList<T> storage = new LinkedList<T>();
public void push (T v){
storage.addFirst(v);
}
public T peek (){
return storage.getFirst();
}
public T pop (){
return storage.removeFirst();
}
public boolean empty (){
return storage.isEmpty();
}
public String toString (){
return storage.toString();
}
}
这里使用泛型,引入了在栈的类定义中最简单的可行示例。T表示告诉编译器这是参数化的类型,实际类型会在使用时被替换。如果我们只需要栈的行为,那使用继承就不合适了,因为这样就会产生具有LinkedList的其他所有方法的类。
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
for (String s : "My dog has fleas" .split(" " )){
stack .push(s);
}
while (!stack .empty()) {
System.out.println(stack .pop() + " " );
}
}
Set
Set不保存重复元素。如果我们需要查询功能,那么用hashSet是很好的选择,因为它专门针对了查找进行优化。
Set和Collection有完全一样的接口。实际上Set就是Collection,只是行为不同。行为的不同是继承和多态的典型应用。Set是基于对象的值来确定归属的。
public static void main (String[] args) {
Random rand = new Random(47 );
Set<Integer> intset = new HashSet<Integer>();
for (int i = 0 ; i < 10000 ; i++){
intset.add(rand.nextInt(30 ));
}
System.out .println(intset);
}
[0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 , 27 , 28 , 29 ]
在hashSet中插入了0-29的10000个随机数,但是实际结果只有0-29,并且是无序的。这里的无序指的是存入和取出的顺序不一致。因为考虑到速度原因,HashSet用到了散列。其顺序与TreeSet和LinkedHashSet都不同,因为他们的实现具有不同的元素存储方式。TreeSet将元素存储在红-黑树数据结构中,而HashSet是散列函数。LinkedHashSet也使用了散列,但是看起来它使用了链表来维护插入顺序。如果我们需要排序,可以用TreeSet来替换HashSet,默认安装字典排序,并且区分大小写。如果想用字母排序,可以向TreeSet构造器传入String.CASE_INSENTIVE_ORDER
public static void main(String [] args) {
String sort = "A B C J Y O M G M U O aa b c j y o m g m u o" ;
Set<String > words = new TreeSet<String >();
for (String s : sort.split(" " )){
words.add(s);
}
System.out.println("not set para:" + words);
Set<String > words2 = new TreeSet<String >(String .CASE_INSENSITIVE_ORDER);
for (String s : sort.split(" " )){
words2.add(s);
}
System.out.println("use set para:" + words2);
}
not set para:[A, B, C, G, J, M, O, U, Y, a, b, c, g, j, m, o, u, y]
use set para:[A, aa, B, C, G, J, M, O, U, Y]
很明显String.CASE_INSENSITIVE_ORDER虽然是按照字符排序了,但是其实际含义是不区分大小写。也就是B和b只能在Set中存在一个了。
Map
将对象映射到其他对象的能力是一种解决编程问题的杀手锏。
public static void main(String[] args) {
Random rand = new Random(47);
Map<Integer, Integer > m = new HashMap<> ();
for(int i = 0; i < 10000 ; i ++){
int r = rand.nextInt (20 );
Integer freq = m.get (r );
m.put (r , freq == null ? 1:freq +1 );
}
System.out.println (m );
}
//out
{0 =481, 1 =502, 2 =489, 3 =508, 4 =481, 5 =503, 6 =519, 7 =471, 8 =468, 9 =549, 10 =513, 11 =531, 12 =521, 13 =506, 14 =477, 15 =497, 16 =533, 17 =509, 18 =478, 19 =464}
自动包装机制,将随机生成的int转换成了Integer类型,用生成的int类型去Map中找,是否有该key,如果没有会返回null,如果有则会返回对应的value值。为null的情况会赋值给freq为1,不为null就进行累加。在累加中,自动拆包又进行的操作,否则Integer不能进行累加的。
Map与数组和其他的Collection一样。可以很简单的扩展到多维结果,比如Map<Stirng,Map<Integer,String>>或者更复杂的数据结构。
//这句话表示,该变量petPeople的数据类型是Map,而Map中key是String,value是List。这个List的类型是未知但且继承String
public static Map<String,List<? extends String>> petPeople = new HashMap<>()
static{
petPeople.put ("Dawn" , Arrays.asList ("Molly" ,"Spot" ))
petPeople.put ("Kata" , Arrays.asList ("Shackleton" ,"Elsie May" ,"Margrett" ))
petPeople.put ("Marilyn" , Arrays.asList ("Louie aka Louis Snorkelstein Dupree" ,
"Stanford aka Srinky el Negro" ,
"Pinkola" ))
petPeople.put ("Luke" , Arrays.asList ("Fuzzy" ,"Fizzy" ))
petPeople.put ("Isaac" , Arrays.asList ("Freckly" ))
}
public static void main(String[] args) {
//keySet返回了所有key组成的Set
System.out .println ("People: " + petPeople.keySet ())
//values返回了所有对应的value,根据Set 的默认排序
System.out .println ("Pets: " + petPeople.values ())
for (String string : petPeople.keySet ()) {
System.out .println (string + " has:" )
for (String s : petPeople.get (string)) {
System.out .println (" " + s)
}
}
}
Queue
队列是保持先进先出,和LinkedList相反。就像排队打饭一样,先排队的人,就能先打到饭,排除插队的情况。
队列在并发编程中特别重要,因为它们可以安全地将对象从一个任务传给另一个任务。
LinkedList提供了方法以支持队列的行为,并且它是想了Queue接口,因此LinkedList可以用做Queue的一种实现。通过将LinkedList向上转型为Queue。
public static void printQ (Queue queue){
while (queue.peek() != null ){
System.out .print(queue.remove() + " " );
}
}
public static void main (String[] args) {
Queue<Integer> queue = new LinkedList<>();
Random rand = new Random(47 );
for (int i = 0 ; i < 10 ; i++) {
queue.offer(rand.nextInt(i+10 ));
}
printQ(queue);
}
offer()将一个元素插入到队列尾端。在将LinkedList转化为Queue后,LinkedList的方法会不能使用。当然我们可以将Queue又转成LinkedList。
PriorityQueue
PriorityQueue在Java SE5中被添加,是为了提供这种行为的一种自动实现。
当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是你可以通过提供自己的Comparator来修改这个顺序。PriorityQueue可以确保当你在调用Peel(),poll()和remove()时,获取的是最高优先级的元素。
public static void printQ (Queue queue){
while (queue.peek() != null ){
System.out .print(queue.remove() + " " );
}
System.out .println();
}
public static void main (String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
Random rand = new Random(47 );
for (int i = 0 ; i < 10 ; i++) {
priorityQueue.offer(rand.nextInt(i + 10 ));
}
PriorityQueueDemo.printQ(priorityQueue);
List<Integer> ints = Arrays.asList(25 ,22 ,20 ,18 ,14 ,9 ,3 ,1 ,1 ,2 ,3 ,9 ,14 ,18 ,21 ,23 ,25 );
priorityQueue = new PriorityQueue<>(ints);
PriorityQueueDemo.printQ(priorityQueue);
priorityQueue = new PriorityQueue<>(ints.size(),Collections.reverseOrder());
priorityQueue.addAll(ints);
PriorityQueueDemo.printQ(priorityQueue);
}
由此可以看出,队列运行重复,数字小的优先级高(比字母高)。大写字母比小写字母优先级高。
Collections.reverseOrder()同样是在JAVA SE5中增加的反序Comparator。
下节会讲Collection和Iterator和Foreach和适配器方法惯用法