您的位置:首页 > 编程语言 > Java开发

Java ArrayList 源码分析与提高性能替代方案

2017-09-25 17:04 453 查看
自娱自乐,不喜莫喷。目前还是大三狗,错误很多,望大神指正。


    看同学java一用到list全部是ArrayList,但是ArrayList真的万能吗?顾名思义Array==数组,添加,删除肯定要有扩张,收缩操作,会拖慢性能,对于常读的自然没关系,但是有时做信息缓冲区时,添加、删除频繁,ArrayList肯定不好。

先看结果:

不带index的添加:新建ArrayList时数组大小为0;第一次添加时新建一个大小为10的数组,以后不够用时,每次扩大一半,即:10,15,22。。。每逢这些大小,就重建数组然后把源数据拷过来,这是非常不符合缓冲区的要求的。

带index的添加:数组空间不够时即:数组大小为[u]10,15,22。。。时先[/u]做一次扩张操作,然后再拷贝一次以完成在目标位置添加,拷贝操作最少一次,最多两次。。。喵喵喵???





java.util.concurrent下有许多集合类,你们可以自己看看,我找到的比较好用的:

ArrayList:数组实现,随机查找,随机读操作非常快,写。。。
LinkedList:链表实现,可以快速增删,顺序读取,缺点时随机读取慢,得遍历一遍,单线程时做缓冲区最适合不过

HashSet:没有重复的无序集合(插入重复元素不会理你)
TreeSet:有序集合(红黑树实现)
HashMap:最常用的map类: transient Node<K,V>[] table;  也是数组实现
linkedHashMap:顾名思义,HashMap的链表实现
weakHashMap:不会增加对象的引用计数,所以gc回收时不会考虑它里面的引用
多线程:java.util.concurrent包下有许多多线程支持的工具,不需要自己加锁来控制哦,自带锁机制:
ConcurrentLinkedQueue:多线程安全的 无边界 非阻塞 队列,链表实现,多线程缓冲区首选

ConcurrentSkipListMap:多线程安全的散列表

下面是ArrayList源码分析:

我们最常用的无参构造器:

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}


private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


可以看出真实数据elementData只指向一个已经定义好的是一个空数组,第一次添加时肯定要重新生成array;

最常用的add第一步调用ensureCapacityInternal(size + 1),确保数组有足够的空间:

public boolean add(E e) {
ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;
return true;
}


我们看看ensureCapacityInternal函数:

private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}

可以看出正如我们之前所想如果elementData是默认的话,就新建一个数组,数组大小为默认大小和所需扩张后大小之间去最大值

默认大小为:

private static final int DEFAULT_CAPACITY = 10;

即10,所以无参ArrayList构造时数组大小为0,第一次添加扩张为10

然后看扩张函数:

private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}


protected transient int modCount = 0;


用transient声明的实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。

modcount只是用来记录数组变化次数的,当它的数据出错时会抛出错误,不过我们不关注,重点是grow函数:

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}

好,这就是我们要找的扩张函数最终实现了:

oldCapacity >> 1 :基本等于oldCapacity/2 取整数部分

所以每次扩张时大小为原来的3/2,即增大一半;

如果满足需求就这样定了,如果不满足,就按需求来;

最后ArrayList最大约束:如果 小,就把原来的数组烤进新数组,最大约束为:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

即:int的最大值减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;
}

哇,欺骗我感情,我还以为会用两个数组之类的重实现,结果就再加8位???喵喵喵???

so,我们得到结果:

添加时,第一次添加新建一个大小为10的数组,以后不够用时,每次扩大一半,即:10,15,22。。。

每逢这些大小,就重建数组然后把源数据拷过来,这是非常不符合缓冲区的要求的。

而带index的添加:

public void add(int index, E element) {
rangeCheckForAdd(index);

ensureCapacityInternal(size + 1);  // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}


可见不够时做一次扩张操作,然后再拷贝一次,

拷贝操作最少一次,最多两次。。。喵喵喵???

删除就不讲了,和带index的插入差不多

所以当我们把需要一个缓冲区时得用什么呢?

java.util.concurrent下有许多集合类,你们可以自己看看,我找到的比较好用的:

ArrayList:数组实现,随机查找,随机读操作非常快,写。。。

LinkedList:链表实现,可以快速增删,顺序读取,缺点时随机读取慢,得遍历一遍,单线程时做缓冲区最适合不过

HashSet:没有重复的无序集合(插入重复元素不会理你)

TreeSet:有序集合(红黑树实现)

HashMap:最常用的map类: transient Node<K,V>[] table;  也是数组实现

linkedHashMap:顾名思义,HashMap的链表实现

weakHashMap:不会增加对象的引用计数,所以gc回收时不会考虑它里面的引用

多线程:java.util.concurrent包下有许多多线程支持的工具,不需要自己加锁来控制哦,自带锁机制:

ConcurrentLinkedQueue:多线程安全的 无边界 非阻塞 队列,链表实现

ConcurrentSkipListMap:多线程安全的散列表

版权声明:本文为博主原创文章,转载请注明来源:http://blog.csdn.net/qq_33213364
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息