您的位置:首页 > 其它

集合学习--ArrayList 源码学习最终版

2016-08-14 15:09 302 查看

List

ArrayList

扩容原理

源码

/**
* The maximum size of array to allocate. 数组可以分配的最大空间
* Some VMs reserve some header words in an array. 一些虚拟机会保存一些头文字在数组中
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit 试图分配最大空间将会导致内存溢出(因为需要的数组容量大于了虚拟机的限制)
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Default initial capacity. 初始容量
*/
private static final int DEFAULT_CAPACITY = 10;

/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure(确保) that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param   minCapacity   the desired minimum capacity  预期需要最小的容量
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != EMPTY_ELEMENTDATA)
// any size if real element table
? 0
// larger than default for empty table. It's already supposed to be
// at default size.
: DEFAULT_CAPACITY;

if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
/**
*         int newCapacity = oldCapacity + (oldCapacity >> 1);
*  新容量为原来的1.5倍
*/
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);
}
/**
*
*/
private static int hugeCapacity(int minCapacity) {
//如果因为二进制的移位而导致溢出变为负数
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)
//返回Interger的最大值0x7fffffff(2147483647)
//否则返回MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}


2017.03.05 新增

ArrayList新增元素

ArrayList的初始容量为10

那么在添加10个元素之后,再添加第11个元素的时候会进行依次扩容

Created with Raphaël 2.1.01.client添加一个元素2.如果当前的容量小与增加完元素的所需容量3.增加之1.5*old容量,并拷贝至当前新建的数组4.添加成功yesno

所涉及到的源码

//客户端调用的add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}


//上面函数的第一步,确保容量可以容纳下新加的元素
private void ensureCapacityInternal(int minCapacity) {
//修改次数加1,因为是添加,所以修改次数+1
modCount++;
// 如果添加这个元素会导致数组溢出
if (minCapacity - elementData.length > 0)
grow(minCapacity);//增加最小的容量
}


//grow()
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);  //1.5倍
if (newCapacity - minCapacity < 0) //防止溢出int型,溢出之后是负数
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //如果新的容量大于数组系统默认容量
newCapacity = hugeCapacity(minCapacity);
// 拷贝原来的数组至新开辟空间的数组内,是一个native方法
elementData = Arrays.copyOf(elementData, newCapacity);
}


//hugeCapacity
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // int型溢出
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;//int类型和数组系统默认容量取最大
}


//java中默认最大为(2^31-1)-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


set方法

set方法设置固定下标位置为指定的元素。

public E set(int index, E e) {
//判断下标是否在数组范围内,如果不在 会抛出下标溢出边界异常IndexOutOfBoundsException();
rangeCheck(index);
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}


像是remove这种方法

这是种什么方法呢

remove(int index)

remove(Object o)

既能根据下标删除,又能根据元素删除,乍一看没什么关系吧,

仔细想一想好像没什么问题

但是int与Integer有什么不一样。。。

如果是删除Integer方法必须要手动包装int

根据对象删除的时候只会删除第一个

public boolean remove(Object o) {
//如果是null 不能调用equals方法,如果是对象可以调用equals方法
//先找出下标再删除
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;
}

//fastremove与根据下标删除,与下面的方法仅仅少了一步rangeCheck 不知为何不使用此方法。
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; // Let gc do its work
}


根据下标删除

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);//调用native方法通过复制内存块来复制数组
elementData[--size] = null; // 手动设置gc

return oldValue;
}


2017.12.16 新增

部分注意项

方法注意点
扩容1.5倍,使用system.arraycopy(),是一个native方法,通过复制内存空间的方式来实现数组拷贝

特殊问题

复制某个ArrayList到另一个ArrayList

使用ArrayList构造方法,比如:ArrayList myObject = new ArrayList(myTempObject)浅拷贝;

使用Collection的copy方法,通过迭代器的方式一个一个的设值,浅拷贝

序列化和反序列化,深拷贝

public class ArrayListCopyTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User u1 = new User("user1", 12);
User u2 = new User("user2", 13);
User u3 = new User("user3", 14);
User u4 = new User("user4", 15);
User u5 = new User("user5", 15);
User u6 = new User("user6", 15);
User u7 = new User("user7", 15);
User u8 = new User("user8", 15);

List<User> userSrc = new ArrayList<>();
userSrc.add(u1);
userSrc.add(u2);
userSrc.add(u3);
userSrc.add(u4);

List<User> userDest = new ArrayList<>(4);
userDest.add(u5);
userDest.add(u6);
userDest.add(u7);
userDest.add(u8);

// 浅拷贝
Collections.copy(userDest, userSrc);

System.out.println(userDest.get(0) == u1);
u1.setName("new_user1");
System.out.println(userDest.get(0).getName());
System.out.println(userSrc.get(0).getName());
for (User user : userDest) {
System.out.println(user.getName());
}

// 浅拷贝2
List<User> userDest2 = new ArrayList<>(userSrc);
System.out.println(userDest2.get(0) == userSrc.get(0));

// 序列化方式 拷贝,这只是jdk的一种格式 还有很多种,比如json/ protobuff
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bos);
os.writeObject(userSrc);
os.flush();
os.close();
byte[] userSrcByte = bos.toByteArray();

ByteArrayInputStream bis = new ByteArrayInputStream(userSrcByte);
ObjectInputStream is = new ObjectInputStream(bis);
List<User> userDest3 = (List<User>) is.readObject();
System.out.println(userDest3.get(0));
System.out.println(userDest3.get(0).getName());
System.out.println(userDest3.get(0) == userSrc.get(0));
bos.close();

}
}


当传递ArrayList到某个方法中,或者某个方法返回ArrayList,什么时候要考虑安全隐患?如何修复安全违规这个问题呢?

首先要判空

复制一个新数组的备份,方式原来的数组中的引用改变导致当前的线程安全。

与linkedList的区别

随机读写测试

public class ListCompare {

public static final int N = 10000000;

public static void main(String[] args) {
Random random = new Random();

List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < N; i++) {
arrayList.add(1);
}
for (int i = 0; i < N; i++) {
linkedList.add(1);

}
System.out.println("初始化ArrayList与LinkList成功");
long begin1 = System.currentTimeMillis();

randomGetAndSet(random, arrayList);

long end1 = System.currentTimeMillis();
System.out.println("ArrayList: " + (end1 - begin1) + " ms");
long begin2 = System.currentTimeMillis();

randomGetAndSet(random, linkedList);

long end2 = System.currentTimeMillis();
System.out.println("LinkedList: " + (end2 - begin2) + " ms");
}

private static void randomGetAndSet(Random random, List<Integer> list) {
for (int i = 0; i < N; i++) {
if (i % 10000 == 0) {
System.out.println("--------------我还活着-----------------");
}
if (random.nextBoolean()) {
list.set(random.nextInt(N), 10);
} else {
list.get(random.nextInt(N));
}
}
}

}


顺序读写的情况下,明显是ArrayList快,因为相对于O(1)的读取速度,和每次都是O(n)的读取速度。ArrayList的扩容导致的system.arraycopy()的性能可以忽略

如果是大数量顺序写的话LinkedList 明显要比ArrayList快,因为它不需要扩容。当然如果每一次都能预知到ArrayList的初始容量的话,复杂度也是一样的。

顺序读的话两种数据结构复杂度是O(n)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ArrayList 源码