当前位置: 首页 > article >正文

【Java数据结构】 ArrayList 顺序表

一、什么是List

在集合框架中,List是一个接口,继承自Collection

Collection也是一个接口,该接口中规范了后序容器中常用的一些方法,具体如下所示:

Iterable 也是一个接口,表示实现该接口的类是可以逐个元素进行遍历的,具体如下:

站在数据结构的角度来看, List 就是一个线性表,即 n 个具有相同类型元素的有限序列,在该序列上可以执行增删 改查以及变量等操作
面试题 Collection 中有那些方法?

二. 常见接口介绍

List 中提供了好的方法,具体如下

方法

解释

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<E> subList(int fromindex, int tolndex)

截取部分 list

虽然方法比较多,但是常用方法如下:

三. List的使用

注意: List 是个接口,并不能直接用来实例化
如果要使用,必须去实例化 List 的实现类。在集合框架中, ArrayList LinkedList 都实现了 List 接口

四、线性表

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

五、顺序表 

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

顺序表底层其实是动态数组

接口的实现

public interface IList{
    // 新增元素,默认在数组最后新增
    public void add(int data);
    boolean isFull();
    // 在 pos 位置新增元素
    public void add(int pos, int data);
    // 判定是否包含某个元素
    public boolean contains(int toFind);
    // 查找某个元素对应的位置
    public int indexOf(int toFind);
    // 获取 pos 位置的元素
    public int get(int pos);
    // 给 pos 位置的元素设为 value
    public void set(int pos, int value);
    //删除第一次出现的关键字key
    public void remove(int toRemove);
    // 获取顺序表长度
    public int size() ;
    // 清空顺序表
    public void clear();
    // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display() ;
}

自行实现MyArrayList实现上述接口

package List;

import java.util.ArrayList;
import java.util.Arrays;

public class MyArrayList implements IList {
    public int[] array;
    public int usedSize;
    public static final int default_capacity = 10;//默认常量容量值

    public MyArrayList() {
        this.array = new int[default_capacity];//分配空间,默认值为0
    }

    //扩容
    private void grow () {
        this.array = Arrays.copyOf(this.array, array.length * 2);
    }
    @Override
    //顺序表是否满了
    public boolean isFull() {
        return this.usedSize == this.array.length;
    }

    @Override
    public void add(int data) {
        if (isFull()) {
            grow();
        }
        this.array[this.usedSize] = data;
        this.usedSize++;
    }


    //下标有问题时,抛异常
    private void checkPos(int pos)throws PosIllegal {
        if (pos<0||pos>usedSize){
            throw new PosIllegal("pos位置不合法");
        }
    }
    @Override
    public void add(int pos, int data) {
        try {
            checkPos(pos);
        }catch (PosIllegal e){
            System.out.println("插入位置pos不合法");
            e.printStackTrace();
            return;
        }
        if (isFull()) {
            grow();
        }
        for (int i = this.usedSize-1; i >=pos ; i--) {
            array[i+1]=array[i];
        }
        array[pos]=data;
        this.usedSize++;
    }

    @Override
    public boolean contains(int toFind) {
        for (int i=0;i<array.length;i++) {
            if(array[i]==toFind){
                return true;
            }
        }
        return false;
    }

    @Override
    public int indexOf(int toFind) {
        for (int i=0;i<array.length;i++) {
            if(array[i]==toFind){
                return i;
            }
        }
        return -1;
    }
    private void checkPos2(int pos)throws PosIllegal {
        if (pos<0||pos>=usedSize){
            throw new PosIllegal("pos位置不合法");
        }
    }
    @Override
    public int get(int pos) {
        try {
            checkEmpty();
            checkPos2(pos);
            return array[pos];
        }catch (PosIllegal e){
            System.out.println("寻找位置pos不合法");
            e.printStackTrace();
        }catch (EmptyException e){
            e.printStackTrace();
        }
        return -1;
    }

