集合整体框架
- List集合:内部实现使用数组进行存储,扩容时会创建更大的数组空间,把原有数据复制到新数组中。
ArrayList
支持快速随机访问,但是插入删除的速度较慢,因为可能需要移动元素。LinkedList
本质是双向链表,插入删除速度快,但是随机访问速度慢。实现了Deque
接口,这个接口继承自Queue
,同时具有栈和队列的性质,其中有指向第一个元素和最后一个元素的指针。
- Queue集合:先进先出的数据结构。
BlockingQueue
具有先进先出和阻塞操作的特点,经常作为Buffer
(数据缓冲区)使用。 - Map集合:
HashMap
线程不安全,ConcurrentHashMap
线程安全,TreeMap
是Key有序的Map类集合。 - Set集合:
HashSet
使用HashMap
来实现,Value固定为一个静态对象。TreeSet
使用TreeMap
实现,保证插入后的集合是有序的。LinkedHashSet
继承自HashSet
,内部使用链表维护了元素的插入顺序。
集合初始化
ArrayList
,默认的初始值大小是10,如果超过此次大小,那么会扩容,每次变为原来的1.5倍,并且把原有数据复制到新数组中。如果需要将1000个元素放入,但未指定初始大小,那么需要扩容13次,此时可能会有OOM
问题。所以此时最好是new ArrayList(1000);
HashMap
,默认的初始值大小是16,如果超过此次大小,那么会扩容,每次扩容增加2倍。不过不是在new的时候初始化的,而是在第一次调用put的时候分配的。Capacity决定容量大小(默认16),Load Factor决定填充比例(默认0.75),基于这两个的乘积,内部用threshold表示其中能放入的元素个数。
数组与集合
类型与中括号紧挨来定义数组,比如String[]
用于指代String
数组对象。
// 静态初始化
String[] args = {"a", "b"};
Object obj = args;
((String[]) obj)[0] = "object";
// 动态初始化
String[] args1 = new String[2];
args1[0] = "a";
args1[1] = "b";
函数式接口进行遍历:
Arrays.asList(args3).stream().forEach(x -> System.out.println(x));
Arrays.asList(args3).stream().forEach(System.out::println);
Arrays是针对数组对象进行操作的工具类,包括数组的排序、查找。对比和拷贝等操作。
数组转集合
public static void main(String[] args) {
String[] stringArray = new String[3];
stringArray[0] = "one";
stringArray[1] = "two";
stringArray[2] = "three";
List<String> stringList = Arrays.asList(stringArray);
// 此处方法可以将集合和数组中的值都进行修改
stringList.set(0, "oneList");
// 输出"oneList"
System.out.println(stringList.get(0));
// 输出"oneList"
System.out.println(stringArray[0]);
// 以下三行可以正常编译,但是运行时会报错
// 是因为asList()返回的是一个继承自AbstractList但未实现add、remove、clear方法的内部类
// 由于是继承,所以编译不会报错,但是AbstractList里的add、remove、clear方法会直接抛出异常
// 那么此时未实现这三个方法的内部类在执行时,实际上执行的就是AbstractList里的add、remove、clear方法
// 所以会抛出异常
stringList.add("four");
stringList.remove(0);
stringList.clear();
}
注意:数组转集合的时候,不能使用其修改集合相关的方法,add/remove/clear
会抛出异常,仅可使用set()
。
通过set()
可以修改元素的值,但是原有数组对应位置的值同时也会被修改,但是不能进行修改元素个数的任何操作。
Arrays.asList()
体现的是适配器模式,后台的数据仍是原有数组,asList()
返回的是一个Arrays
的内部类,它没有实现集合个数的相关修改方法,所以会抛出异常。
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
此ArrayList
非彼ArrayList
,它是Arrays
的内部类,只提供了AbstractList
里部分方法的实现,如下:
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
}
异常是由这个内部类的父类AbstractList
抛出:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}
// 次方法最终会调用remove(),从而也会有异常
public void clear() {
removeRange(0, size());
}
}
所以,在使用数组转集合的时候,需要使用java.util.ArrayList
直接创建一个新集合,此时对这个新集合做修改就不会影响到原来的数组了。
List<String> objectList = new java.util.ArrayList<>(Arrays.asList(stringArray));
集合转数组
public static void main(String[] args) {
List<String> list = new ArrayList<>(3);
list.add("one");
list.add("two");
list.add("three");
// 不要使用无参方法把集合转换成数组,这样会导致泛型丢失
Object[] array1 = list.toArray();
// array2 数组长度小于集合元素个数
// 结果为[null, null]
String[] array2 = new String[2];
list.toArray(array2);
System.out.println(Arrays.asList(array2));
// 结果为[one, two, three]
String[] test = list.toArray(array2);
System.out.println(Arrays.asList(test));
// array3 数组长度等于集合元素个数
// 结果为[one, two, three]
String[] array3 = new String[3];
list.toArray(array3);
System.out.println(Arrays.asList(array3));
}
toArray()
源码如下:
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
核心:如果传入数组的length
大于等于集合的大小,那么会将集合中的数据复制到参数指定的数组中,并且返回;否则会创建一个新的数组,将集合元素复制到数组后,返回此数组。
所以,如果集合是List<String>
, 那么要将其转成数组,一般采用
String[] strArray = list.toArray(new String[0]);
当数组容量等于集合大小时,运行最快,消耗空间最小;如果设置不合理会造成性能和空间的浪费。
在ArrayList
中存储数据的是:
transient Object[] elementData;
集合序列化
transient
表示此字段在序列化的时候会被忽略。在集合序列化时会调用writeObject
写入流中,在网络客户端反序列化的writeObject
时,会重新赋值到新对象的elementData
中。
原因:因为elementData
的容量经常会大于实际存储元素的数量,所以只需发送真正有实际值的数组元素即可。