ArrayList源码笔记 ---- JDK1.8

ArrayList源码笔记 ---- JDK1.8

结构

  1. 父类和接口

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-swp3ghFZ-1628257114936)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210806105005965.png)]

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
        
    //分析:
    	继承了AbstractList,可以继承一些通用的方法。
        实现了RandomAccess 随机访问接口 是一个空的接口 和数组一样可以通过下标访问
        实现了Cloneable 表示可以使用Object.clone方法
        实现了Serializable 序列化 什么是序列化?简单的说,就是能够从类变成字节流传输,然后还能从字节流变成原来的类
         //  而ElementDate[]数组,缺使用了transient修饰,这样不会被序列化。为什么呢?
         对于ArrayList来说,是一个可扩容的数组。比如默认10个容量,现在放入了第11个,则会容量会扩容到15.
         这时候序列化就把那四个闲置的位置也序列化了,空间上的浪费。
         所以ArrayList直接实现了序列化的方法,writeObject()readObject(),在这里面,序列化的是以当前数组真实大小来序列化的
     增加元素是先去判断当前增加临界点 也就是 11 扩容 先扩容 再把第11个加进去
    
     
    //问题1:
        可以看到继承了父类AbstractList也实现了List接口同样ArrayList也实现了List接口,这是为什么呢?
        接口中的某些方法可以被抽取出来作为通用的方法,所有先让一个抽象类实现接口中的某些通用方法,然后ArrayList实现自己的特有方法,这样使代码更加简洁
    //问题2:
        为什么在继承了实现了List接口的AbstractList父类后,还去实现List接口?
        继承了父类后就相当于也要实现List,在实现List是不是多此一举呢?
        https://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete
    	在这里有人提问了这个问题,说是作者 Josh Bloch 一开始以为可能是有用的,后来确实没用。但是也不影响什么就没撤销
        回答原文:I've asked Josh Bloch, and he informs me that it was a mistake. He used to think, long ago, that there was some value in it, but he since "saw the light". Clearly JDK maintainers haven't considered this to be worth backing out later. 
    
  2. 类的属性

        private static final long serialVersionUID = 8683452581122892189L;
    
        /**
         * Default initial capacity. 默认初始容量。大小为10 
         */
        private static final int DEFAULT_CAPACITY = 10;
    
        /**
         * Shared empty array instance used for empty instances. 用于空实例的共享空数组实例
         * 如果是这样的 ArrayList a =new ArrayList(0); 则返回该空数组 可以debug跟踪一下
         */
        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.
         * 如果是这样的 ArrayList a =new ArrayList(); 无参数则返回该空数组
         * 和上面的区别 以了解添加第一个元素时要膨胀多少 ???看下一个属性
         */
        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.
         * 存储ArrayList的元素,ArrayList长度就是该数组大小
         * 如果数组是elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList第一次添加元素直接扩容为DEFAULT_CAPACITY
         */
        transient Object[] elementData; // non-private to simplify nested class access 非私有以简化嵌套类访问
    
        /**
         * The size of the ArrayList (the number of elements it contains).
         * ArrayList 的大小(它包含的元素数)。
         * @serial
         */
        private int size;
    
    
    	//要分配的数组的最大大小。 一些 VM 在数组中保留一些头字。 尝试分配更大的数组可能会导致 OutOfMemoryError:请求的数组大小超出 VM 限
     	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE(0x7fffffff  2^31-1) - 8;
    
  3. 类的构造函数 初始化elementData数组

    public ArrayList(int initialCapacity) 
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//当为指定容量为0时 使用EMPTY_ELEMENTDATA空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
    //构造一个初始容量为 10 的空列表。当添加第一个元素后就会扩容到初始值10 第一次调用add方法时
    public ArrayList() {//无参直接使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    //这个就意味着实现了Collection的类都可以转化为ArrayList Collection<? extends E>:限定了其集合里面的元素只能是E及E的子类
    //<? super E> 限定了其集合里面的元素只能是E或者E的父类   
    public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();//转化为Collection中是返回Object数组,但是每个类的实现方法都不一样
            if ((size = elementData.length) != 0) {//初始化size 且判断不等于0要不然就直接返回空数组和指定容量为0时一样了
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                //每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么就需要使用ArrayList中的方法去改造一下。
                if (elementData.getClass() != Object[].class)
                    //copyOf(要复制的数组,要返回的副本的长度,要返回的副本的类)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
    }
    

常用方法 --增删改查

  1. trimToSize() 将容器容量修改到当前size大小。

    //将此ArrayList实例的容量修剪为列表的当前大小。
    // 应用程序可以使用此操作来最小化ArrayList实例的存储空间。
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {//这个是容器的容量,可以debug试一下,打断点到这里 光标浮在length在执行完之后就会有值的变化
            elementData = (size == 0)
                ? EMPTY_ELEMENTDATA
                : Arrays.copyOf(elementData, size);//在这里修改
        }
    }
    
  2. ensureCapacity(int minCapacity)

    //对于这个方法,如果是由空参构造出的实例对象,且没经过add minCapacity小与默认容量则不会扩容
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)//阻止了空参构造生成的空ArrayList的比默认容量小的扩容,因为没有必要,第一次add时就会进行扩容。
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;
    
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    
  3. //返回元素数
    public int size() {
        return size;
    }
    //封装了一下
    public boolean isEmpty() {
            return size == 0;
    }
    
  4. 包含 contains 如果有则返回true 也是封装了一下

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    
    //返回第一次出现元素的下标,没找到则返回-1,同时也可以找null值。
    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;
    }
    
    //同样还有返回最后一次出现元素下标的方法 倒着找,和数组一样的方式
    public int lastIndexOf(Object o) {
            if (o == null) {
                for (int i = size-1; i >= 0; i--)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = size-1; i >= 0; i--)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
    }
       ```
    
    
  5. clone()方法,很多类都实现了这个方法,返回一个内容一样但是不是一个对象。是浅拷贝

      public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;//用于迭代器
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
    //测试
    	ArrayList cd = new ArrayList();
        cd.add("asdf");
        cd.add("asd");
        ArrayList cb = (ArrayList) cd.clone();
        System.out.println(cd == cb);false
        System.out.println(cb.get(0) == cd.get(0));true
    深拷贝概念
    深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。
    当对象和它所引用的对象一起拷贝时即发生深拷贝。
    深拷贝相比于浅拷贝速度较慢并且花销较大。
    所以ArrayList是浅拷贝
    

6.toArray

  //用于将List转为数组,返回一个大小为size的全新数组。
public Object[] toArray() {
   return Arrays.copyOf(elementData, size);
}
 public <T> T[] toArray(T[] a) {
    if (a.length < size)//如果a的大小不够,则创建一个大小为size的新数组返回
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);//刚好够,则拷贝内容给到a
    if (a.length > size)//大于则给后面内容标记为null,GC
        a[size] = null;
    return a;
}
  1. get方法和数组一样的方式获取内容

      public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    
     E elementData(int index) {    return (E) elementData[index];}
    
  2. 修改指定位置的元素

     public E set(int index, E element) {
            rangeCheck(index);
    
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
     }
       ```
    
    
  3. 添加元素 将指定的元素附加到此列表的末尾。

      public boolean add(E e) {
     ensureCapacityInternal(size + 1);  // Increments modCount!!  add操作涉及到容器的扩容
     elementData[size++] = e;//正常操作,多线程时不安全
     return true;
     }
    
    private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果是通过空参构造生成
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//最小都会扩容至默认大小
    }
    
    ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//使用迭代器遍历元素时,添加元素,扩容会让迭代器失效
    
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//防止是参数为0的构造参数生成的对象,扩容时minCapacity小于现有元素大小
            grow(minCapacity);//扩容操作
    }
    
     private void grow(int minCapacity) {
         // overflow-conscious code
         int oldCapacity = elementData.length;//旧容量
         int newCapacity = oldCapacity + (oldCapacity >> 1);//就是扩容1.5倍左右 oldCapacity >> 1 二进制左移一位,相当于十进制除于2
      	//如果以旧容量扩容后的值小于期望值则按照期望值扩容   
         if (newCapacity - minCapacity < 0)
             newCapacity = minCapacity;
         //如果扩容后的值 > jvm 所能分配的数组的最大值,那么就用 Integer 的最大值
         if (newCapacity - MAX_ARRAY_SIZE > 0)
             newCapacity = hugeCapacity(minCapacity);
         // minCapacity is usually close to size, so this is a win:  minCapacity 通常接近 size,所以这是一个胜利:
         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;
        //MAX_ARRAY_SIZE  Integer.MAX_VALUE 前者比后者小8
    }
    
    public void add(int index, E element) //在此列表中的指定位置插入指定元素
    

    总结:通过源代码可以看出,扩容会扩容1.5倍然后生成一个新的数组将内容拷贝过去,add时可以用null值

    扩容本质就是通过elementData = Arrays.copyOf(elementData, newCapacity); 我们通过 System.arraycopy 方法进行拷贝

      /**
      * @param src     被拷贝的数组
      * @param srcPos  从数组那里开始
      * @param dest    目标数组
      * @param destPos 从目标数组那个索引位置开始拷贝
      * @param length  拷贝的长度 
      * 此方法是没有返回值的,通过 dest 的引用进行传值
      */
     public static native void arraycopy(Object src, int srcPos,
                                         Object dest, int destPos,
                                         int length);
    

    添加全部到List

      public boolean addAll(Collection<? extends E> c) {
         Object[] a = c.toArray();
         int numNew = a.length;
         ensureCapacityInternal(size + numNew);  // Increments modCount
         System.arraycopy(a, 0, elementData, size, numNew);
         size += numNew;
         return numNew != 0;
     }
    
  4. 删除元素 remove(int index)删除指定位置,

    public E remove(int index) {
        rangeCheck(index);
    
        modCount++;//使用迭代器遍历元素时,删除元素会让迭代器失效
        E oldValue = elementData(index);
    
        int numMoved = size - index - 1;//找到要移动到前面的长度
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//把数组index之后的元素向前移1。
        elementData[--size] = null; // clear to let GC do its work
    
        return oldValue;
    }
    
    

    通过值来删除元素

    //允许null值就多了好多代码,有多个o只会删除第一个
    public boolean remove(Object o) {
        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])) {//这里使用了equals如果是自己写的类则需要重写equals方法
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    
       private void fastRemove(int index) {
        modCount++;//删除元素也会导致迭代器失效
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//这个方法很厉害,减少了很多代码
        elementData[--size] = null; // clear to let GC do its work
    }
    

    删除全部元素

    public void clear() {
     modCount++;
    
     // clear to let GC do its work 如果一个引用是null则会有垃圾回收器来清理
     for (int i = 0; i < size; i++)
         elementData[i] = null;
    
     size = 0;
    }
    

    从此列表中删除包含在指定集合中的所有元素

     public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
    }
    

    仅保留此列表中包含在指定集合中的元素。 换句话说,从该列表中删除所有未包含在指定集合中的元素。

    
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }
    
     //removeAll方法 则complement为false c中元素是要被删除的
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                //假如当前list中元素为 a b c d , c中元素为 b c 
                if (c.contains(elementData[r]) == complement)//第一个取出的是 a 在c中未包含,则为false 总表达式就为true 
                    elementData[w++] = elementData[r];//将a放入第一个元素
            	//第二次为 b 在c中可以找到 则返回true 总表达式就为false 跳过
            //这样下来 w 之前都为不删的元素。
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {//如果r!=size说明上面抛出了异常。
                //把r之后的内容拷贝到w之后,r之后的内容还没用被判断
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            //w!=size说明有被删除的元素
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
    

迭代器

此类的iterator和listIterator方法返回的iterator是快速失败的:如果在创建迭代器后的任何时间以任何方式修改列表结构,除了通过迭代器自己的remove或add方法,迭代器将抛出ConcurrentModificationException 。 因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒着任意、非确定性行为的风险。

就是fail-fast 快速失败。多线程下一般错误都是不容易出现但是有可能出现,如果能快速失败就不用担心之后的使用中突然出错。通过modCount来快速失败

不过一般多线程下也不用这个ArrayList类,有JUC包下的类使用,更加可靠

之前没看源码之前就只知道有iterator()去遍历元素,看了之后发现还有个ListIterator

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kMm3ZNtN-1628257114939)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210806210512411.png)]

