数组

一.概览##

1.定义###

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

2.特点###

数组支持随机访问,寻址公式:a[i]_address = base_address + i * data_type_size。
正因为数组维护的是连续的内存空间,所以在修改和插入,在数组中的开销十分巨大。

3.数组下标为什么从0开始###

从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移(offset)”。前面也讲到,如果用a来表示数组的首地址,a[0]就是偏移为0的位置,也就是首地 址,a[k]就表示偏移k个type_size的位置,所以计算a[k]的内存地址只需要用这个公式: a[k]_address = base_address + k * type_size 但是,如果数组从1开始计数,那我们计算数组元素a[k]的内存地址就会变为: a[k]_address = base_address + (k-1)*type_size 对比两个公式,我们不难发现,从1开始编号,每次随机访问数组元素都多了一次减法运算,对于CPU来说,就是多了一次减法指令。

4.使用容器还是数组###

因为java几乎是一门纯面对对象语言,但是为了方便也引入了基本数据类型,但是都提供了他们的包装类型,比如Integer,Long。装包与拆包都会对性能有一些影响。
对于业务开发,直接使用容器就足够了,省时省力。毕竟损耗一丢丢性能,完全不会影响到系统整体的性能。但如果你是做一些非常底层的开发, 比如开发网络框架,性能的优化需要做到极致,这个时候数组就会优于容器,成为首选。

二.手动实现动态数组##

/**
 * rangeCheck(),检查数组下标是否越界;
 * add(),在某个下标位置添加元素;
 * resize(),动态扩容,当数组容量满或者空闲整个数组的3/4时,重新定义容量;
 * get(),获取某个位置的元素值;
 * set(),设置某个下标的元素值;
 * remove(),移除某个下标对应的值;**/
public class MyArrayList<E> {
    private int size;//数组长度
    private Object elementData[];//保存数组元素
    //无参构造函数
    public MyArrayList(){
        this.elementData=new Object[10];
    }
    //带参构造函数
    public MyArrayList(int initialCapacity){
       if(initialCapacity>=0){
            this.elementData=new Object[initialCapacity];
        }else{
            throw new RuntimeException("非法输入初始化容量:"+initialCapacity);
        }
    }
    public int getSize(){
        return size;
    }
    public int getCapacity(){
        return elementData.length;
    }
    public boolean isEmpty(){
        return size == 0;
    }

    public void rangeCheck(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Index is Illegal!");
    }

    public void add(int index,E e){
        if(index<0||index>size){
            throw new IllegalArgumentException("index is illegal!");
        }
        if(size==elementData.length){
            resize(elementData.length);
        }
        //把index后的元素后移
        for(int i=size-1;i>index;i--){
            elementData[i+1]=elementData[i];
        }
        elementData[index]=e;
        size++;
    }
    //末尾添加
    public  void add(E e){
        add(size,e);
    }
   private void resize(int minCapacity){
        int newCapacity=minCapacity+minCapacity<<1;
        Object[]newData=new Object[newCapacity];
        for(int i=0;i<size;i++){
          newData[i]=elementData[i];
        }
        elementData=newData;
   }
    public E remove(int index){  // remove data[index] and return the value
        rangeCheck(index);
        E res = (E) elementData[index];
        for(int i = index; i < size-1; i++){
            elementData[i] = elementData[i+1];
        }
        size--;
        elementData[size] = null;//loitering objects  != memory  leak
        return res;
    }
    public E removeLast(){
        return remove(size-1);
    }
    public E removeFirst(){
        return remove(0);
    }
   public E get(int index){
        rangeCheck(index);
        return (E)elementData[index];
   }
   public E getLast(){
       return (E)elementData[size-1];
   }
    public E getFirst(){
        return (E)elementData[0];
    }
   public void set(int index,E e){
        rangeCheck(index);
        elementData[index]=e;
   }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        for(int i=0;i<size;i++){
            res.append(elementData[i]);
            if(i!=size-1){
                res.append(",");
            }
        }


        return "MyArrayList{" +
                "elementData=" + res +
                '}';
    }
}

三.容器##

ArrayList###

1.概览####

ArryaList实现了List接口,支持所有元素,包括null。ArryaList大概相当于Vector,但是不是线程安全的。因为 ArrayList 是基于数组实现的,所以支持快速随机访问。RandomAccess 接口标识着该类支持快速随机访问。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

数组的默认大小为 10。

private static final int DEFAULT_CAPACITY = 10;

elementData是储存ArrayList元素的缓冲区,transient可以让它不被序列化。如果没有初始化ArrayList大小,在添加第一个元素时,会将数组容量设为10。

transient Object[] elementData; // non-private to simplify nested class access

2.扩容####

添加元素时,通过ensureCapacityInternal(size + 1)判断容量是否足够。容量不够进行扩容,扩容后容量为以前容量的1.5倍oldCapacity + (oldCapacity >> 1)
在使用ArrayList之前最好分配足够大的容量,因为它是通过Arrays.copyOf操作把elementData中的数据拷贝到新的elementData中,开销比较大。

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

3.删除元素####

需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看到 ArrayList 删除元素的代价是非常高的。

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

4.fail-fast####

modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。

在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。

Vector###

1.同步####

Vector的add与get方法都加了synchronized 来保证同步

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);

    return elementData(index);
}

2.扩容###

Vector 的构造函数可以传入 capacityIncrement 参数,它的作用是在扩容时使容量 capacity 增长 capacityIncrement。如果这个参数的值小于等于 0,扩容时每次都令 capacity 为原来的两倍。

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

四.算法题##

1.三数之和###

posted @ 2020-02-28 21:10  Meditation,  阅读(134)  评论(0)    收藏  举报