Java集合框架支持以下两种类型的容器:
基于动态数组实现,支持随机访问。
因为 ArrayList 是基于数组实现的,所以支持快速随机访问。RandomAccess 接口标识着该类支持快速随机访问。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
数组的默认大小为 10
private static final int DEFAULT_CAPACITY = 10;
Vector:和 ArrayList 类似,但它是线程安全的。
LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。
每个链表存储了 first 和 last 指针:
transient Node<E> first;
transient Node<E> last;
---------------------------------------LinkedList存储结构图----------------------------
两种类型比较 | ArrayList | LinkedList |
---|---|---|
获取指定元素 | 速度很快 | 需要从头开始查找元素 |
添加元素到末尾 | 速度很快 | 速度很快 |
在指定位置添加/删除 | 需要移动元素 | 不需要移动元素 |
内存占用 | 少 | 较大 |
通常情况下,我们总是优先使用ArrayList
List
接口允许我们添加重复的元素,即List
内部的元素可以重复 List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("aaa");
for (String str:list
) {
System.out.println(str);
}
aaa
bbb
aaa
List
还允许添加null:public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add(null);
list.add("orange");
System.out.println(list.get(1));
}
}
null
Set:不保证有序,不可重复
List:有顺序,可重复
Set接口并不保证有序,而SortedSet接口则保证元素是有序的
我们来看下面这个例子
package Collection.Set;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
/**
* Set:不保证有序,不可重复
* List:有顺序,可重复
* HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
* TreeSet是有序的,因为它实现了SortedSet接口。
* TreeSet底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。
* TreeSet内部需要对存储的元素进行排序,因此,我们对应的类需要实现Comparable接口。
* 这样,才能根据compareTo()方法比较对象之间的大小,才能进行内部排序。
* 如果我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set
*/
public class TestTreeSet {
public static void main(String[] args) {
Set<String> set = new TreeSet<>();//TreeSet有顺序,这个顺序是元素的排序顺序
System.out.println(set.add("cc"));//true
System.out.println(set.add("aa"));//true
System.out.println(set.add("cc"));//false,不可重复
System.out.println(set);
set.remove("cc");
System.out.println(set);
System.out.println("---------------");
Set<String> set1 = new TreeSet<>();
set1.add("cc");
set1.addAll(set);
System.out.println(set1);
System.out.println("---------------");
/**HashSet无序,注意输出的顺序既不是添加的顺序,也不是String排序的顺序,在不同版本的JDK中,这个顺序也可能是不同的。*/
Set<String> set2 = new HashSet<>();
set2.add("apple");
set2.add("orange");
set2.add("banana");
for (String s:set2
) {
System.out.println(s);
}
System.out.println("---------------");
Set<String> set3 = new LinkedHashSet<>(set2);
set3.forEach(e -> System.out.println(e));
}
}
true
true
false
[aa, cc]
[aa]
---------------
[aa, cc]
---------------
orange
banana
apple
---------------
orange
banana
apple
TreeSet底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。
TreeSet内部需要对存储的元素进行排序,因此,我们对应的类需要实现Comparable
接口或Comparator
接口
这样,才能根据 compareTo(T o) 或 compare(T o1,T o2 )方法比较对象之间的大小,才能进行内部排序。
LinkedList:可以用它来实现双向队列。
PriorityQueue:基于堆结构实现,可以用它来实现优先队列。
public class Test {
public static void main(String[] args) {
Queue<String> queue = new PriorityQueue<>();
queue.offer("apple");
queue.offer("banana");
queue.offer("orange");
queue.offer("blue");
while (queue.size()>0) {
System.out.println(queue.remove() + " ");
}
}
apple
banana
blue
orange
优先队列使用Comparable
以元素的自然顺序进行排序。拥有最小数值的元素被赋予最高优先级,因此最先从队列中删除。如果几个元素具有相同的最高优先级,则任意选择一个。
Map
中不存在重复的key
,因为放入相同的key
,只会把原有的key-value
对应的value
给替换掉,虽然key
不能重复,但value
是可以重复的Map<String, Integer> map = new HashMap<>();
map.put("apple", 123);
map.put("pear", 123); // ok
Map不保证顺序,遍历Map时,不可假设输出的key是有序的!
内部包含了一个 Entry 类型的数组 table
transient Entry[] table;
Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。
HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry
HashMap 类对于定位一个值、插入一个条目以及删除一个条目而言是高效的。
TreeMap 在内部会对 Key 进行排序,这种 Map 就是 SortedMap。注意到SortedMap 是接口,它的实现类是 TreeMap
使用TreeMap时,放入的Key必须实现Comparable接口。
String、Integer这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。
HashMap效率高于TreeMap;在需要排序的Map时才选用TreeMap。
如果作为Key的class没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法:
public class TestTreeMap {
public static void main(String[] args) {
Map<Integer, String> map = new TreeMap<>();
map.put(20, "aa");
map.put(10, "bb");
map.put(5, "ccc");
map.put(5, "cc");//不可重复,直接覆盖
/**按照key递增的方式排序*/
for (Integer key : map.keySet()
) {
System.out.println(key + "-->" + map.get(key));
}
Map<Emp, String> map02 = new TreeMap<>();
map02.put(new Emp(10, "小涛", 10000), "小涛好样的");
map02.put(new Emp(20, "小小涛", 100000), "小小涛好样的");
map02.put(new Emp(30, "小小小涛", 1000000), "小小小涛好样的");
map02.put(new Emp(003, "子涛", 1000000), "子涛好样的");
for (Emp key : map02.keySet()
) {
System.out.println(key + "-->" + map02.get(key));
}
}
}
class Emp implements Comparable<Emp> {
private int id;
private String name;
private double salary;
public Emp(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
/**
* 这里用到compareTO方法:负整数->小于,0->等于,正整数->大于
*/
@Override
public int compareTo(Emp emp) {
if (this.salary < emp.salary) {
return -1;
} else if (this.salary > emp.salary) {
return 1;
} else {
if (this.id < emp.id) {
return -1;
} else if (this.id > emp.id) {
return 1;
} else {
return 0;
}
}
}
@Override
public String toString() {
return "id: " + id + ";name: " + name + ";salary:" + salary;
}
}
5-->cc
10-->bb
20-->aa
id: 10;name: 小涛;salary:10000.0-->小涛好样的
id: 20;name: 小小涛;salary:100000.0-->小小涛好样的
id: 3;name: 子涛;salary:1000000.0-->子涛好样的
id: 30;name: 小小小涛;salary:1000000.0-->小小小涛好样的
注意到Emp类并未覆写equals()和hashCode(),因为TreeMap不使用equals()和hashCode()
LinkedHashMap类用链表实现来扩展HashMap类,它支持映射中条目的排序。
HashMap类中的条目是无序的,但在LindedHashMap中,元素既可以按照它们插入映射的顺序排序(称为插入顺序),也可以按它们被最后一次访问时的顺序,从最早到最晚(称为访问顺序)排序。
无参构造方法是以插入顺序来创建LinkedHashMap的。
要按访问顺序创建LinkedHashMap对象,可以使用构造方法LinkedHashMap(initialCapacity,loadFactor,true).
import java.util.*;
public class Test {
public static void main(String[] args) {
Map<String,Integer> hashMap = new HashMap<>();
hashMap.put("Smith", 30);
hashMap.put("Anderson", 31);
hashMap.put("Lewis", 29);
hashMap.put("Cook", 29);
System.out.println(hashMap + "\n");//随机排序
Map<String,Integer> treeMap = new TreeMap<>(hashMap);
System.out.println(treeMap);//按键的升序排列
//遍历Map写法一
treeMap.forEach((name,age) -> System.out.println("name: " + name + "; age: " + age));
System.out.println();
//写法二
Set<Map.Entry<String,Integer>> ss = treeMap.entrySet();
for (Iterator ite = ss.iterator();ite.hasNext();){
Map.Entry e = ((Map.Entry) ite.next());
System.out.println("name\t" + e.getKey() + "-----" + "age\t" + e.getValue());
}
System.out.println();
Map<String,Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("Smith",30);
linkedHashMap.put("Anderson",31);
linkedHashMap.put("Lewis",29);
linkedHashMap.put("Cook",29);
System.out.println(linkedHashMap);//按插入顺序排列
}
}
{Lewis=29, Smith=30, Cook=29, Anderson=31}
{Anderson=31, Cook=29, Lewis=29, Smith=30}
name: Anderson; age: 31
name: Cook; age: 29
name: Lewis; age: 29
name: Smith; age: 30
name Anderson-----age 31
name Cook-----age 29
name Lewis-----age 29
name Smith-----age 30
{Smith=30, Anderson=31, Lewis=29, Cook=29}
如输出所示,HashMap 中条目的顺序是随机的,而TreeMap 中的条目是按键的升序排列的,LinkedHashMap 中的条目则是按元素插入顺序排列的。
如果更新映射时不需要保持映射中元素的顺序,就使用 HashMap;如果需要保持映射中元素的插入顺序或访问顺序,就使用LinkedHashMap;如果需要使映射按照键排序,就使用 TreeMap。