后端2022秋招面经

美团一面面经

1.HashMap在JDK1.8和JDK1.7之中的区别?

2.红黑数的数据结构的特点?

1.节点是红色或黑色。
2.根是黑色。
3.所有叶子都是黑色(叶子是NIL节点)。
4.每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
5.从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(简称黑高)
任意节点到其每个叶子节点路径最长不会超过最短路径的2倍。原因如下:

当某条路径最短时,这条路径必然都是由黑色节点构成。当某条路径长度最长时,这条路径必然是由红色和黑色节点相间构成(性质4限定了不能出现两个连续的红色节点)。而性质5又限定了从任一节点到其每个叶子节点的所有路径必须包含相同数量的黑色节点。此时,在路径最长的情况下,路径上红色节点数量 = 黑色节点数量。该路径长度为两倍黑色节点数量,也就是最短路径长度的2倍

左旋是将某个节点旋转为其右孩子的左孩子,而右旋是节点旋转为其左孩子的右孩子

3.HashMap在JDK1.8和JDK1.7之中的插入方法的区别?为什么要做这种优化?(为什么要从头插法变成尾插法)

4.HashMap是线程安全的吗?怎么解决HashMap的线程不安全问题?

5.刚才你说到了ConcurrentHashMap,请问ConcurrentHashMap是使用什么机制来保证线程安全的?在JDK1.7和JDK1.8之中分别怎么保证?

1.Hashtable在get,put,remove等这些方法中全部加入了synchronized,这样的问题是能够实现线程安全,但是缺点是性能太差,几乎所有的操作都加锁的,非常容易形成线程的阻塞,但是ConcurrentHashMap的检测操作却是没有加锁的。
2.ConcurrentHashMap检索操作(包括get)通常不会阻塞,因此可能与更新操作(包括put和remove)重叠。
3.ConcurrentHashMap跟Hashtable类似但不同于HashMap,它不可以存放空值,key和value都不可以为null。

ConcurrentHashMap保证线程安全的方式:

一、使用volatile保证当Node中的值变化时对于其他线程是可见的
二、使用table数组的头结点作为synchronized的锁来保证写操作的安全
三、当头结点为null时,使用CAS操作来保证数据能正确的写入

ConcurrentHashMap的putVal方法:

 /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            //当头结点为null,则通过casTabAt方式写入
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
              //正在扩容
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                //头结点不为null,使用synchronized加锁
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            //此时hash桶是链表结构
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            //此时是红黑树
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
                if (binCount != 0) {
                    //当链表结构大于等于8,则将链表转换为红黑树
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                  return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

使用CAS

当有一个新的值需要put到ConcurrentHashMap中时,首先会遍历ConcurrentHashMap的table数组,然后根据key的hashCode来定位到需要将这个value放到数组的哪个位置。

tabAt(tab, i = (n - 1) & hash))就是定位到这个数组的位置,如果当前这个位置的Node为null,则通过CAS方式的方法写入。所谓的CAS,即即compareAndSwap,执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。

使用synchronized

当头结点不为null时,则使用该头结点加锁,这样就能多线程去put hashCode相同的时候不会出现数据丢失的问题。synchronized是互斥锁,有且只有一个线程能够拿到这个锁,从而保证了put操作是线程安全的。

ConcurrentHashMap的get方法:

get没有加锁的话,ConcurrentHashMap是如何保证读到的数据不是脏数据的呢?
volatile登场
对于可见性,Java提供了volatile关键字来保证可见性、有序性。但不保证原子性。

普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

volatile关键字对于基本类型的修改可以在随后对多个线程的读保持一致,但是对于引用类型如数组,实体bean,仅仅保证引用的可见性,但并不保证引用内容的可见性。。

禁止进行指令重排序。

背景:为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。

如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。

在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,当某个CPU在写数据时,如果发现操作的变量是共享变量,则会通知其他CPU告知该变量的缓存行是无效的,因此其他CPU在读取该变量时,发现其无效会重新从主存中加载数据。

我们知道volatile可以修饰数组的,只是意思和它表面上看起来的样子不同。举个栗子,volatile int array[10]是指array的地址是volatile的而不是数组元素的值是volatile的.

