目录
[1.1 List的概念](#1.1 List的概念)
[1.2 常见接口的介绍](#1.2 常见接口的介绍)
[1.3 List的使用](#1.3 List的使用)
[2.1 线性表](#2.1 线性表)
[2.2 顺序表](#2.2 顺序表)
[4.1 ArrayList构造](#4.1 ArrayList构造)
[4.2 ArrayList的常用方法](#4.2 ArrayList的常用方法)
[4.3 ArrayList的遍历](#4.3 ArrayList的遍历)
[4.4 ArrayList的扩容机制](#4.4 ArrayList的扩容机制)
[5.1 简单洗牌算法](#5.1 简单洗牌算法)
[5.2 杨辉三角](#5.2 杨辉三角)
一、List及其使用
1.1 List的概念
在集合框架中,List是一个接口,继承自Collection。
Collection也是一个接口,该接口中规范了后序容器中常用的一些方法:
Iterable也是一个接口,表示实现该接口的类是可以逐个元素进行遍历的:
从数据结构角度看,List就是一个线性表,即n个具有相同类型元素的有限序列,在该序列上可以执行增删改查以及变量等操作。
1.2 常见接口的介绍
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 toIndex) | 截取部分list |
1.3 List的使用
List是个接口,并不能直接用来实例化 。如果要使用,必须去实例化List的实现类。在集合框架中,ArrayList和LinkedList都实现了List接口。
二、线性表和顺序表
2.1 线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列 。 线性表是一种广泛使用的数据结构,常见线性表有:顺序表、链表、栈、队列......
2.2 顺序表
顺序表是用一段物理地址连续的存储单元 依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
模拟实现顺序表中的常用函数
public interface IList { //新增元素,默认在数组最后新增 public void add(int data); // 在 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(); boolean isFull(); public boolean isEmpty(); }
public class MyArrayList implements IList{ private int[] elem; private int usedSize; //顺序表默认大小 private static int DEFAULT_Size=10; public MyArrayList(){ elem=new int[DEFAULT_Size]; } public MyArrayList(int capacity){ elem=new int[capacity]; DEFAULT_Size=capacity; } //添加一个数据 @Override public void add(int data) { chackCapacity(); this.elem[usedSize]=data; this.usedSize++; } //检查容量 private void chackCapacity(){ if(isFull()){ elem= Arrays.copyOf(elem,elem.length*2); } } //在pos插入一个数据 @Override public void add(int pos, int data) { try { chackPosOnAddOrGetOrSet(pos); }catch (PosError e){ e.printStackTrace(); return; } chackCapacity(); for (int i = usedSize; i >pos ; i++) { elem[i]=elem[i-1]; } elem[pos]=data; usedSize++; } /*检查pos的合法性*/ private void chackPosOnAddOrGetOrSet(int pos) throws PosError{ if(pos<0||pos>=this.usedSize) throw new PosError("下标异常"+pos); } //判断顺序表是否包含toFind @Override public boolean contains(int toFind) { if(isEmpty()) return false; for (int x:elem) { if(x==toFind) return true; } return false; } //获取toFing对应的下标 @Override public int indexOf(int toFind) { for (int i = 0; i < this.usedSize; i++) { if(elem[i]==toFind) return i; } return -1; } //获取pos下标对应的元素值 @Override public int get(int pos) { if(isEmpty()) return -1; chackPosOnAddOrGetOrSet(pos); return this.elem[pos]; } //设置下标为pos的元素值为value @Override public void set(int pos, int value) { chackPosOnAddOrGetOrSet(pos); elem[pos]=value; } //删除toRemove @Override public void remove(int toRemove) { int index=indexOf(toRemove); if(index<0){ System.out.println("要删除的数不存在"); return; } for(int i=index;i<this.usedSize-1;i++){ this.elem[i]=this.elem[i+1]; } this.usedSize--; } //获取当前顺序表的大小 @Override public int size() { return this.usedSize; } //清空 @Override public void clear() { for (int i = 0; i < usedSize; i++) { set(i,0); } } /*遍历顺序表当中的元素*/ @Override public void display() { if(isEmpty()){ throw new MyArrayIsEmpty("顺序表是空的"); } for(int i=0;i<elem.length;i++){ System.out.println(this.elem[i]+" "); } System.out.println(); } /*判断顺序表是否已满*/ @Override public boolean isFull() { /*if(usedSize==DEFAULT_Size) return true; else return false;*/ return usedSize==DEFAULT_Size; } @Override public boolean isEmpty() { return elem.length==0; } }
public class MyArrayIsEmpty extends RuntimeException{ public MyArrayIsEmpty(String massage){ super(massage); } }
public class PosError extends RuntimeException{ public PosError(String massage){ super(massage); } }
三、ArrayList介绍
在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架如下:
ArrayList是以泛型方式实现的,使用时必须要先实例化;ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问;ArrayList实现了Cloneable接口,表明ArrayList是可以clone的;ArrayList实现了Serializable接口,表明ArrayList是支持序列化的;ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList;ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表。
四、ArrayList的使用
4.1 ArrayList构造
|----------------------------------------|------------------------------|
| 方法 | 解释 |
| ArrayList() | 无参构造 |
| ArrayList(Collection<? extends E> c) | 利用其他 Collection 构建 ArrayList |
| ArrayList(int initialCapacity) | 指定顺序表初始容量 |
public static void main(String[] args) { //创建一个空列表 ArrayList() 默认size为0 ArrayList<Integer> list1=new ArrayList<>(); //创建一个容量为10的列表 ArrayList(int initialCapacity)创建指定容量的列表 //ArrayList<Integer> 指定存放的元素类型为Integer ArrayList<Integer> list2=new ArrayList<>(10); list1.add(1);//第一次add时,底层数组size为10 list1.add(2); list1.add(3); //ArrayList(Collection<? extends E> c) //参数c必须实现了Collection接口,是E的子类或E本身类型 //list3的元素与list2相同 ArrayList<Integer> list3=new ArrayList<>(list2); }
使用不带参数newArrayList对象时,默认size为0,当第一次add时,底层数组size为10,当数组需要扩容时,是以当前数组size的1.5倍进行扩容。
4.2 ArrayList的常用方法
ArrayList提供方法比较多,但常用方法如下所示:
|-----------------------------------------------|-------------------|
| 方法 | 解释 |
| boolean add(E e) | 尾插 e |
| void add(int index, E e) | 将 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 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 |
public static void main(String[] args) { ArrayList<Integer> list1=new ArrayList<>(10); list1.add(1); list1.add(2); list1.add(3); list1.add(4); System.out.println(list1);// 1 2 3 4 //获取list1中有效元素的个数并打印 System.out.println(list1.size());//4 System.out.println("===="); //获取和设置index位置的元素 System.out.println(list1.get(2));//3 list1.set(2,99); System.out.println(list1.get(2));//99 System.out.println("===="); //在index位置插入value list1.add(2,88); System.out.println(list1);//1 2 88 99 4 System.out.println("===="); //删除指定元素 list1.remove(new Integer(88)); System.out.println(list1);//1 2 99 4 //删除index位置的元素 index不能超过list1的有效元素个数 list1.remove(2); System.out.println(list1);// 1 2 4 System.out.println("===="); //检查list1中是否包含value System.out.println(list1.contains(99));//false System.out.println(list1.contains(2));//true System.out.println("===="); list1.add(2); list1.add(1); System.out.println(list1);//1 2 4 2 1 //获取第一次出现value的下标 System.out.println(list1.indexOf(1));//0 //获取最后一次出现value的下标 System.out.println(list1.lastIndexOf(1));//4 System.out.println("===="); //截取list1 List<E> subList(int fromIndex, int toIndex) List<Integer> list2=list1.subList(1,3); System.out.println(list2);//2 4 //截取的list2并没有分配内存,只是list2这个引用指向下标为1~2的元素 list2.set(0,10); System.out.println(list1);// 1 10 4 2 1 System.out.println(list2);//10 4 }
4.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); list.add(4); //for循环遍历 for(int i=0;i<list.size();i++){ System.out.print(list.get(i)+" "); } System.out.println(); //foreach遍历 for (Integer x:list){ System.out.print(x+" "); } System.out.println(); //迭代器遍历 Iterator<Integer> it=list.iterator(); while (it.hasNext()){ System.out.print(it.next()+" "); } }
4.4 ArrayList的扩容机制
ArrayList是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。以下是ArrayList源码中扩容方式:
检测是否需要扩容,如果需要调用grow函数;估计容量大小,初步估计按1.5倍扩容,若用户所需超过估计的1.5倍大小,按用户所需大小扩容;扩容前进行检测是否能扩容;使用copyOf进行扩容。
五、ArrayList的具体使用
5.1 简单洗牌算法
public class Card { //花色 private String design_and_color; //数值 private int value; public Card(String design_and_color, int value) { this.design_and_color = design_and_color; this.value = value; } @Override public String toString() { return "花色:"+design_and_color+" 数值:"+value; } public String getDesign_and_color() { return design_and_color; } public void setDesign_and_color(String design_and_color) { this.design_and_color = design_and_color; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } }
public class CardText { //花色集合 红桃 ♥ 黑桃 ♠ 方块♦ 梅花 ♣ public static final String[] design_and_colors= {"♥", "♣", "♠", "♦"}; //买一副牌 public static List<Card> buyCard(){ /*没有大小王,共52张牌,J Q K 分别用11 12 13代替*/ List<Card> card=new ArrayList<>(52); for (int i = 0; i < 4; i++) { for(int j=1;j<=13;j++){ Card card1=new Card(design_and_colors[i],j); card.add(card1); } } return card; } //洗牌 public static void brushCard(List<Card> cards){ for (int i = 51; i >0 ; i--) { Random random=new Random(i); int j=random.nextInt(i); swap(cards,i,j); } } //交换两张牌 public static <j> void swap(List<Card> cards, int i,int j){ Card tmp=cards.get(i); cards.set( i,cards.get(j)); cards.set( j,tmp); } public static void main(String[] args) { //取一副牌 List<Card> card=buyCard(); System.out.println("拿到一副牌:"); System.out.println(card); //洗牌 brushCard(card); System.out.println("洗牌后:"); System.out.println(card); //发牌 三人轮流拿牌,每人五张 List<List<Card>> hands=new ArrayList<>();//hands代表三人各自取的牌 hands.add(new ArrayList<>());//new ArrayList<>()代表每人手中的牌的集合 hands.add(new ArrayList<>()); hands.add(new ArrayList<>()); for(int i=0;i<5;i++){ for(int j=0;j<3;j++){ hands.get(j).add(card.remove(0)); } } System.out.println("第一个人手中的牌:"); System.out.println(hands.get(0)); System.out.println("第二个人手中的牌:"); System.out.println(hands.get(1)); System.out.println("第三个人手中的牌:"); System.out.println(hands.get(2)); System.out.println("剩余的牌:"); System.out.println(card); } }
5.2 杨辉三角
public List<List<Integer>> generate(int numRows) { List<List<Integer>> list=new ArrayList<>(); //设置第一行 List<Integer> list2=new ArrayList<>(); list2.add(1); list.add(list2); //设置剩余行 for(int i=1;i<numRows;i++){ //设置当前行 List<Integer> tmp=new ArrayList<>(); //设置当前行第一个数 tmp.add(1); //获取前一行 List<Integer> preRow=list.get(i-1); //设置当前行其他数 for(int j=1;j<i;j++){ int a=preRow.get(j-1)+preRow.get(j); tmp.add(a); } //设置当前行最后一个数 tmp.add(1); list.add(tmp); } return list; }
六、ArrayList的问题
-
ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后移动,时间复杂度为O(N)。
-
增容需要申请新空间,拷贝数据,释放旧空间,有不小的消耗。
-
增容一般是呈1.5倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到150,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了45个数据空间。