    public void checkEmpty() {
        if (isEmpty()) {
            throw new EmptyException("顺序表为空!!!!");
        }
    }
    public boolean isEmpty(){
        return usedSize==0;
    }
    @Override
    //更新数组对应位置内容
    public void set(int pos, int value) {
        try {
            checkEmpty();
            checkPos2(pos);
            array[pos]=value;
        }catch (PosIllegal e){
            System.out.println("寻找位置pos不合法");
            e.printStackTrace();
        }catch (EmptyException e){
            e.printStackTrace();
        }
    }

    @Override
    public void remove(int toRemove) {
        try {
            checkEmpty();
            int pos=indexOf(toRemove);
            if (pos==-1){
                return;
            }else {
                for (int i = pos; i <usedSize-1 ; i++) {
                    array[i] =array[i+1];
                }
                usedSize--;
            }
        }catch (EmptyException e){
            e.printStackTrace();
        }

    }

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

    @Override
    public void clear() {
        //引用类型防止空间泄露
        /*for (int i = 0; i < usedSize; i++) {
            array[i]=null;
        }*/

        usedSize=0;
    }

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

顺序表优点:查询非常快,达到O(1)

顺序表缺点:移动非常慢,达到O(N)

六、ArrayList简介

        在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:

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


Tip:ArrayList<Integer>和List<Integer>的区别

ArrayList<Integer>list=new ArrayList<>();

通过list这个引用 可以调用 当前类的所有可以被调用的方法

List<Integer>list1 = new ArrayList<>();
只要实现这个接口的都能引用,向上转型
通过这个接口 只能调用这个接口当中包含的方法

七、ArrayList使用  

ArrayList底层原码

  • private static final int DEFAULT_CAPACITY = 10;//默认的容量
  • private static final Object[] EMPTY_ELEMENTDATA = {};//空顺序表
  • private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  • transient 0bject[]  elementData;//当时定义顺序表的时候的 数组
  • private int size;//当时自己定义的usedSize

1.ArrayList的构造

(1)ArrayList()

就是在第一次add的时候分配内存,大小为DEFAULT_CAPACITY

如果后面满了,那么就是1.5倍扩容

(2)ArrayList(Collection<? extends E>c)

传入的形参只要满足实现了 Collection接口即可

<? extends E>:?要么是E,要么是E的子类

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list=new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        ArrayList<Integer> list2=new ArrayList<>(list);
    }
}

(3)ArrayList(int initialCapacity)

混合使用:

public static void main(String[] args) {
// ArrayList创建,推荐写法
// 构造一个空的列表
        List<Integer> list1 = new ArrayList<>();
// 构造一个具有10个容量的列表
        List<Integer> list2 = new ArrayList<>(10);
        list2.add(1);
        list2.add(2);
        list2.add(3);
// list2.add("hello"); // 编译失败,List<Integer>已经限定了,list2中只能存储整形元素
// list3构造好之后,与list中的元素一致
        ArrayList<Integer> list3 = new ArrayList<>(list2);
// 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
        List list4 = new ArrayList();
        list4.add("111");
        list4.add(100);
    }

2 ArrayList常见操作

        ArrayList虽然提供的方法比较多,但是常用方法如下所示,需要用到其他方法时,自行查 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<E> subList(int fromindex, int tolndex)

截取部分 list

Tip:

1.remove()默认是删除对应下标,如果想删除表中数据的话,需要new

list2.remove(1);
list2.remove(new Integer(99));

 

2.set方法

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        ArrayList<Integer> list2 = new ArrayList<>(list);
        list2.add(4);
        list2.add(5);
        list2.add(6);
        System.out.println(list2);//1 2 3 4 5 6 
        List<Integer> L=list2.subList(1,3);
        System.out.println(L);//2 3
        L.set(0,99);
        System.out.println(L);//99 3
        System.out.println(list2);//预期应当是1 2 3 4 5 6
    }
}

 L直接引用了list2的1下标至3下标位置,所以修改L同时也修改了list2

