数据结构---ArrayList(Java实现)

简介: 从数据结构的角度看,List就是一个线性表,可以保存n个具有相同类型元素的有限序列,在该序列中,可以进行增删查改以及变量等操作

一. 了解List

为什么要先介绍List呢?因为ArrayList是一个类,它实现了List接口,要想学好ArrayList就必须先了解List

image.png


从数据结构的角度看,List就是一个线性表,可以保存n个具有相同类型元素的有限序列,在该序列中,可以进行增删查改以及变量等操作


List为一个接口,里面提供了许多方法,此处不一一介绍,想了解可以查看List的官方文档

List 的官方文档


🔦关于List的使用:


List是个接口不能实例化对象,所以必须有类来实现它,而ArrayList就实现了List接口


二. ArrayList的介绍

image.png

上图可以清楚的了解到ArrayList继承的类和实现的接口,可以结合底层代码了解

image.png

🪔说明:


🏷️ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问

🏷️ArrayList实现了Cloneable接口,表明ArrayList可以clone

🏷️ArrayList实现了Serializable接口,表明ArrayList可以支持序列化

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


🎯这里注意一个问题:ArrayList与Vector的区别?(面试题可能会考)


Vector与ArrayList一样都实现了List接口,底层也是一段连续的空间,是一个动态类的顺序表,唯一的区别是Vector是线程安全的,可以在多线程下使用,而ArrayList是线程不安全的,只能在单线程下安全使用


Vector是线程安全的,是因为底层对重要方法使用了synchronized加锁,所以才保证了线程安全


Vector底层方法实现:

image.png

三. ArrayList的使用

1. ArrayList的构造方法

构造方法 解释

构造方法 解释
ArrayList() 无参构造
ArrayList(int initalCapacity) 参数表明顺序表的初始容量
ArrayList(Collection<? extends E> c) 使用其它的Collection集合构建ArrayList


对构造方法的使用进行代码展示:

import java.util.ArrayList;
import java.util.List;
public class ArrayListTest {
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();  //无参构造
        List<String> list2 = new ArrayList<>(20); //使用初始容量值构造
        list1.add(1);
        list1.add(2);
        list1.add(3);
        List<Integer> list3 = new ArrayList<>(list1);  //使用已有集合构造
    }
    }


2. ArrayList的常用方法

ArrayList的方法非常多,此处只展示常用方法

方法 说明
boolean add(E e) 尾插e
void add(int index , E e) 将e插入index位置
boolean addAll(Collection<? extends T> c) 尾插集合c中的元素
E remove(int index) 删除index位置上的元素
boolean remove(Object o) 删除第一个遇到的元素o
E get(int index) 或取index位置元素
E set(int index , E e) 将index位置元素设置为e
void clear() 清空
boolean contains(Object o) 判断o是否在该集合中
int indexOf(Object o) 返回第一个o的下标
int lastIndexOf(Object o) 返回最后一个o的下标
List<E> subList(int fromIndex , int toIndex) 截取部分list


2.1 ArrayList的插入操作

尾插:

import java.util.ArrayList;
import java.util.List;
public class ArrayListTest {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        for(int i : list){
            System.out.print(i+" ");
        }
    }
}

打印:1 ,2 ,3依次打印出来

微信图片_20221029160208.png

微信图片_20221029160205.png


将元素插入到指定位置:将9插入到下标为1的位置


       list.add(1,9);

打印:顺序为1,9,2,3

微信图片_20221029160320.png

尾插一个集合的所有元素:

List<Integer> list1 = new ArrayList<>(); //创建新线性表list1
        list1.add(6); 
        list1.add(8);
        list.addAll(list1);  //将list1尾插到list中

   打印结果:1,9,2,3,6,8

微信图片_20221029160257.png


2.2 ArrayList的删除操作

删除指定位置元素:将下标为0的元素删除


       list.remove(0);

打印:发现之前0下标的元素1被删除了


微信图片_20221029160416.png


删除遇到的第一个元素:因为该方法参数是Object类型,所以参数得传入包装类型


       Integer a = 3;

       list.remove(a);

打印:发现元素3被删除了

微信图片_20221029160356.png


2.3 获取指定下标元素

       list.remove(a);

       int b = list.get(2);

       System.out.println(b);

打印:得到2下标对应的元素6

image.png


2.4 将指定下标元素设置为新值

将2下标对应的值设置为7:


       list.set(2,7);

打印结果:发现原来下标为2的元素3被设置为了7

image.png


2.5 判断某个元素是否在线性表中

判断2是否在该线性表中:


       boolean flag1 = list.contains(2);

打印:

image.png


判断5是否在该线性表中:


       boolean flag2 = list.contains(5);

打印:

image.png

2.6 返回指定元素第一次和最后一次出现的下标

比如ArrayList中的元素序列为:9,2,7,8,2


打印元素2第一次出现的下标:

System.out.println(list.indexOf(2));

   

打印:

