集合框架之LinkedList学习

本文详细介绍了Java集合框架中的ArrayList、LinkedList、Set(HashSet、TreeSet)以及Map(HashMap、TreeMap)的特性和使用场景。ArrayList基于动态数组,适合随机访问;LinkedList适合于频繁插入、删除操作;Set接口的实现类提供了不同的元素存储策略;Map接口及其实现类用于键值对存储,HashMap快速访问,TreeMap支持排序。文章还涵盖了集合的遍历、操作方法以及并发修改异常等知识点。

容器框架概述

在JDK8中rt.jar文件中,java.util.*包中的容器主要包括List、Set、Queue和Map四大类,其中List、Set、Queue是和Collection接口相关的容器,而Map是单独列出来的容器。

在这里插入图片描述

  • Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Element)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类JavaSDK提供的类都是继承自Collection的子接口,如List和Set。
  • 所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于构建一个空的Collection,有一个Collection的参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
  • 如何遍历Collection中的每一个元素?
    • 不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个对象,使用该迭代对象即可逐一访问Collection中每一个元素。典型用法如下:
Iterator it = collection.iterator();//获得一个迭代对象
	while(it.hasNext()){
Object obj = it.next();//得到下一个对象
}

具体

  • Set集合
    • Set集合是最简单的集合,集合中的对象不按照特定的方式排序。主要有:HashSet()和TreeSet().
  • HashSet
    • HashSet类按照哈希算法来存取集合中的对象,具有很好的存取性能。当HashSet向集合中加入一个对象时,会调用对象的HashCode()方法获取哈希码,然后根据这个哈希码进一步计算出对象在集合中的存放位置。
  • TreeSet实现了SortedSet接口可以对集合中的元素排序。

List集合

  • List继承自Collection接口。List是一种有序集合,List中的元素可以根据索引(顺序号:元素在集合中处于的位置信息) 进行取得/删除/插入操作。
    跟Set集合不同的是,List允许有重复元素。对于满足e1.equals(e2)条件的e1与e2对象元素,可以同时存在于List集合中。当然,也有List的实现类不允许重复元素的存在。同时,List还提供一个listIterator()方法,返回一个ListIterator接口对象,和Iterator接口相比,ListIterator添加元素的添加,删除,和设定等方法,还能向前或向后遍历,具体的方法往下看。List接口的实现类主要有ArrayList,LinkedList,Vector,Stack等。

List算法

ExplanationName
使用合并排序算法排序List,默认为升序排列sort
使用随机源对指定列表进行置换shuffle
反转指定列表中元素的顺序reverse
根据指定的距离轮换指定列表中的元素rotate
交换指定位置上的元素swap
使用一个值替换列表中出现的所有某一指定值replaceAll
使用指定元素替换列表中的所有元素fill
将所有元素从一个列表复制到另一个列表copy
使用二分搜索指定列表,以获得指定对象binarySearch
返回指定列表中第一次出现指定目标列表的起始位置;如果没有这样的目标则返回-1.indexOfSubList
返回指定列表中最后一次出现指定目标列表的起始位置;如果没有这样的目标则返回-1lastIndexOfSubList

ArrayList

  • ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。size、isEmpty、get、set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。

    每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。

  • 主要方法:

FunctionExplanation
public boolean add(E e)添加元素
Public void add(int index,E element)在指定位置添加元素
public Iterator iterator()取得Iterator对象便于遍历所有元素
public E get(int index)根据索引获取指定位置的元素
public E set(int index,E element)替换掉指定位置的元素
  • 排序方法
FunctiomExplanation
Collections.sort(java.util.List)对List的元素进行自然排序
Collections.sort(java.util.List,java.util.Comparator<? super T>)对List中的元素进行自定义排序

LinkedList

LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:

List list = Collections.synchronizedList(new LinkedList(...));

在这里插入图片描述

Map

  • Map是一种把键对象和值对象进行映射的集合,它的每一个元素都包含一堆键对象和值对象;
  • 向Map添加元素时,必须提供键对象和值对象;
  • 从Map中检索元素时,只要给出键对象,就可以返回对应的值对象;
  • 键对象不能重复,但值对象可以重复;
  • Map有两种常见的实现类:HashMap和TreeMap。
FunctionExplanation
V put(K key,V value)插入元素
V get(Object key)根据键对象获取值对象
Set KeySet()取得所有键对象集合
Collection values()取得所有值对象集合
Set<Map.Entity<K,V>> entrySet()取得Map.Entry代表一个Map中的元素

