本文主要是介绍【牛客网面经整理】7.20shopee一面面经,加入我自己整理的相关拓展问题(redis)),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
作者:咚咚锵233
链接:https://www.nowcoder.com/discuss/497119?type=0&order=7&pos=7&page=1&channel=1009&source_id=discuss_center_0
来源:牛客网
7.20一面:
怎么判断一个链表是否有环?
答:快慢指针
哪些数据结构可以做查询,时间比较快的,并且比较一下?
答:我当时想到的是hashmap和树形结构的查找
1、有序数组 查找快 插入慢删除慢大小固定
2、二叉树 查找插入删除快 算法复杂
3、红黑树 查找插入删除快 算法复杂
4、hash表 存取极快(已知关键字),插入快 删除慢,对存储空间利用不充分
5、堆 插入块、删除快、对大数据项存取快 对其他数据项存取慢 适合较小的索引(目录)
6、B树 查找快 适合文件索引
如果让你设计一个哈希表,你要怎么做?还有就是碰撞多了怎么处理?怎么扩容的?具体怎么实现?rehash在多线程中会出现什么问题?
答:参考上一篇题解;
红黑树和二叉平衡树的区别说一下
答:
1、BST树(二叉排序树、二插搜索树)
二叉排序树或是一颗空树,或是具有下列性质的二叉树:
- 每个结点都有一个作为搜索依据的关键码(key),所有结点的关键码互不相同;
- 左子树(如果存在)上所有结点的关键码都小于根结点的关键码
- 右子树(如果存在)上所有结点的关键码都大于根结点的关键码
- 左子树和右子树也是二叉搜索树
如果对一棵二叉搜索树进行中序遍历,可以按从小到大的顺序,将各结点关键码排列起来,所以也称二叉搜索树为二叉排序树。
2、AVL树
一棵AVL树或者是一棵空树,或者是具有下列相知的二叉搜索树:
- 它的左子树和右子树都是AVL树,且左子树和右子树的高度差绝对值不超过1
节点的平衡因子:
- 每个结点附加一个数字,给出该结点右子树的高度-左子树的高度所得的高度差。这个数字即为结点的平衡因子balance
- 根据AVL树的定义,任一结点的平衡因子只能取-1,0,1;
- 如果一个结点的平衡因子的绝对值大于1或小于-1,则这颗二叉树就失去了平衡,不再是AVL树;
- 如果一颗二插搜索树是高度平衡的,它就成为AVL树。如果它有n个节点,其高度可保持在O(log2n),平均搜索长度也可保持O(log2n)
3、RB树
红黑树是一种自平衡的二叉搜索树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
时间复杂度:O(logn)
性质
红黑树是每个节点都带有颜色属性的查找二叉树。增加如下性质
- 每个节点是红色或黑色;
- 根节点是黑色的;
- 每个叶节点(Null)是黑色;
- 每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任意节点到其每个叶子的所有路径都包含相同数目的黑色节点
这些约束强制了红黑树的关键性质:从根到叶子的最长的可能路径不多于最短路径的可能路径的两倍长。使得这个树在大致上平衡的。
最短路径可能是全是黑色节点;最长路径可能是交替的红色和黑色节点。
只读操作不需要修改;插入和删除时恢复红黑树属性需要少量(O(logn))的颜色变更(在实践中非常快速)并且在删除节点是不超过三次树旋转;插入是不超过两次树旋转;
AVL和RB的比较(实现的功能都可以用AVL树替代,那么为什么还需要引入RB):
1、
2、红黑树不追求“完全平衡”,即不像AVL那样要求节点的balance<=1,它只要求部分达到平衡,但是为节点增加颜色,红黑是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,而AVL是严格平衡树,因此在增加和删除节点的时候,旋转的次数比红黑树要多。
3、插入节点:AVL和RB都是最多两次树旋转来实现复衡re_balance,旋转的量级是
O(1)
4、删除节点导致失衡,AVL需要维护从被删除节点到根节点root这条路径上所有节点的平衡,旋转的量级为O(logN)(当插入更新或删除更新时,如果当前节点的高度没有改变且平衡值在【-1,1】区间,则所有父节点的高度和平衡都不会改变。根据这个推论,AVL的插入删除大部分需要向上回溯两到三层即可,范围十分紧凑)RB树最多需要旋转3次实现复衡,只需要O(1),所以说RBTree删除节点的复衡效率更高,开销更小,但颜色会回溯到根节点,时间O(logN)
5、AVL更平衡,结构上更加直观,时间效率针对读取而言更高,维护稍慢,空间开销大;
RB读取略逊于AVL,维护强于AVL,空间开销与AVL相似,内容较多时优于AVL,故引入RB是功能、性能、空间开销的折中结果
应用场景:实际应用中,如果搜索次数远远大于插入和删除,那么选择AVL,如果搜索、插入次数几乎差不多,应该选择RB
跳表知道吗,讲一下
答:
- 增加了向前指针的链表叫做跳表(SkipList)。
- 跳表是一个随机化的数据结构,实际上是一个可以进行二分查找的有序链表。可以看成是二叉树的一个变种,性能上与红黑树,AVL树不相上下,但是原理很简单,目前在Redis和LeveIDB中都有用到。
- 性质:(1)由很多层构成(2)每一层都是一个有序链表(3)最底层level 1的链表包含所有元素(4)如果一个元素出现在Level i的链表中,则它在level i之下的链表也都会出现(5)每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素
- 跳表在原有的基础上增加了多级索引,通过索引来实现快速查找。
- 跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
- 时间复杂度:搜索,插入,删除操作的复杂度均为O(n+MaxLevel)平均复杂度为O(logn)
- 空间复杂度:O(n*MaxLevel)
讲一下MySQL的搜索引擎以及区别
答:主要研究的是MyISAM、InnoDB、Memory这三个引擎;
InnoDB存储引擎
InnoDB是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键,InnoDB是默认的Mysql引擎。主要特性有:
1、InnoDB给MySQL提供了具有提交、回滚和崩溃回复能力的事务安全(ACID兼容)存储引擎。InnoDB锁定在行级并且也在select语句中提供了一个类似Oracle的非锁定读。这些功能增加了用户部署和性能。在SQL查询中,可以自由地将InnoDB类型的表和其他MySQL的表类型混合起来,甚至在同一个查询中也可以混合。
2、InnoDB是为处理巨大数据量的最大性能设计的。它的CPU效率可能是任何其他基于磁盘的关系型数据库引擎锁不能匹敌的。
3、InnoDB存储引擎完全与MySQL服务器整合,InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池。InnoDB将它的表和索引在一个逻辑表空间中,表空间可以包含数个文件(或原始磁盘文件)。这与MyISAM表不同,比如在M有ISAM表中每个表被存放在分离的文件中。InnoDB表可以是任何尺寸。
4、InnoDB支持外键完整性约束,存储表中的数据时,每张表的存储都按主键顺序存放,如果没有显示在表定义时指定主键,InnoDB会为每一行生成一个6字节的ROWID,并以此为主键
5、InnoDB被用在众多需要高性能的大型数据库站点上。
InnoDB不创建目录,使用InnoDB时,MySQL将在MySQL数据目录下创建一个名为ibdata1的10M大小的自动扩展数据文件,以及两个名为ib_longfile0和ib_logfile1的5MB大小的日志文件。
MyISAM存储引擎
MyISAM基于ISAM存储引擎,并对其进行扩展。它在web、数据仓储和其他应用环境下最常使用的存储引擎之一。
拥有较高的插入、查询速度,但不支持事务。
MEMORY存储引擎
MEMORY存储引擎将表中的数据存储在内存中未查询和引用其他表数据提供快速访问。
使用场景:
1、如果需要提供提交、回滚、奔溃恢复能力的事务安全(ACID兼容)能力,并要求实现并发控制,InnoDB是一个好的选择
2、如果数据表主要用来插入和查询记录,则MyISAM引擎能提供较高的处理效率。
3、如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果。
4、如果只有insert和select操作,可以选择Archive,Archive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archive
灵活选择,一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求,使用合适的存储引擎,将会提高整个数据库的性能
讲一下MVCC
答:
什么是MVCC
多版本并发控制,MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。
MVCC的作用
如何控制并发是数据库邻域中非常重要的问题之一,不过到今天为止事务并发的控制已经有了很多成熟的解决方案,介绍三种机制:悲观并发机制、乐观并发机制、多版本并发控制
- 悲观并发控制:最常见的并发控制机制,也就是锁;
- 乐观并发控制(乐观锁):乐观锁其实不是一种真实存在的锁。
- 多版本并发控制MVCC:可以与前两者任意一个结合使用,以提高数据库的读性能。
悲观并发控制
控制不同的事务对同一份数据的获取是保证数据库的一致性的最根本方法,如果能够让事务在同一时间对同一资源有着独占的能力,那么就可以保证操作同一资源的不同事务不会相互影响。最简单的方法就是加锁,在悲观并发控制中,数据库程序对于数据被修改持悲观的态度,在数据处理过程中都会被锁定,以此来解决竞争的问题。
读写锁
为了最大化数据库事务的并发能力,数据库中的锁被设计为两种模式,分别是共享锁和互斥锁。当一个事务获得共享锁后,它只可以进行读操作,所以共享锁也叫读锁;当一个事务获得一行数据的互斥锁时,就可以对该行数据进行读和写操作,所以互斥锁也叫写锁。
共享锁和互斥锁除了限制事务能够执行的读写操作之外,它们之间还有“共享”和“互斥”的关系,也就是多个事务可以同时获得某一行数据的共享锁,但是互斥锁与共享锁和其他的互斥锁并不兼容。
乐观并发控制
乐观并发控制也叫乐观锁,但是它不是真正的锁,是一种并发控制的思想。
先介绍基于时间戳的并发控制机制,然后在这个协议的基础上进行扩展,实现乐观的并发控制机制。
基于时间戳的协议
锁协议按照不同事务对同一数据项请求的时间依次执行,因为后面执行的事务想要获取的数据已经被前面的事务加锁,只能等待锁的释放,所以基于锁的协议执行事务的顺序与获得锁的顺序有关。基于时间戳的协议能够在事务执行之前先决定事务的执行顺序。
每一个事务都会具有一个全局唯一的时间戳,它既可以使用系统的时钟时间,也可以使用计数器,只能保证所有的时间戳都是唯一并且随时间递增的就可以。
基于时间戳的协议能够保证事务并行执行的顺序与事务按照时间戳串行执行的效果完全相同;每一个数据项都有两个时间戳,读时间戳和写时间戳,分别代表了当前成功执行对应操作的事务的时间戳。
该协议能够保证所有冲突的读写操作都能按照时间戳的大小串行执行,在执行对应操作时不需要关注其他事务只需要关心数据项对应时间戳的值就可以了:无论是读操作还是写操作都会从左到右依次比较读写时间戳的值,如果小于当前值就会直接被拒绝后回滚,数据库系统会给回滚的事务添加一个新的时间戳并重新执行这个事务。
基于验证的协议
乐观并发控制其实本质上就是基于验证的协议,因为在多数的应用中只读的事务占了绝大多数,事务之间因为写操作造成冲突的可能性非常小,也就是说大多数的事务在不需要并发控制机制也能运行的非常好,也可以保证数据库的一致性;而并发控制机制其实向整个数据库系统添加了很多开销,我们其实可以通过别的策略降低这部分开销;
而验证协议就是我们找到的解决办法,它根据事务的只读或者更新将所有事务的执行分为两到三个阶段:
在读阶段,数据库会执行事务中的全部读操作和写操作,并将所有写后的值存入临时变量中,并不会真正更新数据库中的内容;这时会进入下一个阶段,数据库程序会检查当前的改动是否合法,也就是是否有其他事务在READ PHASH期间更新了数据,如果通过测试那么就会直接进入WRITE PHASH将所有存在临时变量中的改动全部写入数据库,没有通过测试的事务会直接被终止。
为了保证乐观并发控制能够正常运行,我们需要知道一个事务不同阶段的发生时间,包括事务开始时间,验证阶段的开始时间以及写阶段的结束时间;通过这三个时间戳,我们可以保证任意冲突的事务不会同时写入数据库,一旦由一个事务完成了验证阶段就会立即写入,其他读取了相同数据的事务就会回滚重新执行。
作为乐观并发控制,它会假定所有的事务在最终都会通过验证阶段并且执行成功,而锁机制和基于时间戳排序的协议是悲观的,因为他们会在发送冲突时强制事务进行等待或者回滚,哪怕有不需要锁也能够保证事务之间不会冲突的可能。
多版本并发控制
上面介绍的两种其实都是通过延迟或者终止相应的事务来解决事务之间的竞争条件来保证事务的可串行化;虽然前面的两种并发控制机制确实能够从根本上解决并发事务的可串行化问题,但是在实际开发中数据库的事务大都是只读的,读请求是写请求的很多倍,如果写请求和读请求之间没有并发控制机制,那么最坏的情况也是读请求读到了已经写入的数据,这对于很多应用是完全可以接受的。
在这个前提下,数据库系统引入了另外一种并发控制机制–多版本并发控制,每一个写操作都会创建一个新版本的数据,读操作会从有限多个版本的数据中挑选一个最合适的结果直接返回;在这时,读写操作之间的冲突就不再需要被关注,而管理和快速挑选数据的版本就成了MVCC需要解决的主要问题。
MVCC并不是一个乐观锁和悲观并发控制对立的东西,它能够与两者很好的结合以增加事务的并发量,在目前流行的sql数据库中都对MVCC进行了实现;但由于它们分别实现了悲观锁和乐观锁,所以MVCC实现的方式不同;
MySQL与MVCC:
MySQL中实现的多版本两阶段锁协议将MVCC和2PL的优点结合了起来,每一个版本的数据行都具有一个唯一的时间戳,
- 当有读事务请求时,数据库程序会直接从多个版本的数据项中具有的最大时间戳的返回。
- 更新操作就稍微有点复杂,事务会先读取最新版本的数据计算出数据更新后的结果,然后创建一个新版本的数据,新数据的时间戳是目前数据行的最大版本+1;
- 数据版本的删除也是根据时间戳来选择的,MySQL会将版本最低的数据定时从数据库中清除以保证不会出现大量遗留内容。
PostgreSQL与MVCC
与MySQL中使用悲观并发控制不同,PostgreSQL中都是使用乐观并发控制的,这也就导致了MVCC在与乐观锁结合时的实现上有一些不同,最终实现的叫做多版本时间戳排序协议,在这个协议中,所有的事务在执行之前都会被分配一个唯一的时间戳,每一个数据项都有读写两个时间戳。
当PostgreSQL的事务发出了一个读请求,数据库直接将最新版本的数据返回,不会被任何操作阻塞,而写操作在执行时,事务的时间戳一定要大于或者等于数据行的读时间戳,否则就会被回滚。
这种MVCC的实现保证了读事务永远都不会失败并且不需要等待锁的释放,对于读请求远远多于写请求的应用程序,乐观锁加MVCC对数据库的性能有着很大的提升;但也会导致两个问题,一个是每一次读操作都会更新读时间戳造成两次的磁盘写入,第二是事务之间的冲突是通过回滚解决的,所以如果冲突的可能性非常高或者回滚代价巨大,数据库的读写性能还不如使用传统的锁等待方式。
讲一下char和varchar的区别?优缺点?索引用char还是varchar好
答:1、char适用于确定长度的字符串:如手机号码,邮政编码
2、varchar适用于不确定字符串的长度,比如:商品名称标题
3、char速度较快,但比较耗费空间,char(m)当实际存储字符串长度小于限定m大小的时候,会用空格右边填充达到m长度;
4、varchar(m)根据实际存储字符串的长度来决定占用空间;
5、varchar(m)的m的取值大小和数据库的编码有关,gdk编码下,中文占2个字节,utf8编码下,中文占3个字节;
6、表的一行记录中char和varchar的所有字段存储长度受65535字节制约
7、text类型不占剧表格中的数据容量限制,最长可存储65535个字节,实际应用中,字符长度大可考虑使用text类型。
聚簇索引和二级索引说一下
答:
- 聚簇索引(B+Tree结构)的叶节点就是数据节点(索引和数据是在同一个结构中),一般主键索引都是聚簇索引;
- 非聚簇索引(二级索引、辅助索引)(B+Tree结构)的叶节点仍然是索引节点,并保留一个链接指向对应数据块。
页和操作系统的关系
1、为什么要有内存管理?
答:因为用到的数据量的大小远远大于内存的大小,虚拟内存就是将程序用到的数据进行划分,暂时用不到的放到磁盘中,用得到的放到内存中。
2、什么是页式内存管理?
答:虚拟内存位于程序和物理内存之间,程序只能看见虚拟内存,不能直接访问物理内存。每个程序都有自己独立的进程地址空间(虚拟地址)。分段和分页就是为了解决怎么从虚拟地址映射到物理地址,因为程序最终肯定是运行在物理内存中。
分页机制就是把内存地址空间分成若干个很小的固定大小的页,每一页的大小由内存决定,提高内存和磁盘的利用率。
3、页的大小为什么是4k?
CPU位数准确地说应该是CPU一次能够并行处理的数据宽度,一般就是指数据总线宽度。
4、mysql索引和页的关系
BTree一般用于数据库的索引;
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。而IO存取消耗太大,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘IO操作次数的渐进复杂度。
为了达到这个目的,磁盘按需读取,要求每次都会预读的长度一般为页的整数倍,而且数据库系统将一个节点的大小设为等于一个页,这样每个节点只需要一次IO就可以完全载入。
- 对于InnoDB和MyISAM引擎中的索引,都分为主键索引和二级索引;
- 在InnoDB中,强制使用主键作为聚簇索引,B+Tree叶子节点存储就是主键数据,而二级索引是非聚簇的,其叶子节点存储的是主键的键值;
- 在MyISAM中,主键索引与二级索引没什么区别,都是采用非聚簇,都是存储的数据对应的地址
总结:
1、主键索引(聚簇索引)查询效率比非主键索引查询效率更高。如果能使用主键查找的,就尽量使用主键索引进行查找
2、主键定义的长度越小,二级索引的大小就越小,这样每个磁盘块存储的索引数据就越多,查询效率就越高。
因为MyISAM的主索引并非聚簇索引,那么他的数据的物理地址必然是凌乱的,拿到这些物理地址,按照合适的算法进行I/O读取,于是开始不停的寻道不停的旋转。聚簇索引则只需一次I/O。(强烈的对比)
不过,如果涉及到大数据量的排序、全表扫描、count之类的操作的话,还是MyISAM占优势些,因为索引所占空间小,这些操作是需要在内存中完成的。
为什么主键通常建议使用自增id?
答:聚簇索引的数据的物理存放顺序与索引顺序是一致的。即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的。
如果主键不是自增id,那么可以想 象,它会干些什么,不断地调整数据的物理地址、分页,当然也有其他一些措施来减少这些操作,但却无法彻底避免。但,如果是自增的,那就简单了,它只需要一 页一页地写,索引结构相对紧凑,磁盘碎片少,效率也高。
最左匹配原则讲一下。假如我现在有A,B,C的复合索引,我条件是A用where B用了between,索引还能命中吗?
答:
索引口诀:
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
like百分写最后,覆盖索引不写星;
join连接类型同,order条件非表达式;
不等空值or和’0/1’,索引失效要少用。
比如本题中,(ABC)相当于创建了(A)(A,B)(A,C)(A,B,C)四个索引
最左匹配原则就是只要 使用到了组合索引的第一个,索引即可生效;即需要先匹配第一关A,然后匹配第二关B,然后第三关C,有时底层会进行SQL优化。
使用explain关键字可以验证,根据最左匹配原则,相当于创建了(A)(AB)(AC)(ABC),where A between B and “”由于范围查询,范围后的索引失效只用到了A
explain指令详解:
- 用来捕捉性能问题,打开慢查询,定位执行效率差的sql(比如我们想知道该SQL的执行计划,比如是全表扫描,还是索引扫描),是查看优化器如何决定执行查询的主要方法。
- 表格中的属性
id:选择标识符
select_type:表示查询的类型。
table:输出结果集的表
partitions:匹配的分区
type:表示表的连接类型
possible_keys:表示查询时,可能使用的索引
key:表示实际使用的索引
key_len:索引字段的长度 单位:字节数 和你建立索引的属性是有关的
ref:列与索引的比较
rows:扫描出的行数(估算的行数)
filtered:按表条件过滤的行百分比
Extra:执行情况的描述和说明
TCP为什么三次握手,不是两次不是四次?
答:
僵尸进程和孤儿进程?
答:
概念
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述仍然保存在系统中,这种进程称之为僵尸进程。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
危害:
unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个 子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
解决办法:
(1)通过信号机制
子线程退出时向父线程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
(2)fork两次
将子进程设为孤儿进程,从而其父进程变为init进程,通过init进程可以处理僵尸进程。
进程,线程,协程的区别
答:进程(同步):(运行起来的程序)
1、进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动;
2、进程是系统进程资源分配和调度的最小单位
3、每个进程都有自己的独立空间,不同进程通过进程间通信来通信。
4、进程比较重量,占据独立内存,所以上下文进程切换开销比较大,但比较稳定。
线程(同步):
1、线程是CPU调度和分派的基本单位;
2、线程是进程的一个实体
3、线程基本不拥有自己的系统资源,只拥有一些运行中必不可少的资源(如程序计数器、一组寄存器和栈);
4、线程间通信主要通过内存共享,上下文切换很快,资源开销较少,但不够稳定。
协程(异步):
1、是一种用户态的轻量级线程
2、协程的调度完全由用户控制
3、协程拥有自己的寄存器上下文和栈。
4、协程调度切换时,将寄存器上下文和栈保存到其他地方,切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈基本没有内核切换的开销,可以不加锁访问全局变量,上下文切换非常快。
稍微问了一下项目,因为我的项目太简陋了
答:
Java的运行时数据区域详细讲一下。
答:组成部分:程序计数器(线程私有)、虚拟机栈(线程私有)、本地方法栈(线程共享)、方法区/永久代(线程共享)、堆(线程共享),运行时常量,直接内存。
1、程序计数器
一块较小的内存区域,可以看做当前线程执行字节码的行号指示器。
1.1作用
(1)字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如顺序执行、选择循环、异常处理……
(2)在多线程情况下,程序计数器用于记录当前线程的执行位置,从而当线程被切换回来的时候能够知道该线程上次运行的位置。
1.2是否线程共享
线程私有
1.3生命周期
随着线程的创建而创建,随着线程的结束而死亡
1.4抛出异常
唯一一个不会出现OOM异常的内存区域
2、java虚拟栈
描述的是java方法执行的内存模型;
由一个个栈帧组成,栈帧由(局部变量表,操作数栈,动态链接,方法出口信息)组成
2.1作用
每一个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息,一个方法从调用知道执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。
2.2是否线程共享
线程私有
2.3生命周期
随着线程的创建而创建,随着线程的结束而死亡
2.4抛出异常
(1)栈溢出异常
(2)OOM异常
3、本地方法栈
3.1作用
和虚拟机栈发挥的作用的相似,但是虚拟机栈是为虚拟机执行java方法而服务的,本地方法栈是为虚拟机执行Native方法而服务的。
3.2是否线程私有
不是线程私有
3.3生命周期
3.4抛出异常
(1)爆栈
(2)OOM
4、堆
java虚拟机下所管理的最大的一块
java堆是所有线程共享的一块内存区域,在虚拟机启动时创建
java堆是垃圾收集管理的主要区域,因此很多时候也被称为GC堆
4.1作用
存放对象实例,几乎所有的对象实例以及数组都是在这里分配内存(运行时的对象)
4.2是否线程私有
不是线程私有
4.3生命周期
4.4抛出异常
如果在堆中没有内存完成实例分配并且堆也无法再扩展时会抛出OOM异常
5、方法区
5.1作用
用于存储已被虚拟机加载的类信息、常量、静态变量、及时编译器编译之后的代码(类型数据)
5.2是否线程私有
不是线程私有
5.3生命周期
整个程序的生命周期
5.4抛出异常
当方法区无法满足内存分配的需要时会抛出OOM异常
6、运行时常量池
注意:已经明确的一点是 字符串常量池、静态变量 在jdk7时从方法区移入Java堆中,那么运行时常量池呢?
我看了jdk6/7/8三版jvm文档,对运行时常量池的描述都是方法区的一部分
作用:
用于存放编译期生成的字面量和符号引用,这部分内容将在类加载之后进入方法区中的运行常量池中存放。
当常量池无法申请到内存时会抛出OOM异常
7、直接内存
直接内存并不是虚拟机运行时数据区域的一部分,也不是java虚拟机规范中定义的内存,但是这部分内存也被频繁使用,也可能导致OOM异常
JVM具体怎么执行的呢?
答:
启动——读取参数“hello world”——作为初始类加载到内存中——进行初始化和动态链接——从main方法开始执行——虚拟机进程启动就绪——虚拟机中的类加载器加载必要的class文件——虚拟机进程解释class字节码指令——将字节码指令翻译成CPU能够识别的指令,才能在CPU运行
jvm在运行过程中有三个子系统来保障它正常运行(类加载器子系统、执行引擎子系统、垃圾收集子系统)
jvm空间:class文件存储的区域:方法区;
线程执行数据区:栈区;
对象存储区:堆区
反问环节
7.24二面:
讲一下项目
怎么理解微信商务号的AppID
redis概念?
内存
key-value
worker 单线程(原子性操作)
io:epoll
如何存储一个弹幕系统的弹幕,使用redis的什么类型,怎么保证热点数据,每秒会产生大量的数据,redis扛不住怎么办
答:List(集合)
redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
如果要从别的部门的业务那里读取信息,但是别的部门产生了问题导致数据没法及时响应,我这边怎么解决
答:将这个没法响应的消息先存放在队列中,不要影响其他消息访问,过一定时间再访问一次
在你的项目中如何运用redis?
答:方向:解决并发,性能。
当客户端请求量大————数据库会达到瓶颈解决方案:(数据库连接池(效果不好))redis内存数据库(缓存层)(热点数据);
服务器先从缓存中找,提高的性能。
在使用redis做缓存层有没有用到什么问题?
答:引入新的问题——缓存雪崩。
原因1:热点数据有有效期;(有效期结束后直接失效请求打到MySQL);MySQL承受不了可能会宕机。————解决:数据设置为随机有效期
原因2:redis服务器宕机————解决:1、设置为多级缓存。2、搭建一个redis集群(多拷贝几个redis)。
产生新的问题———缓存击穿
redis中的一个热门数据(非常热门)有效期到了;
然后对这个热门信息的所有请求都打到数据库;可能会发生缓存击穿;
一般不需要解决,因为一般来说没有这么热门的数据会使得MySQL宕机
问题——缓存穿透
服务器找redis中没有的数据,穿过redis向MySQL中请求
解决:1、去数据库中查询数据放入到缓存层
2、布隆过滤器(标识数据库中所有数据的ID号),如果数据库中没有这个ID,就会请求不到数据库(通过一定错误率)3、互斥分布式锁:(银行叫号机)解决多个个体的排队问题
如果有多个客户端同时向服务器发送非法请求——互斥分布锁
如果陆陆续续发送非法请求——— 方法一
redis集群的Hash一致性算法原理及作用?
答:如何保证数据均匀的分布在这两台服务器中?
key-value 形式存储
方案一:
hash(key)%2==0?redis1:redis2
问题:如果增加服务器,需要%3,计算量太大(需要把之前计算过的都拿出来重新计算分布)
方案二:
哈希一致性算法解决的是集群中有机器发生变动,不至于发生大量的数据移动问题。
哈希环 2^32次方个点
第一台:hash(ip+编号)%2^32
第二台:hash(ip+编号)%2^32
第三台:hash(ip+编号)%2^32
数据存储在?
hash(key)%2^32=value
新增只需移动一小部分
删除只需移动一小部分
不至于全盘调整
缺点:数据倾斜
数据倾斜问题怎么处理?
答:
数据倾斜:存储框架的倾斜(某一台机器存储大量数据,其他存储小部分);计算框架的倾斜(某一台服务器计算了大量数据)
hash算法导致的数据倾斜:少量服务器存储大量数据。大量服务器存储少量数据
解决:采用虚拟节点
redis服务器虚拟成两个节点
经验值:保证总和1000-2000
这篇关于【牛客网面经整理】7.20shopee一面面经,加入我自己整理的相关拓展问题(redis))的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!