究竟是什么样的讲解数组算法的博客让我写了三小时???

news2024/11/16 17:30:06

版本说明

当前版本号[20231004]。

版本修改说明
20231004初版

目录

文章目录

  • 版本说明
  • 目录
  • 二. 基础数据结构
    • 2.1 数组
      • 1) 概述
      • 2) 动态数组
        • 1)插入
          • addlast 方法
            • 测试: addlast 方法
          • add 方法
            • 测试:add方法
          • addlast 方法与 add 方法合并版
          • get 方法
            • 测试:get 方法
        • 2) 遍历
          • 遍历方法1:consumer 遍历
            • 测试:consumer 遍历
          • 遍历方法2:迭代器遍历
            • 测试:迭代器遍历
          • 遍历方法3:stream 遍历
            • 测试:stream 遍历
        • 插入与遍历方法汇总
        • 插入与遍历测试类汇总
        • 3)删除
          • 测试:删除方法
        • 4)扩容
        • 5)插入或删除性能
      • 3) 二维数组
        • **测试**
      • 4) 局部性原理
        • **对效率的影响**

二. 基础数据结构

2.1 数组

1) 概述

定义

在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识

因为数组内的元素是连续存储的,所以数组中元素的地址,可以通过其索引计算出来,例如:

int[] array = {1,2,3,4,5}

知道了数组的数据起始地址 B a s e A d d r e s s BaseAddress BaseAddress,就可以由公式 B a s e A d d r e s s + i ∗ s i z e BaseAddress + i * size BaseAddress+isize 计算出索引 i i i 元素的地址

  • i i i 即索引,在 Java、C 等语言都是从 0 开始
  • s i z e size size 是每个元素占用字节,例如 i n t int int 4 4 4 d o u b l e double double 8 8 8

小测试

byte[] array = {1,2,3,4,5}

已知 array 的数据的起始地址是 0x7138f94c8,那么元素 3 的地址是什么?

答:0x7138f94c8 + 2 * 1 = 0x7138f94ca

空间占用

Java 中数组结构为

  • 8 字节 markword
  • 4 字节 class 指针(压缩 class 指针的情况)
  • 4 字节 数组大小(决定了数组最大容量是 2 32 2^{32} 232
  • 数组元素 + 对齐字节(java 中所有对象大小都是 8 字节的整数倍[^12],不足的要用对齐字节补足)

例如

int[] array = {1, 2, 3, 4, 5};

的大小为 40 个字节,组成如下

8 + 4 + 4 + 5*4 + 4(alignment)

随机访问性能

即根据索引查找元素,时间复杂度是 O ( 1 ) O(1) O(1)

2) 动态数组

1)插入
addlast 方法

意在把新增的元素加入到最后一个到动态数组里。

public class 动态数组
{
    private int size = 0;//逻辑大小
    private int capacity = 8;//所创建数组的数组容量
    private int[] array = new int[capacity];

       /**
     * 向最后位置 [size] 添加元素
     *
     * @param element 待添加元素
     */
    public void addlast(int element)
    {
        array[size] = element;
        size++;
    }
}

这个类具有以下特点:

  • size变量表示当前数组中已存储的元素数量。
  • capacity变量表示数组的容量,即可以容纳的最大元素数量。
  • array是一个整型数组,用于存储元素。
  • addlast(int element)方法用于在数组的末尾添加一个元素,并将size增加1。
测试: addlast 方法

​ 这段代码作为一个JUnit测试类,在测试方法test1中,首先创建了一个动态数组对象,然后向数组中添加了5个元素,最后遍历数组并打印出每个元素的值。

package SuanFa.test;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import SuanFa.初始算法.第一章数组.动态数组;
import java.util.Arrays;

public class 动态数组Test {

    @Test
    public void test1()
    {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);
        dynamicarray.addlast(5);

        for(int i = 0; i < 5; i++)
        {
            System.out.println(dynamicarray.get(i));
        }
    }
}

测试结果如下:

image-20231004104127182

注:接下来的所有方法中将只保留最主要的代码。

add 方法

意在向某一个索引里加入新值。

     /**
     * 向 [0 .. size] 位置添加元素
     *
     * @param index   索引位置
     * @param element 待添加元素
     */
    public void add(int index, int element)
    {
        if(index >=0 && index < size)
        {
            System.arraycopy(array, index, array, index+1, size - index);
            array[index] = element;
            size++;
        }
    }

