数组的基本认知:
如果有面试官问你:为什么数组访问会那么快?我相信你把下面的式子告诉他,那么你就已经赢了一半了。
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
{ -
- 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
- if(data[i] == e){
- return true;
- }
- }
- return false;
- }
-
-
-
- public int indexOf(T e){
-
- for(int i = 0;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
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
- sb.append(data[i]);
- if(i !=size-1){
- sb.append(" , ");
- }
- }
- sb.append(" ]");
- return sb.toString();
- }
-
-
- }
每一行代码思路,已经在视频中我已经介绍过了,这里就不再用文字冗余的再来一遍。愿各位在追求知识的路上,都能有所收获。加油!Go Go !
-
相关阅读:
盘点JAVA中基于CAS实现的原子类, 你知道哪些?
【Transformer Based Cls&Det】Transformer系列分类和检测网络原理和源码讲解导航
GStreamer安装——Mac OS X
java list 通过对象中某一参数去重,返回一个新list
网络层概述
【vue3】一些关于hooks的使用经验
【Java SE】this详解
用DIV+CSS技术设计的体育篮球主题 校园体育网页与实现制作(web前端网页制作课作业)
创建一个Django用户认证系统
Leetcode LCR182:动态口令
-
原文地址:https://blog.csdn.net/wdw18668109050/article/details/127780366