实习面试记录

参考资料:
[1]. 协程
[2]. 冒泡排序
[3.] Why Hashtable does not allow null keys or values?
[4.] redis的特点
[5.] 深度解读Tomcat中的NIO模型
[6.] Java线程池「异常处理」正确姿势:有病就得治
[7].HashMap工作原理和扩容机制
[8].MySQL 乐观锁与悲观锁

面试感悟:
1、大厂的实习面试比较偏重基础。
2、有时聊得好并不代表结果好,发现如果前面几个问题不能让面试官满意的话,那么后面可能就瞎扯起项目来,虽然感觉他们很感兴趣的样子,但其实就是他们在向下兼容你,走完整个面试流程。
3、知识的掌握很多还是不够扎实的话,很快就被问蒙了。
4、运气也是其中很大一部分,同学问到的都是前面准备好的问题,但却栽在心理面试上,我却被问到几个不懂的问题,前面问的问题全都没有被问到。

3.25 腾讯实习面试

  1. N个数据,每个都在N的范围里面,找两个重复数字,时间复杂度O(n),空间复杂度O(1)
  1. 用过哪些数据库?表锁跟行锁的区别?
    MYSQL

  2. java垃圾回收机制
    参考:思维导图

  3. 三次握手有哪些攻击方式?
    Syn攻击就是 攻击客户端 在短时间内伪造大量不存在的IP地址,向服务器不断地发送syn包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直 至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。
    Syn攻击是一个典型的DDOS攻击。检测SYN攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击.在Linux下可以如下命令检测是否被Syn攻击。

  4. 进程线程协程的区别
    线程和进程的区别:进程包含线程,线程比进程要更轻量,进程切换的时候是操作系统级别;线程共享同一个进程的资源,而进程与进程之间是独立的。进程是资源分配的最小单位,线程是CPU调度的最小单位。
    协程:我的理解,在一个线程中控制函数的切换,“因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。”,跟子函数有点像,但是运行的时候可以在子函数之间切换。
    优点:单线程,减少线程开销,不需要线程的锁机制。需要多核的时候,需要开启多个进程然后在每个进程里面开启一个协程。

  5. 进程通信方式+信号量的实现机制
    信号量,队列,共享内存,套接字
    共享内存:
    共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
    因为系统内核没有对访问共享内存进行同步,您必须提供自己的同步措施。例如,在数据被写入之前不允许进程从共享内存中读取信息、不允许两个进程同时向同一个共享内存地址写入数据等。解决这些问题的常用方法是通过使用信号量进行同步。

  6. TCP拥塞控制和流量控制区别
    都拥有一个窗口,拥塞控制是根据固定的流程自己计算出来,流量控制是接收方发送过来的。
    拥塞控制是为了避免网络流量过大,流量控制是为了协调接收方的缓存区大小。

3.27 阿里实习面试

  • redis特点
    参考[4]

1、Redis 是一个基于内存的高性能key-value数据库,
2、Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,支持list,集合等数据类型。
3、Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。(丰富的特性)
4、支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行。
5、可以持久化。
6、memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型。

  • redis数据类型
    string、list、hash、set、zset。

  • 乐观锁,悲观锁,乐观锁实现
    参考[8]

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
乐观锁一般来说有以下2种方式:
使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。
何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

  • 查看MYSQL执行计划的命令
    explain

  • 网络三次握手四次挥手

  • Mybatis的DAO实现,其他的ORM框架使用过吗?
    Mybatis底层实现是用动态代理做的,MyBatis一开始扫描注解或者XML配置,为每个方法(接口全路径+方法名)生成一个MappedStatement,然后对应每个Mapper接口,MyBatis生成一个动态代理类,当调用对应的方法的时候,取出之前扫描的配置,包括MySQL语句和参数等,然后调用JDBC执行。

  • NIO跟BIO的区别
    BIO(同步阻塞):通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在while(true) 循环中服务端会调用 accept() 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接。这就是典型的 一请求一应答通信模型 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过 线程池机制 改善,线程池还可以让线程的创建和回收成本相对较低。使用FixedThreadPool 可以有效的控制了线程的最大数量,保证了系统有限的资源的控制。
    AIO(同步非阻塞):我觉得首先肯定要从 NIO 流是非阻塞 IO 而 IO 流是阻塞 IO 说起。然后,可以从 NIO 的3个核心组件/特性为 NIO 带来的一些改进来分析。

