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); }
执行流程
addensureCapacityInternal确保容量足够大走够大不够大ensureExplicitCapacity容量不足时,扩展容量calculateCapacity计算所需的最小容量无操作grow扩容elementData[size++] = e末尾添加元素
get(int index) 获取指定索引位置的元素
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的拷贝是浅拷贝 } }0
执行流程
检查下标是否越界
返回元素
remove(int index)删除指定索引位置的元素
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的拷贝是浅拷贝 } }1
执行流程
检查下标是否越界
将index后面的元素集体前移一个位置
size--
remove(Object o)删除指定元素值的元素
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的拷贝是浅拷贝 } }2
执行流程
找到该元素的下标
将该元素后面的元素集体向前移动一个位置
size--
contains(Object o)是否包含某个元素
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的拷贝是浅拷贝 } }3
执行流程
遍历数组
retainAll(Collection c)求两个集合的交集
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的拷贝是浅拷贝 } }4
执行流程
遍历本集合元素,看是否包含在目标集合中。
complement的值为true,表示取交集,值为false,表示取差集。
遍历的过程中,collection.contains()方法可能会抛出异常而中断遍历,导致本集合中的元素没有读完。
如果本集合中的元素没有读完,就将剩下的元素,原封不动的添加到本集合末尾。
清除多余的元素
removeAll(Collection c)求两个集合的单方向差集
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的拷贝是浅拷贝 } }5
执行流程
跟retainAll的执行流程一样
序列化
序列化的作用
方便永久化存储对象
方便在网络中传输对象
可以用来创建对象副本
Serializable 序列化的工作机制:
序列化的时候系统会把当前类的 serialVersionUID 写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的 serialVersionUID ,看它是否和当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化,否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量,类型可能发生了改变,这个时候就会抛异常,反序列化失败。
1:当一个对象被序列化时,只保存对象的非静态成员变量(包括声明为private的变量),不能保存任何的成员方法和静态的成员变量。
2:如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被序列化。
3:如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化。
4 : 父类实现Serializable接口,子类没有实现Serializable接口,子类也能实现序列化。
探究默认的序列化方式,对不同类型属性的序列化结果。
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的拷贝是浅拷贝 } }6
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的拷贝是浅拷贝 } }7
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的拷贝是浅拷贝 } }8
运行结果
实现Serializable接口的父类的属性不会被序列化
被transient修饰的属性,不会被序列化
静态变量被所有的类实例共享,所以没必要进行序列化。这里虽然species属性被正常输出,但其实并没有被序列化
java还支持用户自定义类的序列化方式,来灵活的应对不同的场景。
可以在对象内部编写并实现下面两个函数,让类自己控制序列化
private void writeObject(java.io.ObjectOutputStream s),
private void readObject(java.io.ObjectInputStream s)
注意 这两个方法的访问修饰符必须是private,但事实上,即使使用非private关键词修饰这两个方法,编译器也不会报错。但这样的话,jvm将调用默认的序列化方式进行序列化,而不会调用默认用户自定义的序列化函数。
例如 我想让 父类的普通name属性
和 子类的被transient修饰的 age属性
也被序列化。
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的拷贝是浅拷贝 } }9
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); }0
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); }1
运行结果
看,父类的普通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时,调用带参构造方法,指定其初始容量。减少添加元素过程中的扩容次数。
效率对比
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); }2
3. ArrayList插入或删除元素是否一定比LinkedList慢?
实验测试
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); }3
底层原理
ArrayList通过数组的拷贝,实现元素的添加与删除。
LinkedList通过修改元素的pre和next指针实现添加与删除元素。
效率分析
头部插入:
ArrayList需要将集合中所有元素后移一位,效率低
LinkedList只需要修改头部指针,效率高。
中间插入:
两者的时间复杂度都是O(n), 效率应当是相当的。但是ArrayList却比LinkedList快了10倍,这其实要归功于System.arraycopy
方法。 ArrayList中数组的拷贝都是用的这个方法,这是一个native方法,调用的是c语言的代码。
ArrayList需要将集合中一半的元素后移一位
LinkedList需要遍历集合中一半的元素
尾部插入:
ArrayList只需要在数组后面添加一个元素就可以了。
LinkedList也是只需要修改尾部指针。
两者性能差不多。
下面是几种拷贝方式的比较。数据大小是100w
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); }4
4. ArrayList 是线程安全的吗?
不是,ArrayList中没有声明线程安全的方法。但是集合框架中还有一个Vector集合,是线程安全的,但效率要慢的多。
除了Vector集合外,还可以使用为如下方式
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); }5
这样得到的synchronizedList
也是线程安全的。
5. 如何复制某个ArrayList到另外一个ArrayList中去呢?你能列举几种?
for循环
ArrayList(Collection<? extends E> c)
ArrayList.clone();
ArrayList.add(Collection<? extends E> c)
6. ArrayList和LinkedList 的区别?
原文:https://juejin.cn/post/7097853309230252040