ArrayList源码解析
ArrayList是List的一种实现,是为存放不定长数据而设计的一种集合类,是List接口的一种实现方案。 底层使用数组实现,当数组中数据达到数组的最大容量时,会自动扩大容量至原来的1.5倍或至容纳所有元素的最小的容量。
ArrayList特点:
- 底层使用的是数组
- 随机查询效率极快
- 尾部追加效率高,头部追加效率低
- 线程不安全
- 值可重复,也可以为null
ArrayList的继承关系
Serializable 接口
这是一个标记性接口,接口内部没有定义任何内容,在这里,仅仅作为一个标记,标明该类可以被序列化,具体的实现交由jvm来做。
序列化的作用 序列化的对象方便与传输或者是保存到文件(对象的持久化)。
1、方便网络传输。尤其是在套接字中传输对象时使用。
2、可以持久化保存对象的状态(各个属性值)。
对象输入/输出流
ObjectInputStream(InputStream in)
构造一个对象输入流,调用其readObject(Object obj)
可以读入一个对象。
ObjectOutputStream(OutputStream out)
构造一个对象输出流,调用其writeObject(Object obj)
可以写出一个对象。
代码演示
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
for(int i=1;i<1000;i++){
list.add(i);
}
try {
//将list序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/hello.txt"));
objectOutputStream.writeObject(list);
objectOutputStream.close();
//反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:/hello.txt"));
ArrayList<Integer> arrayList= (ArrayList<Integer>) objectInputStream.readObject();
objectInputStream.close();
//反序列化后的结果进行输出
for (Integer integer : arrayList) {
System.out.println(integer);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Cloneable接口
标记性接口,接口内部没有任何属性与方法。只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。
什么是clone?
clone就是创建一个对象的副本,该副本与源对象具有相同的状态 与 属性值,当然还有属性与方法。
克隆分为浅克隆与深克隆
简单概括:如果被克隆的对象内部的属性值,是另一个对象的引用,那么在克隆时,
浅克隆,克隆的只是对象的引用。
深克隆,将会把这个属性值引用的对象也克隆下来。
ArrayList中的拷贝是浅拷贝,下面用代码验证一下。
public class ArrayListTest2 {
public static void main(String[] args) {
ArrayList<Student> list=new ArrayList<>();
//创建一个Studnet对象Anna
Student anna = new Student("Anna");
list.add(anna);
//克隆这个list集合
ArrayList listClone = (ArrayList)list.clone();
//输出看看效果
System.out.println("list: "+list);
System.out.println("listClone: "+listClone);
//判断这个集合的地址是否相等。
System.out.println("list的地址与listClone的地址相同吗: "+(list==listClone));
//修改list中的对象属性
list.get(0).setName("jack");
//查看list 与 listClone 中的元素变化
System.out.println("list: "+list);
System.out.println("listClone: "+listClone);
//说明list 与 listClone 中储存的都是 对象的引用。也说明ArrayList的拷贝是浅拷贝
}
}
RandomAccess接口
还是一个标记性接口。表示这个类可以实现快速随机访问。
集合的工具类Collections中的binarySearch方法很好地解释RandomAccess接口的作用。
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的子类,则执行Collections.indexedBinarySearch(list, key);
方法,否则执行Collections.iteratorBinarySearch(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
}
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使用索引遍历,而未实现RandomAccess接口的List使用迭代器遍历。 而不同的数据结构,用这两种遍历方式的效率也不一样。下面用ArrayList和LinkedList来探寻这两种遍历方式的效率如何。
public class ListTest {
public static void main(String[] args) {
ArrayList<Integer> arrayList=new ArrayList();
LinkedList<Integer> linkedList=new LinkedList<>();
for(int i=1;i<10000;i++){
arrayList.add(i);
linkedList.add(i);
}
System.out.println("ArrayList的for循环花费时间:"+ArrayListFor(arrayList));
System.out.println("ArrayList的Iterator循环花费时间:"+ArrayListIterator(arrayList));
System.out.println("LinkedList的for循环花费时间:"+LinkedListFor(linkedList));
System.out.println("LinkedList的Iterator循环花费时间:"+LinkedListIterator(linkedList));
}
public static long ArrayListFor(ArrayList list){
long start = System.currentTimeMillis();
for(int i=0;i<list.size();i++){
list.get(i);
}
long end = System.currentTimeMillis();
return end-start;
}
public static long ArrayListIterator(ArrayList list){
long start = System.currentTimeMillis();
Iterator iterator = list.iterator();
while(iterator.hasNext()){
iterator.next();
}
long end = System.currentTimeMillis();
return end-start;
}
public static long LinkedListFor(LinkedList list){
long start = System.currentTimeMillis();
for(int i=0;i<list.size();i++){
list.get(i);
}
long end = System.currentTimeMillis();
return end-start;
}
public static long LinkedListIterator(LinkedList list){
long start = System.currentTimeMillis();
Iterator iterator = list.iterator();
while(iterator.hasNext()){
iterator.next();
}
long end = System.currentTimeMillis();
return end-start;
}
}
结果如图:
字段
/**
* Default initial capacity.
* 默认初始化容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
* static修饰的空数组,当指定默认初始化容量为0时,赋值给elementData,避免了重复创建空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* static修饰的空数组,当调用无参构造方法时,赋值给elementData,主要作用为,在添加第一个元素前,
*标记该ArrayList是由无参构造方法创建的,便于将容量初始化为DEFAULT_CAPACITY = 10,同时也避免了重复创建空数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
* 集合中真正存储元素的容器
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
* 集合中元素的个数
* @serial
*/
private int size;
elementData为什么要被transient 修饰???
在ArrayList中有两个方法,private void writeObject(java.io.ObjectOutputStream s)
和private void readObject(java.io.ObjectInputStream s)
,这两个方法是用来让ArrayList自己控制自己的序列化。
/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
//写入size
s.writeInt(size);
// Write out all elements in the proper order.
//写入ArrayList中的元素,可以看到,这里写入的是实际存储元素的大小,而非实际容量。
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
这样做的目的其实是,ArrayList的底层是数组,而这个数组是动态变化的。它通常会预留一些容量,等容量不足时再扩充容量。所以其中会有大量未被使用的容量,如果对这些容量也进行序列化,无疑是一种浪费。
构造方法
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
* 创建指定初始化容量的ArrayList
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
* 创建一个初始容量为10的空数组。
* 刚创建的时候是空的,也就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组,在添加第一个元素时,会被扩容为默认初始容量 DEFAULT_CAPACITY = 10,
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
* 创建新的集合,把传入的集合中的元素,作为本集合的元素。
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
/**
* c.toArray 可能返回的不是 Object[] 类型 ????
* 可是Collection接口中明明定义的是 Object[] toArray();
* 原来是,子类重写父类时,可以重写父类中方法的返回值。get!!
* 所以这里如果elementData不是Object[]类型的话,就需要重新拷贝为Object[]类型
*/
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
常用方法
public boolean add(E e) 向ArrayList末尾添加一个元素
//添加一个元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//这一步是确保集合的容量足够用来添加元素
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//计算所需容量的大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
/**
*如果是通过调用无参构造函数创建的集合,elementData 就会被设为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
*这一步就是通过DEFAULTCAPACITY_EMPTY_ELEMENTDATA来判断,该集合是不是通过无参构造方法创建的,
*如果是,那么所需容量大小最小被设为minCapacity=10,这就是为什么说ArrayList的默认初始容量为10
*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//保证集合容量足够大的关键步骤,如果集合容量不够大,就进行扩容
private void ensureExplicitCapacity(int minCapacity) {
//modCount 是操作数,用来纪录这个集合被操作的次数。
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容算法的具体实现,每次扩容至原容量的1.5倍,如果最小要求的容量大于原容量的1.5倍,就扩容至最小要求的容量
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
执行流程
add
ensureCapacityInternal
确保容量足够大
走够大
不够大
ensureExplicitCapacity
容量不足时,扩展容量
calculateCapacity
计算所需的最小容量
无操作
grow
扩容
elementData[size++] = e
末尾添加元素
get(int index) 获取指定索引位置的元素
public E get(int index) {
//检查索引是否越界
rangeCheck(index);
//返回元素
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
执行流程
- 检查下标是否越界
- 返回元素
remove(int index)删除指定索引位置的元素
public E remove(int index) {
//检查下标是否越界
rangeCheck(index);
//操作数自增
modCount++;
E oldValue = elementData(index);
//纪录下需要移动的元素个数
int numMoved = size - index - 1;
//移动的元素等于0时,就不需要移动
if (numMoved > 0)
//移动数组元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//数组最末尾的元素置空,以便GC将它回收,同时size--
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
//检查下标是否越界
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//取出元素
E elementData(int index) {
return (E) elementData[index];
}
执行流程
- 检查下标是否越界
- 将index后面的元素集体前移一个位置
- size--
remove(Object o)删除指定元素值的元素
//删除指定元素
public boolean remove(Object o) {
//因为null不能调用equals方法,所以要和其他非null元素分开处理
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
//操作数++
modCount++;
//计算要移动的元素个数
int numMoved = size - index - 1;
//移动的元素等于0时,就不需要移动
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
执行流程
- 找到该元素的下标
- 将该元素后面的元素集体向前移动一个位置
- size--
contains(Object o)是否包含某个元素
//是否包含某个元素
public boolean contains(Object o) {
//如果找到这个元素,说明该元素存在。
return indexOf(o) >= 0;
}
//查找元素在集合中的下标
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
执行流程
- 遍历数组
retainAll(Collection c)求两个集合的交集
public boolean retainAll(Collection<?> c) {
//要求参数c不能为null
Objects.requireNonNull(c);
//batchRemove(c, true)代表取交集,batchRemove(c, false)代表取并集
return batchRemove(c, true);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
//r表示读的下标,w表示写的下标
int r = 0, w = 0;
boolean modified = false;
try {
//遍历自己的elementData,complement的值为true,表示取交集,值为false,表示取差集
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
//从第一个元素位置重新写入元素
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
//如果没有读完,就因为抛出异常而退出了,这里有补救措施
if (r != size) {
//将没读完的元素添加到元素末尾
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
//清除后面多余的元素,设为null,方便回收。
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
//修改容量
size = w;
modified = true;
}
}
return modified;
}
执行流程
- 遍历本集合元素,看是否包含在目标集合中。
- complement的值为true,表示取交集,值为false,表示取差集。
- 遍历的过程中,collection.contains()方法可能会抛出异常而中断遍历,导致本集合中的元素没有读完。
- 如果本集合中的元素没有读完,就将剩下的元素,原封不动的添加到本集合末尾。
- 清除多余的元素
removeAll(Collection c)求两个集合的单方向差集
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
//r表示读的下标,w表示写的下标
int r = 0, w = 0;
boolean modified = false;
try {
//遍历自己的elementData,complement的值为true,表示取交集,值为false,表示取差集
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
//从第一个元素位置重新写入元素
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
//如果没有读完,就因为抛出异常而退出了,这里有补救措施
if (r != size) {
//将没读完的元素添加到元素末尾
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
//清除后面多余的元素,设为null,方便回收。
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
//修改容量
size = w;
modified = true;
}
}
return modified;
}
执行流程
跟retainAll的执行流程一样
序列化
序列化的作用
- 方便永久化存储对象
- 方便在网络中传输对象
- 可以用来创建对象副本
Serializable 序列化的工作机制:
序列化的时候系统会把当前类的 serialVersionUID 写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的 serialVersionUID ,看它是否和当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化,否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量,类型可能发生了改变,这个时候就会抛异常,反序列化失败。
1:当一个对象被序列化时,只保存对象的非静态成员变量(包括声明为private的变量),不能保存任何的成员方法和静态的成员变量。
2:如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被序列化。
3:如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化。
4 : 父类实现Serializable接口,子类没有实现Serializable接口,子类也能实现序列化。
探究默认的序列化方式,对不同类型属性的序列化结果。
package src.top.goodbye.test.ListTest;
//父类
public class Animal {
//不能对父类属性序列化
protected String name;
public Animal(String name) {
this.name = name;
}
public Animal() {
}
}
package src.top.goodbye.test.ListTest;
import java.io.Serializable;
//子类
public class Dog extends Animal implements Serializable {
//可以对子类普通属性序列化
private String color;
//不能对子类被transient修饰的属性序列化
transient private Integer age;
//不能对子类静态属性序列化
private static String species="dog";
public Dog() {
}
public Dog(String name, String color, int age) {
super(name);
this.color = color;
this.age = age;
}
public Dog(String color, int age) {
this.color = color;
this.age = age;
}
@Override
public String toString() {
return String.format("父类的普通name属性=%s,\n子类的普通color属性=%s,\n子类的被transient修饰的 age属性=%d,\n子类的静态 species属性=%s",name,color,age,species);
}
}
package src.top.goodbye.test.ListTest;
import java.io.*;
//测试类
public class Test1 {
public static void main(String[] args) {
Dog dog=new Dog("可可","白色",2);
try {
//对象输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/SerializeTest"));
//写出对象
objectOutputStream.writeObject(dog);
//对象写入流
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:/SerializeTest"));
//写入对象
Dog dogCopy = (Dog)objectInputStream.readObject();
System.out.println(dogCopy);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果
- 实现Serializable接口的父类的属性不会被序列化
- 被transient修饰的属性,不会被序列化
- 静态变量被所有的类实例共享,所以没必要进行序列化。这里虽然species属性被正常输出,但其实并没有被序列化
java还支持用户自定义类的序列化方式,来灵活的应对不同的场景。
可以在对象内部编写并实现下面两个函数,让类自己控制序列化
private void writeObject(java.io.ObjectOutputStream s),
private void readObject(java.io.ObjectInputStream s)
注意 这两个方法的访问修饰符必须是private,但事实上,即使使用非private关键词修饰这两个方法,编译器也不会报错。但这样的话,jvm将调用默认的序列化方式进行序列化,而不会调用默认用户自定义的序列化函数。
例如 我想让 父类的普通name属性
和 子类的被transient修饰的 age属性
也被序列化。
//父类方法没有做任何修改
package src.top.goodbye.test.ListTest;
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public Animal() {
}
}
package src.top.goodbye.test.ListTest;
import java.io.IOException;
import java.io.Serializable;
//子类增加了private void writeObject(java.io.ObjectOutputStream s),
//private void readObject(java.io.ObjectInputStream s)两个方法
public class Dog extends Animal implements Serializable {
private String color;
transient private Integer age;
private static String species="dog";
public Dog() {
}
public Dog(String name, String color, int age) {
super(name);
this.color = color;
this.age = age;
}
public Dog(String color, int age) {
this.color = color;
this.age = age;
}
@Override
public String toString() {
return String.format("父类的普通name属性=%s,\n子类的普通color属性=%s,\n子类的被transient修饰的 age属性=%d,\n子类的静态 species属性=%s",name,color,age,species);
}
private void writeObject(java.io.ObjectOutputStream s){
try {
//写入普通属性
s.defaultWriteObject();
//手动写入被transient修饰的属性
s.writeInt(age);
//手动写入父类属性
s.writeObject(name);
} catch (IOException e) {
e.printStackTrace();
}
}
private void readObject(java.io.ObjectInputStream s){
try {
//读入普通属性
s.defaultReadObject();
//手动读入被transient修饰的属性
this.age= s.readInt();
//手动读入父类属性
this.name=(String)s.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
package src.top.goodbye.test.ListTest;
import java.io.*;
public class Test1 {
public static void main(String[] args) {
Dog dog=new Dog("可可","白色",2);
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/SerializeTest"));
objectOutputStream.writeObject(dog);
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:/SerializeTest"));
Dog dogCopy = (Dog)objectInputStream.readObject();
System.out.println(dogCopy);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果
看,父类的普通name属性
和 子类的被transient修饰的 age属性
也被还原了。
序列化小结
- 欲实现序列化,要实现
Serializable
接口,这是一个标记性接口。 - 默认序列化方式不能对父类的属性进行序列化。
- 默认序列化方式不能对被transient修饰的属性序列化。
- 不能对类的静态属性序列化,这样做也完全没必要。
- 如果父类实现了
Serializable
接口,子类也能实现序列化。 - 可以实现 private void writeObject(java.io.ObjectOutputStream s) 和 private void readObject(java.io.ObjectInputStream s)方法,来实现自定义序列化机制。
private void writeObject(java.io.ObjectOutputStream s) 和 private void readObject(java.io.ObjectInputStream s)
这两个方法必须被private修饰,返回值必须为void ,否则,jvm将调用默认的序列化方式进行序列化,而不会调用默认用户自定义的序列化函数。- 如果子类实现了
Serializable
接口,父类必须提供无参构造方法,因为在反序列化时,程序通过父类的无参构造方法创建父类对象。
ArrayList相关面试题
1. ArrayList如何扩容?
每次扩容至原容量的1.5倍,或所需最小容量。
2. ArrayList 频繁扩容导致添加性能急剧下降,如何处理?
可以在创建ArrayList时,调用带参构造方法,指定其初始容量。减少添加元素过程中的扩容次数。
效率对比
//插入100W数据,效率对比
public void Test1(){
//不指定容量
ArrayList<Integer> list1=new ArrayList<>();
//指定容量
ArrayList<Integer> list2=new ArrayList<>(1000000);
//添加100W数据到list1
long start1=System.currentTimeMillis();
for (int i=0;i<1000000;i++){
list1.add(i);
}
long end1=System.currentTimeMillis();
//添加100W数据到list2
long start2=System.currentTimeMillis();
for (int i=0;i<1000000;i++){
list2.add(i);
}
long end2=System.currentTimeMillis();
//优化前耗时
System.out.print("优化前:");
System.out.println(end1-start1);
//优化后耗时
System.out.print("优化后:");
System.out.println(end2-start2);
}
3. ArrayList插入或删除元素是否一定比LinkedList慢?
实验测试
public void Test2(){
ArrayList<Integer> arrayList =new ArrayList<>();
LinkedList<Integer> linkedList=new LinkedList<>();
//向list1 和list2中 添加10w条数据
for (int i = 0; i < 100000; i++) {
arrayList.add(i);
linkedList.add(i);
}
//ArrayList, LinkedList 头部添加元素效率对比
//ArrayList头部添加1w条
long start1=System.currentTimeMillis();
for(int i=0;i<10000;i++){
arrayList.add(0,i);
}
long end1=System.currentTimeMillis();
System.out.println("---------ArrayList头部添加1w条数据耗时---------");
System.out.println(end1-start1);
//LinkedList头部添加1w条数据
long start2=System.currentTimeMillis();
for(int i=0;i<10000;i++){
linkedList.add(0,i);
}
long end2=System.currentTimeMillis();
System.out.println("---------LinkedList头部添加1w条数据耗时---------");
System.out.println(end2-start2);
//ArrayList, LinkedList 中间添加元素效率对比
//ArrayList中间添加1w条
long start3=System.currentTimeMillis();
for(int i=0;i<10000;i++){
int mid=arrayList.size()/2;
arrayList.add(mid,i);
}
long end3=System.currentTimeMillis();
System.out.println("---------ArrayList中间添加1w条数据耗时---------");
System.out.println(end3-start3);
//LinkedList中间添加10条数据
long start4=System.currentTimeMillis();
for(int i=0;i<10000;i++){
int mid=linkedList.size()/2;
linkedList.add(mid,i);
}
long end4=System.currentTimeMillis();
System.out.println("---------LinkedList中间添加1w条数据耗时---------");
System.out.println(end4-start4);
//ArrayList, LinkedList 尾部添加元素效率对比
//ArrayList尾部添加1w条
long start5=System.currentTimeMillis();
for(int i=0;i<10000;i++){
arrayList.add(i);
}
long end5=System.currentTimeMillis();
System.out.println("---------ArrayList尾部添加1w条数据耗时---------");
System.out.println(end5-start5);
//LinkedList尾部添加1w条数据
long start6=System.currentTimeMillis();
for(int i=0;i<10000;i++){
linkedList.add(i);
}
long end6=System.currentTimeMillis();
System.out.println("---------LinkedList尾部添加1w条数据耗时---------");
System.out.println(end6-start6);
}
头部插入 | 中间插入 | 尾部插入 | |
---|---|---|---|
ArrayList | 501 | 229 | 0 |
LinkedList | 2 | 2031 | 0 |
底层原理
- ArrayList通过数组的拷贝,实现元素的添加与删除。
- LinkedList通过修改元素的pre和next指针实现添加与删除元素。
效率分析
-
头部插入:
- ArrayList需要将集合中所有元素后移一位,效率低
- LinkedList只需要修改头部指针,效率高。
-
中间插入:
-
ArrayList需要将集合中一半的元素后移一位
-
LinkedList需要遍历集合中一半的元素
两者的时间复杂度都是O(n), 效率应当是相当的。但是ArrayList却比LinkedList快了10倍,这其实要归功于
System.arraycopy
方法。 ArrayList中数组的拷贝都是用的这个方法,这是一个native方法,调用的是c语言的代码。 -
-
尾部插入:
- ArrayList只需要在数组后面添加一个元素就可以了。
- LinkedList也是只需要修改尾部指针。
- 两者性能差不多。
下面是几种拷贝方式的比较。数据大小是100w
public void Test4(){
int[] array=new int[1000000];
//System.arraycopy 方式
int[] arraycopy1=new int[1000000];
long start = System.currentTimeMillis();
System.arraycopy(array,0,arraycopy1,0,array.length);
long end = System.currentTimeMillis();
System.out.println("-----------System.arrayList耗时-----------");
System.out.println(end-start);
//fori循环方式
int[] arraycopy2=new int[1000000];
long start2=System.currentTimeMillis();
for(int i=0;i<array.length;i++){
arraycopy2[i]=array[i];
}
long end2 = System.currentTimeMillis();
System.out.println("-----------fori耗时-----------");
System.out.println(end2-start2);
//增强for方式
int[] arraycopy3=new int[1000000];
long start3=System.currentTimeMillis();
int j=0;
for(int i:array)
arraycopy3[j++]=i;
long end3 = System.currentTimeMillis();
System.out.println("-----------增强for耗时-----------");
System.out.println(end3-start3);
//iterator 方式
int[] arraycopy4=new int[1000000];
PrimitiveIterator.OfInt iterator = Arrays.stream(array).iterator();
long start4=System.currentTimeMillis();
j=0;
while(iterator.hasNext()){
arraycopy4[j++]= iterator.nextInt();
}
long end4= System.currentTimeMillis();
System.out.println("-----------iterator耗时-----------");
System.out.println(end4-start4);
}
4. ArrayList 是线程安全的吗?
不是,ArrayList中没有声明线程安全的方法。但是集合框架中还有一个Vector集合,是线程安全的,但效率要慢的多。
除了Vector集合外,还可以使用为如下方式
List<String> list = new ArrayList<>();
List<String> synchronizedList = Collections.synchronizedList(list);
这样得到的synchronizedList
也是线程安全的。
5. 如何复制某个ArrayList到另外一个ArrayList中去呢?你能列举几种?
- for循环
- ArrayList(Collection<? extends E> c)
- ArrayList.clone();
- ArrayList.add(Collection<? extends E> c)
6. ArrayList和LinkedList 的区别?
ArrayList | LinkedList | |
---|---|---|
底层 | 数组 | 链表 |
随机查找 | ✔️ 支持 | ❌不支持 |
实现Deque接口 | ❌ 没有实现 | ✔️ 实现了 |
效率比较 | 头部插入慢,中间插入和尾部插入优于LinkedList | 头部插入快,中间插入慢,尾部插入与ArrayList差不多 |
\