顺序表ArrayList

ArrayList

基本概念:
  1. 顺序表的底层是顺序存储结构, 也就是数组
  2. 顺序表的最直观理解是变长数组,动态数组
  3. 顺序表充分体现了封装与抽象之美
java 语言实现

ArrayList 需要实现下面的接口

/**顺序表的增删改查*/
public interface List<T>
{
    /** 第一个位置插入 */
    void addFirst(T data);

    /** 在最后一个位置插入 */
    void addLast(T data);

    /** 删除index对应位置的节点, 并返回该节点的数据 */
    T delete(int index);

    /** 在index 对应位置加入数据 */
    void add(int index, T data);

    /** 更改index对应位置的数据为data */
    void set(int index, T data);

    /** 查询index对应位置的数据 */
    T get(int index);

    /** 获取data元素第一次出现时候的index  */
    int getIndex(T data);

    /**遍历顺序表*/
    void traverse();
    
    /**获取顺序表大小*/
    int size();
}
成员变量与构造函数

ArrayList 底层维护着一个数组对象, 它的最大容量是capacity, size 用来表示ArrayList 插入元素的数目

package com.weinijuan;

public class ArrayList<T> implements List<T>
{
    private int size;
    private int capacity = 16;
    private T[] ar;
    public ArrayList()
    {
        ar = (T[]) new Object[capacity];
    }
}

实现addLast

这是ArrayList 类用的最多的方法, 乍一看貌似会数组下标访问越界, 实则不然, capacity才是数组真正的容量, size 是当前对象最后一个元素下标(index - 1)的后面一个位置的下标。

因为插入了一个元素, 所以size需要加一。

这段代码虽然简单,但几乎是ArrayList 最重要的方法

@Override
public void addLast(T data)
{
    ar[size] = data;
    size++;
}
实现扩容机制

上面的缺陷就是最多只能有16个元素,可以在size == capacity 的时候进行扩容.

扩容原理:

  1. 新创建一个更大容量的数组
  2. 将原先数组的内容拷贝到 新创建的数组中
    (System.arraycopy方法,第一个参数是被拷贝的数组, 第二个是开始拷贝的位置,
    第三个是被拷入的数组, 第四个是开始拷入的位置, 第五个是拷贝的个数 )
  3. 更新ar, 令ar 指向新数组
  4. 更新capacity

System.arraycopy 经常出现在ArrayList 中,需要掌握

private void resize(int newCapacity)
{
    T[] temp = (T[]) new Object[newCapacity];
    System.arraycopy(ar, 0, temp, 0, size);
    ar = temp;
    capacity = newCapacity;
}

@Override
public void addLast(T data)
{
    if (size == capacity)
    {
        resize(2*capacity);
    }
    ar[size] = data;
    size++;
}
删除最后一个元素和缩小容量
  1. 一旦size < capacity/4, 就缩小容量为二分之一, 从而保证ArrayList 的利用率大于等于百分之五十。这个参数可以自己调
  2. 取得返回数据
  3. 逻辑大小减一
  4. 将原先位置的最后一个元素置为null , 从而防止对象游离, 对于原始数据类型这一步可以省略
  5. 返回数据

对象游离: 因为通过数组可以访问到最后一个本应该被删除的对象, 所以垃圾回收器不会回收这个对象, 导致对象游离

public T deleteLast(int index)
{
    if (size < capacity/4)
    {
        resize(capacity/2);
    }
    T retData = ar[size - 1];
    size--;
    ar[size] = null;
    return retData;
}

上面的几个方法非常简单,非常重要,后面的几个方法可能复杂,但是实际中并不是经常使用

实现add(index, data)
  1. 数组边界检查, 🐶 ,
  2. 数组扩容检查
  3. 因为index处必然已经有对象在那了, 所以需要将[index, size - 1 ]处的所有对象向后挪一个位置, 然后让index位置放置data.
  4. 更新成员变量 size

List: add 方法的index 范围是【0,index】, 而删除、查找、更新都是【0, index-1】

    @Override
    public void add(int index, T data)
    {
        if (index < 0  || index > size)
        {
            throw new IllegalArgumentException();
        }
        if (size == capacity)
        {
            resize(2 * capacity);
        }
        for (int i = size - 1; i >= index; i--)
        {
            ar[i + 1] = ar[i]; 
        }
        ar[index] = data;
        size++;
    }
实现delete(index)

核心:

将[index, size - 2]的内容用[index + 1, size - 1]的内容覆盖, 即可实现删除ar[index]

不能简单的讲ar[index] = null, 举个例子

原本arrayList 中存有 0, 1, 2, 3, 4, 5, 6

现在delete(3), 显然剩下的应该是0, 1, 2, 4, 5, 6

而不是0, 1, 2, null, 4, 5, 6

    @Override
    public T delete(int index)
    {
        if (index < 0  || index >= size)
        {
            throw new IllegalArgumentException();
        }
        if (size < capacity/4)
        {
            resize( capacity/2);
        }
        T retData = ar[index];
        for (int i = index; i < size - 1; i++)
        {
            ar[i] = ar[i + 1];
        }
        size--;
        return retData;
    }