这个类具有以下特点:

  • size变量表示当前数组中已存储的元素数量。
  • capacity变量表示数组的容量,即可以容纳的最大元素数量。
  • array是一个整型数组,用于存储元素。
  • add(int index, int element)方法用于在指定索引位置插入到指定索引位置,并确保索引在合法范围内(大于等于0且小于当前数组大小)。
  • 如果索引合法,使用System.arraycopy()方法将原数组中从指定索引位置开始的元素向后移动一个位置,腾出空间来插入新元素。(复制 array 数组中从下标为 index 的元素复制到 array 数组的下标为 index+1 的元素上,并且只复制 size - index 的元素)
  • 将新元素赋值给指定索引位置。
  • 增加size变量的值,表示数组中已存储的元素数量增加了1。
  • 如果索引不合法,该方法不会执行任何操作。
测试:add方法

​ 这段代码作为一个JUnit测试类,在测试方法test1中,首先创建了一个动态数组对象,然后向数组中添加了4个元素,接着使用add方法在索引为2的位置插入了一个值为9的元素。最后遍历数组并打印出每个元素的值。

    @Test
    public void test2()
    {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);
        //dynamicarray.addlast(5);

        dynamicarray.add(2, 9);

        for(int i = 0; i < 5; i++)
        {
            System.out.println(dynamicarray.get(i));
        }
    }
}

测试结果如下:

image-20231004104452737

addlast 方法与 add 方法合并版

由于addlast与add方法中,都有 array[下标] = element; size++; 这两段代码,于是也可以对他们进行优化,放在一起

     /**
     * 向 [0 .. size] 位置添加元素
     *
     * @param index   索引位置
     * @param element 待添加元素
     */
    public void add(int index, int element)
    {
        // 添加逻辑
        if(index >=0 && index < size)
        {
            // 向后挪动, 空出待插入位置
            System.arraycopy(array, index,
                    array, index+1, size - index);
        }
        array[index] = element;
        size++;
    }

       /**
     * 向最后位置 [size] 添加元素
     *
     * @param element 待添加元素
     */
    public void addlast(int element)
    {
        add(size, element);
    }

这个类具有以下特点:

  • size变量表示当前数组中已存储的元素数量。
  • capacity变量表示数组的容量,即可以容纳的最大元素数量。
  • array是一个整型数组,用于存储元素。
  • add(int index, int element)方法用于在指定索引位置插入一个元素。
  • addlast(int element)方法用于在数组的末尾添加一个元素。
  • add(int index, int element)方法将传入的元素插入到指定索引位置,并确保索引在合法范围内(大于等于0且小于当前数组大小)。
  • 如果索引合法,使用System.arraycopy()方法将原数组中从指定索引位置开始的元素向后移动一个位置,腾出空间来插入新元素。
  • 将新元素赋值给指定索引位置。
  • 增加size变量的值,表示数组中已存储的元素数量增加了1。
  • 如果索引不合法,该方法不会执行任何操作。
  • addlast(int element)方法调用add(int index, int element)方法,将元素添加到数组的末尾
  • 数组会自动调整大小以适应新元素的添加。
get 方法

意在接收一个整数参数 index ,返回数组中下标为 index 的元素

        /**
     * 查询元素
     *
     * @param index 索引位置, 在 [0..size) 区间内
     * @return 该索引位置的元素
     */
    public int get(int index)
    {
        return array[index];
    }

这个类具有以下特点:

  • size变量表示当前数组中已存储的元素数量。
  • capacity变量表示数组的容量,即可以容纳的最大元素数量。
  • array是一个整型数组,用于存储元素。
  • 查询方法get接收一个整数参数 index ,返回数组中下标为 index 的元素
测试:get 方法

与测试:addlast 方法相同

    @Test
    public void test1()
    {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);
        dynamicarray.addlast(5);

        for(int i = 0; i < 5; i++)
        {
            System.out.println(dynamicarray.get(i));
        }
    }
2) 遍历
遍历方法1:consumer 遍历