HashMap

HashMap按照哈希算法来存取键对象,有很好的存取性能。和HashSet一样,要求当两个键对象通过equals()方法比较为true时,这两个键对象的hashCode()方法返回的哈希码也一样。

HashCode:

  1. HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap,等,HashCode经藏用语确定对象的存储地址
  2. 如果两个对象相同,equals方法一定返回true,并且这两个对象的HashCode一定相同
  3. 两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能说明这两个对象在一个散列存储结构中
  4. 如果对象的equals()方法被重写,那么对象的HashCode也尽量重写
  • 作用:

    从Object的角度看,JVM每new一个Object,它都会将这个Object怼到一个Hash表中去,这样的话,下次做Object的比较或者取这个对象的时候(读取过程),它会根据对象的HashCode再从Hash表中取这个对象。这样做的目的是提高对象的效率。若HashCode相同再去调用equal。

TreeMap

TreeMap实现了SortedMap接口,能对键对象进行排序。同TreeSet一样,TreeMap也支持自然排序和客户化排序两种方式。

如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

LinkedList

Interface Iterable
  • The functions forEach() and spliterator() both use a keword “default” which means we could provide the default implement in an abstruct class,breaking the previous rule in JAVA.

JDK8 Functional interface

FunctionExplanation
Function<T,R>accept a parameter and return a result
Consumeraccept a parameter without result returned
Predicateaccept a parameter and return a boolean result
Supplierwithout parameter and one result returned

remark1:

  1. Three methods

    1.1 The only abstract method

    1.2 use default to define general methods, invoking through an instance.

    1.3 use static define static methods, invoking through interface name.

  2. a new annotation

    if some interface is functional interface ,then making it has only one abstract method when defining.So there is a new annonation:

    @FunctionInterface

remark2(lambda expression):

use anonymous inner class to impement an interface:

Function<Integer,String> fun = new Function<Integer,String>(){
  @Override
  public String apply(Integer t){
    	return String.valueOf(t);
  }
}

use lambda expression to implement:

Function<Integer,string> -> (t){String.valueOf(t);}
Collection extends Iterable
  • Int size();

    • if the collection contains more than Integer.MAX_VALUE elements, returns Integer.MAX_VALUE, otherwise return the number of elements in it.
  • boolean isEmpty();

    • if the collection empty, return true;
  • boolean contains(Object o)

    • return ture if the collection contain the specified element.
  • Iterator iterator()

    • return an iterator over the elements in the collection, unless provide a guarantee itself, there are no guarantee concerning the order in which the elements are returned.
  • Object[] toArray();

    • returns am array containing all of the elements in this collection.
  • T[] toArray(T[] a);

    • Params:
      • a: the array into which the elements of this collection are to stored,if it is big enough; otherwise. a new array of the same runtime type is allocated for this purpose.If the collecition fits in the specified array with room to spare, the element in the array immediately following the end of the collection is set to null.
    • Returns:
      • an array containing all of the elements in this collection.
  • boolean add(E e);

    • return true if operation successful;
  • boolean remove(Object o)

  • boolean containsAll(Collection<?> c);

    • return true if this collection contains all of the elements in the specified collection.
  • boolean addAll(Collection<? extends E> c);

  • boolean removeAll(Collection<?> c);

  • default boolean removeIf(Predicate<? super E> filter){…}

    • Removes all of the elements of this collection that satisfy the given predicate.
    • true if any elements were removed.
  • boolean retainAll(Collection<?> c);

    • Removes from this collection all of its elements that are not contained in the specified collection.
  • void clear();

    • Removes all of the elements from this collection.The collection will be empty after this methods returns.
  • boolean equals(Object o);

    • Attention: if implements the Controller interface ”directly“ , must exercise care if if they choose to override the Object.equals.(When need compare the value instead of inference)
    • returns true if the specified object is equals to this collection.
  • int hashCode();

    • Attention:if override the method Object.equals then must override Object.hashCode()(because of hashcode contract)
    • Returns the hashcode of the collection
  • default Spliterator spliterator(){…}

    • Creates a Spliterator over the elements in this collection.
      • Spliterator: always used with steam to traverse and divide sequential.
  • default Stream stream() {…}

    • Returns a sequential Stream with this collection as its source.
  • default Stream parallelStream() {…}

    • Returns a possibly parallel Stream with this collection as its source. It is allowable for this method to return a sequential stream.

