Java集合-- Array List 与顺序表

简介: Array List 与顺序表详解

ArrayList与顺序表


一、线性表

  • 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列...
  • 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储

  • 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改 。

二、 ArrayLIst简介

img

说明

  1. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
  2. ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
  3. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
  4. 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者
    CopyOnWriteArrayList
  5. ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表

在集合框架中,ArrayList是一个普通的类,实现了List接口

image-20221005165335074

通过查看ArrayList的源码,我们可以得知ArrayList的底层是由数组来实现的,并初始化了默认的大小,并且实现了Cloneable和RandomAccess 等许多常用接口

2,ArrayList构造方法

  • 无参的构造方法

image-20221005165946090

使用默认的size为10的空数组,在构造方法中没有对数组长度进行设置,会在后续调用add方法的时候进行扩容,里面是一个赋值操作,右边是一个空容量数组,左边则是存储数据的容器,以下是参照源码分析;

//默认空容量数组,长度为0
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//集合真正存储数据的容器
transient Object[] elementData; 
  • 指定初始化容量的构造方法

image-20221005170110532

参数大于0,elementData初始化为initialCapacity大小的数组;参数等于0,elementData初始化为空数组;参数小于0,抛出异常;

  • 参数为Collection类型的构造方法
    image-20221005170219597

将一个参数为Collection的集合转变为ArrayList(实际上就是将集合中的元素换为了数组的形式);如果传入的集合为null会抛出空指针异常,只要传入的参数为E或者为E的子类,都可以传入。

  • 使用
public class TestArraylist {
    public static void main(String[] args) {
        /**
         * 一、Arraylist的构造和初始化
         */
        //1.直接
        List<Integer> list = new ArrayList<>();
        //2.带参数的构造
        List<Integer> list2 = new ArrayList<>(5);
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);

        list2.add(4);
        list2.add(6);
        list2.add(8);
        list2.add(890);
        System.out.println(list2);
        //3.利用其他 Collection  的子类 构建 ArrayList
        //(1)使用list2
        List<Integer> list3 = new ArrayList<>(list2);
        list3.add(34);
        System.out.println(list3.size());
        System.out.println(list3);
        //(2)使用Linklist构建
        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.add(3);
        linkedList.add(9);
        linkedList.add(9);
        linkedList.add(4);
        linkedList.add(4);
        List<Integer> list4 = new ArrayList<>(linkedList);
        System.out.println(list4);
        }
}

三、模拟实现ArrayList