​ 这段代码定义了一个名为 foreach 的方法,该方法接收一个 Consumer 类型的参数consumer。在方法内部,使用for循环遍历数组array,**对于每个元素,调用consumer的accept方法执行传入的操作。**最后,forEach方法没有返回值(void)。

    /**
     * 遍历方法1
     *
     * @param consumer 遍历要执行的操作, 入参: 每个元素
     */
 public void foreach(Consumer<Integer> consumer)
    {
        for(int i = 0; i<size ; i++)
   	   // 提供 array[i]
        // 返回 void
        {
            consumer.accept(array[i]);
        }
    }
测试:consumer 遍历

​ 在该测试方法中,首先创建了一个动态数组对象dynamicarray,并向其中添加了5个元素。然后调用forEach方法遍历数组,对于每个元素,使用Lambda表达式打印出该元素的值。

public class 动态数组Test {

    @Test
    public void test3()
    {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);

        dynamicarray.forEach((element->
        {
            System.out.println(element);
        }));
    }
}

测试结果如下:

image-20231004163648866

遍历方法2:迭代器遍历

​ 该动态数组类实现了Iterable接口,并重写了iterator方法

在类中重写了Iterable接口的iterator方法,返回一个Iterator对象,该对象实现了Iterator接口的方法hasNext和next。其中,hasNext方法用于判断是否还有下一个元素,next方法用于**返回当前元素并将指针移动到下一个元素。**由于没有给出数组元素的添加和删除操作,因此该类的迭代器只能从头到尾遍历一遍数组。

public class 动态数组 implements Iterable<Integer>
{
    private int size = 0;//逻辑大小
    private int capacity = 8;//容量
    private int[] array = new int[capacity];

        /**
     * 遍历方法2 - 迭代器遍历
     */
    @Override
    public Iterator<Integer> iterator(){
        return new Iterator<Integer>() {
            int i = 0;
            @Override
            public boolean hasNext() { // 有没有下一个元素
                return i < size;
            }

            @Override
            public Integer next() { // 返回当前元素,并移动到下一个元素
                return array[i++];
            }
       };
}
测试:迭代器遍历

​ 使用增强for循环遍历数组,对于每个元素,打印出该元素的值。

@Test
    public void test4()
    {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);

        for(Integer element: dynamicarray)
        {
            System.out.println(element);
        }
    }

测试结果如下:

image-20231004112314866

遍历方法3:stream 遍历

​ 这段代码定义了一个名为stream的公共方法,该方法返回一个IntStream类型的对象。在方法内部,使用IntStream.of()方法创建一个包含数组array中前size个元素的IntStream流并返回该流。

    /**
     * 遍历方法3 - stream 遍历
     *
     * @return stream 流
     */
public IntStream stream(){
        return IntStream.of(Arrays.copyOfRange(array,0,size));
    }
测试:stream 遍历
 @Test
    public void test5()
    {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(7);
        dynamicarray.addlast(2);
        dynamicarray.addlast(4);
        dynamicarray.addlast(1);

        dynamicarray.stream().forEach(element->{
            System.out.println(element);
        });
    }

测试结果如下:

image-20231004164731217

插入与遍历方法汇总

​ 以下这段代码定义了一个名为动态数组的类,汇总了所有方法,实现了Iterable接口。该类方法分别有:

  1. add(int index, int element):向指定索引位置添加元素的方法。如果索引有效(即大于等于0且小于当前大小),则将该位置及之后的元素向后移动一位,然后将新元素放置在指定索引位置上,并将大小加1。
  2. addlast(int element):向数组末尾添加元素的方法。调用add(size, element)实现。
  3. get(int index):获取指定索引位置的元素的方法。
  4. foreach(Consumer<Integer> consumer):遍历数组并对每个元素执行给定的消费者操作。
  5. stream():返回一个IntStream流,其中包含数组中的所有元素。使用Arrays.copyOfRange(array, 0, size)创建一个包含数组元素的新数组,并将其转换为IntStream流。
package SuanFa.初始算法.第一章数组;

import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.stream.IntStream;

public class 动态数组 implements Iterable<Integer> {
    private int size = 0;//逻辑大小
    private int capacity = 8;//容量
    private int[] array = new int[capacity];

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            int i = 0;

            @Override
            public boolean hasNext() { // 有没有下一个元素
                return i < size;
            }