混合使用:

  public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("JavaSE");
        list.add("JavaWeb");
        list.add("JavaEE");
        list.add("JVM");
        list.add("测试课程");
        System.out.println(list);
// 获取list中有效元素个数
        System.out.println(list.size());
// 获取和设置index位置上的元素,注意index必须介于[0, size)间
        System.out.println(list.get(1));
        list.set(1, "JavaWEB");
        System.out.println(list.get(1));
// 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
        list.add(1, "Java数据结构");
        System.out.println(list);
// 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
        list.remove("JVM");
        System.out.println(list);
// 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
        list.remove(list.size()-1);
        System.out.println(list);
        // 检测list中是否包含指定元素,包含返回true,否则返回false
        if(list.contains("测试课程")){
            list.add("测试课程");
        }
// 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
        list.add("JavaSE");
        System.out.println(list.indexOf("JavaSE"));
        System.out.println(list.lastIndexOf("JavaSE"));
// 使用list中[0, 4)之间的元素构成一个新的SubList返回,但是和ArrayList共用一个elementData数组
        List<String> ret = list.subList(0, 4);
        System.out.println(ret);
        list.clear();
        System.out.println(list.size());
    }

3 ArrayList的遍历

ArrayList 可以使用三方方式遍历: for 循环 + 下标、 foreach 、使用迭代器
 public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
            list.add(2);
            list.add(3);
            ArrayList<Integer> list2 = new ArrayList<>(list);
            list2.add(4);
            list2.add(5);
            list2.add(6);
            System.out.println("===========for循环输出=============");
            for (int i = 0; i < list2.size(); i++) {
                System.out.print(list2.get(i)+"  ");
            }
        System.out.println();
         System.out.println("===========foreach循环输出=============");
        for (Integer  i:list2) {
            System.out.print(i+"  ");
        }
        System.out.println();
        System.out.println("===========iterator迭代器循环输出=============");
        Iterator<Integer> it=list2.iterator();
        while (it.hasNext()){
            System.out.print(it.next()+" ");
        }
        System.out.println();
        System.out.println("===========listIterator迭代器循环输出=============");
        ListIterator<Integer> it2= list2.listIterator();
        while (it2.hasNext()){
            System.out.print(it2.next()+" ");
        }
        System.out.println();
    }
注意:
1. ArrayList 最长使用的遍历方式是: for 循环 + 下标 以及 foreach
2. 迭代器是设计模式的一种,后序容器接触多了再给大家讲解
3.listIterator迭代器实现了Iterator迭代器,有更多的方法
 System.out.println("===========listIterator迭代器倒序循环输出=============");
        ListIterator<Integer> it3= list2.listIterator();
        while (it2.hasPrevious()){
            System.out.print(it2.previous()+" ");
        }
        System.out.println();
    }


System.out.println("===========listIterator迭代器指定位置倒序循环输出=============");
        ListIterator<Integer> it4= list2.listIterator(3);
        while (it4.hasPrevious()){
            System.out.print(it4.previous()+" ");
        }
        System.out.println();

 

4 ArrayList的扩容机制

总结
1. 检测是否真正需要扩容,如果是调用 grow 准备扩容
2. 预估需要库容的大小
  • 初步预估按照1.5倍大小扩容
  • 如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容
  • 真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
3. 使用 copyOf 进行扩容

八. ArrayList的具体使用

1.删除部分内容

str1: welcome to AHU
str2 : come
删除str1中出现的 所有str2的字符 删除之后的结果:wl t AHU

public class Test2 {
    public static void main(String[] args) {
        String str1="welcome to AHU";
        String str2="come";
        ArrayList<Character> ret=new ArrayList<>();

        for (int i = 0; i <str1.length() ; i++) {
            char ch=str1.charAt(i);
            if(str2.contains(ch+"")==false){
                ret.add(ch);
            }
        }
        for (int i = 0; i < ret.size(); i++) {
            System.out.print(ret.get(i));
        }
    }
}

