• ArrayList


    一、ArrayList

    1、Arraylist 类定义

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
    • ArrayList 继承 AbstractList(这是一个抽象类,对一些基础的 List 操作进行封装);
    • 实现 List,RandomAccess,Cloneable,Serializable 几个接口;
    • RandomAccess 是一个标记接口,用来表明其支持快速随机访问,为Lis提供快速访问功能的;实现了该接口的list可以通过for循环来遍历数据,比使用迭代器的效率要高;
    • Cloneable 是克隆标记接口,覆盖了 clone 函数,能被克隆;
    • java.io.Serializable 接口,ArrayList 支持序列化,能通过序列化传输数据。

    2、构造方法

    // 默认构造函数
    ArrayList()
    // capacity是ArrayList的默认容量大小,当由于增加数据导致容量不足时,容量会增加到当前容器的1.5倍
    ArrayList(int capacity)
    // 创建一个包含collection的ArrayList
    ArrayList(Collection<? extends E> collection){
    	elementData = c.toArray();
    	if ((size = elementData.length) != 0) {
    		// c.toArray might (incorrectly) not return Object[] (see 6260652)
    		if (elementData.getClass() != Object[].class)
    			elementData = Arrays.copyOf(elementData, size, Object[].class);
    	} else {
    		// replace with empty array.
    		this.elementData = EMPTY_ELEMENTDATA;
    	}
    }

    上面包含Collection参数的构造方法里面有个数字:6260652,这是一个Java的bug,意思当给定的集合内的元素不是Object类型时,会转换成Object类型。一般情况下不会触发此 bug 6260652,只有在下面的场景下才会触发:ArrayList初始化之后(ArrayList元素非Object类型),再次调用toArray方法时,得到Object数组,并且往Object数组赋值时才会触发该bug:

    List<String> list = Arrays.asList("hello world", "Java");
    Object[] objects = list.toArray();
    System.out.println(objects.getClass().getSimpleName());
    objects[0] = new Object();

    输出结果:

    Exception in thread "main" java.lang.ArrayStoreException: java.lang.Object
    	at com.jolly.demo.TestArrayList.main(TestArrayList.java:23)
    String[]

    上面bug在jdk9后解决了:Arrays.asList(x).toArray().getClass() should be Object[].class

    3、成员变量

    • transient Object[] elementData

      Object 数组,Arraylist 实际存储的数据。 如果通过不含参数的构造函数ArrayList()来创建ArrayList,默认为空数组

      public ArrayList() {
      	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
      }
      private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

      ArrayList无参构造器初始化时,默认是空数组的,数组容量也是0,真正是在添加数据时默认初始化容量为10;

      • 为什么数组类型是 Object 而不是泛型E:如果是E的话在初始化的时候需要通过反射来实现,另外一个泛型是在JDK1.5之后才出现的,而ArrayList在jdk1.2就有了;

      • ①、为什么 transient Object[] elementData;

        ArrayList 实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化 99 个 null 元素.为了保证在序列化的时候不
        会将这么多 null 同时进行序列化, ArrayList 把元素数组设置为 transient;

      • ②、为什么要写方法:writeObject and readObject

        前面提到为了防止一个包含大量空对象的数组被序列化,为了优化存储.所以,ArrayList 使用 transient 来声明elementData作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写writeObject 和 readObject方法的方式把其中的元素保留下来writeObject方法把elementData数组中的元素遍历的保存到输出流ObjectOutputStream)中。readObject方法从输入流(ObjectInputStream)中读出对象并保存赋值到elementData数组中

    • private int size:Arraylist 的容量

    • protected transient int modCount = 0

      这个为父类 AbstractList 的成员变量,记录了ArrayList结构性变化的次数在ArrayList的所有涉及结构变化的方法中都增加modCount的值,AbstractList 中的iterator()方法(ArrayList直接继承了这个方法)使用了一个私有内部成员类Itr,该内部类中定义了一个变量 expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值,在next()方法中调用了checkForComodification()方法,进行对修改的同步检查;

    4、新增和扩容

    新增元素主要有两步:

    • 判断是否需要扩容,如果需要执行扩容操作;
    • 直接赋值
    public boolean add(E e) {
      //确保数组大小是否足够,不够执行扩容,size 为当前数组的大小
      ensureCapacityInternal(size + 1);  // Increments modCount!!
      //直接赋值,线程不安全的
      elementData[size++] = e;
      return true;
    }

    如果数组容量不够,对数组进行扩容,JDK7 以后及 JDK7 前的实现不一样。扩容本质上是对数组之间的数据拷贝;

    • JDK6:直接扩容,且扩容一般是源数组的 1.5 倍:
      int newCapacity = (oldCapacity * 3)/2 + 1;
    • JDK7:扩容,且一般是之前的 1.5 倍
      int newCapacity = oldCapacity + (oldCapacity >> 1);
      // 拷贝数组
      elementData = Arrays.copyOf(elementData, newCapacity);
    • JDK8:扩容,一般是之前的1.5倍
      private void ensureCapacityInternal(int minCapacity) {
          ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
      }
      private static int calculateCapacity(Object[] elementData, int minCapacity) {
          if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              return Math.max(DEFAULT_CAPACITY, minCapacity);
          }
          return minCapacity;
      }
      private void ensureExplicitCapacity(int minCapacity) {
          modCount++;
          // 如果我们期望的最小容量大于目前数组的长度,那么就扩容
          if (minCapacity - elementData.length > 0)
              grow(minCapacity);
      }
      // 扩容,并把现有数据拷贝到新的数组里面去
      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);
      }

    5、ArrayList 安全隐患

    • 当传递 ArrayList 到某个方法中,或者某个方法返回 ArrayList,什么时候要考虑安全隐患?如何修复安全违规这个问题呢?

      当array被当做参数传递到某个方法中,如果array在没有被复制的情况下直接被分配给了成员变量,那么就可能发生这种情况,即当原始的数组被调用的方法改变的时候,传递到这个方法中的数组也会改变。下面的这段代码展示的就是安全违规以及如何修复这个问题:

      • ArrayList 被直接赋给成员变量——安全隐患:
        public void setMyArray(String[] myArray){
        	this.myArray = myArray;
        }
      • 修复代码
        public void setMyArray(String[] newMyArray){
        	if(newMyArray == null){
        		this.myArray = new String[0];					
        	}else{
        		this.myArray = Arrays.copyOf(newMyArray, newMyArray.length);
        	}
        }
    • 线程安全问题的本质

      因为ArrayList自身的elementData、size、modCount在进行各种操作时,都没有加锁,而且这些数据并不是可见的(volatile),如果多个线程对变量进行操作时,可能存在数据被覆盖问题;

    二、Vector

    1、签名:

    public class Vector<E> extends AbstractList<E>	implements List<E>, RandomAccess, Cloneable, java.io.Serializable

    2、方法与变量:

    大致同 Arraylist,只是 Vector 的方法都是同步的,其是线程安全的

    3、Vector 多一种迭代方式

    Vector vec = new Vector();
    Integer value = null;
    Enumeration enu = vec.elements();
    while (enu.hasMoreElements()) {
    	value = (Integer)enu.nextElement();
    }

    三、面试题

    1、ArrayList 与 Vector

    1.1、区别

    1.2、关于Vector线程安全

    (Vector 是同步的.Vector 是线程安全的动态数组.它的操作与 ArrayList 几乎一样)如果集合中的元素的数目大于目前集合数组的长度时,Vector 增长率为目前数组长度的 100%,而 Arraylist 增长率为目前数组长度的 50%.如过在集合中使用数据量比较大的数据,用 Vector 有一定的优势

    注意:在某些特殊场合下,Vector并非线程安全的,看如下代码

    public static void main(String[] args) {
    	Vector<Integer> vector = new Vector<>();
    	while (true) {
    		for (int i = 0; i < 10; i++) {
    			vector.add(i);
    		}
    
    		Thread t1 = new Thread() {
    			public void run() {
    				for (int i = 0; i < vector.size(); i++) {
    					vector.remove(i);
    				}
    			}
    		};
    		Thread t2 = new Thread() {
    			public void run() {
    				for (int i = 0; i < vector.size(); i++) {
    					vector.get(i);
    				}
    			}
    		};
    		t1.start();
    		t2.start();
    	}
    }	

    上述代码会抛出异常:java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9

    2、ArrayList的sublist修改是否影响list本身

    • 方法实现

      // fromIndex: 集合开始的索引,toIndex:集合结束的索引,左开右闭
      public List<E> subList(int fromIndex, int toIndex) {
      	// 边界校验
      	subListRangeCheck(fromIndex, toIndex, size);
      	return new SubList(this0, fromIndex, toIndex);
      }
      private class SubList extends AbstractList<E> implements RandomAccess {
      	private final AbstractList<E> parent; // parent的具体实现类是 ArrayList
      	private final int parentOffset;
      	private final int offset;
      	int size;
      	SubList(AbstractList<E> parent,
      			int offset, int fromIndex, int toIndex) {
      		this.parent = parent;
      		this.parentOffset = fromIndex;
      		this.offset = offset + fromIndex;
      		this.size = toIndex - fromIndex;
      		this.modCount = ArrayList.this.modCount;
      	}
      }

      subList 可以做集合的任何操作

    • 调用该方法后的生成的新的集合的操作都会对原集合有影响,在subList集合后面添加元素,添加的第一个元素的位置就是上述toIndex的值,而原始集合中toIndex的元素往后移动。其add方法调用过程:

      add(element) --> AbstractList.add(e) --> SubList.add(index, e) --> parent.add(index + parentOffset, e) --> ArrayList.add(newIndex, e)

    3、为什么最好在newArrayList的时候最好指定容量?

    4、SynchronizedList、Vector有什么区别

    • SynchronizedList 是java.util.Collections的静态内部类;Vector是java.util包中的一个类;
    • 使用add方法时,扩容机制不一样;
    • SynchronizedList有很好的扩展和兼容功能,可以将所有的List的子类转成线程安全的类;
    • 使用SynchronizedList的时候,进行遍历时需要手动进行同步处理;
    • SynchronizedList可以指定锁的对象

    5、Arrays.asList(T…args)获得的List特点

    • 其返回的List是Arrays的一个内部类,是原来数组的视图,不支持增删操作;
    • 如果需要对其进行操作的话,可以通过ArrayList的构造器将其转为ArrayList;

    6、Iterator和ListIterator区别

    • 都是用于遍历集合的,Iterator可以用于遍历Set、List;ListIterator只可用于List;
    • ListIterator实现的Iterator接口;
    • ListIterator可向前和向后遍历;Iterator只可向后遍历;
    别废话,拿你代码给我看。
  • 相关阅读:
    acm 总结之大数加法
    hdu 1004
    hdu 1887
    hdu 2007
    hdu 2004
    ACM总结之 A+B problem 总结
    nyoj_42_一笔画问题_201403181935
    最短路径--Floyd算法
    最短路径—Dijkstra算法
    nyoj_114_某种序列_201403161700
  • 原文地址:https://www.cnblogs.com/lvxueyang/p/13707555.html
Copyright © 2020-2023  润新知