            @Override
            public Integer next() { // 返回当前元素,并移动到下一个元素
                return array[i++];
            }
        };
    }

    public void add(int index, int element) {
        if (index >= 0 && index < size) {
            System.arraycopy(array, index,
                    array, index + 1, size - index);
        }
        array[index] = element;
        size++;
    }

    public void addlast(int element) {
        add(size, element);
    }

    public int get(int index) {
        return array[index];
    }

    public void foreach(Consumer<Integer> consumer) {
        for (int i = 0; i < size; i++) {
            consumer.accept(array[i]);
        }
    }

    public IntStream stream() {
        return IntStream.of(Arrays.copyOfRange(array, 0, size));
    }
}
插入与遍历测试类汇总

该类包含了5个测试方法:

  1. test1():测试了向动态数组末尾添加元素的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了5个元素(1到5)。最后使用循环遍历数组并打印每个元素的值。
  2. test2():测试了在指定位置插入元素的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了5个元素(1到5)。接着调用add()方法在索引为2的位置插入了一个新元素9。最后使用循环遍历数组并打印每个元素的值。
  3. test3():测试了使用IntStream流遍历动态数组的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了5个元素(1到5)。接着调用stream()方法获取一个包含数组元素的IntStream流,并使用forEach()方法对每个元素执行打印操作。
  4. test4():测试了使用for-each循环遍历动态数组的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了5个元素(1到5)。接着使用for-each循环遍历数组,并在每次迭代中打印当前元素的值。
  5. test5():测试了在动态数组中间插入元素的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了4个元素(7、2、4、1)。最后使用流
package SuanFa.test;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;


import SuanFa.初始算法.第一章数组.动态数组;

import java.lang.annotation.ElementType;
import java.util.Arrays;


public class 动态数组Test {

    @Test
    public void test1() {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);
        dynamicarray.addlast(5);

        for (int i = 0; i < 5; i++) {
            System.out.println(dynamicarray.get(i));
        }
    }

    @Test
    public void test2() {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);

        dynamicarray.add(2, 9);

        for (int i = 0; i < 5; i++) {
            System.out.println(dynamicarray.get(i));
        }
    }

    @Test
    public void test3() {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);

        dynamicarray.stream().forEach(element -> {
            System.out.println(element);
        });
    }

    @Test
    public void test4() {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);

        for (Integer element : dynamicarray) {
            System.out.println(element);
        }
    }

    @Test
    public void test5() {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(7);
        dynamicarray.addlast(2);
        dynamicarray.addlast(4);
        dynamicarray.addlast(1);

        dynamicarray.stream().forEach(element -> {
            System.out.println(element);
        });
    }
}
3)删除

​ 该方法接受一个整数参数 index,表示要删除元素的索引位置。

​ 首先,它将要删除的元素保存在变量 removed 中。然后,使用 System.arraycopy() 方法将索引位置后的元素向前移动一位,覆盖掉要删除的元素。

​ 接下来,通过将数组的大小减一来更新数组的大小。

​ 最后,返回被删除的元素。

/**
 * 从 [0 .. size) 范围删除元素
 *
 * @param index 索引位置
 * @return 被删除元素
 */
public int remove(int index){
    // 保存要删除的元素
    int removed = array[index];
    
    // 将索引位置后的元素向前移动一位
    System.arraycopy(array, index + 1, array, index, size - index - 1);
    
    // 更新数组的大小
    size--;
    
    // 返回被删除的元素
    return removed;
}
测试:删除方法

​ 这是一个J用于测试动态数组的测试类。首先创建一个dynamicarray的动态数组对象,然后使用addlast方法向数组中添加元素1、2、3、4和5。接着**调用remove方法移除索引为2的元素,并将其值赋给变量removed。**最后,使用stream方法和forEach遍历打印数组中的所有元素。

@Test
    public void test6() {
        动态数组 dynamicarray = new 动态数组();
        dynamicarray.addlast(1);
        dynamicarray.addlast(2);
        dynamicarray.addlast(3);
        dynamicarray.addlast(4);
        dynamicarray.addlast(5);

        int removed = dynamicarray.remove(2);
        System.out.println(removed);
        System.out.println("-----");
        dynamicarray.stream().forEach(element -> {
            System.out.println(element);
        });
    }

测试结果如下:

image-20231004201626597

