数组概念
在计算机科学中,数组是由一组元素(值或者变量)组成的数据结构,每一个元素至少有一个索引或者键来标识
知道了数组的数据起始地址BaseAddress,就可以由公式BaseAddress+i*size计算出索引i元素的地址
·i即索引,在Java、C语言等都是从0开始
·size表示每个元素占用字节,例如int占四个字节,double占八个字节
空间占用,Java中数组结构为
·8字节 markword
·4字节 class指针(压缩class指针的情况)
·4字节 数组大小(决定了数组最大容量是2的32次方)
·数组元素+对齐字节(Java中所有对象大小都是8字节的整数倍,不足的要用对齐字节补齐)
例如
int[] array = {1,2,3,4,5}的大小为40个字节,组成如下:
8 + 4 + 4 + 5*4 + 4(alignemnt补齐字节)
动态数组基本属性
1 2 3 4
| private int size = 0; private int capacity = 8; private int[] array = {};
|
添加元素
1 2 3 4 5 6 7 8 9 10
|
public void addLast(int element) {
add(size, element); }
|
上面的代码可以被下面的代码替代,转为调用add方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
public void add(int index, int element) {
checkAndGrow(); if (index >= 0 && index < size) {
System.arraycopy(array, index, array, index + 1, size - index); array[index] = element; size++; } else { } array[index] = element; size++; }
|
检查容量(判定是否需要扩容)
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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; } }
|
删除元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public int remove(int index) { int removed = array[index]; if (index < size - 1) { System.arraycopy(array, index + 1, array, index, size - index - 1); } size--; return removed; }
|
遍历方法
遍历方法1(常规for循环)
1 2 3 4 5 6 7 8 9
|
public int get(int index) { return array[index]; }
|
遍历方法2(函数式接口)
1 2 3 4 5 6 7 8 9 10 11 12
|
public void foreach(Consumer<Integer> consumer) { for (int i = 0; i < size; i++) { consumer.accept(array[i]); } }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test @DisplayName("遍历测试2") public void test2(){ DynamicArray dynamicArray = new DynamicArray(); dynamicArray.addLast(1); dynamicArray.addLast(2); dynamicArray.addLast(3); dynamicArray.addLast(4);
dynamicArray.forEach((element)->{ System.out.println(element); }); }
|
遍历方法3(迭代器遍历)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
@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++]; } }; }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test @DisplayName("遍历测试3") public void test3(){ DynamicArray dynamicArray = new DynamicArray(); dynamicArray.addLast(1); dynamicArray.addLast(2); dynamicArray.addLast(3); dynamicArray.addLast(4);
for (Integer element : dynamicArray) { System.out.println(element); } }
|
遍历方法4(stream流)
1 2 3 4 5 6 7 8
|
public IntStream stream() { return IntStream.of(Arrays.copyOfRange(array, 0, size)); }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test @DisplayName("遍历测试4") public void test4(){ DynamicArray dynamicArray = new DynamicArray(); dynamicArray.addLast(1); dynamicArray.addLast(2); dynamicArray.addLast(3); dynamicArray.addLast(4);
dynamicArray.stream().forEach(element->{ System.out.println(element); }); }
|
二维数组
1 2 3 4 5
| int[][] array = { {11, 12, 13, 14, 15}, {21, 22, 23, 24, 25}, {31, 32, 33, 34, 35}, };
|
假设寻找25,那么表示为array[1][4]
~ i=外层数组索引位置
~ j=内层数组索引位置
缓存与局部性原理
局部性原理(这里只讨论空间局部性)
~cpu读取内存(速度慢)数据后,会将其放入高数缓存(速度快)当中,如果后来的计算再用到此数据,在缓存中就能够找到的话,就不必读内存了。
~缓存的最小存储单位是缓存行(cache line),一般是64bytes,一次只读少量数据不划算,由此最少读64bytes填满一个缓存行,由此读入某个数据时也会读取其临近的数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
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); }
public static void ji(int[][] a,int rows, int columns){ long sum =0L; for (int j = 0; j < rows; j++) { for (int i = 0; i < columns; i++) { sum += a[i][j]; } } System.out.println(sum); }
|
总结
CPU(皮秒) 缓存 内存(纳秒)
cpu要计算必须从内存中读取数据,数组存储再内存中(读)
cpu计算完会把结果写回内存(写)
由于内存跟不上cpu的运算速度,所以引用缓存概念,即先从缓存里面找数据,没有再去内存找
内存中的数据会复制一份给缓存,一般来说会一次性放64个字节放入缓存(64称为一个缓存行cache line)