• 数组的简单理解


    数组

    简单的说,数组就是一组定长连续的用来存储数据的结构,定长是指,在创建一个数组的时候,会设置该数组的长度,以便在内存里申请连续的内存地址,连续是指,这些申请的内存块是一个接一个的,只要知道了起始的地址,只要加上存储内容的子节长度x要访问的是第几个,就可以方便的找到需要访问的数据。

    Java里的数组操作

    这里看的是jdk1.8里的 javautilArrayList 主要看一下其中的增加和删除方法。

    add

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

    这里的add函数是向数组末尾添加一个对象,代码并不多,首先是一个意思是确定内部容量的函数ensureCapacityInternal ,然后就是把这个元素添加到数组后,size是类的私有变量,记录了数组的长度,接下来我们就来看看确定内部容量函数

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    

    首先看一下这个calculateCapacity计算容量函数,elementData存储的是数组里的数据,DEFAULTCAPACITY_EMPTY_ELEMENTDATA定义的是一个空的对象数组,这个计算容量函数的功能是,确保数组的大小最小是默认大小10,在来看这个ensureExplicitCapacity函数

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

    modCount是一个私有变量,表示数组修改的次数,这个if判断是确保要确认的容量大于存储数据的对象数组的长度,因为只有这样确认容量才有意义,接下来继续看grow函数

    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);
    }
    

    newCapacity 等于原本的容量加上该容量右移一位,相当于原容量的1.5倍,然后用newCapacity -MAX_ARRAY_SIZE,这个MAX_ARRAY_SIZE也是在之前定义了的,为Integer.MAX_VALUE-8,这里就有两种情况了,首先是newCapacity 处于Integer.MAX_VALUEInteger.MAX_VALUE-8之间,其次是newCapacity 已经为负数了,因为之前的1.5倍操作,导致该数值超过了Integer.MAX_VALUE,所以变为了负数(最高位变为1),然后这个hugeCapacity函数就是用来判断如果是负数了就是溢出了抛出异常,没溢出就把最大值放开到Integer.MAX_VALUE,至于为什么是Integer.MAX_VALUE-8代码注释也做了解释,有的vm在数组中保存了数组的长度,所以需要留下8位去存储。最后就调用了Arrays.copyOf来创建新的数组,把之前的数据复制过去,最底层调用了System.arraycopy这个native函数,这块我们就不看了。接下来来看add的两个参数的重载方法

    public void add(int index, E element) {
        rangeCheckForAdd(index);
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    

    首先这个rangeCheckForAdd函数检查了index的范围,不能大于size或者小于0,这很好理解,然后就是确认容量,然后是复制的过程,把index之后的元素都往后移一个单位,然后再index插入需要插入的元素,应该很好理解。

    remove

    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;
    }
    

    先检查index大小是否大于等于size了,数组的实际下标是size -1 大于等于size都会抛出异常,然后获取该index的值,如果index是负数这步会报错,接下来就是把删除index之后的数据全部前移,然后把最后的一个数据设置为null,如果数组只有一个对象那就直接赋值为null,接下来是同名重载函数

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    

    首先判断该对象是不是null,因为null的判断方式不能用equals,不是null的话就用for循环查找到与传入的对象相同的对象的index,然后用fastRemove函数删除,这个函数就是第一个remove函数的简化版,去掉了一些验证,因为已经可以保证参数的准确性了。

    总结

    可以从增删改查这四个操作去理解数组,改查可以算是同一个方向,都是在原数组上操作,不需要对数组进行大规模的修改,都比较简单。删除操作,需要前移后面的数据。增加操作,可能涉及到扩容操作,这时数组已经换成一个更大容量的数组了。

  • 相关阅读:
    mysql sql语句多表合并UNION ALL和UNION
    ajax向后台传递数组参数并将后台响应的数据赋值给一个变量供其它插件使用
    java web项目中后台控制层对参数进行自定义验证 类 Pattern
    java后台实体类设置默认值
    app连接线上数据库进行本地接口测试
    idea常用快捷键
    百度搜索小脚本
    有道翻译小脚本
    洛谷 P3275 [SCOI2011]糖果
    洛谷 P2048 BZOJ 2006 [NOI2010]超级钢琴
  • 原文地址:https://www.cnblogs.com/ljsh/p/12661244.html
Copyright © 2020-2023  润新知