用volatile修饰的Node

get操作可以无锁是由于Node的元素val和指针next是用volatile修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的。

在1.8中ConcurrentHashMap的get操作全程不需要加锁,这也是它比其他并发集合比如hashtable、用Collections.synchronizedMap()包装的hashmap;安全效率高的原因之一。

get操作全程不需要加锁是因为Node的成员val是用volatile修饰的和数组用volatile修饰没有关系。

数组用volatile修饰主要是保证在数组扩容的时候保证可见性。

6.你刚刚说到JDK1.8之中,使用CAS和同步锁的方式保证线程安全,请你分别说说这两种方便在方法的哪一阶段使用?

7.请你设计一种底层使用HashMap,但可以保证写有序的实现机制?

LinkedHashMap

8.Java中的乐观锁和悲观锁

9.乐观锁和悲观锁的使用场景

10.高并发的解决思路

1、静态资源与后台服务进行分离
静态资源主要包括图片、视频、js、css和一些资源文件等,这些文件因为没有状态所以分离比较简单,直接存放到响应的服务器就可以了,一般会使用专门的域名去访问。通过不同的域名可以让浏览器直接访问资源服务器而不需要再访问应用服务器了。
这样在高并发的情况下是很有必要这样做的,可以想象一下,双十一的时候,浏览商品的并发量是远远高于下单的并发量的。架构图如下:


2、做页面静态化
静态化的意思就是将数据渲染后的动态页面变成一个html静态页面的保存起来,如果以后需要再次访问该页面,则将该请求重定向到静态页面。这样就不需要再向数据库或者缓存中拿去数据然后再进行渲染页面,返回到客户端。大大的提高了响应速度,减轻了服务端的负担。这个原理其实和缓存一样,只是将整个页面进行了缓存。nginx就自带了页面静态化的解决方案。

3、分布式和集群
分布式就是对整个系统进行水平拆分和垂直拆分。垂直拆分就是每一个功能模块都拆分成一个可以独立运行的微服务,从客户端发来的请求会根据所请求的功能转发到不同的微服务,这就避免了单体应用服务每一个请求都会请求同一个应用服务的问题。而且微服务还实现了模块与模块之间的解耦合,这样就避免了后期优化的时候,因为不知道谁调用谁而不敢修改代码的问题。而且如果一个微服务的并发量太大,可以对微服务进行水平扩展,也就是增加提供该服务的应用服务器数量,提高并发量,这就是我们所说的集群。水平拆分就是将每个微服务进行分层,实现微服务内部的解耦合。例如分为web层,service层,mapper层等等。分布式和集群是解决高并发的核心,它是整个系统架构的高并发解决方案。

4、反向代理
反向代理就是我们直接访问的服务器并不提供真正的系统服务,它只是个代理服务器,将发来的请求转发到真正的系统服务器里,一般比较常用的是nginx。它可以做负载均衡,避免集群里的应用服务分配请求不均匀导致的个别应用服务器宕机问题。

5、数据库集群和拆分
一个数据库的并发数是有限的,我们需要用多个数据库来提供服务,增加并发量,这是就需要对数据库做一个集群。
数据库本身也是需要进行优化的,我们需要对数据库进行垂直拆分和水平拆分。垂直拆分就是将数据表按照功能模块来进行划分,分别存储在不同的数据库服务器中。水平拆分就是对数据表进行读写分离,一个数据表或许有很多字段,但是有些字段可能在有些页面中读取较多,有些字段在其他页面写入修改较多,这时候我们就应该进行读写分离,将读较多的字段拆分为一个数据表,将写较多的字段拆分为另一个数据表。

6、使用缓存
这个在一定程度上是可以提高用户的并发量的,但是如果访问量大的话,有可能会因为缓存数据过大,造成内存空间不足导致应用服务器挂掉。

7、消息队列流量削峰
前面处理高并发的方案都是通过优化后台服务来是实现的,那我们换个角度,我们对请求进行处理。我们先来模拟一个场景,就是怎么处理抢购的时候的一秒内数十万的高并发。在那么短的时间内,那么高的并发,那很有可能或让代理服务器瘫痪。为了避免这种情况,我们需要使用消息队列来进行流量削峰。原理也很简单,就是我们比如说抢购的商品只有1000件,但是却有10万个人抢购,那我们就用消息队列缓存先到的1000个请求,将后面的那些请求直接响应请求,提示抢购失败,然后慢慢的对缓存中的请求进行处理。