List extends Collection

  • boolean addAll(int index,Collection<? extends E> c);

    • Inserts all of the elements in the specified collection into this list at the specified position.
  • boolean retainAll(Collection<?> c)

    • Retains only the elements in this list that are contained in the specified collection.
  • default void replaceAll(UnaryOperator operator){…}

    • Replaces each element of the list with the result of applying the operator to that element.
  • default void sort(Comparator<? super E> c){…}

    • sorts this list according to the order induced by the specified Comparator.
  • E set(int index, E element);

    • replace the element at the specified position in this list with the specified element.
    • returns the element previously at the specified position.
  • E get(int index)

    • reurns the element at the specified position.
  • int indexOf(Object o);

    • Returns the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element.
  • int lastIndexOf(Object o);

    • Returns the index of the last occurrence of the specified element in this list , or -1 if there is no such index.
  • ListIterator listIterator();

    • returns a list iterator over the elements in this list(in proper sequence).
  • ListIterator listIterator(int index);

    • Returns a list iterator over the elements in this list, starting at the specified position in the list.
  • List subList(int fromIndex,int toIndex);

    • returns a view of portion of this list between the specified fromIndex, inclusive,and toIndex, exclusive.(If fromIndex and toIndex are equal, the returned list is empty).

AbstractList extends AbstractCollection implements list

Transient: the key word to avoid serializable;

checkForComodification();

used to realize fail-fast mechanism.

There are two threads, one is for traversing the list and another is for modifying the list.Sometimes thread A is traversing the list(at this moment expectedModCount=modCount=N), meanwhile thread B adds an element leading to the value of modCount modified(modCount+1=N+1). Method checkForComodification find that expectedModCunt=N, while modCount=N+1 which means two values are not equal, so the ConcurrentModificationException are throwed out and fail-fast mechanism happened.

cursor:the index of next element

lastRet:the index of last element

  • public List subList(int fromIndex, int toIndex)
public List<E> subList(int fromIndex,int toIndex) {
  return (this instance of RandomAccess? new RandomAccessSubList<>(this,fromIndex, toIndex):new SubList<>(this, fromIndex, toIndex));
}
public interface RandomAccess{}

RandomAccess is an empty interface to identify some class backing random access or not. A class that backing random access aparently could use a more efficient arithmetic.

  • classes realized random access:
    • ArrayList
    • AttributeList
    • CopyOnWriteArrayList
    • Vector
    • Stack
    • Etc…

ArrayList

ArrayList是一个动态数组,是线程不安全的,允许元素为null。

其底层数据结构依然是数组,它实现了List, RandomAccess, Cloneable, java.io.Serializable接口,其中RandomAccess代表了其拥有随机快速访问的能力,ArrayList可以以O(1)的时间复杂度去根据下标访问元素。

其底层数据结构为数组,所以占据的是一块连续的内存空间,空间效率不高但是可以根据下标以O(1)的时间读写(改查)元素,因此时间效率很高

  • 当集合中的元素超过这个容量,便会进行扩容操作。扩容操作也是ArrayList的一个性能消耗较大的地方,所以若我们可以提前预知数据的规模,应该通过public ArrayList(int initialCapacity) {}构造方法,指定集合的大小,去构建ArrayList实例,以减少扩容次数,提高效率
  • 在需要扩容的时候,手动调用public void ensureCapacity(int minCapacity){}方法扩容。
    • 不过该方法是ArrayList的API,不是List接口的,所以使用时需要强转
((ArrayList)list.ensureCapacity(30));

每次修改结构时,增加导致扩容,或者删,都会修改modCount

