什么是集合?通俗的讲,集合就是存储一组数据的容器,那么,相比较于同样是存储数据的数组,集合的优势就体现在集合的长度是可变的,而数组的长度是固定的。在我们常见的集合中,主要有两大类:
Collection接口单列集合和Map双列集合(键值对集合)接口,其中实现Collection接口的集合主要有:List、Set、Queue(队列),实现Map接口的集合主要是HashMap、LinkedHashMap、TreeMap等。下面分别介绍这些集合是如何来便利的。
List集合(接口)
特点:有序,可重复
主要实现类:ArrayList:内部基于数组,查找元素、遍历集合的速度快
Linkedlist:内部基于链表,插入、删除元素的速度快
以ArrayList为例,遍历集合
ArrayList<String> list = new ArrayList<String>();
list.add("小鲁班");
list.add("貂蝉");
list.add("后羿");
list.add("白起");
list.add("亚瑟");
list.add("百里守约");
// 方式1:foreach循环
for (String hero : list) {
System.out.print(hero + " ");
}
System.out.println();
// 方式2:for循环
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
// 方式3:迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String hero = it.next();
System.out.print(hero + " ");
}
// 运行结果相同
// 小鲁班 貂蝉 后羿 白起 亚瑟 百里守约
Set集合(接口)
特点:无序,元素不可重复,只用使用迭代器和foreach循环遍历
主要实现类:HashSet:内部基于HashMap,无序,元素不可重复
HashSet<String> set = new HashSet<String>();
set.add("小鲁班");
set.add("貂蝉");
set.add("后羿");
set.add("白起");
set.add("亚瑟");
set.add("百里守约");
// foreach遍历
for(String s : set) {
System.out.print(s + " ");
}
// 运行结果
// 白起 貂蝉 小鲁班 百里守约 后羿 亚瑟 LinkedHashSet:内部基于LinkedHashMap,有序(根据添加的顺序),元素不可重复
LinkedHashSet<String> set = new LinkedHashSet<String>();
set.add("小鲁班");
set.add("貂蝉");
set.add("后羿");
set.add("白起");
set.add("亚瑟");
set.add("百里守约");
// foreach遍历
for(String s : set) {
System.out.print(s + " ");
}
// 运行结果
// 小鲁班 貂蝉 后羿 白起 亚瑟 百里守约 TreeSet:内部基于TreeMap,可以自动排序(实现Comparable接口和Comparator接口,需要重写compareTo方法,实现排序逻辑,如果添加的元素没有实现Comparable接口,那么需要在构造方法时,传入一个Comparator实现类对象),元素不可重复
TreeSet<String> set = new TreeSet<String>();
set.add("小鲁班");
set.add("貂蝉");
set.add("后羿");
set.add("白起");
set.add("亚瑟");
set.add("百里守约");
// 迭代器
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String name = it.next();
System.out.print(name + " ");
}
// 运行结果
// 亚瑟 后羿 小鲁班 白起 百里守约 貂蝉 Queue队列(接口)
特点:线性表结构,遵循先进先出(FIFO)原则,一般来说,只允许在集合前端进行出队操作,在集合后端进行入队操作
遍历方式:foreach循环遍历、迭代器遍历、while循环判空遍历
有界队列:队列容量受构造方法的容量参数限制,主要实现类:ArrayBlockingQueue,超出容量限制时,会抛出IllegalStateException异常
无界队列:队列容量没有限制,主要实现类:LinkedList
优先级队列:添加的元素必须实现Comporable接口,如果没有实现,必须在构造方法时传入一个Comparator实现类对象,内部通过用数组表示的小顶堆实现,队首永远是优先级最高(根据重写的comparTo方法实现的逻辑排序)的元素
以优先级队列为例
Queue<String> queue = new PriorityQueue<String>();
queue.offer("C");
queue.offer("B");
queue.offer("D");
queue.offer("A");
queue.offer("E");
// foreach遍历
for(String s : queue) {
System.out.print(s+" ");
}
System.out.println();
// 迭代器遍历
Iterator<String> it = queue.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.print(s + " ");
}
System.out.println();
// 运行结果相同
// A B D C E
// 使用isEmpty()方法对队列进行判空,然后出队遍历
while(!queue.isEmpty()) {
System.out.print(queue.poll() + " ");
}
// 由于优先级不同,运行结果与上面不同
// A B C D E
Deque双端队列(接口)
特点:线性表结构,相比于Queue可在集合两端进行出队、入队操作
遍历方式:foreach循环遍历、迭代器遍历、while循环判空遍历
| Queue | Deque | |
| 添加元素到队尾 | add(E e) / offer(E e) | addLast(E e) / offerLast(E e) |
| 取首元素并删除 | E remove() / E poll() | E removeFirst() / E pollFirst() |
| 取首元素并不删除 | E element() / E peek() | E getFirst / E peekFirst() |
| 添加元素到队首 | 无 | addFirst(E e) / offerFirst(E e) |
| 取队尾元素并删除 | 无 | E removeLast() / E pollLast() |
| 取队尾元素但不删除 | 无 | E getLast / E peekLast() |
Deque<String> deque = new LinkedList<String>();
deque.offer("A"); // 队尾
deque.offerFirst("V"); // 队头
deque.offer("S"); // 队尾
deque.offerLast("G");
deque.offer("E");
System.out.println(deque);
// 运行结果
// [V, A, S, G, E]
// 迭代器
Iterator<String> it = deque.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.print(s + " ");
}
// foreach循环
for (String s : deque) {
System.out.print(s + " ");
}
// 运行结果
// V A S G E 注意:以下两种方法只能单独使用,当遍历时,由于使用了poll()方法,队列中的元素已经出队,当出队完毕时,队列中已经没有元素,需要再次入队才能进行下一次操作。
// 从队首开始遍历并出队
String item = null;
while((item = deque.pollFirst())!= null) {
System.out.print(item + " ");
}
// 运行结果
// V A S G E // 从队尾开始遍历并出队
String item = null;
while((item = deque.pollLast())!= null) {
System.out.print(item + " ");
}
// 运行结果
// E G S A V Stack栈(类)
特点:后进先出(LIFO),只有入栈和出栈操作
遍历方式:foreach循环遍历、迭代器遍历、while循环判空遍历
Stack继承自Vector类,而Vector实现了List接口,由于Vector的方法都是同步的(Synchronized),是线程安全的,而与之类似的ArrayList的方法不是同步的,因此,ArrayList的性能会比Vector好。
| 压栈 | push(E e) |
| 把栈顶元素弹出 | E pop() |
| 取出栈顶元素但不弹出 | E peek() |
Stack<String> stack = new Stack<String>();
stack.push("A1");
stack.push("A2");
stack.push("A3");
stack.push("A4");
stack.push("A5");
// foreach
for(String s : stack) {
System.out.print(s + " ");
}
System.out.println();
// 迭代器
Iterator<String> it = stack.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.print(s + " ");
}
System.out.println();
// 遍历并出栈
while(!stack.isEmpty()) {
System.out.print(stack.pop() + " ");
}
// 运行结果
// A1 A2 A3 A4 A5
// A1 A2 A3 A4 A5
// A5 A4 A3 A2 A1 由于栈是后进先出原则,在pop时,会先弹出栈顶元素,也就是最后入栈的元素,因此运行结果与前两种不同。
Map集合
特点:键值对集合,映射表,键唯一,值不唯一,一个键可以对应多个值,可以通过键快速查找值
遍历方式:foreach循环遍历KeySet()、foreach循环遍历entrySet()
主要实现类:HashMap:数据结构采用数组+链表+红黑树,数组类型为Node[],每个Node都保存了某个KV键值对的key、value、hash、next等值,当添加新元素时,会按照key的hash值与它的高16位进行异或运算((n-1)^hash),计算数组中的存储位置下标,如果该下标位置已经存在元素,代表哈希冲突,则采用链地址法存储到链表尾部,当链表Node节点数量大于8,如果当数组长度大于64时,会将链表转换成一个红黑树。当数组长度小于64时,就会进行数组扩容,扩容容量是当前容量的2倍;同时如果hashMap中的元素个数达到扩容阈值(数组容量(默认为16)*加载因子(默认为0.75))时,也会进行扩容。
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("apple", 5);
map.put("banana", 9);
map.put("orange", 15);
map.put("pear", 7);
// foreach循环遍历KeySet()
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.print(key + ":" + value + " ");
}
System.out.println();
// foreach循环遍历entrySet()
for (Entry<String, Integer> e : map.entrySet()) {
String key = e.getKey();
Integer value = e.getValue();
System.out.print(key + ":" + value + " ");
}
// 运行结果相同
// banana:9 orange:15 apple:5 pear:7 LinkedHashMap:HashMap的子类,有序,遍历时顺序与添加顺序相同
LinkedHashMap<String, Integer> map = new LinkedHashMap<String, Integer>();
map.put("apple", 5);
map.put("banana", 9);
map.put("orange", 15);
map.put("pear", 7);
// foreach循环遍历KeySet()
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.print(key + ":" + value + " ");
}
System.out.println();
// foreach循环遍历entrySet()
for (Entry<String, Integer> e : map.entrySet()) {
String key = e.getKey();
Integer value = e.getValue();
System.out.print(key + ":" + value + " ");
}
// 运行结果相同
// apple:5 banana:9 orange:15 pear:7 TreeMap:实现了SortedMap接口,可以自动按照排序规则排序,key必须实现Comparable接口或Comparator接口,需要重写compareTo方法,实现排序逻辑,如果添加的元素没有实现Comparable接口,那么需要在构造方法时,传入一个Comparator实现类对象
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
public class Test {
public static void main(String[] args) {
Map<Person, Integer> map = new TreeMap<Person, Integer>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.name.compareTo(o2.name);
}
});
map.put(new Person("Tom"), 90);
map.put(new Person("Bob"), 76);
map.put(new Person("Lily"), 59);
// foreach循环遍历KeySet()
for (Person key : map.keySet()) {
Integer value = map.get(key);
System.out.print(key + ":" + value + " ");
}
System.out.println();
// foreach循环遍历entrySet()
for (Entry<Person, Integer> e : map.entrySet()) {
Person key = e.getKey();
Integer value = e.getValue();
System.out.print(key + ":" + value + " ");
}
// 运行结果相同
// Person [name=Bob]:76 Person [name=Lily]:59 Person [name=Tom]:90
}
}
class Person {
public String name;
Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
}