java容器二:List接口实现类源码分析
一、ArrayList
1、存储结构
动态数组elementData
transient Object[] elementData;
除此之外还有一些数据
//默认初始容量
private static final int DEFAULT_CAPACITY = 10; //共享空数组
private static final Object[] EMPTY_ELEMENTDATA = {}; //默认初始空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //动态数组中对象数量
private int size;
EMPTY_ELEMENTDATA(下面简称EE)和DEFAULTCAPICITY_EMPTY+ELEMENTDATA(以下简称DEE)作用与区别
1、当用无参构造器构造一个ArrayList实例时,使用DEE
/**
* Constructs an empty list with an initial capacity of ten.
* 无参构造器:将DEE空数组传递给elementData
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2、当使用传入初始容量但是大小为0的构造器构造一个空ArrayList实例时,使用EE
/**
* Constructs an empty list with the specified initial capacity.
*有参构造器,判断传入的数组大小,若为有效数字且不为0则根据传递的值构造一个数组
* 若传入的为0,则将EE空数组给elementData
* 若传入的为无效数字,则抛出异常
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
3、区别:作用不同
DEE空数组是无参构造器中调用,后来增加第一个元素时扩容为默认大小10
EE是在构造出空数组(传入有参参数0)时调用,它的作用在于使得所有ArrayList空实例都指向同一个对象也就是EE
2、get/set方法
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
} public E set(int index, E element) {
Objects.checkIndex(index, size);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
3、add方法
(1)在尾部添加一个元素add(E e)
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
} private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
(2)在指定位置上添加一个元素add(int index,E e)
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
(3)grow方法:在两个add方法中都会在插入元素之前检查数组容量是否足够,若不足则需要扩容
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
} private Object[] grow() {
return grow(size + 1);
} private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//重点步骤
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
4、remove方法
(1)移除某一位置上的对象remove(int index)
public E remove(int index) {
Objects.checkIndex(index, size); 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 return oldValue;
}
(2)移除某一对象remove(Object o)
//遍历找到o的位置,然后移除,这里的fastRemove方法相对于上面的remove方法少了index验证的步骤,因为能够找到要被删除元素的index则index一定合理
public boolean remove(Object o) {
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;
}
5、序列化
ArrayList接口是实现了Serializable接口的,说明是可以序列化的
但是我们在前面已经看到保存对象集合的elementData用transient修饰,是不被序列化的
这是因为elementData为了后续扩容会保存一定的容量,为了避免序列化这部分内容,序列化就采取上诉的方式用readObject和writeObject方法来序列化
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff
s.defaultReadObject(); // Read in capacity
s.readInt(); // ignored if (size > 0) {
// like clone(), allocate array based upon size not capacity
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
Object[] elements = new Object[size]; // Read in all elements in the proper order.
for (int i = 0; i < size; i++) {
elements[i] = s.readObject();
} elementData = elements;
} else if (size == 0) {
elementData = EMPTY_ELEMENTDATA;
} else {
throw new java.io.InvalidObjectException("Invalid size: " + size);
}
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size); // Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
} if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
二、Vector
Vector和ArrayList操作基本相同,只不过对方法加了synchronized做同步处理,以add方法为例
//add1
public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount);
return true;
} //add2
public void add(int index, E element) {
insertElementAt(element, index);
} public synchronized void insertElementAt(E obj, int index) {
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
modCount++;
final int s = elementCount;
Object[] elementData = this.elementData;
if (s == elementData.length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = obj;
elementCount = s + 1;
}
//扩容重点步骤:确定扩容的大小
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity <= 0) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
三、LinkedList
1、存储结构
双向链表存储,有一个头结点first,一个尾节点last,每一个节点有两个指针,一个指向它的前驱节点,一个指向它的后继节点
/**
* Pointer to first node.
*/
transient Node<E> first; /**
* Pointer to last node.
*/
transient Node<E> last; private static class Node<E> {
E item;
Node<E> next;
Node<E> prev; Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2、get/set方法
public E get(int index) {
checkElementIndex(index);
return node(index).item;
} /**
* Returns the (non-null) Node at the specified element index.
* 用二分法简单判断一下对象在链表前半部分还是后半部分
* 决定是从first指针向后遍历还是last指针向前遍历
*/
Node<E> node(int index) {
// assert isElementIndex(index); if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
3、add方法
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
} /**
* Inserts element e before non-null Node 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)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
4、remove方法
/**
* Unlinks non-null last node l.
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
四、线程安全解决方案
ArrayList和LinkedList都是线程不安全的,因此若想要实现线程安全有一下替换的实现方案
方案一:用Collections.synchronizedList()方法构造出一个线程安全的list(ArrayList和LinkedList都可以使用)
List<String> synList1 = Collections.synchronizedList(new ArrayList<>());
List<String> synList2 = Collections.synchronizedList(new LinkedList<>());
方案二:用java..concurrent包下面的CopyOnWriteArrayList替代ArrayList
CopyOnWriteArrayList:重点是读写分离
1、原理:
写操作的时候会在一个复制的数组上进行,并且要加锁
读操作在原数组上进行
写操作完毕之后,读操作的数组指针指向新的复制数组
2、关键代码
写操作
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
} public E remove(int index) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
E oldValue = elementAt(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
}
}
读操作
public E get(int index) {
return elementAt(getArray(), index);
}
3、应用场景
因为CopyOnWriteArrayList允许读写同时进行,大大提高了读操作的性能,因此适用于读多写少的应用场景
4、缺点
(1)内存占用:有一个复制的写数组,因此内存占用增加了一倍
(2)数据不一致:读操作不能实时性得到写操作数据
因此不适合与内存敏感、实时性要求高的应用场景
方案三:用Vector替代ArrayList
五、ArrayList与Vector、LinkedList比较
1、ArrayList与Vector比较
(1)ArrayList是线程不安全的,因此读写效率高,Vector是线程安全的
(2)扩容时ArrayList扩大为原来的1.5倍,Vector扩大为原来的2倍
2、ArrayList与LinkedList比较
(1)ArrayList是用动态数组实现,LinkedList是用双向链表实现
(2)ArrayList支持随机访问,LinkedList不支持
(3)LinkedList插入删除更快
java容器二:List接口实现类源码分析的更多相关文章
- List 接口以及实现类和相关类源码分析
List 接口以及实现类和相关类源码分析 List接口分析 接口描述 用户可以对列表进行随机的读取(get),插入(add),删除(remove),修改(set),也可批量增加(addAll),删除( ...
- java 日志体系(四)log4j 源码分析
java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...
- Java的三种代理模式&完整源码分析
Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCache缓存的实现机制](https://www.c ...
- Java SPI机制实战详解及源码分析
背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...
- java中的==、equals()、hashCode()源码分析(转载)
在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. == java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...
- java中List接口的实现类 ArrayList,LinkedList,Vector 的区别 list实现类源码分析
java面试中经常被问到list常用的类以及内部实现机制,平时开发也经常用到list集合类,因此做一个源码级别的分析和比较之间的差异. 首先看一下List接口的的继承关系: list接口继承Colle ...
- Java Properties类源码分析
一.Properties类介绍 java.util.Properties继承自java.util.Hashtable,从jdk1.1版本开始,Properties的实现基本上就没有什么大的变动.从ht ...
- Java并发编程笔记之Unsafe类和LockSupport类源码分析
一.Unsafe类的源码分析 JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库. rt.jar ...
- java并发包——阻塞队列BlockingQueue及源码分析
一.摘要 BlockingQueue通常用于一个线程在生产对象,而另外一个线程在消费这些对象的场景,例如在线程池中,当运行的线程数目大于核心的线程数目时候,经常就会把新来的线程对象放到Blocking ...
随机推荐
- [LeetCode] 113. Path Sum II 路径和 II
Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given su ...
- [LeetCode] 242. Valid Anagram 验证变位词
Given two strings s and t , write a function to determine if t is an anagram of s. Example 1: Input: ...
- [LeetCode] 375. Guess Number Higher or Lower II 猜数字大小 II
We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to gues ...
- [计算机视觉][神经网络与深度学习]Faster R-CNN配置及其训练教程2
faster-rcnn分为matlab版本和python版本,首先记录弄python版本的环境搭建过程.matlab版本见另一篇:faster-rcnn(testing): ubuntu14.04+c ...
- ddl语法
创建表: create table 表名 ( 字段1 varchar2(32) not null primary key, 字段2 date not null ) tablespace 表空间名 事务 ...
- java lambda怎么表达式判断被调用接口名称和接口中方法
1.首先能够用于lambda表达式的只能是interface,并且interface 中只有一个方法. 这就说明,只要找到接口类型就能确定用的是哪个方法.(如下:intTypeInterface.St ...
- adb常用命令总结
针对移动端 Android 的测试, adb 命令是很重要的一个点,必须将常用的 adb 命令熟记于心, 将会为 Android 测试带来很大的方便,其中很多命令将会用于自动化测试的脚本当中. And ...
- 题解 P3957 【跳房子】
对于这题有一个不用单调队列并且不是玄学设置区间最大值的做法 这题校内模拟考的时候打二分+枚举,结果写炸了,跑过来看题解发现为什么他们的区间最大值都是 $ 1005 $ ???特别懵,其实我的代码在dp ...
- quartz2.3.0(一)您的第一个Quartz程序
任务类 package org.quartz.examples.example1; import java.util.Date; import org.slf4j.Logger; import org ...
- Windows服务创建及发布
二.创建Windows Service 1.新建一个Windows Service,并将项目名称改为“MyWindowsService”,如下图所示: 2.在解决方案资源管理器内将Service1.c ...