重申一下List的基本要求:设计一个数据容器,支持随机或者顺序增删改查。
现在我们用数组的形式来实现一版吧。
public class MyArrayList{
/**实际存放数据的地方*/
private Object[] elementData;
/**实际大小*/
private int size;
public MyArrayList() {
/**暂时取默认大小的数组*/
this.elementData = new Object[1<<5];
this.size = 0;
}
public boolean add(Object e) {
elementData[size ++] = e;
return true;
}
/**简单实现删除第一个*/
public boolean remove(Object o) {
int index = indexOf(o);
if(index != -1){
return remove(index) != null;
}else{
return false;
}
}
public Object get(int index) {
return elementData[index];
}
public Object set(int index, Object element) {
Object oldValue = elementData[index];
elementData[index] = element;
return oldValue;
}
public void add(int index, Object element) {
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
public Object remove(int index) {
Object oldValue = elementData[index];
int numMoved = size - index - 1;
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; //friendly for gc
return oldValue;
}
public int indexOf(Object o) {
for(int i=0;i< size;i++){
if(o.equals(elementData[i])) return i;
}
return -1;
}
}
看得出,这是一个及其简化的实现方式,但是一定程度上已经满足了我们对于List的需求。如果仅仅是停留在这个层面的List,那么就和Array有什么不同呢,那么让我们对这个最初版本的Arraylist进行一些优化,做一些java原生数组不能支持的功能。
1.优化点一:支持边界检测
2.优化点二:支持自动扩容
....
稍作修改,抄了两个Jdk的边界检查,自己简单实现了个简化版扩容,代码如下
package collection;
import java.util.Arrays;
import java.util.Collection;
public class MyList {
/** 实际存放数据的地方 */
private Object[] elementData;
/** 实际大小 */
private int size;
public MyList() {
/** 暂时取默认大小的数组 */
this.elementData = new Object[1 << 1];
this.size = 0;
}
public boolean add(Object e) {
checkForGrow();
elementData[size++] = e;
return true;
}
/** 简单实现删除第一个 */
public boolean remove(Object o) {
int index = indexOf(o);
if (index != -1) {
return remove(index) != null;
} else {
return false;
}
}
public Object get(int index) {
rangeCheck(index);
return elementData[index];
}
public Object set(int index, Object element) {
rangeCheck(index);
Object oldValue = elementData[index];
elementData[index] = element;
return oldValue;
}
public void add(int index, Object element) {
rangeCheckForAdd(index);
checkForGrow();
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
public Object remove(int index) {
rangeCheck(index);
Object oldValue = elementData[index];
int numMoved = size - index - 1;
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
elementData[--size] = null;
return oldValue;
}
public int indexOf(Object o) {
for (int i = 0; i < size; i++) {
if (o.equals(elementData[i]))
return i;
}
return -1;
}
// 增加边界检测
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("out of range.by index = " + index);
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("out of range.by index = " + index);
}
// 增加自动扩容
private void checkForGrow() {
// 1.设定扩容条件 设定当前容量已被使用一半及以上时触发扩容
if (size << 1 >= elementData.length) {
// 2.扩容方案:double size..
int oldCapacity = elementData.length;
int newCapacity = oldCapacity << 1;
// 其实需要对newCapacity做一些校验,比如是否超过最多容量限制等..这里偷懒省略了
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
// 裁剪多余的空内容,可以专门设计一个容量自动缩减的方法,比如当容量不足一半时自动缩减一半
// 请大家自行实现,顺便思考性会有什么问题..
public void trimToSize() {
if (size < elementData.length) {
elementData = (size == 0) ? new Object[0] : Arrays.copyOf(elementData, size);
}
}
// 设置内容视图,且不允许内容篡改
public UnmodifiableList view() {
return new UnmodifiableList(this);
}
static class UnmodifiableList {
private static final long serialVersionUID = -283967356065247728L;
final MyList list;
UnmodifiableList(MyList list) {
this.list = list;
}
public boolean equals(Object o) {
return o == this || list.equals(o);
}
public int hashCode() {
return list.hashCode();
}
public Object get(int index) {
return list.get(index);
}
public Object set(int index, Object element) {
throw new UnsupportedOperationException();
}
public void add(int index, Object element) {
throw new UnsupportedOperationException();
}
public Object remove(int index) {
throw new UnsupportedOperationException();
}
public int indexOf(Object o) {
return list.indexOf(o);
}
public boolean addAll(int index, Collection extends Object> c) {
throw new UnsupportedOperationException();
}
}
public static void main(String[] args) {
MyList list = new MyList();
list.add("000");
list.add("111");
list.add("222");
list.add("333");
list.add("444");
list.add("555");
list.add("666");
System.out.println(list.size);
System.out.println(list.get(3));
System.out.println(list.remove("444"));
System.out.println(list.remove("999"));
list.add(4, "4444");
System.out.println(list.get(4));
System.out.println(Arrays.toString(list.elementData));
}
}
关于扩容和减容不建议采取同样的判定方案,当发生抖动的时候,元素+1 -1+1-1时会发生频繁copy造成不必要的开销。
JDK的扩容方案是每次扩容1.5倍,这种在原来长度的基数上的倍数扩容可以有效的动态变化每次增加的容量,至于为什么是1.5估计是多次实验测试后的一个经验优化值吧
目前来看,我们的Arraylist已经小鸟虽小,五脏俱全了。。还有个线程安全的问题,JDK通过记录modcount简单的避免了一部分遍历时的modify操作,但依然还没有解决并发的安全,对于arraylist在javadoc也没有承诺threadsave,事实上他确实也是不安全了