数据结构
java只提供了两种基础数据结构:
- 单个变量
- 数组
之后的各种的数据结构,都是对这两种基础数据结构类型的管理;
ArrayList
本质是一组连续的数组;
new 的时候会先开辟一组空间,先占用着,不够用了再扩容;
扩容时:
- 先去找连续的空闲的合适大小的一组内存空间
- 老的数据先用深拷贝至新的长数组中
- 删除老的数组
缺点:在中间指定位置插入数据时,后面所有元素都得往后移动,效率低;
优点:查询快,直接用下标就可以找到元素;
LinkedList
本质是单个变量;不用连续空间;
用的是双向链表结构:多个元素时,会在当前元素持有下一个元素的地址,并且当前元素还会持有上一个元素的地址;
缺点:查询慢;查询方式:a=总长/2; 与要查的下标index进行比较,a大于index,则从头根据元素中的地址开始一个一个查,小于,则从尾根据元素中的地址开始一个一个查;
优点:在中间指定位置插入数据时,效率高;
性能提升
- 用时间换空间
- 用空间换时间
经典面试题:
在不用第三个变量的情况下,将两个变量值进行交换;
//使用第三方变量temp:使用额外的空间来减少计算时间
int a;
int b;
int temp;
temp=a;
a=b;
b=temp;
//不使用第三方变量:增加计算时间来减少使用空间
int a;
int b;
a=a+b;
b=a-b;//a+b-b=a
a=a-b;//a+b-a=b
Hash函数
对象的hashcode的高16位和低16位进行“异或”运算(不同则为1,相同则为0);
int h = obj.hashCode();
int hash = h ^ (h >>> 16);
HashMap
数组+单向链表;将数据分为N个链表;链表的头放在数组中;
- key-value储存;
- key唯一;
- 初始长度16;
- 判断是否扩容的阈值为:当前长度*0.75f;(0.75f为国际上测试的最适合的值)
- 用key计算的同一个index放在同一个链表中;链表的第一个放在数组中;
参考:为什么HashMap的加载因子是0.75? - 沐雨橙风~~ - 博客园
计算下标index:
- key用hash函数计算得到hash值;
- 得到的hash值与(数组长度-1)进行“与&”运算(都为1则为1,其他为0)
长度-1的二进制都为11111时,才能得到唯一的一个下标;
所以HashMap的长度都是2的幂次方; (2的幂次方-1)得到的数据都是11111之类的;
若不是“1111”之类的,会造成不同的key会落到同一个下标,造成hash冲突;
确定容量:
- 输入一个长度数字
- 取数字最高位的第一个1;
- 将1之后低位的全变为1;
- 将得到的这种“111111”加1;
- 得到的数“1000000”就是2的幂次方
为什么是2倍扩容?
为了解决hash冲突问题,但是没有完全解决;
JDK1.7的HashMap死环问题:
- 扩容时,头插法进行插入数据时,完成数据替换后,会导致引用顺序颠倒
- 在多线程切换时,可能会发生相互引用,发生死环
JDK1.8解决办法:
- 采用尾插法
- 加入高低位分开插入
- 结束后,断掉尾部引用
- 链表长度大于8时,转换为红黑树
红黑树:
HashMap简述及红黑树 - sys_user_findnull - 博客园
HashSet
只储存对象,对象不能重复,使用对象计算hash值;