image.png



打印元素2最后一次出现的下标:

System.out.println(list.lastIndexOf(2));

 

打印:

image.png


2.7 ArrayList的清空操作

list.clear();
        System.out.println(list.size());

   

打印:所有元素被清空,有效元素的个数就为0

image.png


3. ArrayList的遍历操作

3.1 for循环遍历

public class ArrayListTest2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("小王");
        list.add("小张");
        list.add(("小花"));
        for(int i = 0;i < list.size();i++){
            System.out.print(list.get(i)+" ");
        }
        System.out.println();
    }
}

打印结果:

image.png


3.2 foreach遍历

 

for(String s : list){
            System.out.print(s+" ");
        }

 

打印结果:

image.png


3.3 迭代器遍历

Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.print(iterator.next()+" ");
        }

    打印结果:

image.png


四. ArrayList底层的扩容机制

ArrayList是一个动态类型的顺序表,即在插入元素的时候会自动扩容,下面是扩容机制:

微信图片_20221029160816.png

image.png



🕯️总结:


✨当使用无参的构造方法创建ArrayList,容量默认值10在第一次插入的时候才会初始化初始容量,而不是在创建的时候就初始化容量


✨在插入时,会检测是否真正需要扩容,如果需要,调用grow扩容


✨初步预估按照原容量的1.5倍扩容

✨如果用户所需大小超过预估的1.5倍,则按照用户所需大小扩容

✨真正扩容之前检测是否能扩容成功,防止太大导致扩容失败


✨使用copyOf进行扩容


五. 模拟实现ArrayList(面试可能会要求写)

我们这里是模拟实现,所以实现基本功能即可


1. 构造方法

这里给两个构造方法,一个是默认无参的,一个是带有初始容量参数的,默认的容量为10,无参的构造方法中,调用有参的构造方法,不用做的像标准库中那么复杂

public class MyArrayList<E>{
    private E[] elementData;
    private int size;
    private static final int DEFAULT_CAPACITY = 10;
    public MyArrayList(){
        this(DEFAULT_CAPACITY);
    }
    public MyArrayList(int initCapacity){
        if(initCapacity <= 0){
            initCapacity = DEFAULT_CAPACITY;
        }
        elementData = (E[]) new Object[initCapacity];
    }
}


2. 插入方法add

先实现在指定位置插入的方法:


1. 判断参数index是否合法

2. 确认是否扩容

3. 将index和后面位置的元素统一往后搬移

4. 往index位置插入,size++

public void add(int index,E e){
        if(index < 0 || index > size){
            throw new ArrayIndexOutOfBoundsException("add:index越界");
        }
        ensureCapacity(size);
        for(int i = size-1;i >= index;i--){
            elementData[i+1] = elementData[i];
        }
        elementData[index] = e;
        size++;
    }

尾插:


我们发现尾插就是在size的位置进行插入的,所以可以调用在指定位置插入的方法,提高代码的复用

public boolean add(E e){
        add(size,e);
        return true;
    }

 

扩容:


在每次插入是需要检测是否扩容,如果需要则进行扩容


1. 获取旧的容量值

2. 判断size是否大于该容量值

3. 如果大于或者等于,说明需要扩容

4. 采用1.5倍方式更新容量值

public void ensureCapacity(int size){
        int oldCapacity = elementData.length;
        if(size >= oldCapacity){
            int newCapacity = oldCapacity + (oldCapacity>>1);
            elementData = Arrays.copyOf(elementData,newCapacity);
        }
    }

3. 删除方法remove

删除指定位置元素:


1. 判断index是否合法

2. 将index后的元素统一向前搬移一个长度

3. size--

public E remove(int index){
        if(index < 0 || index >= size){
            throw new ArrayIndexOutOfBoundsException("remove:index越界");
        }
        E ret = elementData[index];
        for(int i = index+1;i < size;i++){
            elementData[i-1] = elementData[i];
        }
        size--;
        return ret;
    }

 

删除指定元素:


1. 先判断该元素是否存在,这里可以调用后面要实现的indexOf方法,如果返回为-1,则不存在,其他的都存在

2. indexOf会返回元素下标,所以可以调用上面删除指定位置元素的方法,提高代码复用

public boolean remove(E e){
        int index = indexOf(e);
        if(index == -1){
            return false;
        }
        remove(index);
        return true;
    }

 

4. 查找元素下标方法indexOf,lastIndexOf

查找第一次出现元素的位置:


从头遍历,找到返回下标,找不到返回-1

public int indexOf(E e){
        for(int i = 0;i < size;i++){
            if(e.equals(elementData[i])){
                return i;
            }
        }
        return -1;
    }

查找最后一次出现元素的位置:

public int lastIndexOf(E e){
        for(int i = size-1;i >= 0;i--){
            if(e.equals(elementData[i])){
                return i;
            }
        }
        return -1;
    }

 

从最后向前遍历,找到返回下标,找不到返回-1


