赞
踩
原理:
1.先讲底层实现
2.讲怎么用
顺序表就是一个动态扩容数组,也就是在数组上完成增删查改,如下图:
这里肯定有人说,既然数组能直接表示一个顺序表,那直接用数组不就得了吗?
既然这样,我们来看一个问题:
有一组数据如下:
如果我们不直接把他封装到数据表里面,单纯的用数组会有一个问题。如下把前三个数放到数组里面。
问题1:当前数组里面有多少个有效的数据。这里肯定会有人脱口而出,三个。那我问你,你如何通过程序去计算呢?这里肯定有人又说,肯定是遍历,遍历到0就停止,如果这样说就有问题了,假如下标3就为0呢,那就把他丢掉了。所以存在即合理。所以我们要想办法 ,通过其他的东西和这个数组结合,达到一个目的,比如说,数组里面放了三个元素,我就知道放了三个。
(用顺序表的目的,假如我们有10个数组,放了三个,我怎么知道它放了三个)
这里给两个类 Test(在这个里面进行测试),MyArrayList(定义类),一个接口IList。
- package mylist;
- //这个是定义的接口
- 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();
- }
-

这里在MyArrayList里面定义一个数组elem,对他进行初始化,如下:
- public int[] elem;
-
- /构造方法(分配内存)
- public MyArrayList(){
- this.elem=new int[10];/直接对这个数组进行初始化
- }
这里我们要想,假如在这10个数组里面放了两个元素,那怎么知道我放了两个呢?
很简单,这里定义一个变量叫useSize,默认为0,每当存储一个数据的时候,useSize就加加。这里useSize配合数组,我们就知道这个数组里面有几个元素。
这里把上面代码进行优化如下:
- public int[] elem;
- public int usedSize;//默认为0
-
-
- //优化
- //代表顺序表的默认大小
- public static final int DEFAULT_SIZE=10;
- //1.分派内存,她一定会调用构造方法,在构造方法里面直接进行初始化(这样写更规范)
- public MyArrayList(){
- this.elem=new int[DEFAULT_SIZE];
- }
这里再给一个构造方法:
- public MyArrayList(int capacity){//这个更灵活
- this.elem=new int[capacity];
- }
-
-
- capacity相当于size(大小)
-
- 假如public MyArrayList(int capacity)中capacity给个10,this.elem=new int[capacity]就默认是10
接下来的目标,如果这个数组里面有数据,要怎么操作这些数据呢。在或者说顺序表里面一个元素都没有的情况下,怎么往进放元素...(要么里面没元素,要么里面有元素放往哪放,存往那存,取从那取..)
接着往下看,如果我们想遍历elem这个数组,并不是把整个数组遍历一遍,而是有几个就遍历到哪里,如第一幅图:
这里有三个元素,遍历到下标2就停止.
所以我们假设这个数组里面有元素,我们要打印有多少个元素。用如下这个方法:
- @Override
- public void display() {
-
- }
- 这个方法的作用是遍历顺序表当中的元素
有多少个元素都在usedSize里面,当然在写这个方法的时候,数组里面一个元素都没有。所以在遍历的时候i要小于usedSize,当然在目前场景下是usedSize为0。在如下的场景下是3.
那么0到3是不是就相当于遍历3次,实现代码如下:
- @Override
- public void display() {
- for(int i=0;i<this.usedSize;i++){
- System.out.print(this.elem[i]+" ");//每次直接输出.elem[i]+" "的值
- }
- System.out.println();//回车
- }
这里我们知道怎么打印这个数组里面的东西了,那怎么往这个数组里面放东西呢?这里我们提供了两个方法
- @Override
- public void add(int data) {
-
- }
-
- @Override
- public void add(int pos, int data) {
-
- }
这两个方法参数列表不一样,构成了方法的重载。
通过接口的注释,我们可以看到,新增元素,默认在数组最后新增
那也意味着,假设 下面这幅图
本来有三个元素,现在要往进放个65,往哪放?
这里就要注意了,并不是直接放到3的下标,然后加加就好,如下:
- @Override
- public void add(int data) {
- this.elem[this.usedSize]=data;
- this.usedSize++;
- }
而是先要判断这个数组是否满了,如下是判断是否满了这个方法
- @Override
- public boolean isFull() {
-
- }
那什么时候是满,这里我们就要想,如果usedSize 他的大小已经和数组长度都一样了,你还能放吗,所以在这个方法里面就要判断,如下代码:
- public boolean isFull() {
-
- // if(usedSize == elem.length) {
- // return true;
- // }
- // return false;
-
-
- //优化
- return usedSize==elem.length;//他们相等true,不相等flase
- }
所以在add里面要进行判断,如果这个数组满了,就要进行扩容,那么扩容怎么扩(比如说现在长度是10,要扩容成20,那就相当于elem来说,他现在这个数组,他要指向新的数组且长度是2倍且要把原来的数组拷贝过来),那要怎么扩容,是不是直接调用Arrays.copyOf()这个方法,然后elem重新指向拷贝之后的数组。
如下:
- public void add(int data) {
- if(isFull()){//如果满了在这里面进行扩容
- elem=Arrays.copyOf(elem,elem.length*2);
- }
- this.elem[this.usedSize]=data;
- this.usedSize++;
- }
当我们扩容之后,在往进放,肯定能放下。
接着往下看,上面这个add默认放到了数组最后,那接下来我们看王指定位置放。
当然,第一步和上面一样,他如果是满的,就进行扩容。
而且,我们也可以自己写一个扩容方法,如下:
- private void checkCapacity() {
- if(isFull()){//如果满了在这里面进行扩容
- elem=Arrays.copyOf(elem,elem.length*2);
- }
- }
此时可以将上面两个方法中改一下:
- @Override
- public void add(int data) {
-
- // if(isFull()){//如果满了在这里面进行扩容
- // elem=Arrays.copyOf(elem,elem.length*2);
- // }
-
- checkCapacity();
- this.elem[this.usedSize]=data;
- this.usedSize++;
- }
-
-
-
- @Override
- public void add(int pos, int data) {
- checkCapacity();
- }
-
-
- //自己写的扩容方法
- private void checkCapacity() {
- if(isFull()){//如果满了在这里面进行扩容
- elem=Arrays.copyOf(elem,elem.length*2);
- }
- }

