Java数据结构-------List
三种List:ArrayList,Vector,LinkedList
类继承关系图
ArrayList和Vector通过数组实现,几乎使用了相同的算法;区别是ArrayList不是线程安全的,Vector绝大多数方法做了线程同步。
LinkedList通过双向链表实现。
源代码分析
1、添加元素到列表尾端(Appends the specified element to the end of this list.)
ArrayList:当所需容量超过当前ArrayList的大小时,需要进行扩容,对性能有一定的影响。
优化策略:在能有效评估ArrayList数组初始值大小的情况下,指定其容量大小有助于性能提升,避免频繁的扩容。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! 确保内部数组有足够的空间
elementData[size++] = e; //将元素放在数组尾部
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //如果数组为空数组,取初始容量和minCapacity中的最大值,初始容量DEFAULT_CAPACITY = 10
} ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //被修改次数,iterator成员变量expectedModCount为创建时的modCount的值,用来判断list是否在迭代过程中被修改 // 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); //旧容量的1.5倍。二进制右移一位差不多相当于十进制除以2,对CPU来说,右移比除运算速度更快。如果oldCapacity为偶数,newCapacity为1.5*oldCapacity,否则为1.5*oldCapacity-1。
if (newCapacity - minCapacity < 0) //如果计算出的容量不够用,就使用minCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //如果计算出的容量大于MAX_ARRAY_SIZE=Integer.MAX_VALUE-8,
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//调用System.arraycopy方法复制数组
}
//判断是否大于数组最大值Integer.MAX_VALUE,疑问:设置MAX_ARRAY_SIZE=Integer.MAX_VALUE-8的意义是什么?
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
//
}
LinkedList:每次新增元素都需要new一个Node对象,并进行更多的赋值操作。在频繁的调用中,对性能会产生一定的影响。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null); //每增加一个节点,都需要new一个Node
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
2、在列表任意位置添加元素
ArrayList:基于数组实现,数组需要一组连续的内存空间,如果在任意位置插入元素,那么该位置之后的元素需要重新排列,效率低。
public void add(int index, E element) {
rangeCheckForAdd(index);//检查索引是否越界 ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);//每次操作都会进行数组复制,System.arraycopy可以实现数组自身的复制
elementData[index] = element;
size++;
} private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
LinkedList:先找到指定位置的元素,然后在该元素之前插入元素。在首尾插入元素,性能较高;在中间位置插入,性能较低。
//在列表指定位置添加元素
public void add(int index, E element) {
checkPositionIndex(index);//检查索引是否越界 if (index == size) //index为列表大小,相当于在列表尾部添加元素
linkLast(element);
else
linkBefore(element, node(index));
} //返回指定索引的元素,在首尾查找速度快,在中间位置查找速度较慢,需要遍历列表的一半元素。
Node<E> node(int index) {
// assert isElementIndex(index); if (index < (size >> 1)) { //如果index在列表的前半部分,从头结点开始向后遍历
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { //如果index在列表的后半部分,从尾结点开始向前遍历
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
} //在指定节点succ之前添加元素
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null) //只有succ一个节点
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
3、删除任意位置元素
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);//将删除位置后面的元素往前移动一位
elementData[--size] = null; // clear to let GC do its work 最后一个位置设置为null return oldValue;
}
LinkedList:先通过循环找到要删除的元素,然后删除该元素。删除首尾的元素,效率较高;删除中间元素,效率较差。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
} E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev; if (prev == null) { //x为第一个元素
first = next;
} else {
prev.next = next;
x.prev = null;
} if (next == null) { //x为最后一个元素
last = prev;
} else {
next.prev = prev;
x.next = null;
} x.item = null;
size--;
modCount++;
return element;
}
4、遍历列表
三种遍历方式:foreach,迭代器,for遍历随机访问。
foreach的内部实现也是使用迭代器进行遍历,但由于foreach存在多余的赋值操作,比直接使用迭代器稍慢,影响不大。for遍历随机访问对ArrayList性能较好,对LinkedList是灾难性的。
并发List
Vector和CopyOnWriteArrayList是线程安全的实现;
ArrayList不是线程安全的,可通过Collections.synchronizedList(list)进行包装。
CopyOnWriteArrayList,读操作不需要加锁,
1、读操作
CopyOnWriteArrayList:读操作没有锁操作
public E get(int index) {
return get(getArray(), index);
} final Object[] getArray() {
return array;
} private E get(Object[] a, int index) {
return (E) a[index];
}
Vector:读操作需要加对象锁,高并发情况下,锁竞争影响性能。
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index); return elementData(index);
}
2、写操作
CopyOnWriteArrayList:需要加锁且每次写操作都需要进行一次数组复制,性能较差。
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();
}
}
Vector:和读一样需要加对象锁,相对CopyOnWriteArrayList来说不需要复制,写性能比CopyOnWriteArrayList要高。
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1); //确认是否需要扩容
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
总结:在读多写少的高并发应用中,适合使用CopyOnWriteArrayList;在读少写多的高并发应用中,Vector更适合。
Java数据结构-------List的更多相关文章
- Java数据结构之队列的实现以及队列的应用之----简单生产者消费者应用
Java数据结构之---Queue队列 队列(简称作队,Queue)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在 ...
- JAVA数据结构系列 栈
java数据结构系列之栈 手写栈 1.利用链表做出栈,因为栈的特殊,插入删除操作都是在栈顶进行,链表不用担心栈的长度,所以链表再合适不过了,非常好用,不过它在插入和删除元素的时候,速度比数组栈慢,因为 ...
- Java数据结构之树和二叉树(2)
从这里始将要继续进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来 ...
- Java数据结构之树和二叉树
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
- Java数据结构之线性表(2)
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
- Java数据结构之线性表
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
- java 数据结构 图
以下内容主要来自大话数据结构之中,部分内容参考互联网中其他前辈的博客,主要是在自己理解的基础上进行记录. 图的定义 图是由顶点的有穷非空集合和顶点之间边的集合组成,通过表示为G(V,E),其中,G标示 ...
- java 数据结构 队列的实现
java 数据结构队列的代码实现,可以简单的进行入队列和出队列的操作 /** * java数据结构之队列的实现 * 2016/4/27 **/ package cn.Link; import java ...
- java 数据结构 栈的实现
java数据结构之栈的实现,可是入栈,出栈操作: /** * java数据结构之栈的实现 * 2016/4/26 **/ package cn.Link; public class Stack{ No ...
- Java数据结构和算法
首先,本人自学java,但是只学习了java的基础知识,所以想接下来学习一下数据结构和算法,但是找了很多教材,大部分写的好的都是用c语言实现的,虽然知道数据结构和算法,跟什么语言实现的没有关系,但是我 ...
随机推荐
- 博学谷-数据分析matplotlib
博学谷-数据分析 python数学学科的基础 机器学习课程的基础 1.1 介绍 1.2 jupyter和conda 1.3 matplotlib from matplotlib import pypl ...
- JavaScript 遍历对象查找指定的值并返回路径
问:JavaScript 如何查找对象中某个 value 并返回路径上所有的 key? let obj = { key1: 'str1', key2: { key3: 'str3' }, key4: ...
- Spring @Transactional 浅谈
一般当我们在一个方法里面操作多个数据对象的可持久化操作时,我们通常这些操作能够成功一起事务提交成功.默认情况下,数据库处于自动提交模式.每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成 ...
- JAVA 同步实现原理
Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个: 确保线程互斥的访问同步代码 保 ...
- javascript sprintf方法
转载自: http://demon.tw/programming/javascript-sprintf.html function str_repeat(i, m) { for (var o = [] ...
- 震惊!几道Python 理论面试题,Python面试题No18
本面试题题库,由公号:非本科程序员 整理发布 第1题: 简述解释型和编译型编程语言? 解释型语言编写的程序不需要编译,在执行的时候,专门有一个解释器能够将VB语言翻译成机器语言,每个语句都是执行的时候 ...
- TCP/IP网络编程之基于UDP的服务端/客户端
理解UDP 在之前学习TCP的过程中,我们还了解了TCP/IP协议栈.在四层TCP/IP模型中,传输层分为TCP和UDP这两种.数据交换过程可以分为通过TCP套接字完成的TCP方式和通过UDP套接字完 ...
- 通过Gradle Plugin实现Git Hooks检测机制
背景 项目组多人协作进行项目开发时,经常遇到如下情况:如Git Commit信息混乱,又如提交者信息用了自己非公司的私人邮箱等等.因此,有必要在Git操作过程中的适当时间点上,进行必要的如统一规范.安 ...
- Falsk
flask: 1.配置文件的几种方式: 1.app.config['DEBUG'] =True 2.app.config.from_pyfile("setting.py") 3.a ...
- c#每循环100次提交一次数据,最后一次不足100次提交一次
StringBuilder sb=new StringBuilder(); string strId=dataGridView1.Rows[dataGridView1.CurrentRow.Index ...