集合
1、集合框架
1.1 、集合的简介
通常,我们的程序需要根据程序运行时才知道创建多少个对象。但若非程序运行,程序开发阶段,我们根本不知道到底需要多少个数量的对象,甚至不知道它的准确类型。为了满足这些常规的编程需要,我们要求能在任何时候,任何地点创建任意数量的对象,而这些对象用什么来容纳呢?我们首先想到了数组,但是数组只能放统一类型的数据,而且其长度是固定的,那怎么办呢?集合便应运而生了!
1.2、集合的特点
1、集合只能存储对象,且长度可变,数组长度固定,可以存储基本数据类型,也可以存储引用类型
2、不管是集合还是数组存放的是对象的引用,实际对象本身还是存储在堆内存中
3、集合可以存放不同类型,不限数量的数据类型。数组只能存储同一种数据类型
未使用泛型的情况下,任何对象添加至集合后,自动转变为Object类型,所以想取出的时候,需要进行强制类型转换成集合中元素对应的数据类型去接收,使用泛型便限定了添加到集合中数据类型
1.3、Java集合框架图:
从下面的集合框架图可以看到,Java中的集合框架主要包括两种类型的容器Collection和Map,Collection存储一个元素集合,Map存储键值对映射。
Collection接口有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap等等
1.4、集合框架接口
序号 | 接口名称 | 接口描述 |
1 | Collection | Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。 |
2 | List | List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。 |
3 | Set | Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。Set 接口存储一组唯一,无序的对象。 |
4 | SortedSet | 继承于Set保存有序的集合。 |
5 | Map | Map 接口存储一组键值对象,提供key(键)到value(值)的映射 |
6 | Map.Entry | 描述在一个Map中的一个元素(键/值对)。是一个Map的内部类 |
7 | SortedMap | 继承于 Map,使 Key 保持在升序排列
|
1.4、集合实现类
序号 | 类名 | 类描述 |
1 | AbstractCollection | 实现了大部分的集合接口。 |
2 | AbstractList | 继承于AbstractCollection 并且实现了大部分List接口 |
3 | AbstractSequentialList | 继承于AbstractList,提供了对数据元素的链式访问而不是随机访问 |
4 | LinkedList | 该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。例如: |
5 | ArrayList | 该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低。 |
6 | AbstractSet | 继承于AbstractCollection 并且实现了大部分Set接口 |
7 | HashSet | 该类实现了Set接口,不允许出现重复元素,不保证集合中元素的顺序,允许包含值为null的元素,但最多只能一个 |
8 | LinkedHashSet | 具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现 |
9 | TreeSet | 该类实现了Set接口,可以实现排序等功能 |
10 | AbstractMap | 实现了大部分的Map接口 |
11 | HashMap | HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 |
12 | TreeMap | 继承了AbstractMap,并且使用一颗树 |
13 | WeakHashMap | 继承AbstractMap类,使用弱密钥的哈希表 |
14 | LinkedHashMap | 继承于HashMap,使用元素的自然顺序对元素进行排序 |
2、Iterable
它是Java集合的顶层接口(不包括 map 系列的集合,Map接口 是 map 系列集合的顶层接口),是获取集合元素的方法之一。
查看Iterable源码发现,Iterable里面封装了 Iterator 接口。所以只要实现了只要实现了Iterable接口的类,就可以使用Iterator迭代器了。
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
我们可以在看下集合的顶层Collection 接口的源码,可以看到它继承的是类 Iterable,所以所有的集合类都可以使用迭代器获取集合元素。
2.1IteratorAPI
方法摘要 | |
E | next():返回迭代中的下一个元素,返回值是 Object,需要强制转换成自己需要的类型 |
boolean | hasNext():判断容器内是否还有可供访问的元素 |
void | remove():删除迭代器刚越过的元素,必须与next()方法一起使用 |
3、Collection
3.1CollectionAPI:
方法摘要 | |
boolean | add(E e) 确保此集合包含指定的元素(可选操作)。 |
boolean | addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到此集合(可选操作)。 |
void | clear() 从此集合中删除所有元素(可选操作)。 |
boolean | contains(Object o) 如果此集合包含指定的元素,则返回 true 。 |
boolean | containsAll(Collection<?> c) 如果此集合包含指定 集合中的所有元素,则返回true。 |
boolean | equals(Object o) 将指定的对象与此集合进行比较以获得相等性。 |
int | hashCode() 返回此集合的哈希码值。 |
boolean | isEmpty() 如果此集合不包含元素,则返回 true 。 |
Iterator<E> | iterator() 返回此集合中的元素的迭代器。 |
default Stream<E> | parallelStream() 返回可能并行的 |
boolean | remove(Object o) 从该集合中删除指定元素的单个实例(如果存在)(可选操作)。 |
boolean | removeAll(Collection<?> c) 删除指定集合中包含的所有此集合的元素(可选操作)。 |
default boolean | removeIf(Predicate<? super E> filter) 删除满足给定谓词的此集合的所有元素。 |
boolean | retainAll(Collection<?> c) 仅保留此集合中包含在指定集合中的元素(可选操作)。 |
int | size() 返回此集合中的元素数。 |
default Spliterator<E> | spliterator() 创建一个 |
default Stream<E> | stream() 返回以此集合作为源的顺序 |
Object[] | toArray() 返回一个包含此集合中所有元素的数组。 |
<T> T[] | toArray(T[] a) 返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型 |
4、list
4.1Array List
底层数据结构是数组,查询快,增删慢
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.ArrayList<E>
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList 继承了AbstractList,实现了List。它底层是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。查询快,增删慢
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
public interface RandomAccess {
}
这个接口是空的,那为什么List集合实现这个接口,就能支持快速随机访问呢?查看Collections类中的binarySearch()方法,源码如下:
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
由此可以看出,判断list是否实现RandomAccess接口来实行indexedBinarySerach(list,key)或iteratorBinarySerach(list,key)方法(instanceof其作用是用来判断某对象是否为某个类或接口类型)那么,又有人疑问执行这两个方法有什么不同?
查看下indexedBinarySerach(list,key)方法源码:
private static <T>
int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1;
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
查看下iteratorBinarySerach(list,key)方法源码:
private static <T>
int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
{
int low = 0;
int high = list.size()-1;
ListIterator<? extends Comparable<? super T>> i = list.listIterator();
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = get(i, mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
通过查看源代码,发现实现RandomAccess接口的List集合采用一般的for循环遍历,而未实现这接口则采用迭代器。
接下来,我们将进行下测试ArrayList以及LinkedList采用这两种方法各自的性能是如何!
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CollectionTest {
public static void main(String[] args){
List<Integer> arraylist = new ArrayList<>();
for(int i =0; i<50000; i++) {
arraylist.add(i);
}
System.out.println("ArrayList for循环取值时间: " + arrayListFor(arraylist));
//ArrayList for循环取值时间: 3
System.out.println("ArrayList Iterator循环取值 时间: " + arrayListIterator(arraylist));
//ArrayList Iterator循环取值 时间: 4
}
public static long arrayListFor(List<Integer> list){
long start = System.currentTimeMillis();
for(int j = 0;j < list.size();j++) {
Integer num = list.get(j);
}
long end = System.currentTimeMillis();
return end - start;
}
public static long arrayListIterator(List<Integer> list){
long start = System.currentTimeMillis();
Iterator it = list.iterator();
while(it.hasNext()){
Integer num = (Integer) it.next();
}
long end = System.currentTimeMillis();
return end - start;
}
}
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class LinkTest {
public static void main(String[] args){
List<Integer> linkedlist = new LinkedList<>();
for(int i =0; i<50000; i++) {
linkedlist.add(i);
}
System.out.println("LinkedList for循环取值时间: " + arrayListFor(linkedlist));
//LinkedList for循环取值时间: 2097
System.out.println("LinkedList Iterator循环取值 时间: " + arrayListIterator(linkedlist));
//LinkedList Iterator循环取值 时间: 7
}
public static long arrayListFor(List<Integer> linkedlist){
long start = System.currentTimeMillis();
for(int j = 0;j < linkedlist.size();j++) {
Integer num = linkedlist.get(j);
}
long end = System.currentTimeMillis();
return end - start;
}
public static long arrayListIterator(List<Integer> linkedlist){
long start = System.currentTimeMillis();
Iterator<Integer> it = linkedlist.iterator();
while(it.hasNext()){
Integer num = (Integer) it.next();
}
long end = System.currentTimeMillis();
return end - start;
}
}
从上面数据可以看出,ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for循环遍历快,
4.1.1ArrayList特点
1、ArrayList 实际上是通过一个数组去保存数据的。当我们构造ArrayList时;若使用默认构造函数,则ArrayList的默认容量大小是10。
(02) 当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=“(原始容量x3)/2 + 1”。
2、ArrayList的克隆函数,即是将全部元素克隆到一个数组中。
3、ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
4.1.2API
1)ArrayList的三个构造器:
public ArrayList();
默认的构造器,将会以默认(16)的大小来初始化内部的数组。
public ArrayList(ICollection);
用一个ICollection对象来构造,并将该集合的元素添加到ArrayList。
public ArrayList(int);
用指定的大小来初始化内部的数组。
2)ArrayListAPI:
方法摘要 | |
boolean | add(E e) 将指定的元素添加到此列表的尾部。 |
void | add(int index, E element) |
boolean | addAll(Collection<? extends E> c) |
boolean | addAll(int index, Collection<? extends E> c) |
void | clear() |
Object | clone() |
boolean | contains(Object o) |
void | ensureCapacity(int minCapacity) |
E | get(int index) |
int | indexOf(Object o) |
boolean | isEmpty() |
int | lastIndexOf(Object o) |
E | remove(int index) |
boolean | remove(Object o) |
protected void | removeRange(int fromIndex, int toIndex) |
E | set(int index, E element) |
int | size() |
Object[] | toArray() |
<T> T[] | toArray(T[] a) |
void | trimToSize() |
4.1.3遍历与删除
集合的三种遍历方式:
//迭代器遍历
Iterator<String> it = arraylist.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
//增强for遍历
for(String str : arraylist) {
System.out.println(str);
}
//普通for遍历
for(int i = 0; i < arraylist.size(); i++) {
System.out.println(arraylist.get(i));
}
集合删除元素:
//错误写法一:
arraylist.add("a");
arraylist.add("a");
arraylist.add("b");
arraylist.add("c");
arraylist.add("d");
arraylist.add("e");
for (int i = 0; i < arraylist.size(); i++) {
String s = arraylist.get(i);
if (s.equals("a")) {
arraylist.remove("a");
}
}
System.out.println(arraylist);//[a, b, c, d, e]
//原因是,删除元素后,ArrayList集合的索引重新排列,删除元素后面的元素索引全部向前移动一位,本例中,删除第一个a后,第二个a的索引就由1变成了0,而for循环已经运行过0了,导致第二个a未被删除。
//正确写法一:
for (int i = list.size() - 1; i >= 0; i--) {
String s = list.get(i);
if (s.equals("a")) {
list.remove(i);
}
}
//正确写法二:
int i = 0;
int size = list.size();
while (i < size) {
if (list.get(i).equals("a")) {
list.remove(i);
size = list.size();
} else {
i++;
}
}
4.2LinkedList
底层数据结构是链表,查询慢,增删快,线程不安全,效率高
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
4.2.1API
1)LinkedList的两个构造器:
public LinkedList():构造一个空列表
public LinkedList(Collection<? extends E> c):构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序,要将元素放入此列表的集合,否则会报错NullPointerException
2)LinkedList的API:
方法摘要 | |
boolean | add(E e) 将指定的元素追加到此列表的末尾。 |
void | add(int index, E element) 在此列表中的指定位置插入指定的元素。 |
boolean | addAll(Collection<? extends E> c) 按照指定集合的迭代器返回的顺序将指定集合中的所有元素追加到此列表的末尾。 |
boolean | addAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置开始。 |
void | addFirst(E e) 在该列表开头插入指定的元素。 |
void | addLast(E e) 将指定的元素追加到此列表的末尾。 |
void | clear() 从列表中删除所有元素。 |
Object | clone() 返回此 |
boolean | contains(Object o) 如果此列表包含指定的元素,则返回 |
Iterator<E> | descendingIterator() 以相反的顺序返回此deque中的元素的迭代器。 |
E | element() 检索但不删除此列表的头(第一个元素)。 |
E | get(int index) 返回此列表中指定位置的元素。 |
E | getFirst() 返回此列表中的第一个元素。 |
E | getLast() 返回此列表中的最后一个元素。 |
int | indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。 |
int | lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。 |
ListIterator<E> | listIterator(int index) 从列表中的指定位置开始,返回此列表中元素的列表迭代器(按适当的顺序)。 |
boolean | offer(E e) 将指定的元素添加为此列表的尾部(最后一个元素)。 |
boolean | offerFirst(E e) 在此列表的前面插入指定的元素。 |
boolean | offerLast(E e) 在该列表的末尾插入指定的元素。 |
E | peek() 检索但不删除此列表的头(第一个元素)。 |
E | peekFirst() 检索但不删除此列表的第一个元素,如果此列表为空,则返回 |
E | peekLast() 检索但不删除此列表的最后一个元素,如果此列表为空,则返回 |
E | poll() 检索并删除此列表的头(第一个元素)。 |
E | pollFirst() 检索并删除此列表的第一个元素,如果此列表为空,则返回 |
E | pollLast() 检索并删除此列表的最后一个元素,如果此列表为空,则返回 |
E | pop() 从此列表表示的堆栈中弹出一个元素。 |
void | push(E e) 将元素推送到由此列表表示的堆栈上。 |
E | remove() 检索并删除此列表的头(第一个元素)。 |
E | remove(int index) 删除该列表中指定位置的元素。 |
boolean | remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。 |
E | removeFirst() 从此列表中删除并返回第一个元素。 |
boolean | removeFirstOccurrence(Object o) 删除此列表中指定元素的第一个出现(从头到尾遍历列表时)。 |
E | removeLast() 从此列表中删除并返回最后一个元素。 |
boolean | removeLastOccurrence(Object o) 删除此列表中指定元素的最后一次出现(从头到尾遍历列表时)。 |
E | set(int index, E element) 用指定的元素替换此列表中指定位置的元素。 |
int | size() 返回此列表中的元素数。 |
Spliterator<E> | spliterator() 在此列表中的元素上创建late-binding和故障快速 |
Object[] | toArray() 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 |
<T> T[] | toArray(T[] a) 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。 |
4.3Vector(基本淘汰,不做详解)
底层数据结构是数组,查询快,增删慢,线程安全,效率低
5、Set
Set集合特点:
1、无序(存储顺序和取出顺序不一致),虽然Set集合的元素无序,但是作为集合来说,它肯定有它自己的存储顺序,且元素不可重复,
5.1、HashSet
HashSet特点:
1)不保证 set 的迭代顺序
2)特别是它不保证该顺序恒久不变。该集合是怎么保证元素唯一性的呢?
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
由上可见HashSet是基于HashMap实现的,且底层数据结构是哈希表。HashSet当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置,如果 hashCode 值不同,直接把该元素存储到 hashCode() 指定的位置,如果 hashCode 值相同,那么会继续判断该元素和集合对象的 equals() 作比较,hashCode 相同,equals 为 true,则视为同一个对象,
注意:每一个存储到 哈希 表中的对象,都得提供 hashCode() 和 equals() 方法的实现,用来判断是否是同一个对象,对于 HashSet 集合,我们要保证如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
5.2、LinkedHashSet
1)元素有序唯一
2)由链表保证元素有序
3)由哈希表保证元素唯一
5.3、TreeSet
1)底层数据结构是红黑树。
2)使用元素的自然顺序对元素进行排序或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
定制排序: 创建 TreeSet 对象时, 传入 Comparator 接口的实现类. 要求: Comparator 接口的 compare 方法的返回值和 两个元素的 equals() 方法具有一致的返回值
自动排序:添加自定义对象的时候,必须要实现 Comparable 接口,并要覆盖 compareTo(Object obj) 方法来自定义比较规则如果 this > obj,返回正数 1,如果 this < obj,返回负数 -1,如果 this = obj,返回 0 ,则认为这两个对象相等
6、Map
Map集合有以下特点:
1、将键映射到值的对象
2、一个映射不能包含重复的键
3、每个键最多只能映射到一个值
Map集合主要包括四个实现类,
6.1、HashMap
我们最常用的Map,它根据key的HashCode 值来存储数据,根据key可以直接获取它的Value,同时它具有很快的访问速度。HashMap最多只允许一条记录的key值为Null(多条会覆盖);允许多条记录的Value为 Null。非同步的。
TreeMap:能够把它保存的记录根据key排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。
Hashtable:与 HashMap类似,不同的是:key和value的值均不允许为null;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。
LinkedHashMap: 保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历的时候会比HashMap慢。key和value均允许为空,非同步的。
6.2Hash MapAPI
方法摘要 | |
void | clear() |
Object | clone() |
boolean | containsKey(Object key) |
boolean | containsValue(Object value) |
Set<Map.Entry<K,V>> | entrySet() |
V | get(Object key) |
boolean | isEmpty() |
Set<K> | keySet() |
V | put(K key, V value) |
void | putAll(Map<? extends K,? extends V> m) |
V | remove(Object key) |
int | size() |
Collection<V> | values() |
6.2.1Map遍历
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(1, "java");
map.put(2, "python");
map.put(3, "vue");
//第一种方式: keySet 其实遍历了两次,第一次获取Iterator,第二次根据key获取value,因此性能较差
Set<Integer> keys = map.keySet();
for (Integer key : keys){
String value = map.get(key);
System.out.println("key:" + key);
}
//第二种方式: entrySet 优于keySet,因为一次就把key和value放入了entry)
Set<Map.Entry<Integer, String>> entry = map.entrySet();
for (Map.Entry<Integer, String> stringEntry : entry) {
System.out.println("key: "+ stringEntry.getKey() + " value: " + stringEntry.getValue());
}
//第三种方式:Iterator (可用 it.remove()在遍历时删除)
Iterator<Entry<Integer, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, String> entry1 = it.next();
System.out.println("key: "+ entry1.getKey() + " value: " + entry1.getValue());
}
//第四种方式:Lambda (本质是 entrySet)
map.forEach((key, value) -> {
System.out.println("key: "+ key + " value: " + value );
});
//第五种方式:Values,获取map所有的值,values()返回的是一个集合 Collection(可转List/Set)
Collection<String> collection = map.values();
for (String s : collection) {
System.out.println("value: " + s);
}