测试代码如下:
- public class Test {
- public static void main(String[] args) {
-
- MyArrayList myArrayList=new MyArrayList();
- myArrayList.add(1);
- myArrayList.add(2);
- myArrayList.add(3);
- }
-
-
- }
这里肯定会有人问,为什么要把 checkCapacity()方法定义成 private呢?其实很简单,因为checkCapacity()检查容量问题,他是我们在做这些功能的时候包含的,他不是提供给用户去用的。他是只为当前类中的方法进行服务,不为其他类服务,所以要把它分装起来。
再来回到第二个add方法中,如果这个数组没满,就往里面放(往pos位置放),如果没满,就扩容,但是,我们这里就要注意,我们并不知道pos位置在哪,以下图为例
如果pos要往1下标放,就要把1和2下标上的数据移开,不然就会覆盖,如果往3下标放,可以,如果往-1下标放呢?当然不可以。那如果往4位置放呢?当然也不可以,因为在数据结构当中,每次存储数据的时候,一定记住,必须有一个前驱信息(意思就是,如果往4下标插行不行,取决于你有没有往3下标插过,如果没有,4下标是不能插的),所以在如上图这个合法范围是0<=pos<=3(大于等于0,小于等于3),而3刚好是usedSize,所以0<=pos<=usedSize,所以pos在这个范围类一定是合法的。所以我们要在检查容量之前,先检查下标,如下:
- @Override
- public void add(int pos, int data) {
- //这里对pos进行检查
- if(pos<0 || pos>usedSize) {//在这种情况下不合法
- System.out.println("不合法!");
- return;
- }
- checkCapacity();
- }
我们也可以自己写个检查pos的合法性的方法,如下:
- private void checkPosOnAdd(int pos) {
- if (pos < 0 || pos > usedSize) {//在这种情况下不合法
- System.out.println("不合法!");
- }
- }
这里我们要知道,不合法要返回一个值,接收一下返回值,返回,然后结束这个方法,不能再继续往下执行了,而且如上,打印一个不合法这件事情,当打印的比较多的时候,可能会找不到,那应该怎么办呢?这个时候就有一个东西,抛一个异常。
也就是说,在如下调用checkPosOnAdd(pos)检查异常的时候,如果这里pos不对就抛异常。
- public void add(int pos, int data) {
- checkPosOnAdd(pos);
- checkCapacity();
- }
这个异常相当于pos下标异常,这里自己写一个异常,如下:
- //用来抛pos不合法的异常
- //RuntimeException:运行时错误
- public class PosIllegality extends RuntimeException {
- public PosIllegality(String msg) {
- super(msg);
- }
- }
在如下就插入下标异常,然后声明一下,在调用的时候可能有这个异常,所以在调用的时候要小心。
- //自己写的检查pos的方法,专门用来检查pos的合法性
- private void checkPosOnAdd(int pos) {
- if (pos < 0 || pos > usedSize) {//在这种情况下不合法
- System.out.println("不合法!");
- throw new PosIllegality("插入元素下标异常" + pos);
- }
- }
当然add里面也要声明一下,如下:
- @Override
- public void add(int pos, int data) {
- //这里对pos进行检查
- // if(pos<0 || pos>usedSize) {//在这种情况下不合法
- // System.out.println("不合法!");
- // return;
- // }
- try{
- checkPosOnAdd(pos);
- }catch(PosIllegality e){
- e.printStackTrace();
- }
-
- checkCapacity();
- }
-