4)扩容
  • ​ 该方法首先进行容量检查。
  • ​ 如果当前数组的大小为0,即数组为空,那么将创建一个新的数组,其容量为capacity
  • ​ 如果当前数组的大小等于容量,即数组已满,那么将通过将容量增加至原来的两倍来扩展数组。
  • ​ 然后,创建一个新的数组newArray,其容量为新的容量值。
  • ​ 接下来,使用System.arraycopy()方法将原数组中的元素复制到新数组中。
  • ​ 最后,将新数组赋值给原数组,完成数组的扩展操作。
  • ​ 这个方法通常用于动态数组的管理,以确保数组在插入元素时能够自动调整其容量大小,以适应不断增长的数据量。
  private void checkAndGrow() {
        // 容量检查
        if (size == 0) {
            array = new int[capacity];
        } else if (size == capacity) {
            capacity += capacity >> 1;
            int[] newArray = new int[capacity];
            System.arraycopy(array, 0,
                    newArray, 0, size);
            array = newArray;
        }
    }
5)插入或删除性能

部位置,时间复杂度是 O ( n ) O(n) O(n)

间位置,时间复杂度是 O ( n ) O(n) O(n)

部位置,时间复杂度是 O ( 1 ) O(1) O(1)(均摊来说)

3) 二维数组

int[][] array = {
    {11, 12, 13, 14, 15},
    {21, 22, 23, 24, 25},
    {31, 32, 33, 34, 35},
};
  • 二维数组占 32 个字节,其中 array[0],array[1],array[2] 三个元素分别保存了指向三个一维数组的引用

  • 三个一维数组各占 40 个字节

  • 它们在内层布局上是连续

更一般的,对一个二维数组 A r r a y [ m ] [ n ] Array[m][n] Array[m][n]

  • m m m 是外层数组的长度,可以看作 row 行
  • n n n 是内层数组的长度,可以看作 column 列
  • 当访问 A r r a y [ i ] [ j ] Array[i][j] Array[i][j] 0 ≤ i < m , 0 ≤ j < n 0\leq i \lt m, 0\leq j \lt n 0i<m,0j<n时,就相当于
    • 先找到第 i i i 个内层数组(行)
    • 再找到此内层数组中第 j j j 个元素(列)
测试

Java 环境下(不考虑类指针和引用压缩,此为默认情况),有下面的二维数组

byte[][] array = {
    {11, 12, 13, 14, 15},
    {21, 22, 23, 24, 25},
    {31, 32, 33, 34, 35},
};

已知 array 对象起始地址是 0x1000,那么 23 这个元素的地址是什么?

答:

  • 起始地址 0x1000
  • 外层数组大小:16字节对象头 + 3元素 * 每个引用4字节 + 4 对齐字节 = 32 = 0x20
  • 第一个内层数组大小:16字节对象头 + 5元素 * 每个byte1字节 + 3 对齐字节 = 24 = 0x18
  • 第二个内层数组,16字节对象头 = 0x10,待查找元素索引为 2
  • 最后结果 = 0x1000 + 0x20 + 0x18 + 0x10 + 2*1 = 0x104a

4) 局部性原理

这里只讨论空间局部性

  • cpu 读取内存(速度慢)数据后,会将其放入高速缓存(速度快)当中,如果后来的计算再用到此数据,在缓存中能读到的话,就不必读内存了
  • 缓存的最小存储单位是缓存行(cache line),一般是 64 bytes,一次读的数据少了不划算啊,因此最少读 64 bytes 填满一个缓存行,因此读入某个数据时也会读取其临近的数据,这就是所谓空间局部性
对效率的影响

比较下面 ij 和 ji 两个方法的执行效率

int rows = 1000000;
int columns = 14;
int[][] a = new int[rows][columns];

StopWatch sw = new StopWatch();
sw.start("ij");
ij(a, rows, columns);
sw.stop();
sw.start("ji");
ji(a, rows, columns);
sw.stop();
System.out.println(sw.prettyPrint());

ij 方法

public static void ij(int[][] a, int rows, int columns) {
    long sum = 0L;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < columns; j++) {
            sum += a[i][j];
        }
    }
    System.out.println(sum);
}

ji 方法

public static void ji(int[][] a, int rows, int columns) {
    long sum = 0L;
    for (int j = 0; j < columns; j++) {
        for (int i = 0; i < rows; i++) {
            sum += a[i][j];
        }
    }
    System.out.println(sum);
}

