1.ArrayList简介[1]

ArrayList实现了List接口。ArrayList的方法实现和vector相似,只是线程不安全的。

ArrayList的 size、isEmpty、get、set、iterator等方法的时间复杂度为O(1),add n个元素需要O(n)的时间(add一个元素的时间摊销成本是O(1)),其它方法的运行时间多为线性的。比如remove(int index)的时间复杂度是O(n);remove(Object obj)的时间复杂度是O(n),removeAll的时间复杂度是O(n)。

ArrayList的初始容量是10,扩容机制是oldCapacity+oldCapacity>>1,add一个元素的时间摊销成本是常量。

ArrayList是线程不安全的。当多个线程同时操作ArrayList时,且至少一个线程对ArrayList进行结构性修改时,可能出现线程不安全。需要采取一些策略以确保线程安全,具体详见第3节。

使用iterator()或listIterator(int index)获取的迭代器,是fail-fast的;如果使用了迭代器类之外的方法对list进行结构性修改(remove/add/clear等),再调用迭代器的方法将会抛出异常。这避免了在将来某个不确定的时间出现线程安全问题。

2.使用ArrayList的注意事项[2]

1)如果元素是引用类型,使用remove/removeAll,contains/retainAll前,需要重写元素的equals()方法,重写equals前,需要重写hashcode方法。因为这些方法的底层都调用了equals方法。

2)不要在for(Element e:list)循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁(如果使用的List类未加锁,需要开发者自己加锁)。

在for(Element e:list)里进行元素的add/remove/clear操作时会报错,如图1。报错的的原因是for(Element e:list)的底层是迭代器(如图2)。使用迭代器时,如果使用了迭代器类之外的方法对list进行结构性修改(remove/add/clear等),会有fail-fast的机制,如图3。迭代器的fail-fast是指在使用迭代器遍历时,如果使用了迭代器类之外的方法对list进行结构性修改(remove/add/clear等),则使用迭代器的遍历将终止,并抛出异常ConcurrentModificationException。

public class ArrayListStudy1 {
//forEach中使用add/remove方法
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3); for(Integer i:list){
if(1==i){
list.remove(1);
}
}
}
} 运行报错:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.study.collection.ArrayListStudy2.main(ArrayListStudy2.java:15)

图1 在for(Element e:list)里进行元素的add/remove/clear操作

反编译得到以下代码:

Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
Integer i = it.next();
if (1 == i.intValue()) {
list.remove(1);
}
}

图2 图1代码反编译后的代码(反编译工具jadx-gui-1.3.3-1)

查看迭代器的源码:

/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
} private class Itr implements Iterator<E> {
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
//如果使用了Iterator类之外的方法对list进行结构性修改(remove/add/clear等),则抛出异常(fail-fast)
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}

ArrayList类注释中对fail-fast的详细注释:

/**
* <p><a name="fail-fast">
* The iterators returned by this class's {@link #iterator() iterator} and
* {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
* if the list is structurally modified at any time after the iterator is
* created, in any way except through the iterator's own
* {@link ListIterator#remove() remove} or
* {@link ListIterator#add(Object) add} methods, the iterator will throw a
* {@link ConcurrentModificationException}. Thus, in the face of
* concurrent modification, the iterator fails quickly and cleanly, rather
* than risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.
*/
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
}

3)SubList是ArrayList的视图,它拥有ArrayList相同的方法;但SubList不是ArrayList类或子类,它是ArrayList的内部类,不能将SubList强转为ArrayList类型。如果将SubList强转为ArrayList,则会报错,如图3。所有对ArrayList的非结构性(非add/remove/clear)的操作,subList也会相应变化,反之亦然。如果使用了SubList类之外的方法对list进行结构性修改(remove/add/clear等),对subList的所有操作都会报错。这类似于第二条中的fail-fast机制。

如果使用了SubList类之外的方法对list进行结构性修改(remove/add/clear等),对subList的所有操作都会报错。subList的常用方法如图4所示。在这些方法中都有 checkForComodification()方法,在单线程中,这个方法会判断ArrayList是否有通过了SubList类之外的方法对list进行结构性修改(remove/add/clear等),如果有,则会抛出异常ConcurrentModificationException。

public class ArrayListStudy2 {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
list.add(3); ArrayList list = (ArrayList) list.subList(0, 3);
}
}
运行报错:
Exception in thread "main" java.lang.ClassCastException: java.util.concurrent.CopyOnWriteArrayList$COWSubList cannot be cast to java.util.ArrayList
at com.study.collection.ArrayListStudy2.main(ArrayListStudy2.java:17)

