目录
1.List接口
1.1 List接口的集合关系网络
1.2 List的使用
2. ArrayList与顺序表
2.1 线性表
2.2 顺序表
2.3 ArrayList
2.3.1 ArrayList的集合关系网络
2.3.2 ArrayList的使用
2.3.2.1 ArrayList的构造方法
2.3.2.2 ArrayList的扩容机制逻辑示图如下:
2.3.2.3 常用方法
2.3.2.4 ArrayList的遍历
2.3.3 ArrayList的具体使用
2.3.3.1 字符串处理问题
2.3.3.2 杨辉三角
2.3.3.3 简单的洗牌算法
2.3.4 ArrayList特点
1.List接口
1.1 List接口的集合关系网络
在集合中,List是一个接口,继承自Collection,Collection也是一个接口;
在数据结构中,List是一个线性表,即n个具有想同类型元素的有限序列,在该序列上可以执行增删查改等操作;
1.2 List的使用
List是一个接口,并不能直接用来实例化,如果要使用就必须实例化List的实现类,在集合框架中,ArrayList和LinkedList都实现了List接口;
接下来将依次展示List的实现类使用方法,本篇为ArrayList;
2. ArrayList与顺序表
2.1 线性表
线性表是n个具有相同特性的数据元素的有限序列,线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列等等;
线性表在逻辑上是线性结构,即连续的一条直线,但在物理结构上不一定连续。
线性表在物理存储时通常以数组和链式的形式存储;
2.2 顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改;
下面展示顺序表常用接口的模拟实现:
(1)包类示图:
(2)MyArrayList:
package TestMyArrayList;
import java.util.Arrays;
public class MyArrayList {
public int[] elem;
public int useSize; // 记录顺序表存储的有效数据个数
public static final int DEFAULT_SIZE=10;
public MyArrayList(){
this.elem = new int[DEFAULT_SIZE];
}
// 成员方法1:打印顺序表
public void display(){
for(int i=0;i<this.useSize;i++){
System.out.print(this.elem[i]+" ");
}
}
// 成员方法2:获取顺序表长度
public int size(){
return this.useSize;
}
// 成员方法3:判定是否包含某个元素
public boolean contains(int toFind){
for(int i=0;i<this.useSize;i++){
if(elem[i]==toFind){
return true;
}
}
return false;
}
// 成员方法4:查找某个元素对应的位置
public int indexOf(int toFind){
for(int i=0;i<this.useSize;i++){
if(elem[i]==toFind){
return i;
}
}
return -1; // 数组没有负数下标
}
// 成员方法5:新增元素(默认放在数组最后位置)
public void add(int data){
// 判满
if(this.isFull()){
//扩容
this.resize();
}
elem[this.useSize]=data;
this.useSize++;
}
// 成员方法6:判满
public boolean isFull(){
// 写法1:
// if(this.useSize==this.elem.length){
// return true;
// }
// return false;
// 写法2:
return this.useSize==this.elem.length;
}
// 成员方法7:扩容
private void resize(){ // 扩容只针对当前顺序表,可以进行封装
this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
}
// 成员方法8:在pos位置新增元素
public void add(int pos, int data){
// 判pos是否合法
checkAddIndex(pos);
// 判满
if(isFull()){
this.resize();
}
for(int i=useSize-1;i>=pos;i--){
elem[i+1]=elem[i];
}
this.elem[pos]=data;
this.useSize++;
}
// 成员方法9:判插入数据时,pos是否合法
private void checkAddIndex(int pos){
if(pos<0||pos>this.useSize){
throw new AddIndexOutOfException("pos is illegal!");
}
}
// 成员方法10:获取pos位置的元素
public int get(int pos){
checkAddIndex(pos);
return this.elem[pos];
}
// 成员方法11:判获取数据时,pos是否合法
private void checkGetIndex(int pos){
if(pos<0||pos>=this.useSize){
throw new AddIndexOutOfException("pos is illegal!");
}
}
// 成员方法12:将pos位置的数据设置为value
public void set(int pos, int value){
checkGetIndex(pos);
this.elem[pos]=value;
}
// 成员方法13:删除第一次出现的关键字key
public boolean remove(int toRemove){
int index=indexOf(toRemove);
if(index==-1){
System.out.println("data is non existent!");
return false;
}
for(int i=index;i<this.useSize-1;i++){
// useSize-1可保证不越界
elem[i]=elem[i+1];
}
this.useSize--;
this.elem[this.useSize]=0; // 若是引用类型则置为null
return true;
}
// 成员方法14:清空顺序表
public void clear(){
for(int i=0;i<this.useSize;i++){
this.elem[i]=0;
}
this.useSize=0;
}
}
(3)GetIndexOutOfException
package TestMyArrayList;
public class GetIndexOutOfException extends RuntimeException{
public GetIndexOutOfException(){
}
public GetIndexOutOfException(String message){
super(message);
}
}
(4)AddIndexOutOfException
package TestMyArrayList;
public class AddIndexOutOfException extends RuntimeException{
public AddIndexOutOfException(){
}
public AddIndexOutOfException(String message){
super(message);
}
}
2.3 ArrayList
2.3.1 ArrayList的集合关系网络
说明:
(1)ArrayList是以泛型方式实现的,使用时必须实例化;
(2)ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问;
(3)ArrayList实现了Clone接口,表明ArrayList是可以clone的;
(4)ArrayList实现了Serializable接口,表明ArrayList是支持序列化的;
(对象->字符串:序列化;字符串->对象:反序列化)
(5)相比于Vector,ArrayList不是线程安全的,在单线程下可使用,在多线程中可以选择Vector或者CopyOnWriteArrayList;
(6)ArrayList底层是一段连续空间,并且可以动态扩容,是一个动态类型的顺序表;
2.3.2 ArrayList的使用
2.3.2.1 ArrayList的构造方法
ArrayList的构造方法有以下三种:
示例代码如下:
(1)构造方法1:(无参构造器)
// 构造方法1:
ArrayList<Integer> arrayList1 = new ArrayList<>();
arrayList1.add(97);
arrayList1.add(8);
arrayList1.add(5);
System.out.println(arrayList1);
输出结果为:
(2)构造方法2:(带有初始容量的构造方法)
// 构造方法2:
ArrayList<Integer> arrayList2 = new ArrayList<>(20);
arrayList2.add(1997);
arrayList2.add(85);
arrayList2.add(25);
System.out.println(arrayList2);
输出结果为:
(3)构造方法3:
// 构造方法3:
LinkedList<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
list.add(3);
ArrayList<Integer> arrayList3 = new ArrayList<>(list);
ArrayList<Number> arrayList4 = new ArrayList<>(list);
System.out.println(arrayList3);
System.out.println(arrayList4);
输出结果为:
第三种构造方法的含义为:将另外一个实现了Collection接口的具体类作为构造ArrayList的具体参数,
且满足:该具体类的泛型参数是ArrayList实例化对象的泛型参数本身或其子类,
即可将该类中的所有元素添加至ArrayList实例化的该对象中来;
2.3.2.2 ArrayList的扩容机制逻辑示图如下:
简而言之:
在实例化ArrayList对象进行new操作时,其实并没有为该对象分配内存,但在第一次进行add时,会调用grow方法为其分配长度为10的内存大小;
且扩容方式为1.5倍扩容法;
2.3.2.3 常用方法
方法 | 解释 |
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 toIndex) | 截取部分list |
方法示例:
(1)E remove(int index) 与 boolean remove(Object o):
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(2);
arrayList.add(3);
System.out.println(arrayList);
arrayList.add(0,1);
System.out.println(arrayList);
arrayList.remove(2);
System.out.println(arrayList);
arrayList.remove(new Integer(1));
System.out.println(arrayList);
输出结果为:
(2)List<E> subList(int fromIndex,int toIndex):(注意接收类型为List而非ArrayList)
ArrayList<Integer> arrayList1 = new ArrayList<>();
for(int i=0;i<10;i++){
arrayList1.add(i*2+1);
}
System.out.println(arrayList1);
List<Integer> arrayList2 = arrayList1.subList(3,6);
System.out.println(arrayList2);
输出结果为:
注:原ArrayList对象arrayList1和截取的子串arrayList2共用一个elemData数组,修改元素时二者相互影响:
ArrayList<Integer> arrayList1 = new ArrayList<>();
for(int i=0;i<10;i++){
arrayList1.add(i*2+1);
}
System.out.println("Before modify: ");
System.out.println("arrayList1: "+arrayList1);
List<Integer> arrayList2 = arrayList1.subList(3,6);
System.out.println("arrayList2: "+arrayList2);
arrayList2.set(0,999);
System.out.println("After modify: ");
System.out.println("arrayList1: "+arrayList1);
System.out.println("arrayList2: "+arrayList2);
输出结果为:
2.3.2.4 ArrayList的遍历
方法1:直接输出:
ArrayList类并没有重写toString方法,其父类AbstractList也没有重写toString方法,但是List接口有toString方法,故而可以直接使用System.out.println()方法输出ArrayList对象;
上文代码已有示例,此处不再赘述;
方法2:for循环:
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1997);
arrayList.add(8);
arrayList.add(5);
arrayList.add(25);
for(int i=0;i<arrayList.size();i++){
System.out.print(arrayList.get(i)+" ");
}
输出结果为:
方法:3:for-each:
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1997);
arrayList.add(8);
arrayList.add(5);
arrayList.add(25);
for(Integer x:arrayList){
System.out.print(x+" ");
}
输出结果为:
方法4:迭代器:
迭代器方法源码如下:
试运行以下代码:
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1997);
arrayList.add(8);
arrayList.add(5);
arrayList.add(25);
ListIterator<Integer> it = arrayList.listIterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
// 打印it的同时令it前行一步
}
输出结果为:
2.3.3 ArrayList的具体使用
2.3.3.1 字符串处理问题
题目描述:现有字符串s1:"Welcome to java"和字符串s2:"come",试将s1中包含的s2的字符删去后的结果;
思路:遍历字符串s1,当s2不包含s1当前的字符则添加至一个ArrayList对象中,输出该对象结果即可;
代码如下:
public static void main(String[] args) {
List<Character> list = new ArrayList<>();
String s1 = "Welcome to java";
String s2 = "come";
for(int i=0;i<s1.length();i++){
char ch = s1.charAt(i);
if(!s2.contains(ch+"")){
// 字符串的包含操作参数为CharSequence,String类也实现了CharSequence接口
// 故而需要将获取到的s1中的字符处理为字符串
list.add(ch);
}
}
for(Character x:list){
System.out.print(x);
}
}
输出结果为:
2.3.3.2 杨辉三角
题目描述:
题目链接如下:118. 杨辉三角 - 力扣(LeetCode)
代码如下:
class Solution {
public List<List<Integer>> generate(int numRows) {
//List嵌套List即二维数组
List<List<Integer>> ret = new ArrayList<>();
List<Integer> row = new ArrayList<>();
row.add(1); // 初始化第一行(只有一个元素,为1)
ret.add(row); // 创建二维数组
for(int i=1;i<numRows;i++){
List<Integer> previousRow = ret.get(i-1); //获取上一行元素
List<Integer> currentRow = new ArrayList<>();
currentRow.add(1); // 每一行第一个元素为1
// 每一行非两端位置元素赋值
for(int j=1;j<i;j++){
int x = previousRow.get(j)+previousRow.get(j-1);
currentRow.add(x);
}
currentRow.add(1); // 每一行最后一个元素也为1
ret.add(currentRow);
}
return ret;
}
}
通过用例测试,参考输出示例如下:
2.3.3.3 简单的洗牌算法
(1)创建包类关系如下:
(2)Poker类:
package TestDemo1;
public class Poker {
private String suit; // 花色
private int rank; //数字
public Poker(String suit, int rank) {
this.suit = suit;
this.rank = rank;
}
public String getSuit() {
return suit;
}
public void setSuit(String suit) {
this.suit = suit;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
@Override
public String toString() {
return "{" + suit+" "+ rank+ "}";
}
}
(3) Game类:
package TestDemo1;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Game {
private static final String[] suits = {"♥","♣","♦","♠"};
public List<Poker> buyPoker(){
List<Poker> pokers = new ArrayList<>();
for(int i=0;i<4;i++){
for(int j=1;j<=13;j++){
String suit = suits[i]; // 花色
int rank = j; // 数字
Poker poker = new Poker(suit,j);
pokers.add(poker);
}
}
return pokers;
}
// 洗牌
public void shuffle(List<Poker> pokers){
for(int i=pokers.size()-1; i>0;i--){
Random random = new Random();
int index = random.nextInt(i);
Swap(pokers,i,index);
}
}
private void Swap(List<Poker> pokers,int i,int j){
Poker tmp = pokers.get(i);
pokers.set(i,pokers.get(j));
pokers.set(j,tmp);
}
// 揭牌:三个人轮流揭5张牌
// 每次删除pokers的0下标的元素,并依次添加至3个ArrayList对象中
public List<List<Poker>> game(List<Poker> pokers){
// 创建玩家List
List<List<Poker>> players = new ArrayList<>();
List<Poker> player1 = new ArrayList<>();
List<Poker> player2 = new ArrayList<>();
List<Poker> player3 = new ArrayList<>();
players.add(player1);
players.add(player2);
players.add(player3);
for(int i=0;i<5;i++){ // 控制揭牌揭5轮
for(int j=0;j<3;j++){ // 控制每个玩家每次揭牌揭一张
Poker currentPoker = pokers.remove(0);
// ArrayList的remove方法会将查找下标对应的元素返回
players.get(j).add(currentPoker);
}
}
return players;
}
}
(4)Test类:
package TestDemo1;
import java.util.List;
public class Test {
public static void main(String[] args) {
Game game = new Game();
List<Poker> pokers = game.buyPoker();
int count=1;
for(Poker x:pokers){
System.out.print(x+" ");
count++;
if(count%10==0){
System.out.println();
}
}
game.shuffle(pokers);
System.out.println();
System.out.println("------------------------------------------------------------------");
System.out.println("洗牌:");
count=1;
for(Poker x:pokers){
System.out.print(x+" ");
count++;
if(count%10==0){
System.out.println();
}
}
System.out.println();
System.out.println("------------------------------------------------------------------");
System.out.println("揭牌:");
List<List<Poker>> players = game.game(pokers);
for(int i=0;i<players.size();i++){
System.out.println("第"+(i+1)+"位玩家牌面:");
System.out.println(players.get(i));
}
System.out.println("------------------------------------------------------------------");
System.out.println("余牌牌面:");
count=1;
for(Poker x:pokers){
System.out.print(x+" ");
count++;
if(count%10==0){
System.out.println();
}
}
}
}
(5)输出结果为:
注:ArrayList类的remove方法源码:
可见其逻辑为:删除哪个下标的值,就将该下标的元素作为返回值进行返回;
2.3.4 ArrayList特点
优点:(1)当给定下标时,ArrayList的查找速度非常快。故而ArrayList适合给定下标的查找,复杂度为O(1);
缺点:(1)任意位置插入或删除元素时,需要大量挪动元素,故而ArrayList不适合应用于任意位置插入和删除较为频繁的场景;
(2)每次扩容时也面临着资源浪费的问题,同时申请新空间,拷贝数据再释放旧空间也有不小的消耗;