Fork me on GitHub

面经整理2

美团一面

1. 判断回文

  1. 字符串转置,然后逐位比较;
  2. 字符串转置,直接 equals 比较;
  3. for 循环 length/2 次,首位和末尾逐次比较;

2. jvm,讲讲gc

jvm 将内存划分为:方法区、堆区、虚拟机栈、本地方法栈、程序计数器。

方法区:

  • 要加载的类的信息、静态变量、构造函数、final 定义的信息,包含运行时常量池。
  • 全局共享。
  • 对应持久带,会被 GC,但很少发生。

堆区:

  • 所有线程共享,主要存放对象实例和数组。

虚拟机栈:

  • 每个方法被执行时,产生一个栈帧,用来存储局部变量表、动态链接、操作数和方法出口等信息。

本地方法栈:

  • 执行 native 方法。

程序计数器:

  • 存储当前线程执行的字节码行号。

GC:对方法区和堆区进行垃圾回收,回收的对象多是那些不存在任何引用的对象。
GC 算法:标记-清除算法、复制算法、标记-整理算法、分代收集算法。
GC 收集器:串行收集器、ParNew GC、Parallel Scavenge GC、CMS 收集器、G1 收集器、Serial Old 收集器、Parallel Old 收集器、RTSJ 垃圾收集器。

3. 讲讲 osi 七层模型

  1. 应用层:文件传输、管理,邮件处理。协议:HTTP、FTP等。
  2. 表示层:编码转换、数据解析、管理数据的加解密。协议:Telnet、SNMP、Gopher等。
  3. 会话层:通信连接的建立、保持会话过程通信连接的畅通。协议:DNS、SMTP等。
  4. 传输层:定义一些传输数据的协议和端口。协议:TCP、UDP等。
  5. 网络层:控制子网的运行,如逻辑编址,分组传输,路由选择。协议:IP、ICMP、ARP等。
  6. 数据链路层:对物理层传输的比特流包装,物理寻址等。协议:PPP、SDLC、帧中继等。
  7. 物理层:定义物理设备的标准。协议:IEEE 802.1A、IEEE802.2等。

2

推荐阅读:OSI七层协议大白话解读
OSI七层协议模型、TCP/IP四层模型

4. tcp udp

3

区别:

  • TCP 是面向连接的、UDP 是面向无连接的;
  • UDP 程序结构简单;
  • TCP 是面向字节流的,UDP 是基于数据报的;
    • UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给 UDP 多长的报文,UDP 就照样发送,即一次发送一个报文。(理解:不拆分,所以有原生的边界)。
    • TCP 有一个缓冲,当应用程序传送的数据块太长,TCP 就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP 也可以等待积累有足够多的字节后再构成报文段发送出去。(理解:要拆分,所以没有边界,直接拼接上)。
  • TCP 保证数据正确性、UDP 可能丢包;
  • TCP 保证数据顺序,UDP 不保证。
  • TCP 有流量控制(滑动窗口)、拥塞控制(拥塞窗口)机制。

具体编程时,
socket()的参数不同:

  • UDP Server 不需要调用 listen 和 accept;
  • UDP 收发数据用 sendto/recvfrom 函数;
  • TCP:地址信息在 connect/accept 时确定;
  • UDP:在 sendto/recvfrom 函数中每次均需指定地址信息;
  • UDP:shutdown 函数无效。

推荐阅读:面向报文(UDP)和面向字节流(TCP)的区别

5. 设计模式 懒汉饿汉区别,抽象简单工厂区别

饿汉和懒汉式设计模式中单例模式的两大种方式:

饿汉式,在类创建的同时已经创建好了一个静态对象供使用,是线程安全的,但对内存有一定消耗。

懒汉式,延时加载,线程不安全,为了实现线程安全有几种写法:

  • 加方法锁(有漏洞)
  • 双锁(有漏洞)
  • 双锁 + valatile等。(无漏洞、繁琐)
  • 静态内部类(也叫做 Holder 方法无漏洞)【常用】。
  • 枚举(无漏洞、简单)【常用】。有关枚举方式推荐阅读:为什么我墙裂建议大家使用枚举来实现单例。枚举的分析如下:
    • 枚举类被加载时,会使用ClassLoaderloadClass方法(此方法使用同步代码块保证线程安全)。
    • 枚举的序列化和反序列化是特殊定制的,可避免因反射引起的代码重排问题。
    • 枚举单例的实例代码如下:
1
2
3
4
5
6
7
8
public enum Singleton{
INSTANCE;
public void check(){ sysout ... }
psvm{
INSTANCE.check();
}
}

  + 枚举单例,再增加懒加载功能(配合静态内部类实现)如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton{
private enum EnumHolder{
INSTANCE;
private Singleton instance;
EnumHolder(){
this.instance = new Singleton();
}
private Singleton getSingleton(){
return instance;
}
}
public static Singleton getInstance(){
return EnumHolder.INSTANCE.getSingleton();
}
}