1)Non-blocking IO(非阻塞IO)
IO流是阻塞的,NIO流是不阻塞的。
Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
Java IO的各种流是阻塞的。这意味着,当一个线程调用 read()write() 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
2)Buffer(缓冲区)
IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。
Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。
在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。
3)Channel (通道)
NIO 通过Channel(通道) 进行读写。
通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。
4)Selector (选择器)
NIO有选择器,而IO没有。
选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。
3. AIO (Asynchronous I/O)
AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。(除了 AIO 其他的 IO 类型都是同步的,这一点可以从底层IO线程模型解释,推荐一篇文章:《漫话:如何给女朋友解释什么是Linux的五种IO模型?》 )
查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

拓展:Tomcat运用NIO
简单来说,LimitLatch控制连接的数量,Poller的线程轮询Acceptor,如果有新的事件,Poller好连接交给线程池去执行。


  • Resource和Autowired区别
    @Autowired是默认按照类型装配的
    @Resource默认是按照名称装配的

  • 哪些设计模式
    模板设计模式
    责任链模式
    策略模式
    监听器模式(观察者模式)

  • 冒泡排序和归并排序
    冒泡排序是每次都冒一个剩下中最大的值,冒上去的过程中,每次都会交换顺序,把大的传到上面。
    冒泡排序的简单Python代码