我们可以使用数组来简单实现ArrayList

  • 初始化
 class MyArraylist extends PosWronglyException {
    public int[] elem;
    public int usedSize;
    //默认容量
    private static final int DEFAULT_SIZE = 10;

    public MyArraylist() {
        this.elem = new int[DEFAULT_SIZE];
    }
  • 打印顺序表
 /**
     * 打印顺序表:
     * 根据usedSize判断即可
     */
    public void display() {
        for (int i = 0; i < usedSize; i++) {
            System.out.println(this.elem[i]);
        }
        System.out.println();
    }
  • 新增元素
  // 新增元素,默认在数组最后新增
    public void add(int data) {
        if (isFull()) {
            System.out.println("顺序表此时为满");
            this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
        }
        this.elem[usedSize] = data;
        this.usedSize++;
    }
     /**
     * 在 pos 位置新增元素
     * 如果pos下标不合法,那么就会抛出一个 PosWrongfulException
     */
    // 在 pos 位置新增元素
    public void add(int pos, int data) throws PosWronglyException {
        //1、判断顺序表是否为满
        if (isFull()) {
            System.out.println("顺序表已满");
            this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
        }
        //2、判断pos位置是否合理
        if (pos < 0 || pos > this.usedSize) {
            System.out.println("pos 位置不合法");
            throw new PosWronglyException("pos 位置不合法");
        }
        //3、合理,则开始从后向前挪动元素
        for (int i = usedSize - 1; i >= pos; i--) {
            this.elem[i + 1] = this.elem[i];
        }

        //4、填入元素
        this.elem[pos] = data;
        //5.usedsize增加一个
        this.usedSize++;
    }
  • 判满判空
 /**
     * 判断当前的顺序表是不是满的!
     *
     * @return true:满   false代表空
     */
    public boolean isFull() {
        if (size() >= this.elem.length) {
            return true;
        }
        return false;
    }
 //判断顺序表是否为空
    public boolean isEmpty() {
        return size() == 0;
    }
  • 删除元素
 /**
     * 删除第一次出现的关键字key
     *
     * @param key
     */
    public void remove(int key) {
        if (isEmpty()) {
            System.out.println("当前顺序表为空");
            throw new EmptyException("顺序表 为空");
        }
        int index = this.indexOf(key);
        if(index == -1){
            System.out.println(" 顺序表中没有这个数字");
            return;
        }
        for(int i = index ;i < usedSize -1;i++){
            this.elem[i] = this.elem[i+1];
        }
        this.usedSize--;
    }
  • 查找与获取元素
 // 查找某个元素对应的位置
    public int indexOf(int toFind) {
        for (int i = 0; i < this.size(); i++) {
            if (this.elem[i] == toFind)
                return i;
        }
        return -1;
    }

    // 获取 pos 位置的元素
    public int get(int pos) {
        if (isEmpty()) {
            System.out.println("当前顺序表为空");
            throw new EmptyException("顺序表 为空");
        }
        if (pos < 0 || pos > this.usedSize) {
            System.out.println("pos 位置不合法");
            throw new PosWronglyException("pos 位置不合法");
        }
        return this.elem[pos];
    }
  • 测试
public class TestList {
    public static void main(String[] args) {
        MyArraylist myArraylist = new MyArraylist();
        myArraylist.add(10);
        myArraylist.add(20);
        myArraylist.add(30);
        try{
            myArraylist.add(3,100);
        }catch (PosWronglyException e){
            e.printStackTrace();
        }
        myArraylist.display();

        System.out.println("++++++++++++++++++");
        System.out.println(myArraylist.isEmpty());
        System.out.println(myArraylist.isFull());
        System.out.println(myArraylist.contains(10));

        myArraylist.remove(10);
        myArraylist.display();
        try{
            System.out.println(myArraylist.get(1));
        }catch(PosWronglyException e){
            e.printStackTrace();
        }

    }
}

image-20221005171616843

四、ArrayList中常用方法

方法 解释
boolean add(E e) 尾插 e
void add(int index, E element) 将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c) 尾插 c 中的元素
E remove(int index) 删除 index 位置元素
boolean remove(Object o) 删除遇到的第一个 o
E get(int index) 获取下标 index 位置元素
E set(int index, E element) 将下标 index 位置元素设置为 element
void clear() 清空
boolean contains(Object o) 判断 o 是否在线性表中
int indexOf(Object o) 返回第一个 o 所在下标
int lastIndexOf(Object o) 返回最后一个 o 的下标
List subList(int fromIndex, int toIndex)
  • ArrayList的遍历

    ArrayLsit的遍历包含三种,for 循环遍历、for each()遍历,使用迭代器进行遍历

        /**
         * Arraylist中的遍历
         */
 //1、for循环遍历
        for (int i = 0; i < list4.size() ; i++) {
            System.out.print(list4.get(i)+" ");
        }
        System.out.println();
//2、for each()进行遍历
        for(Integer integer:list4){
            System.out.print(integer+" ");
        }
        System.out.println();
        for (int x:list4) {
            System.out.print(x+" ");
        }
        System.out.println();
 //3、使用迭代器进行遍历
       Iterator it =list4.iterator();
        while(it.hasNext()){
            System.out.print(it.next()+" ");
        }
        System.out.println();

五、ArrayList的扩容机制

扩容的核心源代码:

image-20221005172624944

private void grow(int minCapacity) {
// 获取旧空间大小
        int oldCapacity = elementData.length;
// 预计按照1.5倍方式扩容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
        if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
// 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小
        if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
// 调用copyOf扩容
            elementData = Arrays.copyOf(elementData, newCapacity);
}

总结:

  1. 扩容的大小是原先数组的1.5倍;
  2. 若值newCapacity比传入值minCapacity还要小,则使用传入minCapacity,若newCapacity比设定的最大容量大,则使用最大整数值;
  3. ArrayList底层是数组实现的,那么每次添加数据时会不断地扩容,这样的话会占内存,性能低,所以导致时间很长,我们可以用ArrayList的指定初始化容量的构造方法来解决性能低的问题。
  4. 使用copyOf()进行扩容
相关文章
|
21天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
39 3
|
1月前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
49 5
|
2月前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
56 4
|
2月前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
47 2
|
2月前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
2月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
2月前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
2月前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
2月前
|
Java 开发者
从 Java 中的 Set 集合中删除元素
【10月更文挑战第30天】

热门文章

最新文章