//复制指定数组src到目标数组dest。复制从src的srcPos索引开始,复制的个数是length,复制到dest的索引从destPos开始。
//System.arrayCopy
public static native void arrayCopy(Object src, int srcPos,Object dest, int destPos,int length);
构造方法
//默认构造函数里的空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //存储集合元素的底层实现:真正存放元素的数组
    transient Object[] elementData; // non-private to simplify nested class access
    //当前元素数量
    private int size;

    //默认构造方法
    public ArrayList() {
        //默认构造方法只是简单的将 空数组赋值给了elementData
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    //空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //带初始容量的构造方法
    public ArrayList(int initialCapacity) {
        //如果初始容量大于0,则新建一个长度为initialCapacity的Object数组.
        //注意这里并没有修改size(对比第三个构造函数)
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//如果容量为0,直接将EMPTY_ELEMENTDATA赋值给elementData
            this.elementData = EMPTY_ELEMENTDATA;
        } else {//容量小于0,直接抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    //利用别的集合类来构建ArrayList的构造函数
    public ArrayList(Collection<? extends E> c) {
        //直接利用Collection.toArray()方法得到一个对象数组,并赋值给elementData 
        elementData = c.toArray();
        //因为size代表的是集合元素数量,所以通过别的集合来构造ArrayList时,要给size赋值
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)//这里是当c.toArray出错,没有返回Object[]时,利用Arrays.copyOf 来复制集合c中的元素到elementData数组中
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //如果集合c元素数量为0,则将空数组EMPTY_ELEMENTDATA赋值给elementData 
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

remark: 构造函数走完之后,会构造出数组elementData和数量size。

常用API
1 增
  • 每次add之前,都会判断add后的容量,是否需要扩容。
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;//在数组末尾追加一个元素,并修改size
    return true;
}
    private static final int DEFAULT_CAPACITY = 10;//默认扩容容量 10
    private void ensureCapacityInternal(int minCapacity) {
        //利用 == 可以判断数组是否是用默认构造函数初始化的
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }


private void ensureExplicitCapacity(int minCapacity) {
    modCount++;//如果确定要扩容,会修改modCount 

    // overflow-conscious code
    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)//如果还不够 ,那么就用 能容纳的最小的数量。(add后的容量)
        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);//拷贝,扩容,构建一个新数组,
}
public void add(int index, E element) {
    rangeCheckForAdd(index);//越界判断 如果越界抛异常

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index); //将index开始的数据 向后移动一位
    elementData[index] = element;
    size++;
}
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;
}
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);//越界判断

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount //确认是否需要扩容

    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);//移动(复制)数组

    System.arraycopy(a, 0, elementData, index, numNew);//复制数组完成批量赋值
    size += numNew;
    return numNew != 0;
}

conclusion:

add, addAll

先判断是否越界,是否需要扩容;如果扩容,就复制数组。然后设置对应下标元素值。

1 如果需要扩容的话,默认扩容一半。如果扩容一半不够,就用目标的size作为扩容后的容量。

2 在扩容成功以后,会修改modCount

2 删
public E remove(int index) {
    rangeCheck(index);//判断是否越界
    modCount++;//修改modeCount 因为结构改变了
    E oldValue = elementData(index);//读出要删除的值
    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  //置空原尾部数据 不再强引用, 可以GC掉
    return oldValue;
}
    //根据下标从数组取值 并强转
    E elementData(int index) {
        return (E) elementData[index];
    }

//删除该元素在数组中第一次出现的位置上的数据。 如果有该元素返回true,如果false。
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {//equal前面不能是null,否则会报空指针错误,所以此处分开写
                fastRemove(index);//根据index删除元素
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
//不会越界 不用判断 ,也不需要取出该元素。
private void fastRemove(int index) {
    modCount++;//修改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 boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);//判空
    return batchRemove(c, false);
}
//批量移动
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;//w 代表批量删除后 数组还剩多少元素
    boolean modified = false;
    try {
        //高效的保存两个集合公有元素的算法
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement) // 如果 c里不包含当前下标元素, 
                elementData[w++] = elementData[r];//则保留
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) { //出现异常会导致 r !=size , 则将出现异常处后面的数据全部复制覆盖到数组里。    这个地方的异常指的是什么异常?怎么发生的
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;//修改 w数量
        }
        if (w != size) {//置空数组后面的元素
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;//修改modCount
            size = w;// 修改size
            modified = true;
        }
    }
    return modified;
}

remark: 从这里可以看出,当用来作为删除元素的集合里的元素多于被删除集合时也没关系,只会删除它们共同拥有的元素

conclusion:

1 删除操作一定会修改modCount,而且可能涉及到数组的复制,相对低效

2 批量删除中,涉及高效的保存两个集合公有元素的算法,值得学习

3 改

不会修改modCount,相对增删是高效的操作

public E set(int index, E element) {
    rangeCheck(index);//越界检查
    E oldValue = elementData(index); //取出元素 
    elementData[index] = element;//覆盖元素
    return oldValue;//返回元素
}
4 查

不会修改nodCount,相对增删是高效的操作

public E get(int index) {
    rangeCheck(index);//越界检查
    return elementData(index); //下标取数据
}
E elementData(int index) {
    return (E) elementData[index];
}
5 清空