图3 SubList强转为ArrayList运行报错

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private class SubList extends AbstractList<E> implements RandomAccess {
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
} public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
} private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
}
}

图4 SubList的常用方法

4)toArray()方法后,进行类型转换会报错,如图5。这是因为toArray()得到的数组是Object[]类型,不能强转为Integer[]。应使用toArray(T[] a)方法。

public class ArrayListStudy4 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3); Integer[] objs = (Integer[])list.toArray();
//使用toArray(T[] a)方法
//Integer[] integers1 = list.toArray(new Integer[list.size()]);
}
}
报错:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at com.study.collection.ArrayListStudy4.main(ArrayListStudy4.java:13)

图5 list.toArray()后类型强转报错

Array.asList(T... a)返回的类型时Arrays的内部类ArrayList,没有实现add/remove/clear等方法;如果使用返回的对象调用add等方法,则会报错,如图6所示。

public class ArrayListStudy4 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(new Integer[]{1,2,3});
list.add(4);
}
}
报错:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.study.collection.ArrayListStudy4.main(ArrayListStudy4.java:10)

图6 Array.asList(T... a)返回的值使用add方法报错

Array.asList(T... a)的源码如下图7所示。

public class Arrays {
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
} /**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
//没有实现add/remove/clear等方法
}
}

图7 Array.asList(T... a)的源码

5)泛型通配符<? entends T>来接受返回的数据,此写法的泛型集合不能使用add方法,而<? super T>不能使用get方法,作为接口调用赋值时易出错。如图8所示,<? entends T>写法的泛型集合使用add方法报错,<? super T>写法的泛型使用get方法出错。

public class ArrayListStudy4 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(new Integer[]{1,2,3});
list.add(4); ArrayList<? extends Student> students = new ArrayList<Student>();
//运行报错
students.add(null); ArrayList<? super Student> students2 = new ArrayList<Student>();
students2.add(new Student());
//运行报错
Object object = students2.get(0);
}
} 报错:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.study.collection.ArrayListStudy4.main(ArrayListStudy4.java:12)

图8 <? entends T>写法的泛型集合使用add方法报错,<? super T>写法使用get方法报错

6)在JDK7版本及以上,Comparator实现类要满足如下三个条件,不然Arrays.sort,Collections.sort会报IllegalArgumentException异常。三个条件如下:

a)x,y的比较结果和y,x的比较结果相反;

b) x>y,y>z,则x>z;

c)x=y,则x,z比较结果和y,z比较结果相同;

7)集合泛型定义时,在JDK7以上,使用diamond语法或全省略。

说明:菱形泛型,即diamond,直接使用<>来指代前面已经指定的类型。

//<>diamond方式
HashMap<String,String> userCache = new HashMap<>(16);
//全省略方式
ArrayList<User> users = new ArrayList(10);

3.ArrayList的线程安全

ArrayList的线程安全是指在并发操作ArrayList时,操作的结果是否会达到人们期望的效果。通常线程安全的方法是通过synchronized来保证原子性,可见性和顺序性,也就是要as-if-serial语义。如果没有考虑到代码中执行的原子性,这就可能会出现线程不安全的问题。

比如ArrayList就是线程不安全的。线程不安全的场景主要包括以下几种。

3.1 add方法

如图9多线程并发调用add()方法时,可能会出现图10和11的两种异常情况,一种是抛出ArrayIndexOutOfBoundsException的异常,另一种是ArrayList有些索引没有存入值(为null)。

public class ArrayListStudy {
public static ArrayList arrayList = new ArrayList();
//并发处理
public static void main(String[] args) {
ArrayList<Integer> str = new ArrayList<>();
Thread[] threadArr = new Thread[1000];
for(int i = 0;i<threadArr.length;i++){
//新建一个线程
threadArr[i] = new ArrayListThread();
//线程启动
threadArr[i].start();
} for(int i=0;i<threadArr.length;i++){
try {
threadArr[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
} for(int i=0;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
}
} class ArrayListThread extends Thread{
@Override
public void run(){
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ArrayListStudy.arrayList.add(Thread.currentThread().getName());
}
}

图9 多线程并发调用add()方法

图10 并发调用add()方法时,出现数组越界异常(ArrayIndexOutOfBoundsException)的场景

图11 并发调用add()方法时,索引没有存入值的场景

3.2 remove方法

remove()可能出现集合某些索引的值没有移除的场景。

3.3 clone方法

如图12所示,clone()方法是对原list的浅拷贝,对list中的元素进行了浅拷贝,如果元素是对象,只拷贝元素的地址。拷贝的时候会调用Arrays.copyOf(elementData,size)方法,拷贝的时候有读取原数组元素的操作,是线程不安全的。

/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
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);
}
}

图12 ArrayList的clone()方法

3.4 iterator方法

iterator()获取的是ArrayList的内部类Itr。如图13所示,类Itr的next、previous、add、remove等方法是线程不安全的。且在使用iterator遍历时,如果使用了Iterator类之外的方法对list进行结构性修改(remove/add/clear等),则会抛出ConcurrentModificationException的异常;即fail-fast机制。

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; Itr() {} 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)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}

图13 ArrayList的内部类Itr

3.5 sublist方法

sublist()获取的是ArrayList内部类SubList的实例,如图14所示。它的set、get、add等方法是线程不安全的。

 private class SubList extends AbstractList<E> implements RandomAccess {
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
} public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
} public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
}

图14 ArrayList内部类SubList

3.6 方法组合

方法组合是指对操作List的元素方法的组合。我以两种情况为例。一种情况(后文使用“组合一”引用)是ArrayList的get与迭代器中的remove方法的组合;另一种情况(后文使用“组合二”引用)是ArrayList的get与SubList的add方法的组合。由于ArrayList没有采取任何线程安全的策略,当然这两种情况都是线程不安全的。

3.7 线程安全的措施

ArrayList是线程不安全的,ArrayList的方法没有添加任何措施以保证线程安全。ArrayList只适用于单线程的场景。在多线程下,通常使用下述的3种方式来实现线程安全。

3.7.1 Vector

Vector(已废弃)的方法实现和ArrayList类似,只是是线程安全的[3]。如图15,Vector线程安全的策略是通过在方法上添加synchronized关键字来实现的。Vector的iterator方法获取内部类Itr,该类中的方法是线程安全的。Vector的subList方法获取SynchronizedList类,该类中的方法是线程安全的。但由于内部类Itr中remove方法使用的锁对象是Vector.this,与ArrayList.get方法使用的锁对象this不同,因此组合一是线程不安全的。如图16,subList方法的返回值SynchronizedList中的方法使用的锁对象是当前的Vector实例,与ArrayList.get方法的锁对象相同,因此组合二是线程安全的。

public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//锁对象this
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index); return elementData(index);
}
public synchronized Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
//锁对象Vector.this
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
}
public synchronized List<E> subList(int fromIndex, int toIndex) {
return Collections.synchronizedList(super.subList(fromIndex, toIndex),
this);
}
}

图15 Vector类中的get、iterator、subList方法

public class Collections {
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> { SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public void add(int index, E element) {
//锁对象为当前的Vector实例
synchronized (mutex) {list.add(index, element);}
}
} static class SynchronizedCollection<E> implements Collection<E>, Serializable {
SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
}
}

图16 Collections.SynchronizedList中的方法

3.7.2 Collections.synchronizedList(ArrayList list)

可以通过Collections.synchronizedList(ArrayList list)方法实现线程安全[4]。如图17所示,该方法获取的SynchronizedList的线程安全是通过Synchronized(mutex)修饰代码块来实现的。SynchronizedList.iterator()返回值类型是ArrayList.Iterator(),它是线程不安全的。SynchronizedList.subList的返回值类型也是SynchonizedList,它是线程安全的,SynchronizedList.subList中的锁对象与原SynchronizedList相同,都是原SynchronizedList实例。显然,组合一是线程不安全的,组合二是线程安全的。

public class Collections {
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
} static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> { SynchronizedList(List<E> list) {
super(list);
this.list = list;
} SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
} public void add(int index, E element) {
//锁对象是当前Collections.SynchronizedList的实例
synchronized (mutex) {list.add(index, element);}
}
public List<E> subList(int fromIndex, int toIndex) {
synchronized (mutex) {
//锁对象为原SynchronizedList的实例,与add()方法相同
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
} static class SynchronizedCollection<E> implements Collection<E>, Serializable {
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
} SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
public Iterator<E> iterator() {
//返回值类型ArrayList.iterator(),它是线程不安全的
return c.iterator(); // Must be manually synched by user!
}
}
}

图17 Collections中的内部类SynchronizedList和SynchronizedCollection

3.7.3 CopyOnWriteArrayList

也可以通过CopyOnWriteArrayList方法来实现线程安全[5]。如图18,在调用add方法时,将原数组copy到新数组,元素的新增在新数组中完成。新数组中新增元素后,在将当前的数组指向新数组。由于元素的新增时,setArray(newElements)的操作是原子性的,其它的修改数组的方法也是类似的(先操作copy的数组,然后通过调用setArray(new Elements)方法),所以get方法不需要再加锁。CopyOnWriteArrayList适用于多读的场景。CopyOnWriteArrayList的iterator获取的COWIterator类不支持remove、add等修改方法,因此COWIterator的其它的查询方法不需要加锁。所以组合一是线程安全的。CopyOnWriteArrayList的subList获取的COWSubList是线程安全的,锁对象是原CopyOnWriteArrayList实例的lock属性,与CopyOnWriteArrayList中方法的锁对象相同,因此组合二是线程安全的。

public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
final transient ReentrantLock lock = new ReentrantLock();
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
} final void setArray(Object[] a) {
array = a;
} //get方法不用加锁
public E get(int index) {
return get(getArray(), index);
} public List<E> subList(int fromIndex, int toIndex) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (fromIndex < 0 || toIndex > len || fromIndex > toIndex)
throw new IndexOutOfBoundsException();
return new COWSubList<E>(this, fromIndex, toIndex);
} finally {
lock.unlock();
}
} static final class COWIterator<E> implements ListIterator<E> {
//COWIterator.next()方法不用加锁
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
//COWIterator不支持remove()等修改方法
public void remove() {
throw new UnsupportedOperationException();
}
} private static class COWSubList<E>
extends AbstractList<E>
implements RandomAccess
{
private final CopyOnWriteArrayList<E> l; // only call this holding l's lock
COWSubList(CopyOnWriteArrayList<E> list,
int fromIndex, int toIndex) {
l = list;
expectedArray = l.getArray();
offset = fromIndex;
size = toIndex - fromIndex;
}
public void add(int index, E element) {
final ReentrantLock lock = l.lock;
lock.lock();
try {
checkForComodification();
if (index < 0 || index > size)
throw new IndexOutOfBoundsException();
l.add(index+offset, element);
expectedArray = l.getArray();
size++;
} finally {
lock.unlock();
}
}
}
}

图18 CopyOnWriteArrayList中的方法及内部类COWIterator和COWSubList

下表从常用方法get、add等,以及组合方法的维度对比了三种线程安全类。Vector和Collections.synchronizedList的实现大致相同,他们在多读场景下效率低,且组合一是线程不安全的。Vector和Collections.synchronizedList的不同之处包括iterator()方法获取的迭代器是否线程安全等。Vector中的迭代器实现了线程安全,但在方法组合时又线程不安全;且由于每个方法都使用了同步,效率低下,因此Vector已被弃用。相比而言,CopyOnWriteArrayList的读方法不用加锁,在多读场景下效率高,且组合一和组合二都是安全的。如果是多写场景,Collections.synchronizedList的效率更高,但要注意iterator()获取的迭代器中方法的线程安全,以及方法组合等场景下的线程安全。

表1 Vector、Collections.synchronizedList()、CopyOnWriteArrayList线程安全特性对比

Vector Collections.synchronizedList() CopyOnWriteArrayList
get() synchronized修饰(锁对象是this) 锁对象this 不用锁
add() synchronized修饰 (锁对象是this) 锁对象this 使用l.lock加锁(l为复制数组)
remove() synchronized修饰(锁对象是this) 锁对象this 使用l.lock加锁(l为复制数组)
clone() synchronized修饰(锁对象是this) 未实现 浅拷贝(只拷贝list,未拷贝list中的元素),不用锁
iterator()获取的迭代器类中的方法 方法内使用Vector.this加锁 未加锁,需要额外人工加锁 未加锁,仅支持previous、next等方法,不支持remove、add、set等方法
sublist() this是锁对象 锁对象this 使用l.lock加锁(l为复制数组)
方法组合 组合一线程不安全,组合二线程安全 组合一线程不安全,组合二线程安全 组合一、组合二线程安全

参考资料:

[1] ArrayList源码中的类注释;

[2]《阿里巴巴Java开发手册 终极版 v1.3.0》;

[3] Vector源码;

[4]Collections.synchronizedList源码;

[5]CopyOnWriteArrayList源码;

ArrayList学习总结的更多相关文章

  1. ArrayList学习笔记

    目录 1.继承关系 1.1.Serializable 标记性接口 1.2.Cloneable 标记性接口 1.3.RandomAccess 标记性接口 2.属性 3.构造方法 3.1.无参构造方法-A ...

  2. ArrayList 学习笔记

        接口   ArrayList实现了List接口,因此可以当作一个List来使用. 此外,ArrayList还实现RandomAccess接口和Serializable,说明ArrayList支 ...

  3. ArrayList学习

    核心源码 package java.util; import java.util.function.Consumer; import java.util.function.Predicate; imp ...

  4. Java中ArrayList学习笔记

    1. 先看两段代码 这段代码在执行的时候会报 但是这样写就好着呢: 总结,研究报错的代码 ,在for循环的时候调用next()方法,next方法中调用了checkForComodification这个 ...

  5. java集合类的学习(一)

    为何要用集合类:可以储存不同类型的数据,可以进行动态的删除和修改,不用考虑数组越界的问题. 软件开发常用的集合类:Vector,ArrayList,Stack,HashMap,Hashtable. 3 ...

  6. Java 学习路线图

    一.集合 集合类的分类 * (一)List 结构集合类 * ArrayList LinkedList Vector Stack * (二)Map 结构集合类 * HashMap HashTable * ...

  7. [Java] List / ArrayList - 源代码学习笔记

    在阅读 List / ArrayList 源代码过程中,做了下面的笔记. LinkedList 的笔记较多,放到了另一篇博文 LinkedList / Queue- 源代码学习笔记 List List ...

  8. List接口实现类-ArrayList、Vector、LinkedList集合深入学习以及源代码解析

    学习List接口实现类 ArrayList  Vector  LinkedList List接口的实现类中最经常使用最重要的就是这三个:ArrayList.Vector.LinkedList. JDK ...

  9. 2018/2/14 设计模式学习笔记(一) 自己实现ArrayList,LinkedList和Iterator,以及在此过程中对于面向对象,面向接口,还有抽象类的一些思考感悟

    因为本人目前为止学习编程不过七个月,所以后面的感悟对于一些大神来说可能嗤之以鼻,但对于一些刚刚入门的萌新来说在理解面向对象的思想上,以及抽象类和接口应该怎么设计等方面应该还是会有所帮助的 首先我们定义 ...

  10. Java基础学习笔记六 Java基础语法之类和ArrayList

    引用数据类型 引用数据类型分类,提到引用数据类型(类),其实我们对它并不陌生,如使用过的Scanner类.Random类.我们可以把类的类型为两种: 第一种,Java为我们提供好的类,如Scanner ...

随机推荐

  1. 【代码分享】使用 avx2 + 查表法,优化凯撒加密

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 接上一篇:[代码分享]使用 avx512 + 查表法,优化 ...

  2. 站点用css一键变灰色

    默哀站点变灰色效果看本站即可 css代码如下 <style>html{-webkit-filter:grayscale(100%);-moz-filter:grayscale(100%); ...

  3. 报错ValueError: Can't find 'adapter_config.json'

    前言 在做组内2030项目时,我具体做的一个工作是对大模型进行LoRA微调,在整个过程中有许多坑,其中有些值得记录的问题,于是便产生了这篇博客. 问题 我在得到微调好的模型后,需要对模型进行性能测评. ...

  4. 【题解】U388218 数数

    数数 题目描述 给定 n 个不超过 1.5×10⁹ 的自然数.求这些自然数各自出现的次数,并按照自然数从小到大的顺序输出统计结果. 输入格式 输入的第 1 行是整数 n ,表示自然数的个数. 第 2 ...

  5. .NET Core开发实战(第18课:日志框架:聊聊记日志的最佳姿势)--学习笔记(下)

    18 | 日志框架:聊聊记日志的最佳姿势 除了使用 CreateLogger 指定 logger 的名称,实际上还可以借助容器来构造 logger,通常情况下我们会定义自己的类 namespace L ...

  6. Linux-RAID类型介绍、创建、彻底删除

    一.RAID(磁盘阵列) 1.1.RAID概念 RAID简称为独立冗余磁盘阵列,把多块独立的物理硬盘按不同的方式组合起来形成一个硬盘组(逻辑硬盘),从而提供比单个硬盘更高的存储性能和提供数据备份技术, ...

  7. cached地址和uncached地址的区别

    cached地址和uncached地址的区别是 对cached地址的访问是委托给CPU进行的,也就是说你的操作到底是提交给真正的外设或内存,还是转到CPU缓存,是由CPU决定的.CPU有一套缓存策略来 ...

  8. Vue中data为何以函数形式返回

    data为何以函数形式返回 在使用Vue构建组件化应用时,每个组件的data属性都是以函数形式返回的,这主要是在组件化实现的时候,每个实例可以维护一份被返回对象的独立的拷贝,而不是共享同一个对象的引用 ...

  9. Swoole从入门到入土(20)——WebSocket服务器[协程版本]

    本篇让我们先用一段示例代码开路: <?php Co\run(function () { $server = new Co\Http\Server('0.0.0.0', 9501, false); ...

  10. MySQL表锁定处理

    研发要在一个ol_poster_sign表加字段,表比较大有400多万条,用gh-ost加字段时,在切换过程中一直报错: 无法完成最后的切换: INFO Magic cut-over table cr ...