Java - 顺序表 与 ArrayList

文章详细介绍了Java中ArrayList类的基本实现,包括构造方法、add、contains、indexOf、get、set、remove、size、clear等方法的实现逻辑。还提到了ArrayList的动态扩容机制以及遍历ArrayList的几种方式。并讨论了ArrayList作为顺序表的优缺点,特别是在插入和删除操作上的效率问题。

在Java中 : 它提供了许多集合类, 今天我们看一下ArrayList类.

它的底层其实就一个数组.在下面我写了一些ArrayList类中的一些常用的方法,大家可以自己先写一下.具体解法在后面 : 

public class MyArrayList {
    private int[] elem;
    private int usedSize; // 存储了多少个有效的数据
    public static final int DEAULT_SIZE = 10;
    // 默认构造方法
    MyArrayList(){ 
        this.elem = new int[DEAULT_SIZE]; // 默认情况下我们给它底层容量设置为10
    }
    // 新增元素,默认在数组最后新增
    public void add(int data) { }
    // 在 pos 位置新增元素
    public void add(int pos, int data) { }
    // 判定是否包含某个元素
    public boolean contains(int toFind) { return true; }
    // 查找某个元素对应的位置
    public int indexOf(int toFind) { return -1; }
    // 获取 pos 位置的元素
    public int get(int pos) { return -1; }
    // 给 pos 位置的元素设为 value
    public void set(int pos, int value) { }
    //删除第一次出现的关键字key
    public void remove(int toRemove) { }
    // 获取顺序表长度
    public int size() { return 0; }
    // 清空顺序表
    public void clear() { }
    // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display() { }
}

接下来我们一个一个看下它的方法是怎么实现的 : 

1.display() : 用来打印的  我们要通过usedSize来确定 i 要打印多少次,也就是它的有效数据.

// 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display() {
        for (int i = 0; i < usedSize; i++) {
            System.out.print(elem[i] + " ");
        }
        System.out.println();
    }

2.contains() : 用来判断是否包含某个元素. 我们也是通过usedSize来一一判断.

// 判定是否包含某个元素
    public boolean contains(int toFind) {
        for (int i = 0; i < usedSize; i++) {
            if (elem[i] == toFind) {
                return true;
            }
        }
        return false;
    }

3. add(int data) : 在添加元素的时候,要注意一点,如果usedSize 的值跟elem.length的值一样的话,证明数组满了,我们需要扩充数组.

// 新增元素,默认在数组最后新增
    public void add(int data) {
        if (this.usedSize == elem.length) {
            expand();
        }
        this.elem[this.usedSize] = data;
        this.usedSize++;
    }

//扩大1倍
    public void expand () {
        this.elem = Arrays.copyOf(this.elem,this.elem.length * 2);
    }

4.add(int pos, int data) : 在某个位置去插入元素data : 

1.pos位置必须合法.(不能为负数  不能超过usedSize).

2.如果要插入的位置等于数组的长度(证明数组满了) 那么我们就要扩充数组.

3.到第三步,我们就可以去放了,从最后一个元素依次往后放,注意边界值. i >= pos.(要把pos位置的空腾出来).

4.把data放入pos位置.

5.记得this.usedSize++;

// 在 pos 位置新增元素
    public void add(int pos, int data) throws PosException{
        //如果下标异常的话 抛异常
        if (pos < 0 || pos > this.elem.length) {
            throw new PosException("下标越界");
        }
        if (usedSize == this.elem.length) {
            expand();
        }
        for (int i = usedSize - 1; i >= pos; i--) {
            this.elem[i + 1] = this.elem[i];
        }
        this.elem[pos] = data;
        this.usedSize++;
    }

//异常类
public class PosException extends RuntimeException{
    public PosException() {
        super();
    }

    public PosException(String message) {
        super(message);
    }
}

5. indexOf() : 查找某个元素对应的位置.

// 查找某个元素对应的位置
    public int indexOf(int toFind) {
        for (int i = 0; i < usedSize; i++) {
            if (elem[i] == toFind) {
                return i;
            }
        }
        return -1; // 因为数组中没有-1下标
    }

6. get() :  获取pos位置的元素 :  注意判断pos位置是否是有效的

// 获取 pos 位置的元素
    public int get(int pos) {
        //如果下标异常的话 抛异常
        if (pos < 0 || pos >= this.elem.length) {
            return -1;
        }
        return this.elem[pos];
    }

