ArrayList
ArrayList是一个装载数据的数组集合,也可以说是动态数组。但我们先说一下Java中的普通数组,Java中普通数组只能用来存储基本数据类型的数据,并且容量一旦定义了,就只能装这么多数据。但是ArrayList不是,它的容量能动态增长。允许包括 null 在内的所有元素。除了实现 List 接口外,它还提供一些方法来操作内部用来存储列表的数组的大小。(它大致上等同于 Vector 类,但是它不是线程安全的,而Vector是线程安全的)
结构
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
从代码可以看出,它继承于AbstractList,并实现了List、RandomAccess、Cloneable、Serializable接口。
继承于AbstractList并实现了List接口:提供了添加、删除、修改、遍历等一些功能方法。
RandomAccess接口:随机访问功能,为List提供了快速访问功能,我们可以通过元素的下标快速获取元素对象,这就是快速随机访问。
没有定义任何代码,这种没有任何代码的接口在Java中被称为标记接口,用于声明类的一种属性。
public interface RandomAccess {
}
Cloneable接口:重写了clone()函数,支持克隆。
Serializable接口:意味着ArrayList支持序列化传输。
基本用法
public boolean add(E e) // 添加元素
public boolean isEmpty() // 判断是否为空
public int size() //获取长度
public E get(int index) // 访问指定位置的元素
public int indexOf(Object o) // 查找元素,如果找到,返回索引位置,否则返回-1
public int lastIndexOf(Object o) //是否包含指定元素,依据是equals方法的返回值
public E remove(int index) //删除指定位置的元素,返回值为被删对象
public boolean remove(Object o)// 删除指定对象,只删除第一个相同的对象,返回值表示是否删除了元素 如果o为null,则删除值为null的元素
public void clear() // 删除所有元素
public void add (int index,E element) //在指定位置插入元素,index为0表示插入最前面,index为ArrayList的长度表示插到最后面
public E set(int index,E element) //修改指定位置的元素内容
简单使用
ArrayList<Integer> integerList=new ArrayList<Integer>();
integerList.add(100); // 添加元素
integerList.add(0);
for(int i=0;i<integerList.size();i++){ // 遍历迭代
System.out.println(integerList.get(i));
}
可以看出ArrayList的基本用法是比较简单的,它基本原理也是比较简单的。下面让我们看看它的一些属性值。
属性
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10; // 默认容量为10
private static final Object[] EMPTY_ELEMENTDATA = {}; // 空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // 暂时忽略transient(不可被序列化)这个关键字,elementData会随着实际元素的个数增多而重新分配
private int size; // size则记录实际的元素个数
构造函数
当我们用下面的方式创建一个集合时,那么它会默认选择使用这个构造方法来创建集合。
这个elementData是一个Object类型的数组,至于这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个是一个空数组。这里要记住,它并没有初始化数组,所以现在的容量还为0,当我们去调用add方法是才会进行初始化。
// 源码
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
------------------------------------------------------------------------------------
public class ArrayListTest01 {
public static void main(String[] args) {
ArrayList arr=new ArrayList();
}
}
当我们传入一个容量值时,使用的是这一个构造方法。
public ArrayList(int initialCapacity) {
// 传入容量大于0,则进行初始化数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
// 传入容量为0,则会引用一个空数组对象
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
// 传入容量小于0,则会抛出参数异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
提供一个集合作为参数。会把集合中的所有元素复制到数组中
public ArrayList(Collection<? extends E> c) {
// 将当前集合元素给到一个Object类型的数组
Object[] a = c.toArray();
// 判断集合的长度是否为0
if ((size = a.length) != 0) {
// 查看当前集合是否为ArrayList类型
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
// 创建一个新的数组
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
// 如果集合长度为0,那么就是一个空数组
elementData = EMPTY_ELEMENTDATA;
}
}
add方法
modCount:表示内部修改的次数,为什么要记录修改次数,我们待会解释。
public boolean add(E e) {
// 校验是否扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 插入元素到数组,并且size++,size是记录元素个数的,谨记!!!
elementData[size++] = e;
// 如果插入成功,返回true
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 保证明确的容量 计算容量
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果elementData为空数组,那么赋予它一个默认的容量为10,这里也就是为什么说它默认容量为10的原因。当我们以ArrayList arr=new ArrayList();这种方式初始化一个容器时,看第一个构造方法,会赋予它一个空数组,以至于这里判断,所以赋予它一个为10的默认容量。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 记录内部的修改次数(增加)
modCount++;
// overflow-conscious code
// 如果当前需要的容量大于数组长度,会进行扩容
if (minCapacity - elementData.length > 0)
// 扩容,这里暂且不讲,下面会有讲到
grow(minCapacity);
}
在指定位置,插入元素
public void add(int index, E element) {
// 首先检查下标,下标不能大于元素数量以及不能小于0,否则会抛出数组下标越界异常
rangeCheckForAdd(index);
// 校验是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 插入时,插入位置之后的元素需要进行后移操作
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 插入元素到数组
elementData[index] = element;
// size进行++操作
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove方法
public E remove(int index) {
// 校验我们要删除元素的索引下标是否超过了当前元素数量,如果超过了抛出索引越界异常
rangeCheck(index);
// 删除也算是一种结构变化,所以也要进行++操作
modCount++;
// 拿到要删除的元素,之后会将这个删除的元素进行返回
E oldValue = elementData(index);
// 计算要移动的元素个数, 例如 元素为0,1,2,3,4,5 ,如果我们删除第3个元素,从0开始,size为6,6-3-1=2,那么需要往前移动2个元素,也就是4,5这两个元素需要被移动。
int numMoved = size - index - 1;
// 如果大于0,说明要移动元素。这么为什么要大于0,因为如果我们删除最后一个元素时,根本不需要移动。
if (numMoved > 0)
// 参数说明:
// 1.原数组
// 2.原数组开始的位置,根据要移动元素的个数来确定移动几个元素,放到目标数组当中开始的位置
// 3.目标数组,目标数组开始的位置
// 4.移动元素的个数
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将最后一个元素位置置为null,等待垃圾回收处理
elementData[--size] = null; // clear to let GC do its work
// 返回删除的元素
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 获取到要删除的元素
E elementData(int index) {
return (E) elementData[index];
}
remove(Object o)方法
public boolean remove(Object o) {
// 判断传进来的参数是否为null
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 如果不为null
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
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);
// 删除某个元素,最后一个元素置为null,等待垃圾线程进行回收
elementData[--size] = null; // clear to let GC do its work
}
扩容
// 扩容方法
private void grow(int minCapacity) {
// overflow-conscious code
// 获取当前数组长度
int oldCapacity = elementData.length;
// 右移1,也就是除以2^1加上原来的数组长度,也就是扩容原来的1.5倍,比如默认10个数组长度装不下了,那么下次就扩容为15.
// 这里的位移与jdk1.7的源码不同,jdk1.7没有采用位移运算
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;
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
// 根据扩容后的容量创建一个新的数组
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
// 复制操作
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
get方法
public E get(int index) {
// 校验数组下标
rangeCheck(index);
// 通过数组下标返回元素
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
set方法
为什么指定位置修改元素,没有用到modCount? 因为修改元素,数组的结构并没有任何变化。
public E set(int index, E element) {
// 校验数组下标
rangeCheck(index);
// 获取要替换的数组元素
E oldValue = elementData(index);
// 将此位置上的元素,替换为我们要修改的元素
elementData[index] = element;
// 返回旧值
return oldValue;
}
contains(Object o)
public boolean contains(Object o) {
// 调用了indexOf()方法,如果不返回-1,说明此元素存在
return indexOf(o) >= 0;
}
indexOf()方法
public int indexOf(Object o) {
// 首先校验是否为null
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
// 如果不为null,循环遍历元素,找到后,返回元素下标
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
// 如果没有找到返回-1
return -1;
}
好了,以上就是在操作ArrayList集合时,一些常用的操作方法。
下面我们来看一下迭代以及modCount的作用
迭代
下面这个迭代是我们对ArrayList一个常见操作,循环打印ArrayList中的每个元素
ArrayList<Integer> arr=new ArrayList<Integer>();
arr.add(123);
arr.add(456);
arr.add(789);
for (Integer number : arr) {
System.out.println(number);
}
当然,也不止这一种方式,还有下面这一种。
ArrayList<Integer> arr=new ArrayList<Integer>();
arr.add(123);
arr.add(456);
arr.add(789);
for (int i = 0; i < arr.size(); i++) {
System.out.println(arr.get(i));
}
对比以上两种方式,还是foreach上更为简洁一些,而且这种方式适用于各种容器,更为通用。
foreach背后实现方式
foreach语法背后是怎么实现的呢?其实在我们去编译代码的时候,它会转换为这种方式。
ArrayList<Integer> arr=new ArrayList<Integer>();
arr.add(123);
arr.add(456);
arr.add(789);
Iterator<Integer> iterator = arr.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
ArrayList实现了Iterable接口,Iterable表示可以迭代。定义很简单,就是要求实现iterator方法。返回了一个Iterator接口的对象
只要对象实现了Iterable接口,就可以使用foreach语法,编译器就转换为调用Iterable和Iterator接口的方法。初次见到Iterable和Iterator可能会容易混淆。我们来看看它们的作用
- Iterable表示对象可以被迭代,它有一个方法iterator(),返回Iterator对象,实际通过Iterator接口的方法进行遍历。
- 如果对象实现了Iterable,就可以使用foreach用法。
- 类可以不实现Iterable,也可以创建Iterator对象。
Iterable接口
iterator为迭代器对象,对集合进行遍历的底层依赖,iterable接口是所有Collection类的父接口,内部封装了Iterator迭代器对象,因此集合类可以使用迭代器遍历元素。
public interface Iterable<T> {
Iterator<T> iterator();
// 以下两个默认方法是在Java 8添加进来,因为在Java 8以前,接口不能有任何实现方法。只能有抽象方法和常量
//
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
// 为遍历和分隔元素提供更便捷的操作
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
Iterator接口
这个接口jdk1.8与jdk1.8之前有不同的地方。 remove()方法有所不同
public interface Iterator<E> {
// 判断是否元素未访问
boolean hasNext();
// 返回下一个元素
E next();
// void remove(); jdk1.8之前的
// 下面两个默认方法在jdk1.8 才有的
default void remove() {
// 无支持的操作异常
throw new UnsupportedOperationException("remove");
}
// 这里封装了hasNext()以及next()方法,并且提供了null值的判断,利用lambda表达式表达式进行函数式接口的调用,可以在任何类中直接使用更简洁的语法对元素进行访问操作。
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
// 对null值的判断
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
ListIterator
除了iterator(),ArrayList还提供了两个返回Iterator接口的方法:增加了一些方法,如:向前遍历、添加元素、修改元素、返回索引位置等
public ListIterator<E> listIterator() {
return new ListItr(0);
}
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
public interface ListIterator<E> extends Iterator<E> {
// 判断是否元素未访问
boolean hasNext();
// 返回下一个元素
E next();
// 判断前面元素是否未访问
boolean hasPrevious();
// 返回上一个元素
E previous();
// 返回下一个元素的索引位置
int nextIndex();
// 返回上一个元素的索引位置
int previousIndex();
// 删除
void remove();
void set(E e);
void add(E e);
listIterator():方法返回的迭代器从0开始,而listIterator(int index)方法返回的迭代器从指定位置index开始。例如:比如从末尾往前遍历。
@Test
public void test04(){
ArrayList<Integer> arr=new ArrayList<Integer>();
arr.add(123);
arr.add(456);
arr.add(789);
ListIterator<Integer> integerListIterator = arr.listIterator(arr.size());
while(integerListIterator.hasPrevious()){
System.out.println(integerListIterator.previous());
}
}
Fail-Fast(快速失败)机制
Fail-Fast简介
快速失败指的是某个线程在迭代集合类的时候,不允许其他线程修改该集合类的内容,这样迭代器出来的结果就不会准确。它是一种错误机制,只能被用来检测错误,因为JDK并不保证fail-fast机制一定会产生。
关于迭代器,有一种常见的误用,就是在迭代的中间调用容器的删除方法,比如,要删除一个整数ArrayList中所有小于100的数,我们可能会这么写:
@Test
public void test05(){
ArrayList<Integer> arr=new ArrayList<Integer>();
for (int i = 0; i < 100; i++) {
arr.add(i);
}
for (Integer num : arr) {
if(num<100){
arr.remove(num);
}
}
}
实际编译器编译后大概是这样子的:
@Test
public void test08(){
ArrayList<Integer> arr=new ArrayList<Integer>();
for (int i = 0; i < 100; i++) {
arr.add(i);
}
Iterator<Integer> iterator = arr.iterator();
while(iterator.hasNext()){
if(iterator.next()<100){
// 此方法是ArrayList的remove()方法
arr.remove(iterator.next());
}
}
}
随后抛出异常
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at 集合.ArrayListTest01.test05(ArrayListTest01.java:74)
发生了并发修改异常,为什么呢?
因为迭代器内部会维护一些索引位置相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置就失效了。所谓的结构性变化就是添加、插入、删除元素,修改元素内容不算结构性变化,这也是上文中提到的modCount的作用。
我们先来看正确的删除方法,如何避免异常的发生,稍后在分析出现异常的原因。
@Test
public void test06(){
ArrayList<Integer> arr=new ArrayList<Integer>();
for (int i = 0; i < 100; i++) {
arr.add(i);
}
Iterator<Integer> iterator = arr.iterator();
while(iterator.hasNext()){
if(iterator.next()<100){
// 这里的remove()方法与上文中出现异常的remove()方法不同
// 一个是ArrayList的remove方法,这个是iterator的remove方法
iterator.remove();
}
}
}
那么迭代器如何知道发生了结构性变化,并抛出异常?它自己remove()方法为什么可以使用?
expectedModCount可以检测出结构是否发生变化。
到这里后,大家可以再去看看ArrayList的remove()方法,里面有一个fastRemove()方法会修改modCount的值。如果我们使用ArrayList的remove()方法,那么在我们去删除元素的时候,modCount会有所变化。当我们在使用迭代器时,会创建一个Itr()对象,初始化cursor、lastRet、expectedModCount的值,此时expectedModCount等于当前的modCount。在迭代元素的时候,expectedModCount不会发生任何变化,迭代过程中,会进行结构是否变化的判断,如果modCount和expectedModCount不相等就会抛出并发修改异常!!
public Iterator<E> iterator() {
return new Itr();
}
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;
}
final void checkForComodification() {
// 在我们去迭代元素时 ,会检查结构是否发生变化。
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
我们点击下面报错的信息,861行。
迭代器的实现原理
private class Itr implements Iterator<E> {
// 表示下一个要返回的元素位置
int cursor; // index of next element to return
// 表示最后一个返回的索引位置,如果没有返回-1
int lastRet = -1; // index of last element returned; -1 if no such
// 期望修改的次数,初始化为外部的modCount,成员内部类可以直接访问外部类的实例变量,每次发生结构性变化时modCount都会进行++,而每次迭代器操作的时候都会检查expectedModCount是否与modCount相同,这样就能检测出变化
int expectedModCount = modCount;
Itr() {}
// 如果下一个元素位置不等于size元素数量,说明后面还有元素,继续遍历
public boolean hasNext() {
return cursor != size;
}
// 获取下一个元素
// 压制警告
@SuppressWarnings("unchecked")
public E next() {
// 检查结构是否变化
checkForComodification();
// i为元素下标
int i = cursor;
// 检查i是否大于了size的元素数量,如果大于了,说明后面没有元素,直接抛出异常
if (i >= size)
throw new NoSuchElementException();
//
Object[] elementData = ArrayList.this.elementData;
// 在遍历的过程中,可能会有添加 插入,需要判断。
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 下一个元素的下标
cursor = i + 1;
// lastRet记录最后一个返回的索引位置,所以必须跟i看齐,返回对应的元素
return (E) elementData[lastRet = i];
}
public void remove() {
// 如果删除时,ArrayList中没有元素,lastRet为-1,不能删除,抛出异常
if (lastRet < 0)
throw new IllegalStateException();
// 检查结构是否有所变化
checkForComodification();
try {
// 调用了ArrayList的remove()方法
ArrayList.this.remove(lastRet);
// 同时更新cursor、lastRet、expectedModCount的值
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
// 判断集合是否为null,如果为null,抛出空指针异常
Objects.requireNonNull(consumer);
// 当前ArrayList的size数量
final int size = ArrayList.this.size;
// 下个元素的坐标
int i = cursor;
// 如果i大于size的元素数量直接return,因为已经没有元素了
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
// 如果在遍历时,进行了添加插入操作,i可能会大于数组长度
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
// 当i不等于size元素数量以及期望修改的次数相等,则获取元素
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() {
// 这里就是上文中的出现异常的地方,911行
// 如果modCount不等于expectedModCount就会出现并发修改异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
而这个remove方法,里面虽然也是用ArrayList的remove()方法,但是对cursor、lastRet、expectedModCount做了及时更新,所以不会出现并发修改异常。
不过,需要注意在调用remove()方法前必须先调用next()方法。这个方法时将所有元素删除,我们也可以直接用现成的clear()方法。
@Test
public void test07(){
ArrayList<Integer> arr=new ArrayList<Integer>();
for (int i = 0; i < 100; i++) {
arr.add(i);
}
Iterator<Integer> iterator = arr.iterator();
while(iterator.hasNext()){
// 如果不调用next()方法会抛出异常
//iterator.next();
iterator.remove();
}
}
java.lang.IllegalStateException
at java.util.ArrayList$Itr.remove(ArrayList.java:874)
at 集合.ArrayListTest01.test07(ArrayListTest01.java:103)
ArrayList 采用了快速失败的机制,通过记录 modCount 参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
迭代器的好处
为什么要通过迭代器这种方式访问元素呢?直接get(index)语法不是也可以嘛?之前我也很质疑。
但是在一些场景下,确实没有什么区别,两者都可以。不过,foreach语法更为简洁一些,更重要的是,迭代器语法更为通用,它适用于各种容器类。需要访问容器元素的代码只需要一个Iterator接口的引用,不需要关注数据的实际组织方式,可以使用一致和统一的方式进行访问。
在ArrayList中,get(index)语法和迭代器性能是差不多的,但是在后续介绍的其他容器中,则不一定,比如LinkedList,迭代器性能就要高很多。
迭代器表示的是一种关注点分离的理想,将数据的实际组织方式与数据迭代遍历相分离,是一种常见的设计模式。—来自《Java编程逻辑》一书
总结
后续我会介绍各种容器和数据的组织方式,每一种不同的方式都有不同的特点,而不同的特点就有不同的使用场合。性能是其中一个很重要的一个部分。但是对于数据结构来说,性能高,就不安全;性能低,那么就安全。对于ArrayList来说,它不是线程安全的,插入和删除效率低,它的特点如下:
- 可以随机访问,按照索引位置进行访问,直接一步到位。时间复杂度为O(1)。
- 按照内容查找元素效率比较低,有n个元素,就遍历n个元素。时间复杂度为O(N),性能与数组的长度为正比。
- 在数组末尾添加元素为O(1)。添加N个元素为O(N)。
- 插入和删除元素的效率比较低,因为需要移动元素。时间复杂度为O(N)。
- ArrayList的默认容量为什么是10,这个。。。据说是因为sun的程序员对一系列广泛使用的程序代码进行了调研,结果就是10这个长度的数组是最常用的最有效率的。也有说就是随便起的一个数字,8个12个都没什么区别,只是因为10这个数组比较的圆满而已。。。。。
- 在我们使用new ArrayList()时或者new ArrayList(int initialCapacity) 刚开始并没有进行初始化数组。大家可以自行去试。
@Test
public void test09(){
ArrayList arr1=new ArrayList();
System.out.println(arr1.size());
arr1.set(0,1);
ArrayList arr=new ArrayList(10);
System.out.println(arr.size());
arr.set(0,2);
}
希望大家有所收获,学生党知识有限!!