这段代码的意思是,对pos检查是否合法,如果合法,容量也够了,就可以往进插了。这里我们先来检验一下不合法时抛得异常。如下:
处理异常直接ruturn即可,如下:
- @Override
- public void add(int pos, int data) {
- //这里对pos进行检查
- // if(pos<0 || pos>usedSize) {//在这种情况下不合法
- // System.out.println("不合法!");
- // return;
- // }
- try{
- checkPosOnAdd(pos);
- }catch(PosIllegality e){
- e.printStackTrace();
- return;
- }
-
- checkCapacity();
- }

把上面说的都完成之后,接下来我们看怎么插入, 还是来看这幅图,如果要把pos放到1下标,就要移元素,把36和25给移开。
这里我们就要想,那怎么移动呢?
这里我们只能从最后一个元素25开始移动,如果移动36的话,往后移动,就会把25给覆盖了,这肯定是不行的。为了方便理解,这里把把2下标定义成i,而这里代码实现的逻辑就是[i+1]=[i],然后进行i减减。直到 i<pos 即可(如果当i>=pos的时候,还要继续减减 ),然后把pos放到下标1的位置。代码如下:
- @Override
- public void add(int pos, int data) {
- //这里对pos进行检查
- // if(pos<0 || pos>usedSize) {//在这种情况下不合法
- // System.out.println("不合法!");
- // return;
- // }
- try{
- checkPosOnAdd(pos);
- }catch(PosIllegality e){
- e.printStackTrace();
- return;
- }
-
- checkCapacity();
-
- //第一步:从最后一个有效数据开始往后移动,第二步:当i<pos就结束
- for (int i=usedSize-1;i>=pos;i--){
- elem[i+1]=elem[i];
- }
- //第三步:存放元素到pos位置
- elem[pos]=data;
- //第四步:usedSize++
- usedSize++;
- }