def bubbleSort(arr):
    for i in range(1, len(arr)):
        for j in range(0, len(arr)-i):
            if arr[j] > arr[j+1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr
  • HashMap和Hashtable的区别,HashMap是否支持Null?
    Hashtable只是简单地在HashMap的基础上增加Synchronized关键字,因此Hashtable是线程安全的,HashMap表示线程安全的。
    但是HashMap是支持Null键和值的,Hashtable不支持,根据[3],一个说的是因为Hashtable比较老,HashMap比较新,所以支持Null,另外一个答案说是因为Hashtable支持多线程,如果支持null值的话,获取键值应该用下面的代码,因为get返回null在这种情况下有歧义,可能是键值为null,也可能是因为没有这个键,所以需要先判断有没有这个键。但是如果另外一个线程在contains之后,get之前去掉了键,但是本线程接下来还是会get到null,没有键值却获取到了null,产生错误。
if (map.contains(key)) {
    return map.get(key);
} else {
    throw new KeyNotFoundException;
}
  • 重载与重写的区别
    重载是不同的参数
    重写是继承的时候

  • 类加载器
    应用类加载器(application)
    拓展类加载器(extent)
    引导类加载器(Bootstrap)

4.4 腾讯实习面试初试

这次面试体验非常好,面试官循循善诱,给提示,问到你不会为止。

  • 重载跟重写的区别
    重写是为了增强类的重用性和复用性,扩展性;重写是对类中方法的扩充,因为继承用的是父类的东西,重写则不仅得到父类的东西,同时也加入了自己的东西。

  • HashMap
    HashTable的区别

当map中包含的Entry的数量大于等于threshold = loadFactor * capacity的时候,且新建的Entry刚好落在一个非空的桶上,此刻触发扩容机制,将其容量扩大为2倍。(为什么2倍,而不是1.5倍,3倍,10倍;解释见最后的补充)
当size大于等于threshold的时候,并不一定会触发扩容机制,但是会很可能就触发扩容机制,只要有一个新建的Entry出现哈希冲突,则立刻resize。

2倍的原因

通过限制length是一个2的幂数,h & (length-1)和h % length结果是一致的。这就是为什么要限制容量必须是一个2的幂的原因。

  • ArrayList跟Vector的区别
    1、Vector是线程安全的,ArrayList不是线程安全的。
    2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
    Vector只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性。
    自己拓展:StringBuilder和StringBuffer的区别

StringBuffer is synchronized, StringBuilder is not.

  • final的用法
    类——不能继承
    方法——不能重写
    变量——不能修改,常量

  • JVM内存分为哪几块区域
    JVM内存区域分为五个部分,分别是堆,方法区,虚拟机栈,本地方法栈,程序计数器。


堆:堆是Java对象的存储区域,任何用new字段分配的Java对象实例和数组,都被分配在堆上,Java堆可使用-Xms -Xmx进行内存控制,值得一提的是从JDK1.7版本之后,运行时常量池从方法区移到了堆上。

方法区:它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,方法区在JDK1.7版本及以前被称为永久代,从JDK1.8永久代被移除。

虚拟机栈:虚拟机栈中执行每个方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。

本地方法栈:与虚拟机栈发挥的作用相似,相比于虚拟机栈为Java方法服务,本地方法栈为虚拟机使用的Native方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。

程序计数器:指示Java虚拟机下一条需要执行的字节码指令。
以上五个区域是Java虚拟机内存划分情况,其中方法区和堆被JVM中多个线程共享,比如类的静态常量就被存放在方法区,供类对象之间共享,虚拟机栈,本地方法栈,pc寄存器是每个线程独立拥有的,不会与其他线程共享。

  • 程序编译分为哪几个过程,静态链接和动态链接的区别
    预处理->编译->汇编->链接
    预处理:处理比如#include,得到完整的文件,结果后缀为.i
    编译:转化为汇编,结果后缀为.s
    汇编:转化为机器语言指令,结果后缀为.o
    链接:由链接器将代码在执行过程用到的其他目标代码和库文件链接成为一个可执行程序也就是目标程序。
    静态链接:在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中,因此对应的链接方式称为静态链接。
  1. 静态库对函数库的链接是放在编译时期完成的。
  2. 程序在运行时与函数库再无瓜葛,移植方便。
  3. 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
    为什么需要动态库,其实也是静态库的特点导致,空间浪费是静态库的一个问题。另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。
    动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
    动态链接:




    动态库特点总结:
    动态库把对一些库函数的链接载入推迟到程序运行的时期。
    可以实现进程之间的资源共享。(因此动态库也称为共享库)
    将一些程序升级变得简单。
    甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
    动态链接缺点:
    (1)当系统中多个应用程序都用了一个动态链接库,但是要求的版本不同,这时动态链接库之间就会相互干扰,容易出现dll地狱的问题。
    (2)性能开销。动态链接库为了做到“共享代码,但是不共享数据”,引入了不小的开销,调用动态链接库中的函数,需要好几次间接内存访问才能走到函数入口,全局数据也是。

  • 进程跟线程调度起来有什么区别?
    进程的优点
    多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
    为什么线程死掉会导致进程死掉
    会崩溃。线程没有自己单独的内存地址空间。在一个线程中把另外一个线程的栈空间写坏是再正常不过的事情了。这与进程有没有自己的栈空间无关,因为无论他们有没有自己的栈空间,都可以通过内存地址访问到其他线程的栈空间,所以指针数据的错误可以导致任何同地址空间内其他线程的崩溃,当然也可以导致进程崩溃。一般而言,没有绝对必要的共享内存空间的需求就不要使用线程,用进程会安全很多。

  • 僵尸进程
    简单来说,父进程的子进程需要处理善后事宜,但是父进程没有,造成大量的资源消耗。如果父进程死掉那还可以,子进程作为孤儿进程,系统会处理善后事宜。

什么是僵尸进程?
Unix进程模型中,进程是按照父进程产生子进程,子进程产生子子进程这样的方式创建出完成各项相互协作功能的进程的。当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。如果父进程没有这么做的话,会产生什么后果呢?此时,子进程虽然已经退出了,但是在系统进程表中还为它保留了一些退出状态的信息,如果父进程一直不取得这些退出信息的话,这些进程表项就将一直被占用,此时,这些占着茅坑不拉屎的子进程就成为“僵尸进程”(zombie)。系统进程表是一项有限资源,如果系统进程表被僵尸进程耗尽的话,系统就可能无法创建新的进程。
那么,孤儿进程又是怎么回事呢?
孤儿进程是指这样一类进程:在进程还未退出之前,它的父进程就已经退出了,一个没有了父进程的子进程就是一个孤儿进程(orphan)。既然所有进程都必须在退出之后被wait()或waitpid()以释放其遗留在系统中的一些资源,那么应该由谁来处理孤儿进程的善后事宜呢?这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程“凄凉地”结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。
这样来看,孤儿进程并不会有什么危害,真正会对系统构成威胁的是僵尸进程。
那么,什么情况下僵尸进程会威胁系统的稳定呢?
设想有这样一个父进程:它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵尸进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。
严格地来说,僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵尸进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵尸进程时,答案就是把产生大量僵尸进程的那个元凶枪毙掉(通过kill发送SIGTERM或者SIGKILL信号)。枪毙了元凶进程之后,它产生的僵尸进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经“僵尸”的孤儿进程就能瞑目而去了。

  • 线程池的线程抛异常之后怎么处理?
    参考[6]
    ThreadPoolExecutor中会对异常进行捕获,然后执行afterExecute,但是afterExecute一般是没有实现的。

如何避免这种问题
思路很简单。
1、在提交的任务中将异常捕获并处理,不抛给线程池。
2、异常抛给线程池,但是我们要及时处理抛出的异常。

第二种思路中,

  1. 可以继承ThreadPoolExecutor然后复写afterExecute方法。


  2. 利用线程工厂复写线程的UncaughtExceptionHandler


  3. ??
  4. Future对象get的时候会抛出异常


  • 怎么在TCP和UDP上的应用层建立可靠传输?
    序列号保证发送和接收的顺序相同,丢包重传机制,校验和。

  • TCP只会重发少的那个包还是后面所有包?
    ACK确认的是连续收到的数据,然后发送方会重发后面所有的包,效率比较低。
    SACK是TCP的一个选项,会选择性地确认收到了。

  • TCP的序列号溢出会怎么样?
    序列号是随机开始的,直接重新从零开始。

  • TCP怎么分段的?

  • MTU是多少?
    链路层最大传输字节个数,1500

  • 要查A,B,A和B字段需要建立哪些索引?
    A和B,B
    或者
    B和A,A
    索引最左匹配

4.26腾讯实习常规批

审题很重要!!
暴力解法也可以,至少可能有几率通过!!

  • 实现队列
    用LinkedList实现,因为是题中题,第二层循环的时候需要重新new一个...

  • 两堆点,求最近的距离


启发:不懂的话可以暴力

  • 翻牌


  • 两个栈模拟一个队列
    75%超时,??

import java.util.Scanner;
import java.util.Stack;

public class Queue {
    private static class Queue1{
        private Stack stack1 = new Stack<>();
        private Stack stack2 = new Stack<>();

        public void add(Integer num){
            stack1.push(num);
        }

        public Integer peek(){
            if(stack1.size()==0&&stack2.size()==0){
                return null;
            }

            if(stack2.size()==0){
                transfer1to2();
            }

            return stack2.peek();
        }

        public Integer poll(){
            if(stack1.size()==0&&stack2.size()==0){
                return null;
            }

            if(stack2.size()==0){
                transfer1to2();
            }

            return stack2.pop();
        }

        public void transfer1to2(){
            while(stack1.size()!=0) {
                stack2.push(stack1.pop());
            }
        }
    }

    public static void main(String[] args) {
        Queue1 queue = new Queue1();
        Scanner sc = new Scanner(System.in);
        Integer numLines = sc.nextInt();
        sc.nextLine();
        for (int i = 0; i < numLines; i++) {
            String line = sc.nextLine();
            String[] splits = line.split("\\s+");
            String op = splits[0].toLowerCase();
            if(op.equals("add")){
                Integer num = Integer.valueOf(splits[1]);
                queue.add(num);
            }else if(op.equals("peek")){
                Integer peek = queue.peek();
                System.out.println(peek);
            }else if(op.equals("poll")){
                queue.poll();
            }
        }
    }
}
  • 二叉树的第n层父亲
    50%,错在读取数据的时候没有用nextLong,10^18次方,这种要用long。


import java.util.Scanner;


public class TreeParent {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        long numQuery = sc.nextInt();

        for (int i = 0; i < numQuery; i++) {
            long childIndex = sc.nextLong();
            int parentDepth = sc.nextInt();
            System.out.println(getFather(childIndex, parentDepth));
        }
    }

    private static long getFather(long childIndex, int parentDepth){
        int childDepth = depthOf(childIndex);
        if(parentDepth>=childDepth){
            return -1;
        }else{
            int delta = childDepth - parentDepth;
            return childIndex >> delta;
        }
    }

    private static int depthOf(long index){
        int depth = 0;
        while(index>0){
            depth += 1;
            index /= 2;
        }
        return depth;
    }
}