💡注意:上述比较的时候,用equals方法比较,不能用==比较


5. subList方法

1. 判断参数是否合法

2. 用要截取长度为参数,创建新的MyArrayList

3. 从截取的起始位置依次往新的list中添加元素直到截取的结束位置

MyArrayList<E> subList(int from,int to){
        if(from > to){
            throw new IllegalArgumentException("subList:非法参数,from>to");
        }
        int newSize = to-from;
        MyArrayList<E> list = new MyArrayList<>(newSize);
        while(from < to){
            list.add(elementData[from]);
            from++;
        }
        return list;
    }

 

✨注意:subList截取的范围为[from,to),是左闭右开的


6. contains,get,set,size,clear方法

contains方法:


我们可以调用indexOf位置查找该元素下标,若为-1则不包含,否则包含

public boolean contains(E e){
        return -1==indexOf(e);
    }

 

get方法:


先判断参数是否合法,合法就直接返回该位置元素

public E get(int index){
        if(index < 0 || index >= size){
            throw new ArrayIndexOutOfBoundsException("get:index越界");
        }
        return elementData[index];
    }

 

set方法:


先判断参数是否合法,合法就直接修改

public void set(int index,E e){
        if(index < 0 || index >= size){
            throw new ArrayIndexOutOfBoundsException("set:index越界");
        }
        elementData[index] = e;
    }

 

size方法:


直接返回size即可

public int size(){
        return size;
    }

clear方法:


将size置为0,并且从头遍历元素,将每个位置置为null

public void clear(){
        size = 0;
        for(int i  =0;i < size;i++){
            elementData[i] = null;
        }
    }


7. 对MyArrayList进行测试

public static void main(String[] args) {
        MyArrayList<Integer> list = new MyArrayList<>(4);
        list.add(1);   //尾插1
        list.add(2);   //尾插2
        list.add(3);   //尾插3
        list.add(3,4);  //在位置3插入4
        System.out.println(list);  
        System.out.println(list.get(2));  //输出下标为2的元素
        list.set(0,5); //将位置0的元素设置为5
        System.out.println(list);
        System.out.println(list.contains(4)); //判断是否包含4
        System.out.println(list.contains(9)); //判断是否包含9
        list.remove(0); //删除位置0的元素
        System.out.println(list);
        list.remove((Integer) 4); //删除元素4
        System.out.println(list);
        list.add(3);
        System.out.println(list.indexOf(3)); //输出第一个3的位置
        System.out.println(list.lastIndexOf(3)); //输出最后一个3的位置
        MyArrayList<Integer> list2 = list.subList(0,1); //截取部分List
        System.out.println(list2);
        list.clear(); //清空
        System.out.println(list.size()); //看看是否清空
    }

 

打印结果:满足基本使用

image.png


相关文章
|
2月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
110 4
|
3月前
|
缓存 Java 开发者
Java 开发者必看!ArrayList 和 LinkedList 的性能厮杀:选错一次,代码慢成蜗牛
本文深入解析了 Java 中 ArrayList 和 LinkedList 的性能差异,揭示了它们在不同操作下的表现。通过对比随机访问、插入、删除等操作的效率,指出 ArrayList 在多数场景下更高效,而 LinkedList 仅在特定情况下表现优异。文章强调选择合适容器对程序性能的重要性,并提供了实用的选择法则。
210 3
|
5月前
|
Java 索引
Java ArrayList中的常见删除操作及方法详解。
通过这些方法,Java `ArrayList` 提供了灵活而强大的操作来处理元素的移除,这些方法能够满足不同场景下的需求。
542 30
|
6月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
327 3
|
7月前
|
人工智能 安全 JavaScript
Java ArrayList:动态数组
本文探讨Java中的数组,对比C/C++、JS/PHP/Python等语言的数组特性。文章分析了Java数组的定义、创建方式及其规范,指出其优缺点。Java数组作为引用类型,在堆上分配内存,支持动态大小,避免了C/C++中裸数组的常见问题(如越界访问)。然而,Java数组也存在性能瓶颈和设计缺陷,例如运行时的安全检查影响速度,无法创建超大数组或泛型数组,且多线程场景下缺乏同步机制。作者建议在实际开发中用集合替代数组以规避这些问题。
186 1
|
8月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
269 1
|
8月前
|
存储 Java 编译器
Java 中 .length 的使用方法:深入理解 Java 数据结构中的长度获取机制
本文深入解析了 Java 中 `.length` 的使用方法及其在不同数据结构中的应用。对于数组,通过 `.length` 属性获取元素数量;字符串则使用 `.length()` 方法计算字符数;集合类如 `ArrayList` 采用 `.size()` 方法统计元素个数。此外,基本数据类型和包装类不支持长度属性。掌握这些区别,有助于开发者避免常见错误,提升代码质量。
804 1
|
12月前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
190 5
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
224 6
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
564 4
Java ArrayList扩容的原理

热门文章

最新文章