简单工厂:拥有一个工厂方法,接受一个参数,通过不同的参数实例化不同的产品类。
优点:简单、代码量少。
缺点:

  • 此工厂方法负责生产任何“东西”,负担太重;
  • 在遵循开闭原则下,简单工厂无法增加新的产品。

工厂方法:针对每一种产品提供一个工厂类,通过不同的工厂实例来创建不同的产品实例。
优点:可解决简单工厂的缺点,当需要增加某类“东西”时,不需要修改原工厂,只需要添加生产这类“东西”的工厂即可。
缺点:对于某些可以生成产品族的情况处理较复杂。比如可以将奔驰所有车看做一个产品族。

抽象工厂:用于应对产品族概念的,与工厂方法类似,将工厂方法中的共同点抽象出来。
优点:便于增加产品族。

另一种总结:

  • 简单工厂:定义了一个类来负责创建其他产品类的实例。
  • 工厂方法:工厂父类负责定义创建产品对象的公共接口,而工厂子类则决定生成具体的产品对象,这样类实例的创建就可以在子类中单独完成了。
  • 抽象工厂:提供一个创建一系列相关或互相依赖对象的接口,而无须指定它们具体的类。

推荐阅读:简单工厂、工厂方法、抽象工厂、策略模式、策略与工厂的区别

6. linux 删除日志文件最后十行

1
2
A=$(sed -n '$=' a.txt) // 输出a.txt的行数赋给 A,取值时用$
sed $(($A -10+1)),${A}d a.txt

注:如果将 10 改成 50,就成了删除最后 50 行了。a.txt 可以修改为题目中的日志文件。

推荐阅读:Linux sed命令
shell中sed命令的用法

7. sql 注入

SQL 注入:SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编程时的疏忽,通过SQL语句,实现无帐号登录,甚至篡改数据库。

注入思路:

  1. 寻找到SQL注入的位置
  2. 判断服务器类型和后台数据库类型
  3. 针对不同的服务器和数据库特点进行SQL注入攻击

应对办法:

  1. 采用预编译语句集 PreparedStatement;
  2. 使用正则表达式过滤;
  3. 字符串过滤;
  4. jsp 中进行函数检查;

推荐阅读:SQL 注入
如何防止sql注入

8. 快排时间复杂度,空间复杂度

1

快排时间复杂度的三种证明办法:为什么快速排序的时间复杂度是 O(nlogn)

Tips:

  • 快速排序排的是一个左右闭合区间;
  • 快排每一趟只将一个数排好,放在正确的位置;
  • key 能选最左边也能选最右边,也可以随机选
  • 当一组数为有序时使用快排对这组数排序,此时为快排的最坏情况;时间复杂度最高,为 O(N^2);

顺口溜:

  • 不稳定:快些(希)小伙伴。
  • 时间复杂度O(NlogN):快归队(堆)。

9. 数据库隔离级别

数据库事务的隔离级别有 4 种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。

  1. Read uncommitted
    读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。
  2. Read committed
    读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。
  3. Repeatable read
    重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。(MySQL 默认)
  4. Serializable 序列化
    Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
    • 与重复读的区别,个人理解:序列化是读写必加锁,两个事务完全互斥,不可能发生干扰。而重复读的要求没有那么严格(要弱上很多),要求在 A 事务期间,B 事务不会修改 A 事务所涉及的资源。

10. 如何提高服务器性能

单服务器的优化办法有:

  1. 使用内存数据库;
  2. 使用 RDD(Resilient Distributed Datasets);
  3. 增加缓存;
  4. 使用 SSD;
  5. 优化数据库;
  6. 选择合适的 IO 模型;
  7. 使用多核处理策略;
  8. 分布式部署程序。

分布式服务器的相关技术栈:

  1. 负载均衡(反向代理);
  2. 应用服务器集群;
  3. 数据库集群、读写分离;
  4. 动静分离;
  5. 缓存;
  6. 消息中间件;
  7. NoSQL;
  8. 分布式文件系统;
  9. 并行计算;
  10. 实时分析;
  11. 高可用、虚拟化、云计算;
  12. CDN

推荐阅读:
服务器性能优化的8种常用方法

20个Linux服务器性能调优技巧

11. 有关类加载器的几点思考

常见的类加载器有三种:

  1. BootStrap(引导):加载的基础文件,用 C 语言编写的。
  2. ExtclassLoader(扩展):同样加载基础文件,范围略大于 bootStrap。
  3. AppclassLoader(应用):加载 ClassPath 指定的所有 jar 和目录等。

如何获得类加载器?

  • 由“字节码对象”(即内存中的 class),可以调 API 拿到其类加载器。

拿到类加载器能干什么?

  • 可以通过类加载器读出 src 目录下的其他各种文件,主要读出各种配置文件如 properties 等。
1
2
3
class clazz = class.forName("Mydemo");
ClassLoader loader = clazz.getClassLoader();
loader.getResource("jdbc.properties");
-------------The End-------------