代码结果:
如上就是往任意位置放的整个逻辑。
上面我们把add写完了,剩下的就比较简单了,接着往下看contains,如下:
判断是否包含某个元素之前,我们想一下,如果是空的,那要不要判断呢?当然,如果是空的,就不需要判断,因此,在这里我们新增一个方法,如下:
- @Override
- public boolean isEmpty() {
- return usedSize==0; //当usedSize==0的时候为空
- }
contains的代码实现如下:
- @Override
- public boolean contains(int toFind) {
- if (isEmpty()) {
- return false;//如果为空,直接return false即可
- }
- //如果不为空,直接遍历这个数组即可
- for (int i = 0; i < usedSize; i++) {
- if (elem[i] == toFind) {//如果elem[i]等于你要找的这个值,返回true,否则返回false
- return true;
- }
- }
- return false;
- }
这里我们测试一下,看是否包含1.
结果是正确的,说明包含。
这里我们看一下代码中用红色箭头标注的这个步骤
注意:如果这里查找引用数据类型,一定要重写方法。
接下来看indexOf()。
indexOf()也是查找,只不过它查找的的是位置。这里按照上面的逻辑,但是这里如果是空的返回-1,因为正常的数组下标没有负数,代码如下:
- @Override
- public int indexOf(int toFind) {
- if (isEmpty()) {
- return -1;//如果为空,直接return false即可
- }
- //如果不为空,直接遍历这个数组即可
- for (int i = 0; i < usedSize; i++) {
- if (elem[i] == toFind) {//如果elem[i]等于你要找的这个值,返回true,否则返回false
- return i;
- }
- }
- return -1;
- }
接下来我们看get()方法。
再来看这幅图。
如图,如果pos是这副图上0,1,2中任意一个下标,则都可以获取,如果要获取的pos是负数,或者是下标3,则不能获取。此时上面检测pos是否合法的那个方法就不能用了,这里重新定义一个检查pos是否合法的方法 checkPosOnAddGet(),如下:
- private void checkPosOnAddGet(int pos) throws PosIllegality {
- if (pos < 0 || pos >= usedSize) {//在这种情况下不合法
- System.out.println("不合法!");
- throw new PosIllegality("获取指定下标元素异常" + pos);
- }
- }
所以,在get这个方法中,先要检查一下pos 的合法性,然后看他是否为空,如果为空,就抛一个异常,如下定义的异常和代码:
- @Override
- public int get(int pos) {
- checkPosOnAddGet(pos);
- //如果是空的,就抛一个异常
- if(isEmpty()){
- throw new MyArrayListEmpty("获取指定下标元素时"+"顺序表为空");
- }
- return elem[pos];
- }
-
接下来看set方法给 pos 位置的元素设为 value,就是说
还是下面这幅图
给 pos 位置的元素设为 value,意思就是比如pos在2下标,而2下标的数是56,现在把他改成159,所以set的意思就是更新的意思。
set的方法如下:
- @Override
- public void set(int pos, int value) {
-
- }
当然,这里我们还是要判断pos合不合法 ,这里就和上面 checkPosOnAddGet里面的代码一样,pos不能大于等于usedSize,这里我将这个方法改成checkPosOnAddGetSet,如下:
- private void checkPosOnAddGetSet(int pos) throws PosIllegality {
- if (pos < 0 || pos >= usedSize) {//在这种情况下不合法
- System.out.println("不合法!");
- throw new PosIllegality("获取指定下标元素异常" + pos);
- }
- }
接下来就简单了,直接让1下标的pos等于value.,代码如下:
- @Override
- public void set(int pos, int value) {
- checkPosOnAddGetSet(pos);
- elem[pos]=value;
- }
这里看一下测试结果:
接下来看size
对于size就更简单,直接如下代码即可
- @Override
- public int size() {
- return this.usedSize;
- }
接着看remove
还是如下图,假如这是数据表
假如我们现在要删除36,首先,我们先要找一下放36在哪里个位置,然后删除36就很简单。
比如我们找到36在下标1的位置,我们要删36,之后让25往前移动,然后修改usedSize(减减)数据即可。
- 三步:
- 1.找到要删除的数据
- 2.挪动数据
- 3.修改size
-
-
- @Override
- public void remove(int toRemove) {
- int index=indexOf(toRemove);
- if(index==-1){
- System.out.println("没有这个数字!");
- }
- //如果index不等于-1
- for(int i=index;i<usedSize-1;i++){
- elem[i]=elem[i+1];
- }
- usedSize--;
- }

