分分钟带你解决数据结构问题---- List接口中的ArrayList

Posted 梦の澜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分分钟带你解决数据结构问题---- List接口中的ArrayList相关的知识,希望对你有一定的参考价值。

声明:博主gitee内附Java学习笔记

文章前言:

那么集合框架就是 Java 写好的一些数据结构,像是顺序表,链表,栈和队列……这些数据结构不需要我们去手动的进行实现,我们要做的就是去了解一下每个集合框架背后的数据结构,那么我们今天就去了解一下List接口中的ArrayList


文章目录:

1、预备知识----泛型
2、预备知识----包装类
3、正式开始---- ArrayList与顺序表
4、模拟底层实现ArrayList
5、ArrayList实战模拟实现扑克牌


1、预备知识----泛型

声明:在这里的泛型了解为主,后面会有专门的博客去解释泛型,这里只是简单的预备知识,以看得懂原码为主要目的



以上的代码会引发一定的问题:






由于使用泛型 E/T 不可以实例化数组,这里先提供一个可以使用,但不完全正确的写法,我们先看泛型使用的结果

由此我们开始探索泛型的意义:





正确书写代码需要使用到反射的知识,后面有专门的博客进行讲解,这里知道需要使用反射表示即可


2、预备知识----包装类

Java引入的一种特殊的类,即 8 种基本类型的包装类

注意:不是所有类都有包装类,包装类主要针对的是 8 种基本数据类型

初识包装类

那么有基本数据类型,为什么还需要有包装类?

使用包装类实现上述问题:

我们发现使用包装类去实现转换功能十分的简单,省去编写一个方法去实现转换的操作

所以,包装类的好处:

装包(装箱)与拆包(拆箱)

声明:利用 (Javap -c 文件名) 指令即可查看汇编代码

①、装包(装箱)

②、拆包(拆箱)

所以装箱和拆箱在底层是会帮助我们去做一些东西

那么显示的拆箱和装箱又是怎么样的呢

①、显示的装箱

由于 Integer 是一个类(包装类),所以显示的装箱也可以这样写:

②、显示的拆箱

相关的一道阿里面试题:

相同的数值为什么会存在两个结果 ?常规来说两个数值一致应该都会返回 true

所以把 valueOf 这个方法作为问题的一个切入点,Ctrl + 点击 valueOf 查看其原码

分析原码:

计算 cache 数组 范围:




所以数值在 [-128 ~ 127] 范围内就会直接返回数组中的该数值,不在范围内会实例化一个新的对象

所以:
第一种情况 a 和 b 是相同的,因为没有实例化新的对象,从缓存数组中取出来直接赋值给 a 和 b
第二种情况 a 和 b 不相同的,因为 129 超过范围实例化两个完全不同的对象,所以在比较的时候 a 和 b 是不相同的

3、正式开始---- ArrayList与顺序表

ArrayList简介:

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



ArrayList的使用:

①、实例化一个ArryaList

②、向 ArrayList 中添加数据(由于 ArrayList 重写了 toString 方法所以可以直接打印 list 即可)

循环遍历顺序表:

由于 ArrayList 实现了 Iterable 接口所以可以使用增强 for 循环进行打印

还可以使用迭代器进行打印

第二种针对于 List 的迭代器

有关 ListIterator 和 Iterator 的详解

声明该内容参考博客

①、Iterator 接口

forEachRemaining(Consumer<? super E> action) 为每个剩余元素执行给定的操作,直到所有的元素都已经被处理或行动将抛出一个异常
hasNext(): 如果迭代器中还有元素,则返回true。
next(): 返回迭代器中的下一个元素
remove(): 删除迭代器新返回的元素。

在使用 remove 方法的时候,一定注意要先把元素全部迭代出来后,再进行操作,否则会引发异常


注意:
  1. Iterator只能单向移动。
  2. Iterator.remove()是唯一安全的方式来在迭代过程中修改集合;如果在迭代过程中以任何其它的方式修改了基本集合将会产生未知的行为。而且每调用一次next()方法,remove()方法只能被调用一次,如果违反这个规则将抛出一个异常。

②、ListIterator接口

ListIterator是一个功能更加强大的, 它继承于Iterator接口,只能用于各种List类型的访问。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator, 还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。

ListIterator接口可以实现的功能:

  1. 双向移动(向前 / 向后遍历)
  2. 产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引.
  3. 可以使用set()方法替换它访问过的最后一个元素.
  4. 可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一个元素.

