腾讯面试
一、单链表反转
递归方法:
public Node reverse(Node head) { if (head == null || head.next == null) return head; Node temp = head.next; Node newHead = reverse(head.next); temp.next = head; head.next = null; return newHead; } //总体来说,递归法是从最后一个Node开始,在弹栈的过程中将指针顺序置换的。
遍历方法:
public static Node reverseList(Node node) { Node pre = null; Node next = null; while (node != null) { next = node.next; node.next = pre; pre = node; node = next; } return pre; } //先将下一节点纪录下来,然后让当前节点指向上一节点,再将当前节点纪录下来,再让下一节点变为当前节点。
二、最长公共子序列问题
三、线程池
四、死锁
1、什么是死锁:死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
2、死锁的四个必要条件:
互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有,此时若有其他进程请求该资源,则请求进程只能等待;
请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放;
不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能主动释放);
循环等待条件:若干进程间形成首尾相接循环等待资源的关系;
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
3、死锁的避免:指定获取锁和释放锁的顺序。
4、死锁的预防:破坏导致死锁的必要条件,使其不被满足。
五、Http协议
4、Http1.0,Http1.1,Http2.0主要特性对比
5、http和https的区别:
(1)https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用;
(2)http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议;
(3)http和https使用的端口不同,前者是80,后者是443;
(4)http的连接很简单,是无状态的;https协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
六、内存泄漏
1、内存溢出:
是指程序在申请内存时,没有足够的内存空间供其使用,出现内存溢出,比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出;内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
2、内存泄漏:
是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光,内存泄漏最终会导致内存溢出。内存泄漏是指你向系统申请分配内存进行使用,可是使用完了以后却不归还,结果你申请到的那块内存你自己也不能再访问,而系统也不能再次将它分配给需要的程序。
3、内存泄漏如何产生:
Java内存泄漏的根本原因是长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。
七、hashMap内部具体如何实现的?
HashMap基于哈希思想,实现对数据的读写,底层采用数组+链表实现,可以存储null键和null值,线程不安全:
- 当我们往HashMap中put元素时,先根据key的hashCode重新计算hash值,然后根据得到的hash值通过hash&(tab.length–1)计算出对应的数组下标,如果数组该位置上已经存放有其他元素了,那么新元素将以链表的形式按照头插法存入链表,如果数组该位置上没有元素,就直接将新元素放到此数组中的该位置上,当存储的链表长度大于8时会将链表转换为红黑树;(hash方法根据key的hashCode重新计算一次散列,该算法加入了高位计算,防止低位不变,高位变化时,造成的hash冲突)
- 当我们从HashMap中get元素时,先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素;
- 当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,所以为了提高查询的效率,就要对HashMap的数组进行扩容,当HashMap中元素个数超过(数组大小*加载因子)时,就会进行数组扩容,数组大小默认值为16,加载因子默认值为0.75,把数组的大小扩展为原来的2倍,然后重新计算每个元素在数组中的位置,这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设数组的合适长度能够有效的提高HashMap的性能。
八、同步异步
1、同步:
同步就是发起一个请求,直到请求返回结果之后,才进行下一步操作;简单来说,同步就是必须一件事一件事的做,等前一件做完了,才能做下一件事。
2、异步:
异步很明显是与同步相对,二者的区别在于是否需要等待某操作的返回结果;简单来说,我们还是一个网络请求,如果我们此时不需要依赖这个请求的结果就能进行后续操作,那么此时这个网络请求就是一个异步操作。
3、同步阻塞:
效率最低,专心等待事情完成,什么别的事都不做。
4、异步阻塞:
异步操作是可以被阻塞的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
5、同步非阻塞:
实际上是效率低下的,一边干别的事情一边还需要抬头看上一件事完成没有,如果把干别的事情和观察完成情况看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。
6、异步非阻塞:
效率更高,因为等待事情完成是等待者的事,而通知你则是消息触发机制的事,程序没有在这两种不同的操作中来回切换。
九、装饰者模式
1、简介:
装饰者模式通过组合的方式扩展对象的特性,这种方式允许我们在任何时候对对象的功能进行扩展甚至是运行时扩展,若我们用继承来完成对类的扩展则只能在编译阶段实现,所以在某些时候装饰者模式比继承要更加灵活。
2、装饰者模式在jdk中的使用场景:
java.io包(BufferInputStream相当于一个装饰器实现者,使得FilterInputStream读取的数据能暂存在内存中,从减少I/O的角度提高了读取效率)。
3、装饰者模式简单的业务使用场景:
咖啡店里的咖啡加上不同的配料(牛奶、糖、奶泡)有不同的价钱,可以使用装饰者模式实现;首先定义一个咖啡基类,接着定义一个装饰器类继承咖啡基类,最后来实现添加配料的装饰子类。
十、单例模式
1、手写单例模式实现:
//饿汉式 public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance(){ return instance; } } //懒汉式 public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance(){ if (instance == null) { instance = new Singleton(); } return instance; } }
首先单例类必须要有一个private访问级别的构造函数,只有这样,才能确保单例不会在系统中的其他代码内被实例化,这点是相当重要的;其次,singleton成员和getSingleton()方法必须是static的。
2、多线程中的懒汉式单例模式(synchronized方法级别锁):
public class Singleton { private static Singleton instance;
private Singleton() { } public static synchronized Singleton getInstance(){ if (instance == null) { instance = new Singleton(); } return instance; } }
3、双重锁实现的单例模式:
public class Singleton { private volatile static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
双重加锁要对instance域加上volatile修饰符,synchronized并不是对instance实例进行加锁(因为现在还并没有实例),所以线程在执行完instance = new Singleton()后,instance的值被修改,应该将修改后的instance立即写入主存,而不是暂时存在寄存器或者高速缓冲区中,以保证新的值对其它线程可见。
4、静态内部类实现的单例模式:
public class SingleTon{ private SingleTon(){ } private static class SingleTonHoler{ private static SingleTon instance = new SingleTon(); } public static SingleTon getInstance(){ return SingleTonHoler.instance; } }
外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化instance,故而不占内存,即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化instance,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
十一、redis
1、redis实现分布式锁:
使用redis命令 set key value NX EX max-lock-time 实现加锁
使用redis命令 EVAL 实现解锁
2、redis的两种持久化方式:
十二、数据库里怎么优化查询效率
1、储存引擎选择,如果数据表需要事务处理,应该考虑使用InnoDB, 因为它完成兼容ACID特性(即事务的四大特性),如果不需要事务处理,使用默认储存引擎MyISAM。
2、对查询进行优化,尽量避免全表扫描,可以考虑在where及order by涉及的列上建立索引。
3、应尽量避免在where子句中对字段进行null值判断,否则将导则引擎放弃使用索引而进行全表扫描。
4、对于多张大数据量表的JOIN,要先分页再JOIN,否则逻辑读会很高,性能差。
十三、SSL和TLS
1、SSL:
SSL(安全套接层)是TCP/IP协议中基于HTTP之下TCP之上的一个可选协议层;起初HTTP在传输数据时使用的是明文,是不安全的,为了解决这个隐患,网景公司推出了SSL,越来越多的人也开始使用HTTPS(HTTP+SSL)。
2、TLS:
SSL更新到3.0时,互联网工程任务组将其更名为TLS1.0(安全传输层协议),TLS也就是SSL的新版本3.1。
3、区别:
(1)SSL与TLS两者所使用的算法是不同的
(2)TLS增加了许多新的报警代码,比如解密失败、拒绝访问等,但同时也支持SSL协议上所有的报警代码
十四、TCP和UDP
1、TCP:可靠、面向连接、面向字节流、传输效率低
2、UDP:不可靠、无连接、面向报文、传输效率高
3、TCP应用场景:
当对网络通信质量要求高的时候,比如整个数据要准确无误的传递给对方,这时就使用TCP,比如浏览器使用的HHTP协议、QQ文件传输。
4、UDP应用场景:
当对网络通讯质量要求不高的时候,要求网络通讯速度尽量的快,这时就使用UDP,比如QQ语音、QQ视频。
十五、进程间通信方式
管道、消息队列、信号量、共享内存。
十六、线程间通信方式
线程上下文、共享内存、Socket套接字(不同的机器之间进行通信)。
十七、Redis和MySQL最大的区别
Redis是内存数据库,数据保存在内存中,访问速度快;
MySQL是关系型数据库,功能强大,存储在磁盘中,数据访问速度慢。
十八、MySQL的事务
1、MySQL只有Innodb引擎支持数据库的事务操作,事务是一条或多条数据库操作的集合,在事务中的操作,要么都执行修改,要么都不执行,MySQL事务具有原子性、一致性、隔离性、持久性(ACID)。
2、事务的隔离级别:
未提交读:事务中的修改,即使没有提交,在其他事务也都是可见的;事务可以读取未提交的数据,这也被称为脏读。
提交读:一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的;这个级别有时候也叫做不可重复读,因为两次执行相同的查询,可能会得到不一样的结果,因为在这两次读之间可能有其他事务更改这个数据,每次读到的数据都是已经提交的。
可重复读:解决了脏读,也保证了在同一个事务中多次读取同样记录的结果是一致的;可重复读隔离级别还是无法解决另外一个幻读的问题,指的是当某个事务在读取某个范围内的记录时,另外一个事务也在该范围内插入了新的记录,当之前的事务再次读取该范围内的记录时,会产生幻行。
可串行化:通过强制事务串行执行,避免了前面说的幻读的问题,但由于读取的每行数据都加锁,会导致大量的锁征用问题,因此性能也最差。
十九、对40亿个qq号去重
通过哈希算法,将40亿个qq号按照哈希值散落到多个文件中,重复的qq号有相同的哈希值,肯定位于一个文件中,这样就可以分别对每个文件排序删除重复的qq号。
二十、描述从浏览器输入网址到返回的过程
DNS域名解析 –>
发起TCP的三次握手 –>
建立TCP连接后发起http请求 –>
服务器响应http请求,浏览器得到html代码 –>
浏览器解析html代码,并请求html代码中的资源(如javascript、css、图片等) –>
浏览器对页面进行渲染呈现给用户。
二十一、session和cookie的区别
(1)cookie通过在客户端记录信息确定用户身份,session通过在服务器端记录信息确定用户身份;
(2)cookie数据存放在客户的浏览器上,session数据存放在服务器上;
(3)cookie不是很安全,他人可以分析存放在本地的cookie进行cookie欺骗,而session较为安全,考虑安全问题应当使用session;
(4)session会在一定时间内保存在服务器上,当服务器的访问量增多时,会占用服务器的内存,考虑减轻服务器负载的问题,应该使用cookie;
二十二、TCP的滑动窗口
二十三、cpu平均负载
cpu平均负载是指某段时间内占用cpu时间的进程和等待cpu时间的进程数,这里等待cpu时间的进程是指等待被唤醒的进程,不包括处于wait状态进程。
二十四、如何查看本机ip
在命令窗口中输入 ipconfig 即可查看。
二十五、tcp三次握手
第一次握手:客户端发送一个带SYN的报文到服务器端,表示客户端想要和服务器端建立连接;
第二次握手:服务器端接收到客户端的请求,返回客户端报文,这个报文带有SYN和ACK确认标识;
第三次握手:客户端再次响应服务端一个ACK确认,表示已经准备好了。
二十六、tcp四次挥手
第一次挥手:客户端发送一个FIN,关闭客户端到服务器端的连接;
第二次挥手:服务器端收到这个FIN后发回一个ACK确认标识,确认收到;
第三次挥手:服务器端发送一个FIN到客户端,服务器端关闭客户端的连接;
第四次挥手:客户端发送ACK报文确认,这样关闭完成。
二十七、类的加载过程
类加载过程包括加载、验证、准备、解析和初始化五个阶段。
二十八、java类加载器
1、java类加载器的种类:
引导类加载器(bootstrap classloader);
扩展类加载器(extensions classloader);
应用程序类加载器(application classloader);
自定义类加载器(java.lang.classloder)。
2、类加载器之间的关系:
启动类加载器,由C++实现,没有父类;
拓展类加载器,由Java语言实现,父类加载器为null;
系统类加载器,由Java语言实现,父类加载器为拓展类加载器;
自定义类加载器,父类加载器肯定为系统类加载器。
3、双亲委派机制工作原理:
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成。
4、双亲委派机制的作用:
通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要让子类加载器再加载一次;其次是考虑到安全因素,java核心api中定义的类型不会被随意替换。
二十九、session是如何区分用户的
第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,就能知道是哪个用户了。
三十、tcp拥塞控制
在某段时间内,对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏,这种情况就称为拥塞,简单的说拥塞产生的原因有两点:接收方容量不够、网络内部有瓶颈。
TCP进行拥塞控制的四种算法:慢启动、拥塞避免、快速重传、快速恢复。