4.29 华为实习面试

  • 字符串去掉k个字符,得到字典序最小的字符串

    贪心,每次去掉一个,去掉的时候依次比较得到最小。
    Java字符串没有单独去掉某个字符的方法,只能用String s = M.substring(0, j) + M.substring(j + 1);
        Scanner sc = new Scanner(System.in);
        String M = sc.next();
        int k = sc.nextInt();

        for (int i = 0; i < k; i++) {
            String temp = M.substring(1);
            for (int j = 1; j < M.length(); j++) {
                String s = M.substring(0, j) + M.substring(j + 1);
                if(temp.compareTo(s)>0){
                    temp = s;
                }
            }
            M = temp;
        }

        System.out.println(M);
  • 花费最小的情况下的最小距离
    Floyd算法的基础排除掉花费不符合的
public class Main2 {
    private static int MAX = 100000000;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int K = sc.nextInt(); // coin
        int N = sc.nextInt(); // city
        int R = sc.nextInt(); // road

        int[][] L_Matrix = new int[N][N];
        int[][] T_Matrix = new int[N][N];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                L_Matrix[i][j] = 100000000;
            }
        }

        for (int i = 0; i < R; i++) {
            int S = sc.nextInt(); //1~N
            int D = sc.nextInt(); //1~N
            int L = sc.nextInt(); //长度
            int T = sc.nextInt(); //花费

            L_Matrix[S-1][D-1] = L;
            T_Matrix[S-1][D-1] = T;
        }

        smallestRoad(L_Matrix, T_Matrix, N, K);

        System.out.println(L_Matrix[0][N-1]);
    }

    private static void smallestRoad(int[][] L_Matrix, int[][] T_Matrix, int NumOfCity, int cost){
        for (int i = 0; i < NumOfCity; i++) {
            for (int j = 0; j < NumOfCity; j++) {
                for (int k = 0; k < NumOfCity; k++) {
                    if(L_Matrix[j][i]!=MAX&&L_Matrix[i][k]!=MAX){
                        int temp_L = L_Matrix[j][i] + L_Matrix[i][k];
                        int temp_T = T_Matrix[j][i] + T_Matrix[i][k];
                        if(temp_T <= cost){
                            if(L_Matrix[j][k] > temp_L ){
                                L_Matrix[j][k] = temp_L;
                                T_Matrix[j][k] = temp_T;
                            }else if(L_Matrix[j][k] == temp_L&&temp_T < T_Matrix[j][k]) {
                                    L_Matrix[j][k] = temp_L;
                                    T_Matrix[j][k] = temp_T;
                            }
                        }
                    }
                }
            }
        }
    }
}


