背景
最近在准备面试,结合之前的工作经验和近期在网上收集的一些面试资料,准备将Android开发岗位的知识点做一个系统的梳理,整理成一个系列:Android应用开发岗 面试汇总。本系列将分为以下几个大模块:
Java基础篇、Java进阶篇、常见设计模式
Android基础篇、Android进阶篇、性能优化
网络相关、数据结构与算法
常用开源库、Kotlin、Jetpack
注1:以上文章将陆续更新,直到我找到满意的工作为止,有跳转链接的表示已发表的文章。
注2:该系列属于个人的总结和网上东拼西凑的结果,每个知识点的内容并不一定完整,有不正确的地方欢迎批评指正。
注3:部分摘抄较多的段落或有注明出处。如有侵权,请联系本人进行删除。
本节内容只对部分数据结构做概述,算法的学习和刷题,文章底下会给出推荐的公众号
一、八大数据结构分类
二、常见集合
1 ArrayList
- 数据结构:ArrayList的底层数据结构是一个数组,数组元素的类型为Object类型,对ArrayList的所有操作底层都是基于数组的。
- 线程不安全:由于ArrayList进行添加元素的操作的时候是分两个步骤进行的,即第一步先在object[size]的位置上存放需要添加的元素;第二步将size的值增加1。由于这个过程在多线程的环境下是不能保证具有原子性的,因此ArrayList在多线程的环境下是线程不安全的。
- 继承关系: ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。
-
扩容机制: 将原元素数组的长度增大1.5倍(oldCapacity + (oldCapacity >> 1)),然后对扩容后的容量与minCapacity进行比较:
① 新容量小于minCapacity,则将新容量设为minCapacity;
②新容量大于minCapacity,则指定新容量。
最后将旧数组拷贝到扩容后的新数组中。 - 默认大小:在没有指定ArrayList大小的时候,默认为10。
-
增删改查效率:
①查询、修改:由于是基于数组的,可以通过数组下标直接定位到元素,因此查询和修改的时间复杂度为O(1)
②增加、删除:同理,在增加和删除元素时,增/删某个位置的元素后,后面的元素需要进行位移,因此时间复杂度为O(n)
2 LinkedList
- 数据结构:LinkedList的底层数据结构是一个双向链表,数组元素的类型为Object类型,包含两个重要的成员:header 和 size。
- header: 是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。
- 继承关系: 同ArrayList。
-
扩容机制: 不需要扩容机制,因为添加元素,是改变头尾节点指向的元素。
增删改查效率:
①查询、修改:由于是基于双向链表的,只能通过头尾节点去遍历定位到元素,因此查询和修改的时间复杂度为O(n)
②增加、删除:效率是比较高的,因为只涉及到指向节点的指针的改变,找到相应的节点之后进行替换设置好指针的前后指向地址,因此时间复杂度为O(1)
3 HashMap
3.1 数据结构
用到了4中数据结构:数组、链表、哈希表、红黑树(Java 8引入)
3.2 map.put(k,v)实现原理
(1)首先将k,v封装到Node对象当中(节点)。
(2)然后它的底层会调用K的hashCode()方法得出hash值。
(3)通过哈希表函数/哈希算法,将hash值转换成数组的下标,
①下标位置上如果没有任何元素,就把Node添加到这个位置上。
②如果下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行Object.equal()。
③如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。
④如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
3.3 map.get(k)实现原理
(1)先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(2)通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。
①如果这个位置上什么都没有,则返回null。
②如果这个位置上有单向链表,那么它就会拿着K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。
③如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。
3.3 HashMap红黑树原理分析
相比 jdk1.7 的 HashMap 而言,jdk1.8最重要的就是引入了红黑树的设计,红黑树除了插入操作慢其他操作都比链表快
-
转化时机:
当hash表的单一链表长度超过 8 个的时候,数组长度大于64,链表结构就会转为红黑树结构。当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。 -
为什么要这样设计呢?
好处就是避免在最极端的情况下链表变得很长很长,在查询的时候,效率会非常慢。
3.4 其他知识点
- 初始容积:数组大小为16,即2的4次方
- 扩容时机:容积大小(默认16) x 负载系数(0.75)= 12,即当数组达到12时,记性扩容,扩容成之前的两倍,即16 x 2 = 32.
- hash算法:对key值进行hashCode,在对hash值进行取模,如:hash=1001,取模后为1,Hashmap中有对取模后的值进行二次hash(目的:让hash值尽量分散,避免碰撞),最终作为数组的下边,将元素存入数组。
- hash碰撞:当hash=1和hash=1001,假设进行取模后,hash都为1,则出现hash碰撞,此时,引入链表,通过头插法,将最新的值放入链表的最前面
- 什么时候创建数组:第一次put()时
- 树化时机:单一链表长度超过 8 个的时候,数组长度大于64,链表结构就会转为红黑树结构。当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。
三、算法
算法的学习和刷题是一个长期的过程,这里推荐一个不错的学习算法的地方:
labuladong 的算法小抄