会修改modCount

public void clear() {
    modCount++;//修改modCount
    // clear to let GC do its work
    for (int i = 0; i < size; i++)  //将所有元素置null
        elementData[i] = null;

    size = 0; //修改size 
}

注意到这里,如果是批量移除,modCount会增加移除的数量,但是清空,modCount只增加1

6 包含 contain
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
//普通的for循环寻找值,只不过会根据目标对象是否为null分别循环查找。(防止equals报空指针错误)
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;
}
7 判空
public boolean isEmpty(){
  return size==0;
}
迭代器 Iterator
public Iterator<E> iterator(){
  return new Itr();
}
/**
 * An optimized version of AbstractList.Itr
 */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return //默认是0
    int lastRet = -1; // index of last element returned; -1 if no such  //上一次返回的元素 (删除的标志位)
    int expectedModCount = modCount; //用于判断集合是否修改过结构的 标志

    public boolean hasNext() {
        return cursor != size;//游标是否移动至尾部
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)//判断是否越界
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)//再次判断是否越界,在 我们这里的操作时,有异步线程修改了List
            throw new ConcurrentModificationException();
        cursor = i + 1;//游标+1
        return (E) elementData[lastRet = i];//返回元素 ,并设置上一次返回的元素的下标
    }

    public void remove() {//remove 掉 上一次next的元素
        if (lastRet < 0)//先判断是否next过
            throw new IllegalStateException();
        checkForComodification();//判断是否修改过

        try {
            ArrayList.this.remove(lastRet);//删除元素 remove方法内会修改 modCount 所以后面要更新Iterator里的这个标志值
            cursor = lastRet; //要删除的游标
            lastRet = -1; //不能重复删除 所以修改删除的标志位
            expectedModCount = modCount;//更新 判断集合是否修改的标志,
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
//判断是否修改过了List的结构,如果有修改,抛出异常
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
总结

1 增删改查中,增导致扩容,则会修改modCount,删一定会修改。改和查不一定会修改modCount。

2 扩容操作会导致数组复制,批量删除会导致找出两个集合的交集,以及数组复制操作,因此,增,删,都相对低效。而改,查都是很高效的操作。

3 因此,结合特点,在使用中,以Andriod中最常用的展示列表为例,列表滑动时需要展示每一个Item(element)的数组,所以操作是最高频的。相对来说,增操作只有在列表加载更多是才会用到,而且是在列表尾部插入,所以也不需要移动数据的操作。而善操作则更低频。故选用ArrayList作为保存数据的结构。

内部也是数组做的,区别在于vector在API上都加了synchronized所以它是线程安全的,以及vector扩容时,是翻倍size,而ArrayList是扩容50%。

ArrayList和LinkedList的区别
  1. 对ArrayList和LinkedList而言,在列表末尾添加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。

  2. 在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被一定;而在LinkedList的中间插入或删除一个元素的开销是固定的。

  3. LinkedList不支持高效的随机元素访问。

  4. ArrayList的空间浪费主要体现在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它每一个元素都需要消耗相当的空间。

  • 当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当操作时在一列数据的前面或中间添加或删除数据,并且安装包顺序访问其中的元素时,就应该使用LinkedList了。

其他的一些笔记:

  • 序列化ID的作用:

    • 序列化ID起着关键作用,它决定着是否能够成功反序列化。简单来说,java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。
  • 序列化ID如何产生:

    • 当我们一个实体类中没有显示的定义一个名为“serialVersionUID”、类型为long的变量时,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次变异生成的class才会生成相同的serialVersionUID。譬如,当我们编写一个类时,随着时间的推移,我们因为需求改动,需要在本地类中添加其他的字段,这个时候再反序列化时便会出现serialVersionUID不一致,导致反序列化失败。解决方法便是在本地类中添加一个“serialVersionUID”变量,值保持不变,便可以进行序列化和反序列化。
  • 具体实现:

    • 序列化,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内,这时调用writeObject()方法,即可将对象序列化,并将其发送给OutputStream(对象序列化是机遇字节的,因此使用的InputStream和OutputStream继承的类)。

      在这里插入图片描述

  • 反序列化,即反向进行序列化的过程,需要将一个InputStream封装在ObjectInputStream对象内,然后调用readObject()方法,获得一个对象引用(它是指向一个向上转型的Object),然后进行类型强制转换来得到该对象。

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值