执行结果

0
0
StopWatch '': running time = 96283300 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
016196200  017%  ij
080087100  083%  ji

可以看到 ij 的效率比 ji 快很多,为什么呢?

  • 缓存是有限的,当新数据来了后,一些旧的缓存行数据就会被覆盖
  • 如果不能充分利用缓存的数据,就会造成效率低下

以 ji 执行为例,第一次内循环要读入 [ 0 , 0 ] [0,0] [0,0] 这条数据,由于局部性原理,读入 [ 0 , 0 ] [0,0] [0,0] 的同时也读入了 [ 0 , 1 ] . . . [ 0 , 13 ] [0,1] ... [0,13] [0,1]...[0,13],如图所示

image-20221104164329026

但很遗憾,第二次内循环要的是 [ 1 , 0 ] [1,0] [1,0] 这条数据,缓存中没有,于是再读入了下图的数据

image-20221104164716282

这显然是一种浪费,因为 [ 0 , 1 ] . . . [ 0 , 13 ] [0,1] ... [0,13] [0,1]...[0,13] 包括 [ 1 , 1 ] . . . [ 1 , 13 ] [1,1] ... [1,13] [1,1]...[1,13] 这些数据虽然读入了缓存,却没有及时用上,而缓存的大小是有限的,等执行到第九次内循环时

image-20221104164947154

缓存的第一行数据已经被新的数据 [ 8 , 0 ] . . . [ 8 , 13 ] [8,0] ... [8,13] [8,0]...[8,13] 覆盖掉了,以后如果再想读,比如 [ 0 , 1 ] [0,1] [0,1],又得到内存去读了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1060438.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyCat实现分库分表技术

目录 一、分库分表 1.1介绍 1.1.1问题分析 1.1.2拆分策略 1.1.3垂直拆分 1.1.3.1垂直分库 1.1.3.2垂直分表 1.1.4水平拆分 1.1.4.1水平分库 1.1.4.2水平分表 1.1.5实现技术 二、MyCat概述 2.1介绍 2.2下载 2.3安装 2.4目录介绍 2.5概念介绍 三、MyCat入门 3.…

某道翻译逆向

文章目录 前文crypto模块分析完整代码结尾 前文 本文章中所有内容仅供学习交流&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; crypto模块 Crypto是加密的简称&#…

10月4日作业

server #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//实例化一个服务器server new QTcpServer(this);connect(server, &QTcpServer::newConnection, …

CANoe.Diva生成测试用例

Diva目录 一、CANoe.Diva打开CDD文件二、导入CDD文件三、ECU Information四、时间参数设置五、选择是否测试功能寻址六、勾选需要测试服务项七、生成测试用例 一、CANoe.Diva打开CDD文件 CANoe.Diva可以通过导入cdd或odx文件&#xff0c;自动生成全面的测试用例。再在CANoe中导…

Android学习之路(20) 进程间通信

IPC IPC为 (Inter-Process Communication) 缩写&#xff0c;称为进程间通信或跨进程通信&#xff0c;指两个进程间进行数据交换的过程。安卓中主要采用 Binder 进行进程间通信&#xff0c;当然也支持其他 IPC 方式&#xff0c;如&#xff1a;管道&#xff0c;Socket&#xff0…

9.3 链表从指定节点插入新节点

一、从指定节点后方插入 插入逻辑如图&#xff1a; 插入前&#xff1a;A指向B&#xff0c;B指向C 插入后&#xff1a;B为插入点&#xff0c;当要插入D时就要让B指向D&#xff0c;D再指向C&#xff08;插入前B的指向&#xff09; #include <stdio.h>struct Test {int d…

C/C++字符函数和字符串函数详解————内存函数详解与模拟

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.前言 2 .memcpy函数 3.memmove函…

mysql双主+双从集群连接模式

架构图&#xff1a; 详细内容参考&#xff1a; 结果展示&#xff1a; 178.119.30.14(主) 178.119.30.15(主) 178.119.30.16(从) 178.119.30.17(从)

【软件设计师-中级——刷题记录6(纯干货)】

目录 管道——过滤器软件体系结构风格优点&#xff1a;计算机英语重点词汇&#xff1a;单元测试主要检查模块的以下5个特征&#xff1a;数据库之并发控制中的事务&#xff1a;并发产生的问题解决方案:封锁协议原型化开发方法&#xff1a; 每日一言&#xff1a;持续更新中... 个…