2.杨辉三角

 public static List<List<Integer>> generate(int numRows) {
        List<List<Integer>> ret= new ArrayList<>();
        List<Integer> firstRow=new ArrayList<>();
        firstRow.add(1);
        ret.add(firstRow);
        for (int i = 1; i <numRows ; i++) {//第一行已经放入
            List<Integer> curRow=new ArrayList<>();
            //首元素
            curRow.add(1);
            //找到当前行的上一行的数组
            List<Integer> prevRow=ret.get(i-1);
            //处理中间元素
            for (int j = 1; j < i; j++) {//第一列和最后一列已经放入
                int val1= prevRow.get(j);
                int val2= prevRow.get(j-1);
                curRow.add(val1+val2);
            }
            //末元素
            curRow.add(1);
            ret.add(curRow);
        }
        return ret;
    }

具体进行每个元素打印

 public static void main(String[] args) {
        List<List<Integer>> ret= Solution.generate(4);
        for (int i = 0; i < ret.size(); i++) {
            for (int j = 0; j < ret.get(i).size(); j++) {
                System.out.print(ret.get(i).get(j)+" ");
            }
            System.out.println();
        }
    }

3 简单的洗牌算法

package WashCard;

public class Card {
    private String suit;//花色
    private int rank;//面额

    public Card(String suit, int rank) {
        this.suit = suit;
        this.rank = rank;
    }

    public String toString(){
        return "{"+this.suit+","+this.rank+"}";
    }
}

1.买52张牌

 public static final String[] suits={"♥","♠","♣","♦"};
    public List<Card> buyCard(){
        List<Card> cardList=new ArrayList<>();
        for (int i = 0; i < 13; i++) {
            for (int j = 0; j < 4; j++) {
                int rank=i;
                String suit=suits[j];
                Card card=new Card(suit,rank);
                cardList.add(card);
            }
        }
        return cardList;
    }
2.洗牌
public void shuffle(List<Card> cardList){
        Random random=new Random();
        for (int i = cardList.size()-1; i >0; i--) {
            int index=random.nextInt(i);
            swap(cardList,i,index);
        }
    }
    private void swap(List<Card> cardList,int i,int j){
        Card tmp=cardList.get(i);
        cardList.set(i,cardList.get(j));
        cardList.set(j,tmp);
    }
3.3个人  每个人   轮流揭牌5张
public List<List<Card>> play(List<Card> cardList){
        List<Card> hand0=new ArrayList<>();
        List<Card> hand1=new ArrayList<>();
        List<Card> hand2=new ArrayList<>();
        List<List<Card>> hand=new ArrayList<>();
        hand.add(hand0);
        hand.add(hand1);
        hand.add(hand2);
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 3; j++) {
                Card card=cardList.remove(0);
                hand.get(j).add(card);
            }

        }
        return hand;
    }

测试:

package WashCard;

import java.util.List;

public class Test {
    public static void main(String[] args) {
        //1.买52张牌
        CardDemo cardDemo=new CardDemo();
        List<Card> cardList=cardDemo.buyCard();
       // System.out.println(cardList);
        //2.洗牌
        cardDemo.shuffle(cardList);
        //3.3个人  每个人   轮流揭牌5张
        List<List<Card>> hand=cardDemo.play(cardList);
        for (int i = 0; i < hand.size(); i++) {
            System.out.println("第"+(i+1)+"个人的牌有这些:  "+hand.get(i));
        }
    }
}

综合完整代码:

package WashCard;

public class Card {
    private String suit;//花色
    private int rank;//面额

    public Card(String suit, int rank) {
        this.suit = suit;
        this.rank = rank;
    }

    public String toString(){
        return "{"+this.suit+","+this.rank+"}";
    }
}



package WashCard;

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

