线性表,顾名思义就像一条线一样,线性表是有序的,这个“有序”不是从小到大之类的概念。当然与之对应就是散列表,散就是乱的,无序的。Java中List和Set,我们遍历List每次结果都是一样,这就是所谓的有序,遍历Set,每次出来的结果可能都不一样,这就是无序的。

 

数组是一种相同数据类型的元素组成的集合,是一种线性表,同样属于线性表结构的还有链表,队列,栈。

数组在内存需要连续的空间来存放,这就是为什么申明数组的时候一定要设置大小,因为只有设置了大小,才知道这个数组需要多大的内存空间,才能去内存中寻找大小合适的空间存放。正是因为它是连续的,而且都是有下标索引的,所以具有很好的随机访问性。

为什么数组具有很好的随机访问性?为什么数据可以根据下标访问?为什么下标要从0开始?

这就要说一下数组存储结构。

比如int[] arr = new int[2] 这个数组去内存开辟空间的时候,

假如arr在内存的位置是从1000开始的,那么

 

Arr[0] 在内存中的位置是   1000

Arr[1] 在内存中的位置是   1000 + arr[0]数据大小

Arr[2] 在内存中的位置是   1000 + arr[0]数据大小 + arr[1]数据大小

 

因为数组都是同一种数据类型所以每个元素的数据大小是一样的,换做如下表示就更清晰了

 

Arr[0] 在内存中的位置是   1000 + 0*dataSize(元素数据大小,比如int类型4个字节,long8个字节,对应在内存中的大小)

Arr[1] 在内存中的位置是   1000 + 1*dataSize

Arr[2] 在内存中的位置是   1000 + 2*dataSize

 

这样一来是不是每个元素的位置就刚好是索引位置乘以数据大小,这样我们就可以根据下标直接定位元素位置了。而且因为它是连续的空间,所以可以借助CPU的缓存机制,预读数据,索引访问效率更高。

但是有利就有弊,当我们想要插入和删除的时候,为了保证连续性,会做很大的搬迁工作。

而且容量有限,还需要注意数组越界问题。

当然如果数组长度声明的过大也会造成空间浪费。Java中的ArrayList就是用数组实现的,正因为如此,所以《effective java》中建议我们声明ArrayList预估容量,创建时就指定合适的容量,因为如果发生扩容,需要重新去创建一个更大的数组来存放,要寻找新的内存空间,一个是耗时,二来还要将原来数组的数据进行搬迁。

ArrayList是对数组进行了封装,提供更多的操作。如果不确定数据多少的情况下肯定选用ArrayList,如果确定了长度,而且有十分在乎性能,那就用数组。

简单实现一个自己的ArrayList,便于理解按照自己的逻辑写,并没有处理越界那些问题,和JDK的ArrayList里面的方法逻辑是有出入的:

 

package com.nijunyang.algorithm.list;
/**
 * Description:
 * Created by nijunyang on 2020/3/30 23:25
 */
public class MyArrayList<E>{

    private static final int DEFAULT_SIZE = 10;
    /**
     * 数据(数组存放)
     */
    private Object[] elements;
    /**
     * 当前存放到数组的index
     */
    private int currentIndex;
    /**
     * 总容量大小(数组长度)
     */
    private int size;

    public MyArrayList() {
        this.elements = new Object[DEFAULT_SIZE];
        this.size = DEFAULT_SIZE;
    }

    public MyArrayList(int size) {
        this.elements = new Object[size];
        this.size = size;
    }

    public void add(E element) {
        elements[currentIndex++] = element; //后++是先赋值了之后再++
        //放到最后就进行扩容 容量翻倍
        if (currentIndex == size) {
            this.size = this.size * 2 ;
            Object newData[] = new Object[this.size];
            for (int i = 0; i < elements.length; i++) {
                newData[i] = elements[i];
            }
            this.elements = newData;
        }
    }

    /**
     * 按索引移除
     * @param index
     */
    public void remove(int index) {
        if (index >= 0 && index < currentIndex) {
            //后面的元素来覆盖当前这个元素,然后后面的全部前移
            for (int j = index; j < this.elements.length - 1; j++) {
                elements[j] = elements[j + 1];
                //null 就后不用再移动了
                if (elements[j] == null) {
                    break;
                }
            }
            this.currentIndex--;
        }
    }

    public E get(int index) {
        if (index >= 0 && index < currentIndex) {
            return (E) this.elements[index];
        }
        return null;
    }

    public int size() {
        return currentIndex;
    }

    public String toString() {
        if (currentIndex == 0) {
            return "[]";
        }
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (int i = 0; i < currentIndex; i++) {
            if (i == currentIndex - 1) {
                sb.append(elements[i].toString());
                sb.append(']');
                break;
            }
            sb.append(elements[i].toString());
            sb.append(',');
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        MyArrayList<Integer> myArrayList = new MyArrayList<>(2);
        myArrayList.add(0);
        myArrayList.add(1);
        myArrayList.add(2);
        System.out.println(myArrayList.get(0));
        System.out.println(myArrayList.size());
        System.out.println(myArrayList);
        myArrayList.remove(1);
        System.out.println(myArrayList.get(0));
        System.out.println(myArrayList.size());
        System.out.println(myArrayList);
    }
}