import java.util.Arrays;
/**
* 模拟ArrayList的实现
* @author zhidanfeng
* @date Sep 25, 2013 2:38:17 PM
* @packName com.zhi.test1
* @param <T>
*/
@SuppressWarnings("unchecked")
public class SequenceList<T> {
private final int DEFAULT_SIZE = 6;
// 保存数组的长度
private int capacity;
// 定义一个数组用于保存顺序线性表的元素
private Object[] elementData;
// 保存顺序表中元素的当前个数
private int size = 0;
/**
* 以默认长度创建空顺序线性表
*/
public SequenceList() {
capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
}
/**
* 以一个初始化元素来创建顺序线性表
* @param element
*/
public SequenceList(T element) {
this();
elementData[0] = element;
size++;
}
/**
* 以指定长度的数组来创建顺序线性表
* @param element 指定顺序线性表中第一个元素
* @param initSize 指定顺序线性表底层数组的长度
*/
public SequenceList(T element, int initSize) {
capacity = 1;
// 把capacity大小设定为指定长度的最小的2的n次方
while(capacity < initSize) {
capacity <<= 1;
}
elementData = new Object[capacity];
elementData[0] = element;
size++;
}
/**
* 获取顺序线性表的长度
* @return
*/
public int length() {
return size;
}
/**
* 获取指定索引为i处的元素
* @param i 索引位置
* @return
*/
public T get(int i) {
if(i < 0 || i > size) {
throw new IndexOutOfBoundsException("线性表索引越界");
}
return (T) elementData[i];
}
/**
* 查询指定元素在顺序线性表中位置
* @param element 要查询的指定元素
* @return
*/
public int locate(T element) {
for(int i = 0; i < size; i++) {
if(elementData[i].equals(element)) {
return i;
}
}
return -1;
}
/**
* 在指定索引index处插入指定元素element
* @param element 要插入的指定元素
* @param index 要插入的索引位置
*/
public void insert(T element, int index) {
// 1. 判断插入索引的正确性
if(index < 0 || index > size) {
throw new IndexOutOfBoundsException("线性表索引越界");
}
// 2.
ensureCapacity(size + 1);
// 3. 将指定索引出后面的元素集体向后移动一格
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 4. 将指定元素插入指定索引index处
elementData[index] = element;
// 5. 将顺序线性表长度+1
size++;
}
/**
* 性能很差
* @param minCapacity
*/
public void ensureCapacity(int minCapacity) {
// 如果数组的原有长度小于目前所需的长度,就必须增大所谓的数组长度
// 但是我们知道数组一旦创建,长度就不可更改,那么只能重新创建一个更大容量的数组
// 然后将原来的数组拷贝过去
if(minCapacity > capacity) {
while(capacity < minCapacity) {
// 把capacity大小设定为指定长度的最小的2的n次方
capacity <<= 1;
}
// 将旧数组拷贝至一个容量更大的新数组中
elementData = Arrays.copyOf(elementData, capacity);
}
}
/**
* 在线性表的开始出添加一个元素
* @param element
*/
public void add(T element) {
insert(element, size);
}
/**
* 删除指定索引处的元素
* @param index
* @return
*/
public T delete(int index) {
if(index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("线性表索引越界");
}
// 获取要删除的元素
T oldValue = (T) elementData[index];
// 要移动的元素个数
int numMoved = size - index - 1;
// 当移动的元素大于0才要移动,否则的话就是删除最后一个元素,当然不用移动
if(numMoved > 0) {
// src:被复制的数组 srcPos:从第几个元素开始复制
// dest:要复制到的数组 destPos:从第几个元素开始粘贴l
// ength:一共需要复制的元素个数
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
// 清空最后一个元素,否则的话只是不将引用指向原有的元素对象,
// 原有的对象仍然占用了空间,得不到释放
elementData[--size] = null;
return oldValue;
}
/**
* 移除顺序线性表最后一个元素
* @return
*/
public T remove() {
return delete(size - 1);
}
/**
* 判断顺序线性表是否为空
* @return
*/
public boolean empty() {
return size == 0;
}
/**
* 清空线性表
*/
public void clear() {
Arrays.fill(elementData, null);
size = 0;
}
public String toString() {
// 空顺序线性表用[]表示
if(size == 0) {
return "[]";
}
else {
// 我们输出的最终格式为[xx, xx, xx]
StringBuilder builder = new StringBuilder("[");
for (int i = 0; i < size; i++) {
builder.append(elementData[i].toString() + ", ");
}
int len = builder.length();
// 去除最后面的", ",并添加"]"封口
return builder.delete(len - 2, len).append("]").toString();
}
}
}
创建测试类:SequenceListTest.java
public class SequenceListTest {
public static void main(String[] args) {
SequenceList<String> list = new SequenceList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.insert("dddd", 1);
System.out.println(list);
list.delete(2);
System.out.println(list);
System.out.println("ccc在list中的位置为:" + list.locate("ccc"));
}
}
测试结果:
[aaa, dddd, bbb, ccc]
[aaa, dddd, ccc]
ccc在list中的位置为:2
最后再介绍一下ensureCapacity这个方法的想法(从网上看到的):
我们知道ArrayList的内部是采用数组来存储元素的,由于java数组都是定长的,所以这个数组的大小一定是固定的,这个大小就是capacity。我们可以肯定capacity一定是大于或等于ArrayList的size,那么当size不断增加到了要超过capacity的时候,ArrayList就不得不重新创建新的capacity来容纳更多的元素,这时需要首先建立一个更长的数组,将原来的数组中的元素复制到新数组中,再删除原来的数组。可见当ArrayList越来越大时,这种操作的消耗也是越来越大的。
为了减少这种不必要的重建capacity的操作,当我们能肯定ArrayList大致有多大(或者至少会有多大)时,我们可以先让ArrayList把capacity设为我们期望的大小,以避免多余的数组重建。
假设ArrayList自动把capacity设为10,每次重建时将长度递增原来的三分之二,那么当我们需要大约存储50个元素到ArrayList中时,就会大约需要重建数组4次,分别是在增加第11、第17、第26、第39个元素的时候进行的。如果我们一开始就让ArrayList的capacity为50,那么不需要任何数组重建就能完成所有插入操作了。
java允许我们在构造ArrayList的同时指定capacity,如new ArrayList(50),也允许在以后将它设得更大,而增大capacity就是使用ensureCapacity()方法。注意:capacity只能比原来的更大,而不能比原来的更小,否则java会忽略该操作。ArrayList的初始默认capacity为10,所以给capacity指定小于10的整数是毫无意义的。
最后说说ArrayList的size,前面说过,size一定小于等于capactiy,而且更重要的是,访问超过size的位置将抛出异常,尽管这个位置可能没有超过capacity。ensureCapacity()只可能增加capacity,而不会对size有任何影响。要增加size,只能用add()方法。