ArrayList是List接口的动态数组实现,允许存放所有元素,包括null。除了实现List接口外,ArrayList类还提供了操作数组大小的方法。功能与Vector类似,但是不同步。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList类继承1个抽象类,实现4个实现。
【扩展】对于随机存储数据和连续访问数据,可以参考Collections.binarySearch()方法的源码。
// Collections
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
// for循环遍历实现
return Collections.indexedBinarySearch(list, key);
else
// 迭代器实现
return Collections.iteratorBinarySearch(list, key);
}
性能比较:ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for循环遍历快!!
ArrayList是List接口的动态数组实现,允许存放所有元素,包括null。除了实现List接口外,ArrayList类还提供了操作数组大小的方法。功能与Vector类似,但是不同步。
时间复杂度:get、set以及迭代器操作时间复杂度是O(1)。add操作以均摊常数时间运行,也就是说,添加n个元素需要O(n)时间。其他操作时间复杂度为O(n),以线性时间运行。与 LinkedList 实现相比,该实现的常数因子更低。
扩容:ArrayList实例的容量capacity是指存储元素的数组大小,总是大于等于列表大小。随着不断添加元素,容量会自动扩展。在添加大量元素之前,ArrayList采用ensureCapcity方法来增加实例容量,有效的减少递增式再分配的数量。
线程安全性:该实现不是同步的。多个线程同时访问一个ArrayList实例,并且存在至少一个线程对列表做结构性修改时,必须进行同步。针对于ArrayList,结构性修改是指任何添加或删除一个或者多个元素,或者显式调整底层数组大小的操作;修改元素值不是结构性修改。一般通过对自然封装该列表的对象进行同步操作来完成,如果不存在这样的对象,应该采用Collections.synchronizedList将当前列表封装起来(最好是在创建时完成,以防意外对列表的不同步访问)。
ArrayList的迭代器操作具有快速失败(Fast-Fail)机制,在创建迭代器后,除非是自身remove或add操作,任何其他对列表的结构性修改,迭代器都会抛出ConcurrentModificationException。
ArrayList架构比较简单,底层就是一个Object数组–elementData。
其他常量
// 可分配的最大数组大小,由于有些虚拟机会保留位置,分配更大数组会导致OutOfMemoryError
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 共享的空数组实例,用于空实例。
private static final Object[] EMPTY_ELEMENTDATA = {
};
// 共享的空数组实例,用于默认大小的空实例。
// 与EMPTY_ELEMENTDATA做区分,以便了解在添加第一个元素时需要扩展多少数组容量。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
};
无参构造方法 – 默认容量
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
初始化为共享空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,添加元素时进行扩容。
指定容量作为参数的构造方法
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);
}
}
大于0,创建指定大小的Object数组;等于0,初始化共享空数组EMPTY_ELEMENTDATA;其他抛出参数不合法异常。
指定集合作为参数的构造方法
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 如果c.toArray返回的不是Object[]类型,转为Object
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 集合元素个数为0,使用空数组代替
this.elementData = EMPTY_ELEMENTDATA;
}
}
需要特别注意的是,ArrayList无参构造初始化时,默认创建空数组,在首次添加元素时才会初始为10。
ArrayList类add(E e)方法,将指定元素添加到列表末尾,时间复杂度为O(1),但考虑到动态数组扩容的性质,添加操作为均摊O(1)。
public boolean add(E e) {
// 判断底层数组容量是否足够,不够则进行扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 直接赋值--线程不安全
elementData[size++] = e;
return true;
}
在源码中,添加元素操作分为了两步:扩容和赋值。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果底层数组仍为默认空数组,设定为初始值10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 列表结构性修改次数加1
modCount++;
// 如果期望最小容量大于当前数组容量,则扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容操作
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 扩容后容量是当前数组容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容后容量仍然小于期望最小容量,设为期望容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果扩容后容量大于JVM所能分配数组容量的最大值Integer.MAX_VALUE - 8,则使用Integer的最大值Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 创建新数组,复制原数组所有元素到新数组后,最后指针指向新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
在新增过程中,
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
源码问题:计算最小期望容量方法中,当elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,应该直接返回默认值,而不需要Math.max判断。因为只有ArrayList无参构造初始化时,elementData才是DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果底层数组仍为默认空数组,设定为初始值10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
ArrayList删除元素,可以通过底层数组索引删除,通过值来删除以及其他批量删除。
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);
// 置空末尾元素,便于GC回收内存
elementData[--size] = null;
return oldValue;
}
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 {
// 值不为空
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
根据值删除,存在值为空和非空两种情况,在遍历时分别通过==和equals方法来匹配。需要特别注意的是,删除的是匹配到的第一个元素,而不是所有匹配元素。
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
}
除了边界校验,fastRemove方法与remove(int index)方法代码几乎一致。同时我们也发现了System.arraycopy的使用,这一操作在涉及到数组操作中有着广泛应用,例如String。
public E get(int index) {
// 边界校验,index大于等于size,抛出IndexOutOfBoundsException
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
elementData数组中元素是Object类型,获取指定类型需要强转。
public E set(int index, E element) {
// 边界校验
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
ArrayList中提供了多种迭代器实现,例如Itr迭代器、ListIterator迭代器。Itr迭代器是AbstractList.Itr的优化版本,而ListIterator迭代器继承Itr,并对其提供了一些扩展,例如add和set操作。
private class Itr implements Iterator<E> {
// 下一个元素的索引位置
int cursor;
// 最新被返回的元素索引,如果没有更多元素,设为-1
int lastRet = -1;
// 期望结构修改次数
int expectedModCount = modCount;
Itr() {
}
...
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
迭代器变量中,expectedModCount是校验数组结构修改的关键变量。而lastRet变量用于标识是否还有元素可以删除,迭代开始值为-1,无元素;remove操作后,lastRet值也置为-1,表示不能连续删除。
Itr迭代器实现Iterator接口,主要包含了3个方法。
// 是否还有未迭代的元素
public boolean hasNext() {
// 如果下一个元素位置是size,表示没有元素可以迭代
return cursor != size;
}
// 返回下一个元素值
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;
// 返回元素值,并设置lastRet
return (E) elementData[lastRet = i];
}
public void remove() {
// 如果数组中没有元素
if (lastRet < 0)
throw new IllegalStateException();
// 校验迭代过程中,数组是否有结构性修改
checkForComodification();
try {
// 调外部类remove,删除当前迭代元素
ArrayList.this.remove(lastRet);
// 因为是删除操作,迭代索引位置不变
cursor = lastRet;
// -1表示元素已经被删除
lastRet = -1;
// 修改期望modCount值
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
我们可以关注到:
迭代器有expectedModCount属性用于保存迭代器被调用时的modCount值。如果在迭代过程中,迭代器的增add、删remove、改set、查next/previous 4种操作方法会对modCount进行校验是否被修改。如果被修改,抛出ConcurrentModificationException。
所谓的Fail-Fast机制核心就是下面两句
int expectedModCount = modCount;
条件表达式判断
modCount == expectedModCount
modCount属性继承自AbstractList,标识当前List结构修改的次数。这是一种fail-fast行为,用于防止并发导致不一致的结果。
protected transient int modCount = 0;
【注】注意的是modCount属性并不是volatile的。查阅ConcurrentModificationException源码注释,我们可以发现该异常并不总是表示对象被不同线程并发修改。同一个线程也可能触发该异常。例如,假如集合具有fail-fast迭代器,如果在进行迭代操作时修改数据,迭代器会抛出该异常。
* Note that this exception does not always indicate that an object has
* been concurrently modified by a <i>different</i> thread. If a single
* thread issues a sequence of method invocations that violates the
* contract of an object, the object may throw this exception. For
* example, if a thread modifies a collection directly while it is
* iterating over the collection with a fail-fast iterator, the iterator
* will throw this exception.
html<div class="pop"> <div class="arrow"></div></div>css/*气泡*/.pop { position:relative; width:100px; height:35px; line-height: 33px; /*background:#fff;*/ color:#fff; border-radius:5px; /* 圆角 */ background-col_css实现聊天气泡
Java语言中,为各种变量、方法和类等起的名字称为标识符Java标识符的命名规则:应以字母、下划线、美元符开头后跟字母、下划线、美元符或数字Java标识符大小写敏感,长度无限制1.java中能用作标识符的有:26个英文字母(大、小写),数字,下划线,美元符号$。但是不能以数字开头。2.类名首个字母必须大写,多个单词组成的,每个单词首字母都要大写。3.方法名一般首个字母小写(构造方法例外),多个单词..._变量名的定义中,符合 java标识符命名要求
基于paddleslim对检测模型MobileNet-YOLOv3进行剪枝训练
#系统环境:CentOS6.5 x64#首先安装jdk7u80mkdir /javatar -zxvf jdk-7u80-linux-x64.gz -C /java/vim /etc/profile#添加以下变量,并用export宣告给所有子shell。JAVA_HOME=/java/jdk1.7.0_80/JAVA_BIN=/java/jdk1.7.0_80/binPATH=$PATH..._linux python3.6安装opencve
MIT课程:missing semester工具课的意义具体内容体会之前在油管看到推荐了MIT的一个课程 Missing Semester,本来以为就是普通的课程视频,但是刷了两节之后发现这课还是有它的独特之处,所以在这里整理、放上一些我的理解。这门课程不是那种一个学期的大课,它只是MIT寒假小学期的,一门由三个博士生讲授的一门 工具课,讲一些对计算机相关学生来说非常有用的工具和服务,总的来说,能让你的工作流更加高效。如果充分应用这门课,可以让你在操作上更像一个专业人士。这是课程页面地址: https_missing semester
前言 前脚刚进行完一次最让我失望的自考,后脚就要投身于软考。对于团队学习,我第一次选择了不相信,觉得其中一定是有什么出现了问题,但是我目前的主要矛盾不是解决目前团队学习的弊病,所以我选择独自思考学习方法上的缺憾和独自完成准备软考的朝圣。 穷则独善其身,达则兼济天下。 经过了这次学习的朝圣,感觉自己找到了些适合自己的学习方...
跟我一起学extjs5(01--开发的总体说明) 我之前使用extjs4+java spring MVC架构了一套“模块常规功能自定义的系统”(博客详见点击打开链接),该系统中详细讲解了设计思想,但并未有实现过程。由于extjs5的发布,并有许多新特性,而我对原系统正有重构和加入新功能的想法,因此决定对该系统进行升级,并将升级的开发过程写成博客。
JS中六种数据类型:String、Number、Boolean、NULL、Undefined、Object,前五种是基本数据类型,最后一种是引用数据类型。 可以使用typeof检查变量类型: let a=5;typeof a; // Number Number类型中的一些特殊值: // MAX_VALUE:JS中可以表示的数字的最大值,如果数字超过最大值,则会返回Infi...
脑电植入:治疗抑郁症的新方法?重磅!UCSF研究人员成功治疗一例重度抑郁症患者
今天写一个开头,明天再上传相关的_utf8转gb2312 工具
问题:应用快速排序方法对一个记录序列进行生序排序(运用分治法)策略:请读者先了解快速排序的基本概念(《数据结构》或百度百科)如下所示是一个快速排序的完整例子:(化成中间一个数,左边的比他小,右边的比他大)23 13 35 6 19 50 28[19 13 6] 23 [35 50 28][6 13] 19 23 [28] 35 [50]6 [13] 19 23 28 35
ubuntu 系统默认已安装ufw.(以下内容引自:http://www.jmhdtv.com/post/198.html)1.安装sudo apt-get install ufw2.启用sudo ufw enablesudo ufw default deny运行以上两条命令后,开启了防火墙,并在系统启动时自动开启。关闭所有外部对本机的访问,但本机访_sudo ufw limit ssh/tcp