学习路线:
看的时候,体系学习班可以先跳过有序表实现原理(视频第36、37、38),跳过四边形不等式(视频第42、43),跳过DC3算法(视频第45、46),其他都要看
大厂刷题班没有跳过的课,从第一节开始看,至少看到45节
1.数组
1.1、数组为什么检索快
任何数据结构在内存上分为两种,一种是紧密结构,如数组,在内存中是连续的,会分配一片连续的内存区域,另一种结构是跳转结构,当前元素中存储了下个元素的地址,根据地址访问下一个元素,如链表,一个Node中存储了value和地址,单链表就是存储了next的内存地址,双链表就是存储了Next、last两个内存地址
int[]arr = {1,2,3,4,5}当我们访问角标为2的位置上的元素时我们可以直接通过计算得出该元素对于的内存位置: 1000+2∗4
其中1000是我们的基地址,2代表了我们的偏移量,4代表了每个元素所占内存的大小(int占4个字节)这样通过一次计算,我们就能直接找到数组中对应角标的位置了。
如果我们的角标是从1开始的话,我们上面的公式是不是也得发生变化,就变成了下面这个样子:
1000+(3−1)∗4
多了一次减法的运算,使用0可以加快我们的访问速度
连续的内存空间跟相同类型的元素这两大利器决定了数组随机访问的特性。另外还需要补充的是,因为数组在内存空间中是连续的,所以CPU在读取时,可以对数组进行“预读”。什么是预读呢?CPU在从内存中加载数据时,会先把读取到的数组加载到CPU的缓存中。而CPU每次从内存中读取数据,并不是只读取那个特定的要访问的地址,而是读取一个数据块,并保存到CPU缓存中,然后下次访问内存数据的时候就会先从CPU缓存开始查找,如果找到就不需要再从内存中取,这样就实现了比直接访问内存更高效的访问机制(这样做的原因是因为,CPU处理数据的速度高于从内存中读取数据的速度)。
1.2 动态数组
1.动态数组ArrayList扩容复杂度为O(1),为什么?
ArrayList每次扩容为之前大小的一倍,会创建一个新的数组,然后将之前的数组copy到新扩容的数组,1,2,4,8,16,32.....,那么复杂度就为1+2+4+8+16+32+...+N,O(N),那么O(N)/N = O(1)
2.HashMap
HashMap没有值传递只有引用传递。
HashMap<Node,Node>,Node为对象,问这个HashMap在内存中占用的字节是多少,HashMap中只会存储Node的引用地址,而Node本身数据所占大小和HashMap无关,只和Node有关。所以大小为8+8=16
3.treeMap
可以借助treeMap做排序,见下图
2.堆栈
2.1栈解释:
虚拟机栈是Java方法执行的内存模型,栈中存放着栈帧,每个栈帧分别对应一个被调用的方法,方法的调用过程其实就是栈帧在虚拟机中入栈到出栈的过程。当程序中某个线程开始执行一个方法时就会相应的创建一个栈帧并且入栈(位于栈顶),在方法结束后,栈帧出栈
栈帧: 用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素,每个栈帧中包括:
1.局部变量表
用来存储方法中的局部变量(非静态变量、函数形参)。当变量为基本数据类型时,直接存储值,当变量为引用类型时,存储的是指向具体对象的引用
2.操作数栈
Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中的栈就是指操作数栈
3.指向运行时常量池的引用
存储程序执行时可能用到常量的引用
4.方法返回地址
存储方法执行完成后的返回地址
2.2 值传递和引用传递区别?
Java所有数据类型的传递,不管是基本类型还是引用类型都是值传递。
分为两种情况:
1.如果是基本类型数据,那么在不同栈帧(即不同方法),形参的操作不会影响原始值
2.如果是引用类型,分两种情况,1.实参和形参指向同一个对象地址,那么形参的操作会影响到实参指向的对象的内容。2.形参被改动指向新的对象地址(重新赋值引用),则形参的操作不会呀影响到原始值。
3.比较器
3.1.Comparotor
Priorityqueue、TreeMap、TreeSet都可排序,可以使用Comparator来修改比较算法,实现Comparator接口,重写compare方法五
3.2.字符串
字符串的比较compareTo是使用的字典序来比较的
字典序:
1.如果str1和str2的长度一样,那么按照高位比较,如abc<abd
2.如果str1和str2长度不一样,那么长度小的补最小的ACCII码0,如abcd<abcd,abc补0为abc00
4.运算符
1.异或^,相同为false,相异为true,相同为0,相异为1
5.堆
5.1大根堆的实现
5.2堆排序
举例:
一个数组arr1 = [1,2,4,6,0,3,],先调用heapInsert方法将数组变为大根堆arr2=[6,4,3,1,0,2], 然后将arr[2],末尾和顶部交换,heapSize减1,(把头部最大节点放在末尾,新的顶部节点和子节点比较大小,如果小于子节点,heapify下沉交换,再次比较新顶部节点和尾部节点大小,一直循环。。当heapSize=0时证明排序已经完成)
heapiInsert,上升,建立大根堆,时间复杂度为O(N)
heapify,下沉,
6.Hash函数
6.1Hash函数的特点:
1.固无穷大输入,固定输出
2.hash函数的均匀性
3.防碰撞性
无穷大的输入通过Hash函数,再%一个值,就会得到一个固定的值,比如%17,就会得到17以内的值,每个值在hash表中为一个桶,即为数组,当Hash为相同值时,就会在桶的后面追加链表。
因为Hahs函数和有均匀性,所以0-16的桶上的数据都是均匀分布的。
当发现其中一个桶的数据>6 等于7时,那么hash表就会自动扩容为原来的2倍,即为34,然后所有数据重新%34,重新进桶。
为什么Hash表的时间复杂度为O(1)??
每次扩容都是一个等比数列,总时间复杂度为O(N),除扩容数量的代价(即多少数需要重新进桶)N,得到的就是O(1).,
例:加到两个数的时候,代价为2,加到4个数据的时候代价为4,加到8的时候代价为16,加到N的时候代价为N,所以Hash表单次代价的时间复杂度就为O(N)/N = O(1),极低
为什么Java中桶后的链表结构改为了红黑树,因为红黑树可以放更多的链表数据,减少了之前的链表因为到了7之后的再次扩容的次数。
6.2布隆过滤器:
(41条消息) 详解布隆过滤器的原理和实现_kevwan的博客-CSDN博客_布隆过滤器的原理
利用了Hash函数的性质
概念:假如str1来了,每str1通过K个hash函数,然后%一个数值,落到m上(m为一个bit数组,一个byte等于8个bit),将k个m上的值置为1,当str2来了之后,会再执行K个hash函数,如果K个位置上的值一样,那么证明当前数据已经存在(hash特性,固定值输入,固定值输出),这样就极大的缩减了空间,但是存在了一定的失误率,非0失误率。
背住三个公式,面试官如果考布隆过滤器,那么先问面试官咱们的系统允许有失误率吗?
如果允许,那么就是考布隆过滤器,如果不允许那就一定不是考布隆过滤器
然后问面试官m布隆过滤器的大小可以扩大吗?如果可以,用扩大的m真,算出K真,再算出P真(真实失误率),那么所的到的失误率一定比面试官给的失误率小
如何得到K个Hash函数??
用两个Hash函数就可以,
1-->f1()+f2()
2-->2*f1()+f2()
3-->3*f1()+f2()
K个--->K*f1()+f2()
布隆过滤器的应用,如过滤垃圾邮件,判断用户是否登录,解决redis缓存穿透,具体应用见链接:redis缓存穿透解决方案(布隆过滤器的实现)_个人文章 - SegmentFault 思否
6.3Hash一致性
Hash一致性的应用,Nginx的Hash一致性算法,用来均匀的将请求落到每一台服务器上,最主要的是确定业务HashKey,目的是为了使请求均匀的落在每一台服务器上。但是如果新增或减少一台机器,所有的请求就需要重新hash(),然后就得重新%,迁移的代价是全量的。
讲述:
1.将Hash函数的返回值想象为一个环,比如hash后的范围为0-2的64次方-1,假设现在有三台不同的机器m1、m2、m3,这三台机器的有些属性不一样如hostName、IP、MAC地址,现在我们以IP来说。将三个IP认为是字符串,用唯一的hash函数f(),将三台机器上环。
假如来了一个数据(左,35),那么左也会落到环上,顺时针取第一个机器节点。
如何顺时针找???
如m1的hash值为7,m2的hash值为64,m3的hash值为9,三台机器排完序之后,就存在了一张路由表【7,9,64】,前端服务器该如何组织如何组织,每个逻辑端服务器都存储这个路由表,假设现在左请求来了,随机打到一台上算出来的hash值为56,就相当于在有序数组中找到>56最左的位置,可以使用二分,速度很快。
2.问题
如何保证m1、m2、m3在环上均分?加完机器后如何保证均分?加完机器后是否需要全量迁移?也有可能hash后会离得很近。
答:使用虚拟节点,m1分配1000个虚拟节点,m2分配1000个虚拟节点,m3分配1000个虚拟节点,这样每个请求落在虚拟节点上就均分了,然后找到虚拟节点指向的机器节点即可,这样就保证了负载均衡,当然如果m1性能好,m2性能中等,m3性能较差,就可以将m1分配2000虚拟节点,m2分配1000,m3分配500,这样实现了负载调整。
如果新增了m4,那么就从m1、m2、m3中分别取出1/12的数据,放入m4,这样就保证了m1、m2、m3、m4均等为1/4。这样迁移量就从全量变为1/12加1/12加1/12,变为1/4,减少了数据迁移代价。
递归
时间复杂度计算:
排序
归并排序