4.30 哔哩哔哩实习面试

  • Python字典的实现
    不想知道

  • Python GIL
    为啥要用GIL?

  • HTTPS为什么数字签名之后就可以证明是你自己?为什么第三方不能对它进行修改?
    数字签名采用的是非对称加密,对方拥有公钥,自己拥有私钥,先私钥再公钥可以进行解密。
    由于黑客没有我们的私钥,修改内容后,无法对内容进行加密。

  • 布隆过滤器用于缓存击穿,还其他方案吗?

  • IO多路复用
    参考IO多路复用

  • 为什么进程之间不能共享内存?
    每个进程都有他自己在页表所对应的页表项,只能映射到它自己的内存。

  • Java/Python垃圾回收

  • 数据库乐观锁和悲观锁
    乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。悲观锁适用于读比较少的情况下(多写场景),如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

  • 链表是否交叉怎么判断
    先分别遍历两个列表,然后长的链表长出来的部分先遍历,然后再一起遍历,如果在一起遍历的过程中有相同的节点就是交叉的。

5.17 华为初面

  • 讲讲封装,继承,多态
    。。。

  • 两个队列模拟一个栈

  • JVM垃圾回收

  • 讲下堆排序,稳定吗?


  • 设计模式

  • 项目

  • 手撸:两个有序数组求中位数
    参考资料:LeetCode-4 寻找两个有序数组的中位数
    中位数求法如下,

    public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        int l = (m + n + 1) / 2;
        int r = (m + n + 2) / 2;
        return (getKthNum(nums1, 0, nums2, 0, l) + getKthNum(nums1, 0, nums2, 0, r)) / 2.0;
    }