N. Number Reduction

Problem - 1765N - Codeforces 发现如果是无前导0最小数那么在保证删除k个数时第1位是最小的&#xff0c;第二位一定是相对最小的&#xff0c;且答案第一位和第二位在原位置的间隔是小于等于还可以删除的位数的。 因此&#xff0c;对于原数字长度位n&#xff0c;要删除k&#…

Spring:通过@Lazy解决构造方法形式的循环依赖问题

一、定义2个循环依赖的类 package cn.edu.tju.domain2;import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component;Component public class A {private final B b;public B getB() {return b;}Lazypublic A(B b){this.b b;//Sy…

python获取时间戳

使用 datetime 库获取时间。 获取当前时间&#xff1a; import datetime print(datetime.datetime.now()) . 后面的是微秒&#xff0c;也是一个时间单位&#xff0c;1秒1000000微秒。 转为时间戳&#xff1a; import datetimedate datetime.datetime.now() timestamp date…

计算机系统中丢失MSVCR100.dll的解决方法,三个方法快速搞定

msvcr100.dll 是一个非常重要的系统文件&#xff0c;它属于 Microsoft Visual C Redistributable 的一个组件。这个文件包含了用于 C 编程的一些运行时库&#xff0c;对于许多软件和游戏的运行至关重要。如果您的电脑提示找不到 msvcr100.dll 文件&#xff0c;您可能会遇到一些…

STM32驱动步进电机

前言 &#xff08;1&#xff09;本章介绍用stm32驱动42步进电机&#xff0c;将介绍需要准备的硬件器材、所需芯片资源以及怎么编程及源代码等等。 &#xff08;2&#xff09;实验效果&#xff1a;按下按键&#xff0c;步进电机顺时针或逆时针旋转90度。 &#xff08;3&#xff…

浏览器从输入URL到展示的流程

文章目录 1. URL输入2. DNS解析3. 建立TCP连接4. 发送http或者https请求5. 服务器端响应请求6. 浏览器解析渲染页面7. 断开TCP连接 1. URL输入 输入URL后&#xff0c;浏览器会对URL进行以下的判断 是否合法如果合法&#xff0c;则判断URL是否完整&#xff0c;如果不完整&…

【C 语言进阶(12)】动态内存管理笔试题

文章目录 题目 1题目 2题目 3题目 4 题目 1 运行 Test 函数后的结果是什么&#xff1f; void GetMemory(char* p) {p (char*)malloc(100); }void Test(void) {char* str NULL;GetMemory(str);strcpy(str, "hello world");printf(str); }代码结果 程序崩溃。 代…

openGauss学习笔记-89 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用查询原生编译

文章目录 openGauss学习笔记-89 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用查询原生编译89.1 查询编译&#xff1a;PREPARE语句89.2 运行命令89.3 轻量执行支持的查询89.4 轻量执行不支持的查询89.5 JIT存储过程89.6 MOT JIT诊断89.6.1 mot_jit_detai…

MATLAB 函数签名器

文章目录 MATLAB 函数签名器注释规范模板参数类型 kind数据格式 type选项的支持 使用可执行程序封装为m函数程序输出 编译待办事项推荐阅读附录 MATLAB 函数签名器 MATLAB 函数签名器 (FUNCSIGN) &#xff0c;在规范注释格式的基础上为函数文件或类文件自动生成函数签名&#…

c++ 基础知识(一)

文章目录 1. C关键字 2. 命名空间 3. C输入&输出 4. 缺省参数 文章内容 1. C关键字(C98) C总计63个关键字&#xff0c;C语言32个关键字 ps&#xff1a;下面我们只是看一下C有多少关键字&#xff0c;不对关键字进行具体的讲解。后面我学了以后再细讲。 2. 命名空间 …

简单查找重复文本文件

声明这是最初 我的提问给个文本分类清单input查找文件夹下 .py .txt .excel .word 一模一样的文本不是找文件名 找相同格式下的文件文本是否一样 文件单独复制到文件夹下两个文件全部复制到文件夹下 print 打印相同文本文件的名字 比如查找到了3.py与4.5.是.py文件中的文本文件…