public class CardDemo {
    public static final String[] suits={"♥","♠","♣","♦"};
    public List<Card> buyCard(){
        List<Card> cardList=new ArrayList<>();
        for (int i = 0; i < 13; i++) {
            for (int j = 0; j < 4; j++) {
                int rank=i;
                String suit=suits[j];
                Card card=new Card(suit,rank);
                cardList.add(card);
            }
        }
        return cardList;
    }
    public void shuffle(List<Card> cardList){
        Random random=new Random();
        for (int i = cardList.size()-1; i >0; i--) {
            int index=random.nextInt(i);
            swap(cardList,i,index);
        }
    }
    private void swap(List<Card> cardList,int i,int j){
        Card tmp=cardList.get(i);
        cardList.set(i,cardList.get(j));
        cardList.set(j,tmp);
    }
    public List<List<Card>> play(List<Card> cardList){
        List<Card> hand0=new ArrayList<>();
        List<Card> hand1=new ArrayList<>();
        List<Card> hand2=new ArrayList<>();
        List<List<Card>> hand=new ArrayList<>();
        hand.add(hand0);
        hand.add(hand1);
        hand.add(hand2);
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 3; j++) {
                Card card=cardList.remove(0);
                hand.get(j).add(card);
            }

        }
        return hand;
    }
}


package WashCard;

import java.util.List;

public class Test {
    public static void main(String[] args) {
        //1.买52张牌
        CardDemo cardDemo=new CardDemo();
        List<Card> cardList=cardDemo.buyCard();
       // System.out.println(cardList);
        //2.洗牌
        cardDemo.shuffle(cardList);
        //3.3个人  每个人   轮流揭牌5张
        List<List<Card>> hand=cardDemo.play(cardList);
        for (int i = 0; i < hand.size(); i++) {
            System.out.println("第"+(i+1)+"个人的牌有这些:  "+hand.get(i));
        }
    }
}

九、ArrayList的问题及思考

1. ArrayList 底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈 2 倍的增长,势必会有一定的空间浪费。例如当前容量为 100 ,满了以后增容到 200 ,我们再继续插入了5 个数据,后面没有数据插入了,那么就浪费了 95 个数据空间。
思考 : 如何解决以上问题呢?

http://www.kler.cn/news/328665.html

相关文章:

  • Android-Handle消息传递和线程通信
  • LeetCode 面试经典150题 66.加一
  • 【数据类型】C和C++的区别
  • 【C#生态园】探索地理信息系统软件套件与库:功能、API和应用
  • CSS | CSS中强大的margin负边距
  • 深度学习中的卷积神经网络
  • Ubuntu安装Docker和Docker Compose
  • 高性价比PCB分板机高速主轴SycoTec 4025 HY
  • LeetCode 面试经典150题 172.阶乘后的零
  • PCL 最远点采样(FPS)
  • 微服务SpringSession解析部署使用全流程
  • 10.数据结构与算法-线性表的应用(线性表与有序表的合并)
  • 【K8S系列】深入解析 Kubernetes 网络策略(二)
  • Redis篇(Java操作Redis)
  • 微服务JSR303解析部署使用全流程
  • tailwindcss group-hover 不生效
  • Spring Boot驱动的足球青训俱乐部管理解决方案
  • 鹏哥C语言62---第9次作业:函数递归练习
  • 2025 年 IT 前景:机遇与挑战并存,人工智能和云计算成重点
  • 【Android 源码分析】Activity生命周期之onPause
  • local minima 的问题如何解决
  • .Net 基于IIS部署blazor webassembly或WebApi
  • 用Python+flask+mysql等开发的Excel数据资产落地工具
  • 【一文读懂】C#如何实现通用的排序功能
  • 车辆重识别(利用扩散模型合成有效数据进行行人再识别预训练)论文阅读2024/9/27
  • 【树莓派系列】树莓派首次开机配置
  • LeetCode 面试经典150题 50.Pow(x,n)
  • VMware 设置静态IP
  • 鸿蒙开发(NEXT/API 12)【硬件(取消注册智慧出行连接状态的监听)】车载系统
  • 记录Mybatis分页查询排序问题: Encountered unexpected token: “and“ “AND“