因此题目转化为求两个有序数组第k排序。

    private static int getKthNum(int[] nums1, int start1, int[] nums2, int start2, int k) {
        if(start1 > nums1.length-1) return nums2[start2 + k - 1];
        if(start2 > nums2.length-1) return nums1[start1 + k - 1];

        if(k == 1) return Math.min(nums1[start1], nums2[start2]);

        // 一定一个数组的可用个数大于k/2的,不管怎么样,我们去掉前面k/2个
        int nums1Mid = start1 + k/2 - 1 < nums1.length ? nums1[start1 + k/2 - 1] : Integer.MAX_VALUE;
        int nums2Mid = start2 + k/2 - 1 < nums2.length ? nums2[start2 + k/2 - 1] : Integer.MAX_VALUE;

        // nums1 前k/2比较小,去掉,并调整k=k-k/2
        if(nums1Mid < nums2Mid)
            return getKthNum(nums1, start1 + k/2, nums2, start2, k-k/2);
        else
            return getKthNum(nums1, start1, nums2, start2 + k/2, k-k/2);
    }

5.24 腾讯正式批面试

  1. 介绍自己和项目
  1. 你用户登录是怎么样做的,如果用户登录只限定30min,怎么做?
    session过期,redis过期,延时队列删除掉对应用户的登录信息。

  2. CSRF攻击和CORS
    是谁控制了

  3. 写一个最熟悉的排序算法
    在面试官面前写代码的诀窍:乱的时候,想清楚思路是比较重要的,不要先动手,把各个细节想清楚在写。我写的时候,连冒泡排序都写错了,一开始马上就写出来两层for循环,然后很乱,不知道外层循环是干什么的,内层循环是干什么的,故在最里层操作的时候自然很难写对。
    冒泡

public class Bubble {
    public static void main(String[] args) {
        int[] array = new int[]{4,3,2,1};
        bubble(array);
        System.out.println(Arrays.toString(array));
    }
    
    static void bubble(int[] array){
        // 趟数
        for (int i = 0; i < array.length; i++) {
            // 每趟比较的次数
            for (int j = 0; j < array.length - i - 1; j++) {
                if(array[j] > array[j + 1]){
                    swap(array, j);
                }
            }
        }
    }

    static void swap(int[] array, int i){
        int temp = array[i+1];
        array[i+1] = array[i];
        array[i] = temp;
    }
}
  1. 你说你喜欢看源码,例子?
    应该说下IOC和AOP的,tomcat。

  2. 你还有什么擅长技术是我没有问到的吗?
    创造力,毅力(田径队,电子设计大赛4天3夜)

  3. linux 用过吗?怎么查看CPU占有率?系统调用CPU占用VS用户调用CPU占用?
    top

  4. IOC和AOP解释下

  5. 问问题?
    没问,为啥不问问实习做什么?

  6. 可以实习多久?

你可能感兴趣的:(实习面试记录)