7. set () : 改变pos位置的值.

// 给 pos 位置的元素设为 value
    public void set(int pos, int value) {
        //如果下标异常的话 抛异常
        if (pos < 0 || pos >= this.elem.length) {
            throw new PosException("下标越界");
        }
        this.elem[pos] = value;
    }

8. remove () : 删除第一次出现的关键字key : 

1.先通过indexof() 判断一下这个值在数组中的下标

2.如果这个值存在,那么就返回的是它的地址,否则是-1

3.通过循环,从返回的下标,到 i < usedSize - 1; (注意这个边界值)

4.最后注意this.usedSize--;

//删除第一次出现的关键字key
    public void remove(int toRemove) {
        int index = indexOf(toRemove);
        if (index == -1) {
            return;
        }
        for (int i = index; i < usedSize - 1; i++) {
            this.elem[i] = this.elem[i + 1];
        }
        this.usedSize--;
        elem[usedSize] = 0;
    }

9.size() : 获取顺序表的长度 : 

// 获取顺序表长度
    public int size() { 
        return this.usedSize;
     }

10. clear() : 清空顺序表 : 

// 清空顺序表
    public void clear() {
        this.usedSize = 0;
    }

这里如果顺序表里面操作的是引用数据类型,那么就需要把它每个元素置为空.

 这就是我们经常要使用的一些ArrayList集合类里面的一些方法.

总结 : 

顺序表的缺点 : 插入元素的时候,如果插入的位置是0下标,那么它的时间复杂度就达到了O(n).

删除也是,O(n)

因此顺序表不是很适合 频繁的对数据进行插入和删除的场景. 

它比较适合给定下标 然后查找.

接下来我们来看一下,Java中的Arraylist类 

ArrayList是以泛型方式实现的,使用时必须要实例化.

ArrayList实现了Cloneable接口,表明ArrayList是可以clone的

ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表.

我们先来了解一下它的构造方法

 1.无参构造.当我们去new一个ArrayList对象时,它会默认给我们分配内存且大小为10.

public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
    }

2.我们也可以给它传过去一个内存大小.

public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>(15);
    }

3.也可以传过去一个类型(实现了Collection接口的类 并且它是E(泛型)的本身或者是子类)

public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(19);
        list.add(18);
        ArrayList<Integer> arrayList = new ArrayList<>(list);
        System.out.println(arrayList);
    }
}

运行结果为 : 

这些是ArrayList的一些常用方法 : 

 我们重点来看一下最后一个方法 : 它的作用是截取

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(19);
        list.add(18);
        list.add(12);
        list.add(17);
        list.add(11);
        //截取[2,4)的值 返回值类型是List<Integer>
        List<Integer> list1 = list.subList(2, 4);
        //我们来吧list1中的0下标的值改为999 也就是把12改为999
        list1.set(0,999);
        System.out.println(list1);
    }
}

运行结果 : 

重点来了 : 

这时候我们去打印一下list的值 : 

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(19);
        list.add(18);
        list.add(12);
        list.add(17);
        list.add(11);
        //截取[2,4)的值 返回值类型是List<Integer>
        List<Integer> list1 = list.subList(2, 4);
        //我们来吧list1中的0下标的值改为999 也就是把12改为999
        list1.set(0,999);
        System.out.println(list1);
        System.out.println(list);
    }
}

 运行结果 : 

 是的,你没看错,其实通过这个方法截取下来的部分,和原来数组共用了一块地址.

 

最后说一下ArrayList的遍历 : 

1.通过toString方法来输出 : 

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(123);
        list.add(678);
        System.out.println(list);
    }
}

 

2.使用循环 使用get方法 : 

public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(123);
        list.add(678);
        //System.out.println(list);
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
    }

 3. foreach : 

public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(123);
        list.add(678);
        //System.out.println(list);
//        for (int i = 0; i < list.size(); i++) {
//            System.out.print(list.get(i) + " ");
//        }
        for (Integer a: list) {
            System.out.print(a + " ");
        }
    }

4. 迭代器 : 

public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(123);
        list.add(678);
        //迭代器
        ListIterator<Integer> it = list.listIterator();
        while (it.hasNext()) {
            System.out.print(it.next() + " ");
        }
    }

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值