超神之路 数据结构 1 —— 关于数组的基本认知及动态数组的简单实现

本文介绍了数组访问速度快的原因,解析了数组访问的内存计算原理,并探讨了为什么数组索引通常从0开始。此外,文章通过Java代码展示了如何实现一个动态数组类,包括动态扩容、缩容、添加、删除等操作,强调了数据结构在实际应用中的考量因素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        数组的基本认知:

        如果有面试官问你:为什么数组访问会那么快?我相信你把下面的式子告诉他,那么你就已经赢了一半了。

addr = base_addr + i * type_size

        如果你连这个都不知道如何去回答,我觉得,你和我曾经有一段时间一样,也太可爱了。好好听着,大家都是这么一点点学的。

        式中的 base_addr 是此数组首元素在内存中基本位置。外加上数组是一组大小连续的内存空间,所以每个元素的大小 type_size相同, i 就是偏移量,通过这个公式 一算 就非常容易计算出a[i] 地址, 从而实现 访问元素一个公式计算一步到位 ,

这就是为什么人们 常说:数组的访问是 O(1) 的 时间复杂度。就是这么快!

        为什么索引从0开始而不是从1开始呢?

        我们的公式的i 计算的是偏移量。如果从1开始,偏移量每次就要减一,那么CPU要多做一次减法运算,降低了访问速度。

        另外还有一个知识点:CPU从内存中加载数据到CPU缓存时,并非只读要访问的特定地址,而是会读取一个数据块到CPU缓存中,因为这CPU缓存这地方访问效率更高,所以在下次访问该数组时,会先从CPU缓存 开始查找,实在找不到才会到内存中读取数据。

        从上可知,数组的最大优点就是可以做到:首个元素的地址就是整个数组的地址,因为首个元素的偏移是0 ,数组的索引本质就是数组的偏移量。所以根据偏移量 i,也就是索引,根据公式就 可快速访问查询到特定的元素。所以,数组最好应用在索引有语意的情况,但是也不是所有有寓意的索引都适合数组,如果索引过大,意味着偏移过大,就意味着开辟的内存空间就会很大,比如:身份证账号这种情况。

        理想很丰满,现实很骨感,工作中遇到的大部分的应用场景都是索引没有语意的情况。没有语意情况下:如何表示没有元素,如何添加,删除元素?等...一系列的问题,需要处理。带着这些问题,我们要基于java的数组,封装一个属于自己的可动态扩容缩容的动态数组类。

        这是我最近通过视频创作的动态数组的Java版本,里面有一个比较完整的从0到1,逐步建构出一个可用的动态数组的全过程实现,支持泛型,动态的扩容缩容,及一些算法分析的知识点,我即将加入一些JDK的动态数组 ArrayList 源码分析。数据结构 Java实现 - 播单 - 优酷视频

             视频的详细代码如下:

package com.company.array;

public class DynamicArray<T> {

    private static final int DEFAULT_SIZE = 10;

    // container:篮子:element:apple
    private T[] data;
    private int size;


    public DynamicArray(int capacity) {
        data = (T[]) new Object[capacity];
        size = 0;
    }

    public DynamicArray() {
        this(DEFAULT_SIZE);
    }


    //todo delete
    public int getSize(){
        return size;
    }



    // 0 ==> O(n)  O(1) ==> index 发生概率  O( n/2 ) 概率正态分布  c * n  O(n)
    public void add(T e, int index) {

        if (index < 0 || index > size) {
            throw new IllegalArgumentException(" index illegal");
        }


        if (data.length == size) {
            resize( data.length + (data.length >> 1) );
        }


        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }

        data[index] = e;

        size++;
    }

    private void resize(int newCapacity) {
        T [] newData = (T[]) new Object[newCapacity];
        for(int i = 0 ;i < size; i++){
            newData[i] = data[i];
        }
        data = newData;
    }

    public void addFirst(T e){
        add(e,0);
    }

    public void addLast(T e){
        add(e,size);
//        if (data.length == size) {
//            throw new IllegalArgumentException(" array is full .");
//        }
//        data[size] = e;
//        size ++ ;
    }

    /**
     *  查询data中,index位置的元素。
     * @param index 查询索引
     * @return 返回查询索引得到的元素。
     */
    public T get(int index){
        if (index < 0 || index > size-1) {
            throw new IllegalArgumentException(" index illegal");
        }
        return data[index];   // baseAddress +  type_size * i == 访问。
    }

    public void set(T e,int index){

        if (index < 0 || index > size-1) {
            throw new IllegalArgumentException(" index illegal");
        }

        data[index] = e;
    }



    //contains,indexOf,remove

    public boolean contains(T e){

        for( int i = 0;i<size;i++){
            if(data[i] == e){
                return true;
            }
        }
        return false;
    }



    public int indexOf(T e){

        for(int i = 0;i<size;i++){
            if(data[i] == e){
                return i ;
            }
        }
        return -1;
    }


    public T remove(int index){

        if (index < 0 || index > size-1) {
            throw new IllegalArgumentException(" index illegal");
        }

        // [index...size-1]
        T ret = data[index];
        for(int i = index;i<size-1;i++){
            data[i] =data[i+1];
        }

        //loitering objects.  GC ,内存溢出:GC内存没有被管理到的。
        data[size-1] = null;

        size--;

        if(size <= data.length/2  && data.length/2 != 0){
            resize(data.length/2);
        }

        return ret;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(String.format("Dynamic Array : size/capacity = %d/%d \n [  ",size,data.length));
        for(int i = 0;i<size;i++){
            sb.append(data[i]);
            if(i !=size-1){
                sb.append(" , ");
            }
        }
        sb.append(" ]");
        return sb.toString();
    }


}

        每一行代码思路,已经在视频中我已经介绍过了,这里就不再用文字冗余的再来一遍。愿各位在追求知识的路上,都能有所收获。加油!Go Go !

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值