数组&链表
数组
用一组连续的内存空间,来存储一组具有相同类型的数据。
数组特性
- 数组中各个元素是有先后顺序的,在内存按照这个顺序连续存放在一起
- 相同类型的数据
- 支持随机访问,查找快
- 插入删除慢,因为要维护数组的连续性
- 数组下标可以表示有意义的数据
动态数组
当存不下新元素是,需要扩容数组:
- 重新开辟一块大小为当前容量两倍的数组
- 拷贝原数据
- 删除原数组
ArrayList与数组
ArrayList,由JDK封装,底层使用数组实现,不需要自实现扩容等操作。
如何选用
- 不知道数据大小的时候选ArrayList
- 知道数据大小,要求性能高,选用数组
注意点
数组最需要注意的是越界,尤其是头尾,需要更多关注。
堆栈内存
new创建出来的对象和数组存放在堆内存中。
堆栈区别
- 栈的速度更快
- 栈内存中的数据可以共享,主要存一些基本数据类型
int a = 3; //在栈中创建变量a 然后给a赋值,先不会创建一个3而是先在栈中找有没有3,如果有直接指向。如果没有就加一个3进来。
堆栈试题
String s1 = "a";
String s2 = "a";
System.out.println(s1 == s2);
/*结果:true
s1与s2指向同一个对象,在栈中的“a”
*/
String s1 = "a";
String s2 = "a";
s1 = "b";
System.out.println(s1 + s2);//ba
System.out.println(s1 == s2);//false
/*虽然s1与s2都指向同一个变量"a",但s1引用变化后不会改变s2的值
*/
String s1 = new String("a");
String s2 = "a";
System.out.println(s1 == s2);
/*结果:false
new的对象存放在堆中,s2的“a”存放在栈中。
*/
String s1 = "ja";
String s1 = "va";
String s3 = "java";
String s4 = s1 + s2;
System.out.println(s3 == s4);
/*结果:false
因为Java重载了+,当你使用“+”时,其实调用了StringBuild,会new对象。
*/
数组下标为什么从“0”开始
数组的内存空间是连续的。
int a[] = new int[3];
比如a数组内存地址为1001,1002,1003
内存寻址如下:
数组下标从0开始
a[0] => 1001 ==> 1001 + 0 * 4byte
a[1] => 1002 ==> 1001 + 1 * 4byte
a[2] => 1003 ==> 1001 + 2 * 4byte
数组下标从1开始
a[1] => 1001 ==> 1001 + (1-1) * 4byte
a[2] => 1002 ==> 1001 + (2-1) * 4byte
a[3] => 1003 ==> 1001 + (3-1) * 4byte
需要多一次减运算。
二维数组
int a[][] = new int[5][5];
/*
a[3][2]的内存寻址公式=(3*5+2) * 4byte
a[i][j] = (i*n+j) * 4byte(数据类型大小)
*/
链表
链表是一种物理上地址非连续、非顺序的存储结构,由一系列节点组成,每个节点包括两个部分:
- 存储数据的数据域
- 存储下一个节点地址的指针
链表结构
单向链表
head头节点需要自己记录
tail尾节点下一个节点指向null
插入与删除
双向链表
循环链表
链表特性
- 不需要连续的内存空间
- 有指针
Java中链表的应用
双向链表LinkedList
JDK1.7&1.8中的List
JDK1.7中的HashMap:数组+链表
JDK1.8中的HashMap:红黑树
LRU缓存淘汰算法
缓存最新使用的
使用链表实现:
- 在链表里查找,如果找到了,就删除,然后把新的节点插入到头部;
- 不在链表里,有空间就插入头部,没有空间就删除最后一个节点然后再插入头部;
- 链表要有大小限制。
约瑟夫问题
使用单循环链表实现。
链表与数组
区别
-
数组使用连续的内存空间,可以借助CPU缓存机制,预读数组中的数据,访问效率更高。
链表没有使用连续的内存空间,没有办法预读数据。
-
数组大小固定,新建的时候就占用整块连续的内存空间,数组过大,系统可能没有足够的连续内存空间分配,数组过小,可能会出现不够用的情况,需要动态扩容。
而链表没有大小限制,天然支持动态扩容。