数组存储数据的位置和方式
1、寻址操作次数链表要多一些。数组只需对 [基地址+元素大小*k] 就能找到第k个元素的地址,对其取地址就能获得该元素。链表要获得第k个元素,首先要在其第k-1个元素寻找到其next指针偏移,再将next指针作为地址获得值,这样就要从第一个元素找起,多了多步寻址操作,当数据量大且其它操作较少时,这就有差距了。
该回答源自:http://tieba.baidu.com/p/5069120437
2、CPU缓存会把一片连续的内存空间读入,因为数组结构是连续的内存地址,所以数组全部或者部分元素被连续存在CPU缓存里面,平均读取每个元素的时间只要3个CPU时钟周期。 而链表的节点是分散在堆空间里面的,这时候CPU缓存帮不上忙,只能是去读取内存,平均读取时间需要100个CPU时钟周期。这样算下来,数组访问的速度比链表快33倍! (这里只是介绍概念,具体的数字因CPU而异)。
因此,程序中尽量使用连续的数据结构,这样可以充分发挥CPU缓存的威力。这种对缓存友好的算法称为 Cache-obliviousalgorithm。
List接口
ArrayList
特点:内部采用动态数组顺序存储
适用场合 :
1.数据连续读写,需要根据index进行查找
2.按index写入和删除少
不适用的场合:
1.需要按照index写入数据和删除
动态数组的实现方式,JAVA8中是怎么实现的
源码分析:
//添加方法
public void add(int var1, E var2) {
//检验是否下标越界异常
this.rangeCheckForAdd(var1);
//检验内部容量
this.ensureCapacityInternal(this.size + 1);
System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1);
this.elementData[var1] = var2;
++this.size;
}
//如果下标为负数或者大于数组长度,那么直接抛下标越界异常
private void rangeCheckForAdd(int var1) {
if (var1 < 0 || var1 > this.size) {
throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));
}
}
//检验内部容量
private void ensureExplicitCapacity(int var1) {
++this.modCount;
//元素个数超过了内部容量,然后执行扩容方法
if (var1 - this.elementData.length > 0) {
this.grow(var1);
}
}
//拷贝方法
第一个参数是要被复制的数组
第二个参数是被复制的数字开始复制的下标
第三个参数是目标数组,也就是要把数据放进来的数组
第四个参数是从目标数据第几个下标开始放入数据
第五个参数表示从被复制的数组中拿几个数值放到目标数组中
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
//扩容方法啊
private void grow(int var1) {
int var2 = this.elementData.length;
//默认是扩容一半
int var3 = var2 + (var2 >> 1);
//如果扩容以后还不够,那么直接将数组大小设置为元素的个数
if (var3 - var1 < 0) {
var3 = var1;
}
//不过数组的大小也有限制
if (var3 - 2147483639 > 0) {
var3 = hugeCapacity(var1);
}
this.elementData = Arrays.copyOf(this.elementData, var3);
}
//判断新数组的长度是否超过了系统分配的最大容量
private static int hugeCapacity(int var0) {
if(var0 < 0) {
throw new OutOfMemoryError();
} else {
return var0 > 2147483639?2147483647:2147483639;
}
}
ArrayList为什么查找快,插入和删除慢?见博客
ArrayList为什么是线程不安全的?
源码分析:
public boolean add(E var1) {
this.ensureCapacityInternal(this.size + 1);
this.elementData[this.size++] = var1;
return true;
}
线程不安全原因一:
this.ensureCapacityInternal(this.size + 1);
这句代码是可以用来确定容量大小的,上面有这个方法的源码,单线程是没有问题的
现在有线程A,线程B同时执行add方法,此时数组容量为10,现在数组已经有9个值了,
线程A执行到 if (var1 - this.elementData.length > 0)这句,此时var1==10,判断不需要扩容,
此时B同时执行到这句,也判断不需要扩容,两个线程同时执行到this.elementData[this.size++] = var1;
然后现在有11个元素,但是容量为10,那么下标越界
线程不安全原因二:
this.elementData[this.size++] = var1;
我们知道size++,其实就是size=size,然后运行完以后再size=size+1;
那么this.elementData[this.size++] = var1可以分解为下面两步:
this.elementData[size] = var1;
size=size+1;
那么骚操作就来了,现在假设size=10,现在有两个线程,线程B正好走到了this.elementData[size] = var1这步,
给第十个位置赋值为“我去”,没来得及size=size+1,线程B也正好走到这步,重新给第十个位置赋值为"不去"
那么数据就会覆盖掉.
解决办法:Collections.synchronizedList(new ArrayList<int>>()),上同步锁
链表
哈希表