首页>>后端>>java->ArrayList源码解析

ArrayList源码解析

时间:2023-12-05 本站 点击:0

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

头部插入中间插入尾部插入ArrayList5012290LinkedList220310

底层原理

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 的区别?

ArrayListLinkedList底层数组链表随机查找✔️ 支持❌不支持实现Deque接口❌ 没有实现✔️ 实现了效率比较头部插入慢,中间插入和尾部插入优于LinkedList头部插入快,中间插入慢,尾部插入与ArrayList差不多

原文:https://juejin.cn/post/7097853309230252040


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/java/12238.html