11.请说一下TCP/IP的三次握手机制

12.请说一下HTTPS协议分别在这三次握手中做了什么?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9psfRR0-1639378953676)(C:\Users\张琳梓\AppData\Roaming\Typora\typora-user-images\1617694065281.png)]

1.客户端发送请求到服务器端
2.服务器端返回证书和公开密钥,公开密钥作为证书的一部分而存在
3.客户端验证证书和公开密钥的有效性,如果有效,则生成共享密钥并使用公开密钥加密发送到服务器端
4.服务器端使用私有密钥解密数据,并使用收到的共享密钥加密数据,发送到客户端
5.客户端使用共享密钥解密数据
6.SSL加密建立…

13.浏览器中输入一个url后,请从ISO七层模型的每一层中分析具体是如何响应的?

14.请问当查询本地域名dns缓存未查到时,浏览器要如何处理?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BUccR5re-1639378953678)(C:\Users\张琳梓\AppData\Roaming\Typora\typora-user-images\1617676898809.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWtOOGHi-1639378953680)(C:\Users\张琳梓\AppData\Roaming\Typora\typora-user-images\1617676930229.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bloSLELl-1639378953681)(C:\Users\张琳梓\AppData\Roaming\Typora\typora-user-images\1617676953973.png)]

15.请问你平时如何解决乱码问题?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p2pTd9zr-1639378953683)(C:\Users\张琳梓\AppData\Roaming\Typora\typora-user-images\1617676998603.png)]

16.是否使用过unicode?

Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求

将全世界所有的字符包含在一个集合里,计算机只要支持这一个字符集,就能显示所有的字符,再也不会有乱码了

Unicode存在的问题:

比如,汉字严的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。也就是说,这个符号至少需要2个字节来表示其它更大的符号。

因为需要至少2个字节来表示更大的符号,这就导致了计算机怎么知道该字符是2个字节还是3个字节甚至更多。第二个问题是,众所周知,英文字母只需要一个字节来进行编码,但是如果用2个字节3个字节甚至更多字节来表示这就会造成相应倍数的存储空间的增加,造成了存储空间上的极大浪费。

所以最后也出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式来表示Unicode,随着互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式,同时也存在UTF-16以及UTF-32,
UTF-8是一种变长的编码方法,字符长度从1个字节到4个字节不等。越是常用的字符,字节越短,最前面的128个字符,只使用1个字节表示,与ASCII码完全相同。

再次强调一下,UTF-8与Unicode的关系是:UTF-8是Unicode的实现方式之一。

UTF-8是一种边长编码方案,使得在存储上节省了许多空间,因此目前也受到了诸多欢迎,接下来,我们再讲一下UTF-8的编码规则。

UTF-8 的编码规则很简单,只有二条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码

17.请说说你所使用过的存储引擎?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nTSLYSTq-1639378953684)(C:\Users\张琳梓\AppData\Roaming\Typora\typora-user-images\1617677042061.png)]

18.请介绍一下你所说的存储引擎有什么区别?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bNZxEHFe-1639378953686)(C:\Users\张琳梓\AppData\Roaming\Typora\typora-user-images\1617677042061.png)]

19.请说说Innodb引擎底层如何支持事务?

20.请你说说事务的隔离级别?

21.场景题目:请问给你一个表,表中假如有三个字段,A,B,C,其中,BC字段都加了索引,请问什么情况下数据查询时会不走索引?

一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码


17.请说说你所使用过的存储引擎?

[外链图片转存中...(img-nTSLYSTq-1639378953684)]

18.请介绍一下你所说的存储引擎有什么区别?

[外链图片转存中...(img-bNZxEHFe-1639378953686)]

19.请说说Innodb引擎底层如何支持事务?

20.请你说说事务的隔离级别?

21.场景题目:请问给你一个表,表中假如有三个字段,A,B,C,其中,BC字段都加了索引,请问什么情况下数据查询时会不走索引?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值