get/set

和数组访问一摸一样, 只不过它是IndexOutOfBoundsException异常, 我们用的是不合法参数异常, 我猜 java 底层就是在c++对象的基础之上加了这样一层异常检查

   @Override
    public void set(int index, T data)
    {
        if (index < 0  || index >= size)
        {
            throw new IllegalArgumentException();
        }
        ar[index] = data;
    }

    @Override
    public T get(int index)
    {
        if (index < 0  || index >= size)
        {
            throw new IllegalArgumentException();
        }
        return ar[index];
    }
traverse / size
    @Override
    public void traverse()
    {
        for (int i = 0; i < size; i++)
        {
            System.out.print(ar[i] + " ");
        }
    }

    @Override
    public int size()
    {
        return size;
    }
getIndex

这里第一个 == 等号是用来检测当data 和ar[i] 均为null 或者是完全相同对象的时候。

    @Override
    public int getIndex(T data)
    {
        for (int i = 0; i < size; i++)
        {
            if (data == ar[i] || data != null && data.equals(ar[i]))
            {
                return i;
            }
        }
        return -1;
    }
测试

使用之前测试LinkedList 的测试文件 🐶 , 真好啊,

测试List 的其他实现类时只需要将 new 后面的类名改一下就可以了。

package com.weinijuan;

import org.junit.Assert;
import org.junit.Test;

public class ListTest
{
    @Test
    public void testAddFirst()
    {
        List<Integer> list = new ArrayList<>();
        int[] test = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int[] ans = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
        for (int i = 0; i < test.length; i++)
        {
            list.addFirst(test[i]);
        }
        list.traverse();
        for (int i = 0; i < ans.length; i++)
        {
            if (i == 5)
            {
                System.out.println();
            }
            Assert.assertEquals(ans[i],(int) list.get(i));
        }
    }
    @Test
    public void testAddLast()
    {
        List<Integer> list = new ArrayList<>();
        int[] test = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int[] ans = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        for (int i = 0; i < test.length; i++)
        {
            list.addLast(test[i]);
        }
        list.traverse();
        for (int i = 0; i < ans.length; i++)
        {
            Assert.assertEquals(ans[i],(int) list.get(i));
        }
    }
    @Test
    public void testDelete()
    {
        List<Integer> listFirst = new ArrayList<>();
        List<Integer> listMid = new ArrayList<>();
        List<Integer> listEnd = new ArrayList<>();
        int[] test = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int[] ansFirst = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        int[] ansMid = {0, 1, 2, 3, 4, 6, 7, 8, 9};
        int[] ansEnd = {0, 1, 2, 3, 4, 5, 6, 7, 8};
        for (int i = 0; i < test.length; i++)
        {
            listFirst.addLast(test[i]);
            listMid.addLast(test[i]);
            listEnd.addLast(test[i]);
        }
        listFirst.delete(0);
        listMid.delete(test.length/2);
        listEnd.delete(test.length - 1);
        for (int i = 0; i < ansFirst.length; i++)
        {
            Assert.assertEquals(ansFirst[i],(int) listFirst.get(i));
            Assert.assertEquals(ansMid[i],(int) listMid.get(i));
            Assert.assertEquals(ansEnd[i],(int) listEnd.get(i));
        }
    }
    @Test
    public void testSize()
    {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 100; i++)
        {
            list.addLast(i);
        }
        Assert.assertEquals(100, list.size());
    }
    @Test
    public void testSet()
    {
        List<Integer> list = new ArrayList<>();
        int[] test = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int[] ans = {10, 1, 2, 3, 4, 20, 6, 7, 8, 30};
        for (int i = 0; i < test.length; i++)
        {
            list.addLast(test[i]);
        }
        list.set(0, 10);
        list.set(test.length/2, 20);
        list.set(test.length - 1, 30);
        list.traverse();
        for (int i = 0; i < ans.length; i++)
        {
            Assert.assertEquals(ans[i],(int) list.get(i));
        }
    }
    @Test
    public void testGetIndex()
    {
        List<Integer> list = new ArrayList<>();
        int[] test = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 5};
        int ans = 5;
        for (int i = 0; i < test.length; i++)
        {
            list.addLast(test[i]);
        }
        Assert.assertEquals(5, list.getIndex(5));
        Assert.assertEquals(-1, list.getIndex(100));
    }

}

性能估计
操作时间复杂度备注
addFirstO (N)
addLastO(1)
addO(N)需要额外移动其他对象,很慢
setO(1)
getO(1)
getIndexO(N)
traverseO(N)
sizeO(1)
deleteO(N)删除最后第一个是O(1)
删除第一个是O(N)

经常在开头中间插入删除使用链表, 经常查找更新使用ArrayList

一般情况下:用ArrayList 更好, 因为它充分利用了空间,也快

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值