//迭代器遍历时,不能增加,删除,扩容操作可以修改。
public Iterator<E> iterator() {
    return new Itr();
}
//此类实现了Iterator接口,该接口一共就四个方法,我只用过前三个
//hasNext()判断还好没有下一个
//next取出下一个值
//remove删除一个
 private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;//fail-fast机制就是靠这个

        public boolean hasNext() {
            return cursor != size;//下一个索引小于size
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();//检查modCount有没有被修改
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;//下一个下标
            return (E) elementData[lastRet = i];//返回当前下标的内容
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
		
       //这个方法对集合中剩余的元素进行操作,直到元素完毕或者抛出异常 
     	//感觉不好用,Consumer函数式接口支持Lambda
        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;//调用该方法并未重新初始化此类,也就是说cursor是上次遍历后留下来的值。上次全部遍历则调用该方法也无用。
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

ListIterator 这个类只是在List或者它的实现类下可以使用或者说有,提供了更多方法,add,set还可以倒序遍历 hasPrevious previous,ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r2ECZt8r-1628257114940)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210806212809375.png)]

private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor - 1;
    }

    @SuppressWarnings("unchecked")
    public E previous() {
        checkForComodification();
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        return (E) elementData[lastRet = i];
    }

    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

内部类SubList SubList到底怎么转化为ArrayList?

