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

目录

一. 了解List

二. ArrayList的介绍

​编辑

三. ArrayList的使用

1. ArrayList的构造方法 

2. ArrayList的常用方法

2.1 ArrayList的插入操作

2.2 ArrayList的删除操作

2.3 获取指定下标元素

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

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

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

2.7 ArrayList的清空操作

3. ArrayList的遍历操作

3.1 for循环遍历

3.2 foreach遍历

3.3 迭代器遍历

四. ArrayList底层的扩容机制

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

1. 构造方法

2. 插入方法add

3. 删除方法remove

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

5. subList方法

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

7. 对MyArrayList进行测试


一. 了解List

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

数据结构---ArrayList(Java实现)_第1张图片

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

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

关于List的使用:

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

二. ArrayList的介绍

数据结构---ArrayList(Java实现)_第2张图片

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

说明:

️ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
️ArrayList实现了Cloneable接口,表明ArrayList可以clone
️ArrayList实现了Serializable接口,表明ArrayList可以支持序列化
️ArrayList底层是一段连续空间,可以动态扩容,是一个动态类型的顺序表

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

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

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

Vector底层方法实现:

数据结构---ArrayList(Java实现)_第3张图片

三. ArrayList的使用

1. ArrayList的构造方法 

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

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

import java.util.ArrayList;
import java.util.List;

public class ArrayListTest {
    public static void main(String[] args) {
        List list1 = new ArrayList<>();  //无参构造

        List list2 = new ArrayList<>(20); //使用初始容量值构造

        list1.add(1);
        list1.add(2);
        list1.add(3);

        List list3 = new ArrayList<>(list1);  //使用已有集合构造
    }
}

2. ArrayList的常用方法

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

方法 说明
boolean add(E e) 尾插e
void add(int index , E e) 将e插入index位置
boolean addAll(Collection 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 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 list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        for(int i : list){
            System.out.print(i+" ");
        }
    }
}

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

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

        list.add(1,9);

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

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

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

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

2.2 ArrayList的删除操作

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

        list.remove(0);

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

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

        Integer a = 3;
        list.remove(a);

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

2.3 获取指定下标元素

        list.remove(a);
        int b = list.get(2);
        System.out.println(b);

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

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

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

        list.set(2,7);

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

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

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

        boolean flag1 = list.contains(2);

打印:

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

        boolean flag2 = list.contains(5);

打印:

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

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

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

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

打印:

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

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

打印:

2.7 ArrayList的清空操作

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

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

3. ArrayList的遍历操作

3.1 for循环遍历

public class ArrayListTest2 {
    public static void main(String[] args) {
        List 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();
    }
}

打印结果:

3.2 foreach遍历

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

打印结果:

3.3 迭代器遍历

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

打印结果:

四. ArrayList底层的扩容机制

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

数据结构---ArrayList(Java实现)_第4张图片

数据结构---ArrayList(Java实现)_第5张图片

️总结:

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

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

✨初步预估按照原容量的1.5倍扩容
✨如果用户所需大小超过预估的1.5倍,则按照用户所需大小扩容
✨真正扩容之前检测是否能扩容成功,防止太大导致扩容失败

✨使用copyOf进行扩容 

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

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

1. 构造方法

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

public class MyArrayList{
    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 subList(int from,int to){
        if(from > to){
            throw new IllegalArgumentException("subList:非法参数,from>to");
        }
        int newSize = to-from;
        MyArrayList 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 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 list2 = list.subList(0,1); //截取部分List
        System.out.println(list2);
        list.clear(); //清空
        System.out.println(list.size()); //看看是否清空
    }

打印结果:满足基本使用

数据结构---ArrayList(Java实现)_第6张图片

你可能感兴趣的:(数据结构,数据结构,线性表,Java,ArrayList)