这个代码中, for(int i=index;i<usedSize-1;i++)中 ,如果如上图,有0到5个下标,假如
它每个下标都有元素,一定要;i<usedSize-1,如果i<used Size,则可能会越界。
结果如下:
接下来看clear
还是这幅图
清空是指把上面图中的3个元素重置,那该怎么做呢?
这个是要分情况的,对于当前这里面如果放的是基本数据类型,直接将usedSize置为i0,如上图,如果usedSize为0,再来一个新的数据也没关系,因为新数据会把 21盖掉,因为他每次都在往usedSize位置放,所以对于当前代码,直接将usedSize=0就好,不管它里面有些撒数据,没关系。
代码结果如下:
这里要注意一个问题,像上面这个数组是int类型的数组,如果现在把他改成引用类型的数组,此时,要把数组清空,在这里usedSize就不可以等于0了,如果把他置为0,这里就出现了内存泄漏的问题。所以如果是引用类型,就通过for循环一个一个将数组置为null。
所有的代码如下:
- package mylist;
- //定义的类
- import javax.xml.bind.annotation.XmlType;
- import java.util.Arrays;
-
- public class MyArrayList implements IList {
- //创建一个方法,可以知道在顺序表中放了几个元素,如下:
- public int[] elem;
- //2.怎么分辨他放了几个元素呢?重新定义一个变量,叫usedSize,它每次存一个数字叫加一
- public int usedSize;//默认为0
- //优化
- public static final int DEFAULT_SIZE=10;
- //1.分派内存,她一定会调用构造方法,在构造方法里面直接进行初始化(这样写更规范)
- public MyArrayList(){
-
- this.elem=new int[DEFAULT_SIZE];
- }
- public MyArrayList(int capacity){//这个更灵活
-
- this.elem=new int[capacity];
- }
-
- @Override
- public void display() {
- for(int i=0;i<this.usedSize;i++){
- System.out.print(this.elem[i]+" ");
- }
- System.out.println();
- }
- @Override
- public void add(int data) {
- // if(isFull()){//如果满了在这里面进行扩容
- // elem=Arrays.copyOf(elem,elem.length*2);
- // }
- checkCapacity();
- this.elem[this.usedSize]=data;
- this.usedSize++;
- }
-
- @Override
- public boolean isFull() {
- // if(usedSize == elem.length) {
- // return true;
- // }
- // return false;
- //优化
- return usedSize==elem.length;
- }
-
- @Override
- public void add(int pos, int data) {
- //这里对pos进行检查
- // if(pos<0 || pos>usedSize) {//在这种情况下不合法
- // System.out.println("不合法!");
- // return;
- // }
- try{
- checkPosOnAdd(pos);
- }catch(PosIllegality e){
- e.printStackTrace();
- return;
- }
-
- checkCapacity();
-
- //第一步:从最后一个有效数据开始往后移动,第二步:当i<pos就结束
- for (int i=usedSize-1;i>=pos;i--){
- elem[i+1]=elem[i];
- }
- //第三步:存放元素到pos位置
- elem[pos]=data;
- //第四步:usedSize++
- usedSize++;
- }
-
- //自己写的检查pos的方法,专门用来检查pos的合法性
- private void checkPosOnAdd(int pos) throws PosIllegality {
- if (pos < 0 || pos > usedSize) {//在这种情况下不合法
- System.out.println("不合法!");
- throw new PosIllegality("插入元素下标异常" + pos);
- }
- }
- //自己写的扩容方法
- private void checkCapacity() {
- if(isFull()){//如果满了在这里面进行扩容
- elem=Arrays.copyOf(elem,elem.length*2);
- }
- }
-
- @Override
- public boolean contains(int toFind) {
- if (isEmpty()) {
- return false;//如果为空,直接return false即可
- }
- //如果不为空,直接遍历这个数组即可
- for (int i = 0; i < usedSize; i++) {
- if (elem[i] == toFind) {//如果elem[i]等于你要找的这个值,返回true,否则返回false
- return true;
- }
- }
- return false;
- }
-
- @Override
- public boolean isEmpty() {
- return usedSize==0;
- }
-
- @Override
- public int indexOf(int toFind) {
- if (isEmpty()) {
- return -1;//如果为空,直接return false即可
- }
- //如果不为空,直接遍历这个数组即可
- for (int i = 0; i < usedSize; i++) {
- if (elem[i] == toFind) {//如果elem[i]等于你要找的这个值,返回true,否则返回false
- return i;
- }
- }
- return -1;
- }
-
- @Override
- public int get(int pos) {
- checkPosOnAddGet(pos);
- //如果是空的,就抛一个异常
- if(isEmpty()){
- throw new MyArrayListEmpty("获取指定下标元素时"+"顺序表为空");
- }
- return elem[pos];
- }
-
- private void checkPosOnAddGet(int pos) throws PosIllegality {
- if (pos < 0 || pos >= usedSize) {//在这种情况下不合法
- System.out.println("不合法!");
- throw new PosIllegality("获取指定下标元素异常" + pos);
- }
- }
-
- @Override
- public void set(int pos, int value) {
- checkPosOnAddGetSet(pos);
- elem[pos]=value;
- }
- private void checkPosOnAddGetSet(int pos) throws PosIllegality {
- if (pos < 0 || pos >= usedSize) {//在这种情况下不合法
- System.out.println("不合法!");
- throw new PosIllegality("获取指定下标元素异常" + pos);
- }
- }
-
- @Override
- public void remove(int toRemove) {
- int index=indexOf(toRemove);
- if(index==-1){
- System.out.println("没有这个数字!");
- }
- //如果index不等于-1
- for(int i=index;i<usedSize-1;i++){
- elem[i]=elem[i+1];
- }
- usedSize--;
- }
-
- @Override
- public int size() {
- return this.usedSize;
- }
-
- @Override
- public void clear() {
- this.usedSize=0;
- }
-
-
- }

- package mylist;
- //在这个里面进行测试
- public class Test {
- public static void main(String[] args) {
-
- MyArrayList myArrayList=new MyArrayList();
- myArrayList.add(1);
- myArrayList.add(2);
- myArrayList.add(3);
- myArrayList.add(1,199);
- myArrayList.display();
- System.out.println(myArrayList.contains(1));
- myArrayList.set(2,159);
- myArrayList.display();
- myArrayList.remove(1);
- myArrayList.display();
-
- myArrayList.clear();
- System.out.println("============");
- myArrayList.display();
-
- }

- package mylist;
- //第一个异常
- //用来抛pos不合法的异常
- //RuntimeException:运行时错误
- public class PosIllegality extends RuntimeException {
- public PosIllegality(String msg) {
- super(msg);
- }
- }
- package mylist;
- //第二个异常
- public class MyArrayListEmpty extends RuntimeException{
- public MyArrayListEmpty(String msg){
-
- }
- }
接口在最开始就写了,这里就不写了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。