使用实例:(remove方法和上面介绍的使用方法一致)

两个接口之间的区别:

  1. ListIterator有add()方法,可以向List中添加对象,而Iterator不能
  2. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
  3. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
  4. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改

有关线程安全问题:

ArrayList 可实现的功能

①、插入元素

②、在某个位置插入元素

③、删除指定位置的元素

④、删除指定对象

⑤、获取指定下标位置的元素


⑥、更新指定下标位置的元素

⑦、清空顺序表

⑧、在顺序表中查找对应元素是否存在

⑨、查找元素在顺序表中的下标位置

⑩、按照给定下标范围分割顺序表

看一个问题:如果改变分割后的顺序表会影响到原来的顺序表吗

观察一个代码的有无问题


那大小为:0 的时候为什么也可以向顺序表中添加元素,那我们看一下 add 方法的源代码解决此问题



4、模拟底层实现ArrayList

搭建顺序表框架(参照源代码)

实现顺序表的操作(只实现前两个看得懂即可):

1、表尾添加数据

2、指定位置添加元素


5、ArrayList实战模拟实现扑克牌


1、搭建扑克牌框架(花色+大小)

2、提供合适的构造方法

3、花色和大小初始化的 Get Set 方法,可以供其他使用

4、重写 toString 方法方便一会打印花色和大小

扑克牌的基本框架搭建好后,进行玩牌三步走:

步骤一、买一副扑克牌(忽略大小王)

步骤二、买来的牌都是连着花色和大小的,所以我们需要进行洗牌

步骤三、洗好的牌,最后一步就是发到每个人的手中

整体代码:

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

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Lenovo
 * Date: 2022-01-05
 * Time: 13:49
 */
class Card
    private int sank;
    private String suit;

    public Card(int sank, String suit) 
        this.sank = sank;
        this.suit = suit;
    

    public int getSank() 
        return sank;
    

    public void setSank(int sank) 
        this.sank = sank;
    

    public String getSuit() 
        return suit;
    

    public void setSuit(String suit) 
        this.suit = suit;
    

    @Override
    public String toString() 
        return "["+this.suit+" "+this.sank+"]";
    

public class Test3 
    private static final String[] suits = "♥","♣","♦","♠";
    private static List<Card> buyCard()
        ArrayList<Card> list = new ArrayList<>();
        for (int i = 0; i < 4; i++) 
            for (int j = 1; j <= 13; j++) 
                list.add(new Card(j,suits[i]));
            
        
        return list;
    
    private static void swap(List<Card> cards, int i, int j)
        Card tmp  = cards.get(i);
        cards.set(i, cards.get(j));
        cards.set(j,tmp);
    
    private static void shuffle(List<Card> cards)
        int size = cards.size();
        for (int i = size - 1; i > 0 ; i--) 
            Random rd = new Random();
            int rand = rd.nextInt(i);
            swap(cards,i,rand);
        
    
    private static void sendCards(List<Card> cards)
        ArrayList<List<Card>> hand = new ArrayList<>();
        List<Card> hand1 = new ArrayList<>();
        List<Card> hand2 = new ArrayList<>();
        List<Card> hand3 = new ArrayList<>();
        hand.add(hand1);
        hand.add(hand2);
        hand.add(hand3);
        for (int i = 0; i < 5; i++) 
            for (int j = 0; j < 3; j++) 
                Card card = cards.remove(0);
                hand.get(j).add(card);
            
        
        System.out.println("第一个人牌:"+hand1);
        System.out.println("第二个人牌:"+hand2);
        System.out.println("第三个人牌:"+hand3);
        System.out.println("剩余的牌"+cards);
    
    public static void main(String[] args) 
        List<Card> cards = buyCard();
        //买牌
        System.out.println("买牌:"+cards);
        //洗牌
        shuffle(cards);
        System.out.println("洗牌:"+cards);
        //发牌
        sendCards(cards);
    



本章结束

有任何问题随时联系博主,尽全力解决,感谢支持!!!!!!

❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️

以上是关于分分钟带你解决数据结构问题---- List接口中的ArrayList的主要内容,如果未能解决你的问题,请参考以下文章

五分钟带你了解Java是如何从容而优雅地实现接口数据校验

几分钟带你解决数据结构问题-------单链表(超详细)

5分钟带你利用测试框架优化接口测试(干货)

5分钟带你理解一致性Hash算法。

带你学够浪:Go语言基础系列 - 10分钟学方法和接口

一文带你认识STL序列式容器--list