java深入理解ArrayList

1、 ArrayList基础:

底层是数组实现的。其实它就是动态数组。当数组长度未知时使用它是较好的选择。实现了Collection和List接口。它的继承关系如下:
这里写图片描述
实现的接口如下:
这里写图片描述
内部的参数:
private static final int DEFAULT_CAPACITY = 10; //默认容量大小
private static final Object[] EMPTY_ELEMENTDATA = {}; //用于空实例的共享空数组实例。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //用于默认大小的空实例的共享空数组实例
/*
ArrayList的容量是此数组缓冲区的长度。 添加第一个元素时,任何带有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList都将扩展为DEFAULT_CAPACITY。
*/ transient Object[] elementData; //存储ArrayList元素的数组缓冲区。

private int size; // ArrayList的大小(它包含的元素数)。
elementData用户存放实际的数据。它是transient类型的。
Transient是什么关键字呢?自己从来没用过诶。
将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。就是不进行序列化的标志字段。序列化后也不能被访问。
详细说明参考:https://www.cnblogs.com/lanxuezaipiao/p/3369962.html
其实静态变量也不能被序列化。
1) 那么为什么要将elementData[]定义为transient呢?
因为ArrayList 实际上是动态数组,每次在放满以后会扩容,如果数组扩容后,实际上只放了一个元素,那就会序列化很多null 元素,浪费空间。
2) 难道elementData数据序列化时就不保存了吗?
当然不是。对象实现java.io.Serializable 接口以后,序列化的动作不仅取决于对象本身,还取决于执行序列化的对象。
以ObjectOutputStream 为例,如果ArrayList 或自定义对象实现了writeObject(),readObject(),那么在序列化和反序列化的时候,就按照自己定义的方法来执行动作,所以ArrayList 就自定义了writeObject 和readObject 方法,然后在writeObject 方法内完成数组元素的自定义序列化动作,在readObject 方法内完成数组元素的自定义反序列化动作。可以看到它自己实现序列化elementData只取了size长度。

三种构造函数:

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);                  
    }                                                                         
}                                                                                                                                                      
public ArrayList() {                                                          
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;                     
}                                                                                                                                                     
public ArrayList(Collection<? extends E> c) {                                 
    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;                                 
    }                                                                         
}   

可以看出来,如果事先指定已知的容量能减少扩容带来的开销。(对集合的添加是深克隆。?)

提供的方法包括:
这里写图片描述
Size,isEmpty,get,set,iterator,以及listIterator操作以恒定时间运行。add操作在分摊的常量时间中运行,即添加n个元素需要O(n)时间。 所有其他操作都以线性时间运行(粗略地说)。 与 LinkedList 实现相比,常数因子较低。
Add(i,value)//将后面的元素依次后移。时间复杂度为O(n)
set(i,value)//覆盖掉原来位置i的值
contains(value)//遍历元素,时间复杂度为O(n)因此大量的包含判断应考虑用HashSet。
扩容的函数:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;     

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);           
}                                                                                                                                     
private static int hugeCapacity(int minCapacity) {                   
    if (minCapacity < 0) // overflow                                 
        throw new OutOfMemoryError();                                
    return (minCapacity > MAX_ARRAY_SIZE) ?                          
        Integer.MAX_VALUE : 
        MAX_ARRAY_SIZE;                                              
}                      

默认是扩容原来容量的一半及制定值得最大值。拷贝是浅拷贝

Remove方法:先找到要删除的元素,然后之后的元素依次向前移动。时间复杂度O(n)
排序:

@Test
    public void testSort(){
        List<Integer> list=new ArrayList<Integer>();
        int n=1;
        Random random=new Random();
        while(++n<10){
            list.add(random.nextInt(100));          
        }
        System.out.println(list);
        list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-o2;
            }
        });//传入自己实现的Comparator。
        System.out.println(list);
    }

2、进阶

1、 ArrayList和LinkedList区别在哪里?
1) ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2) 对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。
3) 对于新增和删除操作ad(I,value)和remove,LinedList比较占优势,因为ArrayList要移动数据。
4) ArrayList每次扩容会固定为之前的1.5倍,所以当你ArrayList达到一定量之后会是一种很大的浪费,并且每次扩容的过程是内部复制数组到新数组,耗时,耗空间;LinkedList的每一个元素都需要消耗一定的空间。

2、 ArrayList是线程不安全,多线程环境下可能出现什么问题?
1)Add(value)方法可能出现越界错误,或赋值失败,导致本该有值得某值为空。一下的测试代码多次执行可出现这两个问题。

public static int threadNum =10;
    public static AtomicInteger aInteger=new AtomicInteger(0);
    public static CountDownLatch countDownLatch = new CountDownLatch(threadNum);
    /**
     * 测试list多线程添加问题
     */
    @Test
    public static void testAddThreads() throws InterruptedException{
        ArrayList<Integer> list=new ArrayList<Integer>();
        int n=threadNum;
        while(n-->0){
            new Thread(new Runnable() {
                public void run() {
                    int n=100;
                    while(n-->0){
                        list.add(aInteger.incrementAndGet());                       
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println(aInteger);
        System.out.println(list.size());
        for(int i=0;i<list.size();i++){
            System.out.print(list.get(i)+" ");
        }
        System.out.println();
    }

2)使用for循环进行删除,会出现:删除被覆盖,导致并不是所有想删除的数据都能被删除掉。数据尾出现空或者尾数数字重复,数据实际长度小于size等。

扫描二维码关注公众号,回复: 3275542 查看本文章
@Test
    public  void testRemoveThreads() throws InterruptedException{
        ArrayList<Integer> list=new ArrayList<Integer>();

        int n=40;
        for(int i=0;i<n;i++){
            list.add(i);
        }
        System.out.println(list);
        System.out.print("每次移除的数据是:");
        for(int i=0;i<threadNum;i++){
            new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }for(int i=0;i<2;i++){
                        System.out.print(list.remove(0)+" ");
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println();
        System.out.println("长度:"+list.size());
        System.out.println(list);
        System.out.println(list.get(list.size()-1));
    }

猜你喜欢

转载自blog.csdn.net/qq_16753341/article/details/81607642