1 线性表
1.1 数组
- 简单:数组是一种最简单的数据结构。
- 占用连续内存:数组空间连续,按照申请的顺序存储,但是必须制定数组大小。
- 数组空间效率低:数组中经常有空闲的区域没有得到充分的应用。
- 操作麻烦:数组的增加和删除操作很麻烦 。
1.2 顺序表
内存连续
-
ArrayList的大小是如何自动增加的? add时,数组需要的大小超过原有大小,扩容到原有大小的1.5倍。
int newCapacity = oldCapacity + (oldCapacity >> 1);
-
什么情况下你会使用ArrayList?
ArrayList优点:尾插效率高,支持随机访问。缺点:中间插入或者删除(修改)效率低。应用:所以用到修改频率低,查找效率高的情景。
-
在索引中ArrayList的增加或者删除某个对象的运行过程?效率很低吗?解释一下为什么?
arrayCopy是很耗内存,耗时间的操作。
-
ArrayList如何顺序删除节点
迭代器删除 for循环只能删除最后一个节点
-
arrayList的遍历方法
- 迭代器 next hasNext
- for循环
1.3 链表
内存不连续。
-
增加节点
P点后增加S节点:
s.next = p.next;
p.next = s;
-
删除节点
删除P后面的节点:
p.next = p.next.next
-
修改
p.data = new data();
-
查找
循环查找。
手写一个单链表,并且实现单链表元素的逆置,(a0, a1,a2,a3,..an)-> (an,an-1,… a1, a0),算法的空间复杂度和时间复杂度经可能低。
手写双向链表, 实现增删改查,同时对比自己的LinkList 和源码Linkedlist的异同点
LinkedList、ArrayList比较:
- LinkedList 基于链表实现,链表内存是散列的,增删快(增删只需修改节点的引用关系,指向新的节点),查找慢(需要对链表进行遍历);
- ArrayList 基于数组实现,非线程安全,效率高,增删慢(增删需要复制原有数据),查找快(内存连续,直接使用下标访问元素)。
1.4 什么是内存缓存
预先将数据写到了容器(list,map,set)等数据存储单元中,就是软件内存缓存。
1.5 LRU算法(Least Recently Used)
算法思想:喜新厌旧
- 新数据插入到链表头部
- 当缓存命中(即缓存数据被访问),数据要移到表头
- 当链表满的时候,将链表尾部的数据丢弃
1.6 HashMap
两个key计算出的数组下标index = hash&(length-1),相等时,会产生哈希冲突。 链表存储结构可解决哈希冲突,多个Node对象,以链表的形式,存储在同一个bucket上。
HashMap默认大小为16,加载因子默认值0.75,当map中键值对的大小大于加载因子*length时,HashMap会扩容。
扩容是为了避免哈希冲突。
当加载因子为0.75时,在最大限度利用内存空间的前提下,减少哈希冲突。
HashMap在扩容的时候最耗内存,为避免扩容,new HashMap需要传入map的大小:map大小的预估值/0.75 +1,在HashMap构造方法中,会把该值转化成比该值大的,最接近该值的2次幂,作为map的大小。
HashMap的数组什么时候初始化的?
HashMap的数组初始值为空,put的时候,检测到数组为空,调用reSize,初始化数组。
put方法是HashMap真正使用的时候才调用的,所以在put方法中初始化数组是为了节约内存。
HashMap大小为什么是2的次幂:在计算数组下标的时候,2的次幂能充分利用hashCode的每一位,减少哈希碰撞。
加载因子*length=阈值,由于阈值的存在会造成hashMap有25%的内存空间浪费,这是HashMap为了提升效率,而采用的空间换时间大法。
Android设备内存一般不是特别大,为了减少内存的浪费出现了SparseArray。
-
什么是HashMap?为什么用它?
HashMap的数据结构:
- JDK1.7及之前:数组+链表
- JDK1.8:数组+链表+红黑树 其中链表/红黑树中节点的数据结构为Node{hash、key、value、next}。
hashMap结合数组(查找快)、链表(增删快)两者的优点,提高存储性能。
-
HashMap工作原理?
HashMap通过map.put(key,value),向map中存放数据,通过get(key)获取value。
put过程:对key进行hash,计算数组下标index = hash&(length-1),创建Node{hash、key、value、next}节点,插入到链表中。
get过程:对key进行hash,计算数组下标index = hash&(length-1),定位到目标bucket,遍历链表,比较hash和key均相等时,返回目标value。
-
当两个对象的hsahCode相同时会发生什么? 哈希冲突
-
当两个对象的hsahCode相同时怎么获取对象?
map.get过程中,遍历链表时,满足hash相同和key相同两个条件,才会返回value。
-
如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
负载因子默认值0.75,当HashMap大小超过了loadFactor*length时,HashMap会扩容成原来的2倍。
-
重新调整hashMap大小存在什么问题?
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了
-
为什么String、Interger这样的warpper类适合做为键?
因为String是不可变的,而且已经重写了equals()和hashCode()方法了。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到想要的对象。不可变性还有其他的优点如线程安全。
-
自定义对象可以作为key吗?
重写hsahCode和equels方法就可以作为key。
1.7 SparseArray
SparseArray是基于HashMap + 二分查找的思想实现的双数组结构,一个int[]保存key,一个Object[],通过数组下标建立映射关系。
key升序排列,
put:通过二分查找法,找到插入的位置,通过ArrayCopy实现插入。
get:使用二分查找法,找到目标key,并定位出value。
remove:通过二分查找法,找到目标位置,并不真正删除value,而是把对应位置的value设置成delete。减少删除时的ArrayCopy带来的性能损耗,并且再次向该位置put数据的时候,直接修改value即可。达到了越用越快的目的。
SparseArray节约内存的同时,也不会出现冲突的问题。
SparseArray速度越用越快。
缺点:key为int。
根据SparseArray的这些特点。我们能分析出其使用场景:
- key为整型;
- 元素个数相对较少;
1.8 ArrayMap
ArrayMap是基于HashMap + SparseArray实现的,与SparseArray原理一致。
ArrayMap解决了SparseArraykey为int的问题;
SparseArray解决HashMap内存浪费的问题。
HashMap解决ArrayList增删慢、LinkedList查找慢的问题。
1.9 容器总结
Collection依赖Iterator,包括:List(有序的)和Set(元素不允许重复)
Map:
2 树
2.1 树
N个节点构成的有限集合。
术中常用术语:
- 根节点:树中有一个称为”根(Root)”的特殊结点。
- 子树:其余节点可分为若干个互不相交的树,成为原来节点的“子树”。
- 结点的度:结点的子树个数。
- 树的度:树中所有结点中最大的度。
- 叶结点:度为0的结点。
- 父结点:有子树的结点是其子树的根结点的父结点。
- 子结点:若A是B的父结点,B就是A的子结点。
树的特点:
- 子树是不相交的
- 除了根结点之外,每个结点只有且只有一个父结点
- 一个N个结点的树只有N-1条边
2.2 二叉树
度为2的树,并且子树有左右顺序之分。
2.3 哈夫曼树
3 堆栈
4 图论
5 排序和查找算法
hashMap:1.8之前用的synchronized实现,1.8使用volatile+CAS实现