foreach方法

public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

//测试
 	ArrayList a =new ArrayList();
    a.add("222");
    a.add("333");
    a.forEach(b-> System.out.println(b));
    System.out.println(a.toString());

问题

  1. ArrayList的扩容机制?

    无参构造,第一次add时会初始化且扩容
    首先无论你现在期望容量是多少,第一次都会以默认容量10扩容。
    每一次都会去判断该不该扩容,判断依据是 期望容量 - 当前容量 > 0 
    当是第一次时,期望容量是默认容量 10 - 当前容量 0 是个空的 。必然大于0
    则执行grow方法,扩容。
    扩容进入首先 计算新数组大小 新容量 = 旧容量 + 旧容量 >> 1; 
    然后比较期望容量和新容量哪个大一点,第一次扩容肯定是期望大,所以使用期望容量10。
    之后再有一些判断,判断不能大于Interger的最大值等。
    最后就是扩容了 , 使用Arrays.copyof 将旧数组复制到新数组。里面还是使用的是System.arraycopy
    
    超过容量扩容
    每一次add都会判断是不是要去扩容。判断的依据就是 期望容量(当前数组大小+1) - 当前数组大小(10 也就是当前容量) > 0
    如果是默认大小初始化 10 
    那么当第11个元素add时,会扩容。
    这个时候grow中用的就不会是期望容量了,而是新容量 = 旧容量 + 旧容量 >> 1; 当前为15 10+10>>1(5) 
    最后扩容
    
  2. ArrayList怎么排序?

    1.使用Collections.sort(List<String>)方法,此方法有一个重载方法Collections.sort(List<A>,new Comparator接口的匿名内部类,需要实现compare()方法)。
    	第一个一个参数sort,需要List中对象类型实现Comparable接口并实现其方法compareTo().
    	第二个二个参数的方法,适用于自定义类型或者说所有没有实现内部比较器Comparator的CompareTo()的类。需要其实现外部比较器
    	Comparator的compare()方法。
    2. 使用ArrayList自己内部的方法,arrayList.sort(Comparator<? super E> c) 这个方法需要一个外部比较器,不过如果是null,则使用的是内部比较强Comparable 必须arrayList存储的对象实现该接口的compareTo方法。
    
    3. 且1中实现调用的是2的sort 也就是说俩排序都是list.sort(),而list.sort()中调用的则是Arrays类的sort(),Arrays类有一堆sort排序。
    
  3. ArrayList怎么去重?

    1. 将ArrayList整个放到HashSet中,利用set的特性,没有重复的值。不过无法保证取出时是原来的顺序
    2. 利用LinkedHashSet 底层是LinkedHashMap,保证存入和取出顺序一致,并且无重复值
    3. 自己for循环
    
  4. ArrayList和LinkedList区别?

    ArrayList 
    	底层是数组
    LinkedList
    	底层是双向链表
    在之后就是数组和链表的性能问题了
    但是也不能直接说数组查找修改元素快,但是增加删除元素慢。LinkedList增删效率比较高且不需要初始化容量
    具体分析
    增加元素,可以插入头,尾,中间
    ArrayList 有两个增加元素的方法,add(E) 直接插到尾部 add(int,E)指定位置插入
    LinkedList 有挺多 addFirst(E)添加到头部 addLast(E)添加到尾部 add(E)添加到尾部 和addLast()没啥区别,一个无返回,一个默认返回true add(int,E)指定位置添加 如果指定位置是尾部,则直接添加到尾部(调用linkLast()),如果不是则调用linkBefore() 
    这些方法都只是对内部方法的封装,linkFirst(),linkLast(),linkBefore()。
    
    对于插入到尾部,ArrayList直接就添加了,但是若是扩容就会耗时长一点。LinkedList插入尾部需要改变指向,把节点链接上去,所以ArrayList会快一点。
    
    对于插入中间,ArrayList 是个数组需要将这个位置之后的元素都向后移一位,实现是使用System.arraycopy(),将旧数组复制到新数组。就是将index开始的向后移一位,空出index位。LinkedList则是需要遍历找到该index位置上的节点,改变指向。遍历找节点是有优化的简易的二分,首先,将链表分成前一半,后一半,这样只需要判断index是>mid还是<mid,只需要遍历一半。这里ArrayList快一点
    
    对于插到头部,ArrayList的性能会很差了,要将整体移后一位,空出第一个位置。而LinkedList,则直接改变指向就行,毕竟是双向链表。
    
    删除就都一样了。
    
    遍历
    ArrayList遍历,for,foreach,迭代器都行,差不了太多
    LinkedList for遍历就会差很多,for遍历 通过get(i)查找,get(i)会调用node(i)去查找i位置的元素,虽然会是前后二分遍历。但是每一次for都会这样遍历一次去查找元素。而迭代器只node一次,之后通过hasNext()和next()
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值