前言
在准备面试的过程中发现很多会需要相互比较的内容,为了方便自己与大家学习与比较,遂整理。
很多知识点的比较可能并不全面,甚至可能会出现错误,但是也只能暂列出来,等待以后慢慢考证,修改,尽量做到最准确、最细致。如果有读者看到出错的地方,还请不吝指出。
表格是 Typora 自动调整的格式,所以有些排版有些丑陋。
网络篇
说说TCP和UDP的区别吧
TCP | UDP |
---|---|
首部至少20字节,开销大 | 首部8字节,开销小 |
面向连接,应用程序在使用 TCP 协议之前,必须先建立 TCP 连接。在传送数据完毕后,必须释放已经建立的 TCP 连接。 | 无连接的,即发送数据之前不需要建立连接,发送数据之后也不需要断开连接,因此减少了开销和发送数据之前的时延。 |
面向字节流、TCP 中的 “流”(stream) 指的是流入到进程或从进程流出的字节序列。 | 面向报文 |
通过 TCP 连接传送的数据,无差错、不丢失、不重复,并且按序到达。 | 尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的连接状态表。 |
滑动窗口,流量控制,拥塞控制,停止等待协议,连续 ARQ 协议 | 没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低。这 |
传输效率低 | 传输效率高 |
一对一、点对点传输 | 支持一对一、一对多、多对多的传输 |
说说Cookie和Session的区别吧
Cookie 简介
- 由服务器发给客户端的特殊信息,以文本的形式存放在客户端
- 客户端再次请求的时候,会把
Cookie
回发 - 服务器收到后,会解析
Cookie
生成与客户端相对应的内容
Session 简介
- 服务器端的机制,在服务器上保存的信息
- 解析客户端请求并操作
Session Id
,按需保存状态信息 - 实现方式
- 使用
Cookie
来实现 - 使用
URL
回写来实现
- 使用
Cookie
和 Session
的区别
Cookie
数据存放在客户的浏览器上,Session
数据存放在服务器上Session
相对于Cookie
更安全- 若考虑减轻服务器负担,应当使用
Cookie
说说 HTTP 和 HTTPS 的区别吧
方面 | HTTP | HTTPS |
---|---|---|
开发目的 | 发布和接收 HTML 页面 浏览器和网站服务器之间传递信息 |
提供对网站服务器的身份认证 保护交换数据的隐私与完整性。 |
默认端口 | 80 | 443 |
安全协议 | 无 | SSL/TLS |
加密方式 | 无 | 对称加密、非对称加密、哈希算法、数字签名 |
安全性 | 明文传输、相对较低 | 加密传输、相对较高 |
建立连接 | TCP三次握手交换3个包 | TCP3个包+SSL9个包 |
响应速度 | 相对较快 | 相对较慢 |
所需费用 | 免费 | 可能需要购买证书 |
说说GET和POST的区别吧
比较方面 | GET | POST |
---|---|---|
请求主体(Body) | 只有URL,无请求主体 | 有请求主体 |
响应主体 | 有 | 有 |
后退按钮/刷新 | 无副作用 | 数据会被重新提交 |
书签 | 可收藏为书签 | 不可收藏为书签 |
缓存 | 可被缓存 | 不能缓存 |
编码类型 | application/x-www-form-urlencoded |
application/x-www-form-urlencoded multipart/form-data text/plain |
历史 | 参数保留在浏览器历史中 | 参数不会保存在浏览器历史中 |
URL长度 | 有限制,但不同浏览器限制又不同 常说的2KB其实是指IE8,Chrome和Apache为8KB |
无限制(Post请求本身不限制长度,但是服务器是会限制的。) |
对数据类型的限制 | 只允许 ASCII 字符 | 没有限制、也允许二进制数据,Percent Encoding编码 |
安全性 | 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET | POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。 |
幂等性 | 支持 | 不支持 |
可见性 | 数据在 URL 中对所有人都是可见的 | 数据不会显示在 URL 中 |
操作系统篇
有待完善
数据库篇
说说 MyISAM 和 InnoDB 的区别吧
比较方面 | MyISAM | InnoDB |
---|---|---|
锁级别 | 只支持表级锁 | 支持表级锁、行级锁 |
事务 | 不支持事务 | 支持事务 |
外键支持 | 不支持外键 | 支持外键 |
索引支持 | 非聚簇索引、索引和数据是分开的 | 聚簇索引、数据文件本身就是索引文件 |
辅助索引 | 和主索引没有多大区别 | data域存储相应记录主键的值而不是地址 |
全文索引 | 支持 FULLTEXT类型的全文索引 | 不支持FULLTEXT类型的全文索引 但是InnoDB可以使用sphinx插件支持全文索引,并且效果更好。 |
索引数据结构 | 索引利用B+Tree实现 | 索引利用B+Tree树实现 |
统计行数 | 变量保存整个表的行数 select count() |
需要select count(1) from tb 扫描表获取总行数 |
存储结构 | 表定义文件、数据文件、索引文件分开存放 | 所有的表都保存在同一个数据文件 |
自增支持 | 增长列必须是索引,如果是组合索引,自动增长可以不是第一列,可以根据前面几列进行排序后递增。 | 必须包含只有该字段的索引。引擎的自动增长列必须是索引,如果是组合索引也必须是组合索引的第一列。 |
CRUD | 查询性能极高 | 查询、修改、插入、删除总体比较高 |
说说 Redis 和 memcached 的区别吧
比较方面 | Redis | Memcached |
---|---|---|
数据结构 | 更丰富的数据结构 支持String, Hash, List, Set, ZSet |
支持简单的数据类型 String |
数据持久化 | 支持数据持久化 | 不支持 |
集群支持 | 具有原生集群 | 没有原生集群 |
线程IO模型 | 单线程的IO多路复用模型 | 多线程非阻塞IO复用的网络模型 |
附加功能 | 发布订阅模式、主从分区、序列化支持、脚本支持 | 多线程服务支持 |
事件库 | 自封装简易事件库AeEvent | LibEvent事件库 |
说说 B+ 树和 B-树的区别吧
对于 $m$ 阶 $B-、B+$ 树
比较方面 | B-树 | B+树 |
---|---|---|
结点与分支 | 具有 $n$ 个关键字的结点有 $n+1$ 个分支 | 具有 $n$ 个关键字的结点有 $n+1$ 个分支 |
根节点关键字个数范围 | $1 \le n \le m-1$ | $2 \le n \le m$ |
非根节点关键字个数范围 | $\lceil \frac{m}{2}\rceil - 1 \le n\le m-1$ | $\lceil \frac{m}{2}\rceil \le n\le m$ |
叶子结点 | 存储关键字 | 包含信息,并且包含全部关键字,叶子结点引出的指针指向记录。 |
非叶子结点 | 每个关键字对应一条记录的存储地址 | 仅起索引作用,结点中的每个索引项仅含有对应子树的最大关键字和指向该子树的指针,不含有关键字对应记录的存储地址。 |
额外指针 | 无 | 有一个指针指向关键字最小的叶子结点,所有叶子结点连接成一个线性表 |
说说跳表和红黑树的区别吧
比较方面 | 跳表 | 红黑树 |
---|---|---|
实现难易程度 | 相对简单 | 相对复杂 |
插入、删除结点 | 仅需局部调整 | 可能需要全局调整 |
时间复杂度 | 总体概率上 $O(\lg n)$ | $O(\lg n)$ |
非并发性能 | 所差无几 | 所差无几 |
并发性能 | 相对较高 | 相对较低 |
Java基础篇
说说String、StringBuffer、StringBuilder的区别吧
JDK 1.8 中
比较方面 | String | StringBuilder | StringBuffer |
---|---|---|---|
底层实现 | final char[] |
char[] |
char[] |
对象可变性 | 不可变 | 可变 | 可变 |
继承自 | 无 | AbstractStringBuilder |
AbstractStringBuilder |
实现接口 | Comparable<String>, CharSequence |
CharSequence |
CharSequence |
线程安全 | 否 | 否 | 是 |
对象操作:
String: 如果字符串常量池不存在的话会生成新对象,然后将指针指向新的 String 对象
StringBuilder, StringBuffer: 对对象本身进行操作,而不是生成新的对象并改变对象引用。
说说 Overload 和 Override 的区别吧
比较方面 | 重载方法 | 重写方法 |
---|---|---|
发生范围 | 同一个类中 | 子类重写父类,或者实现接口定义方法 |
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可修改 | 不可修改 |
异常 | 可修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
发生阶段 | 运行期 | 编译器 |
说说接口和抽象类的区别吧
比较方面 | 接口 | 抽象类 |
---|---|---|
方法修饰符 | 默认 public, 使用 default 的话需要添加方法体 | 可以被各种修饰符修饰 |
与类的关系 | 一个类可以实现多个接口 | 只能继承一个抽象类 |
变量 | 仅可有static、final 变量 | 可有各种变量 |
设计目的 | 对类的行为进行约束 | 代码复用 |
说说成员变量和局部变量的区别吧
比较方面 | 成员变量 | 局部变量 |
---|---|---|
所属对象 | 类 | 类中的方法 |
可用修饰符 | private、public、static、final | final |
存储位置 | 被static修饰,成员变量属于类,存储在运行时数据区的方法区,未被static修饰存在堆 | 存储在运行时数据区的Java虚拟机栈 |
生存时间 | 随着对象的创建而存在 | 随着方法的调用而自动消失 |
初始化值 | 未被赋初值则为类型的默认值,final修饰的需要显式赋值 | 不会自动赋值 |
说说 for 和 foreach 的区别吧
比较方面 | for | foreach |
---|---|---|
更适合作用于 | 数组 | 集合 |
实现基础 | 数组下标索引 | 迭代器 Iterator |
删除/修改元素 | 可以实现 | 出现 ConcurrentModificationException |
遍历不修改元素 | 效率高 | 效率低 |
在测试时中向 ArrayList、LinkedList
添加100万个元素,发现总是 for
循环的遍历时间比较快,只是遍历,未做修改添加删除。
说说 BIO、NIO、AIO的区别吧
说说 == 和 equals 的区别吧
==
: 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。
equals()
: 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
- 情况 1:类没有覆盖
equals()
方法。则通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象。 - 情况 2:类覆盖了
equals()
方法。一般,我们都覆盖equals()
方法来比较两个对象的内容是否相等;若它们的内容相等,则返回true
。
说说 Error 和 Exception 的区别吧
Error
类和Exception
类的父类都是Throwable
类,他们的区别是:
- Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。
- Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
- Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用
try-catch
捕获,要么用throws
字句声明抛出,交给它的父类处理,否则编译不会通过。
Java集合框架篇
说说 List、Set、Map 的区别吧
比较方面 | List | Set | Map |
---|---|---|---|
存储数据 | 同类型数据 | 同类型数据 | 键值对 |
重复元素 | 允许重复 | 不可重复 | HashMap 不允许key 重复,允许value 重复 |
顺序性 | 有序 | 无序 | TreeMap 有序、HashMap 无序 |
对null 支持 |
不支持 | 最多一个null | HashMap 可允许一个null key、value |
主要实现类 | ArrayList,LinkedList,Vector |
HashSet,LinkedHashSet |
HashMap |
说说 ArrayList、LinkedList、Vector 的区别吧
比较方面 | ArrayList | Vector | LinkedList |
---|---|---|---|
继承的类 | AbstractList |
AbstractList | AbstractSequentialList |
实现的接口 | List<E>, RandomAccess, Cloneable |
List<E>, RandomAccess, Cloneable |
List<E>, Deque<E>, Cloneable |
底层实现 | 对象数组transient Object[] elementData; |
对象数组 protected Object[] elementData; |
双向链表 |
初始容量 | 10 | 10 | 0 |
扩容操作 | 有 | 有 | 无 |
线程安全 | 否 | 是 | 否 |
最适合操作 | 查询 | 查询 | 增加、删除 |
说说 HashMap、HashTable、ConcurrentHashMap的区别吧
比较方面 | HashMap | Hashtable | ConcurrentHashMap |
---|---|---|---|
是否线程安全 | 否 | 是 | 是 |
线程安全采用的方式 | 无 | 采用synchronized 类锁,效率低 |
CAS + synchronized ,锁住的只有当前操作的bucket,不影响其他线程对其他bucket的操作,效率高 |
数据结构 | 数组+链表+红黑树(链表长度超过8则转红黑树) | 数组+链表 | 数组+链表+红黑树(链表长度超过8则转红黑树) |
是否允许null 键值 |
是 | 否 | 否 |
哈希地址算法 | (h=key.hashCode())^(h>>>16 |
key.hashCode() |
(h=key.hashCode())^(h>>>16)&0x7fffffff |
定位算法 | (n-1)&hash |
(hash&0x7fffffff)%n |
(n-1)&hash |
扩容算法 | 当键值对数量大于阈值,则容量扩容到原来的2倍 | 当键值对数量大于等于阈值,则容量扩容到原来的2倍+1 | 当键值对数量大于等于sizeCtl,单线程创建新哈希表,多线程复制bucket到新哈希表,容量扩容到原来的2倍 |
链表插入 | 将新节点插入到链表尾部 | 将新节点插入到链表头部 | 将新节点插入到链表尾部 |
继承的类 | 继承abstractMap 抽象类 |
继承Dictionary 抽象类 |
继承abstractMap 抽象类 |
实现的接口 | 实现Map 接口 |
实现Map 接口 |
实现ConcurrentMap 接口 |
默认容量大小 | 16 | 11 | 16 |
默认负载因子 | 0.75 | 0.75 | 0.75 |
统计size方式 | 直接返回成员变量size |
直接返回成员变量count |
遍历CounterCell 数组的值进行累加,最后加上baseCount 的值即为size |
说说 JDK1.7 与 JDK 1.8 中 HashMap 的变化吧
说说 JDK1.7 与 JDK 1.8 中 ConcurrentHashMap 的变化吧
说说TreeMap、HashMap、LindedHashMap、HashTable的区别吧
比较方面 | HashMap | TreeMap | LinkedHashMap | HashTable |
---|---|---|---|---|
迭代顺序 | 随机 | 根据键的顺序 | 根据插入顺序 | 随机 |
Get、Put、Remove、ContainsKey效率 | $O(1)$ | $O(\lg n)$ | $O(1)$ | $O(1)$ |
Null Keys/Keys | 允许 | 不允许 | 允许 | 不允许 |
接口 | Map | Map、SortedMap、NavigableMap | Map | Map |
线程安全 | 否 | 否 | 否 | 是 |
实现 | 桶+链表+红黑树 | 红黑树 | HashTable、LinkedList、桶的双向链表 | 桶 |
备注 | 高效 | 维持树需要付出代价 | 无额外开销的具有TreeMap的优势 | 过时 |
多线程与并发篇
说说进程与线程的区别吧
进程是一个独立的运行环境,而线程是在进程中执行的一个任务。他们两个本质的区别是是否单独占有内存地址空间及其它系统资源(比如I/O):
- 进程单独占有一定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不干扰;而线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。
- 进程单独占有一定的内存地址空间,一个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性高;一个线程崩溃可能影响整个程序的稳定性,可靠性较低。
- 进程单独占有一定的内存地址空间,进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收以及页调度,开销较大;线程只需要保存寄存器和栈信息,开销较小。
另外一个重要区别是,进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位,即CPU分配时间的单位 。
说说 run()
和 start()
的区别吧
start()
: 通过 start () 方法来启动的新线程,处于就绪(可运行)状态,并没有运行,一旦得到 cpu 时间片,就开始执行相应线程的 run () 方法,这里方法 run () 称为线程体,它包含了要执行的这个线程的内容,run 方法运行结束,此线程随即终止。start () 不能被重复调用。用 start 方法来启动线程,真正实现了多线程运行,即无需等待某个线程的 run 方法体代码执行完毕就直接继续执行下面的代码。这里无需等待 run 方法执行完毕,即可继续执行下面的代码,即进行了线程切换。run()
:run()
就和普通的成员方法一样,可以被重复调用。如果直接调用 run 方法,并不会启动新线程!程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待 run 方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。- 总结:调用
start()
方法会创建一个新的子线程并启动,run()
方法只是Thread
的一个方法调用,还是在原线程里运行。
说说 wait()
和 sleep()
的区别吧
sleep()
是Thread
类的方法,wait()
是Object
类的方法sleep()
方法可以在任何地方使用wait()
方法只能在synchronized
方法或synchronized
块中使用Thread.sleep()
: 只会让出 CPU,不会导致锁行为的改变Object.wait()
: 不仅让出 CPU,还会释放已经占有的同步资源锁
说说 Thread 和 Runnable 的区别吧
Thread
是实现了Runnable
接口的类,使得run()
支持多线程- 因为类的 单一继承原则,推荐多使用
Runnable
接口
说说 synchronized 和 volatile 的区别吧
Synchronized | Volatile |
---|---|
实例方法、类方法(静态方法)、代码块 | 只修饰变量 |
保证可见性和原子性 | 保证可见性,不保证原子性 |
可能会造成线程阻塞 | 不会造成线程阻塞 |
实现基于对象头中的Mark Word | 实现基于JMM |
锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住(加锁) | (不是加锁)本质是在告诉 JVM 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取 |
解决对个线程之间访问资源的同步性 | 解决变量在多个线程之间的可见性 |
说说 synchronized 和 ReentrantLock 的区别吧
synchronized | ReentrantLock |
---|---|
关键字 | 类 |
可重入 | 可重入 |
基于JVM,JVM层面 | java.util.concurrent.locks 包下的类、JDK层面 |
基于对象头中的Mark Word实现 | 基于AQS实现 |
非公平锁 | 可实现公平锁 |
不可中断 | 等待可中断、超时等待、选择性通知 |
使用完后自动释放锁 | 需要手动加锁与释放锁 |
说说乐观锁与悲观锁的区别吧
比较方面 | 乐观锁 | 悲观锁 |
---|---|---|
核心思想 | 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改, 所以每次在拿数据的时候都会上锁, |
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁, 但是更新时会检查一下 |
主要实现方式 | 版本号机制、CAS | synchronized、Lock、排它锁 |
适用场景 | 多读少写 | 多写少读 |
缺点 | ABA 问题、循环时间长开销大、只能保证一个共享变量的原子操作 | 容易造成阻塞 |
说说类锁与对象锁的区别吧
- 获取对象锁的两种方法
- 同步代码块 (
synchronized(this)
,synchronized(类实例对象)
),锁的是小括号中的实例对象 - 同步非静态方法 (
synchronized method
),锁的是当前对象的实例对象
- 同步代码块 (
- 获取类锁的两种方法
- 同步代码块 (
synchronized(类.class)
),锁的是小括号中的类对象 (Class 对象) - 同步静态方法 (synchronized static method),锁的是当前对象的类对象
- 同步代码块 (
- 对象锁和类锁的总结
- 有线程访问对象的同步代码块时,另外的线程可以访问对象的非同步代码块
- 若锁住的是同一个对象,一个线程在访问对象同步代码块时,另一个访问对象的同步代码块的线程会被阻塞
- 若锁住的是同一个对象,一个线程在访问对象同步方法时,另一个访问对象的同步方法的线程会被阻塞
- 若锁住的是同一个对象,一个线程在访问对象同步代码块时,另一个访问对象的同步方法的线程会被阻塞;反之亦然
- 同一个类的不同对象的对象锁互不干扰
- 类锁由于也是一种特殊的对象锁,因此表现和前 4 条一致,而由于一个类只有一把对象锁,所以同一类的不同对象使用类锁将会是同步的
- 类锁和对象锁互不干扰
JVM与GC篇
说说几种类加载器之间的区别吧
说说主内存与工作内存的区别吧
- 主内存
- 存储 Java 实例对象
- 包括成员变量,类信息,常量,静态变量等
- 属于数据共享的区域,多线程并发操作时会引发线程安全问题
- 工作内存
- 存储当前方法的所有本地变量信息,本地变量对其他线程不可见
- 字节码行号指示器,Native 方法信息
- 属于线程私有数据区域,不存在线程安全问题
- JMM 与 Java 内存区域划分
- JMM 与 Java 内存区域划分是不同的概念层次。
- JMM 描述的是一组规则,围绕原子性,有序性,可见性展开。
- 它们的相似点是都存在共享区域与私有区域。
- 主内存与工作内存的数据存储类型以及操作方式
- 方法里的基本数据类型本地变量将直接存储在工作内存的栈帧结构中
- 引用类型的本地变量:引用存储在工作内存中,实例存储在主内存中
- 成员变量、static 变量、类信息均会存储在主内存中
- 主内存共享的方式是线程各自拷贝一份数据到工作内存,操作完成后刷新回主内存
说说 CMS 与 G1 的区别吧
比较方面 | CMS | G1 |
---|---|---|
设计目标 | 获取最短回收停顿时间 | 可预测垃圾回收的停顿时间 |
作用空间 | 老年代 | 不再区分新生代与老年代, 划分为Region |
清除算法 | 标记-清除 | 整体上标记-清除、局部上复制 |
回收过程 | 初始标记、并发标记、重新标记、并发清除 | 初始标记、并发标记、最终标记、筛选回收 |
STW的阶段 | 初始标记、重新标记 | 初始标记、最终标记、筛选回收 |
空间利用率 | 相对较低 | 相对较高 |
优点 | 并发收集、低停顿 | 分区收集、空间整合、可预测停顿 |
缺点 | 资源敏感,内存碎片,浮动垃圾 | 分配大对象时可能难以找到空间 |
常用框架篇
说说 @Component
和 @Bean
的区别吧
比较方面 | @Component | @Bean |
---|---|---|
作用对象 | 类 | 方法 |
目的 | 注册bean到Spring容器中 | 注册bean到Spring容器中 |
作用方式 | 扫描类路径自动侦测以及自动装配到Spring容器中 | 告诉Spring这是某个类的实例 |
自定义性 | 一般 | 较强,引用第三方库只能使用@Bean |
说说 JDK Proxy 和 Cglib 的区别吧
JDK动态代理是面向接口,在创建代理实现类时比CGLib要快,创建代理速度快。
CGLib动态代理是通过字节码技术底层生成一个继承代理类的类来实现,然后重写代理类的方法(如果被代理类被final关键字所修饰,将会失败,因为被final修饰的类无法被集成; 如果代理类中方法被final修饰,该方法无法代理成功, 因为被final修饰的方法无法被重写),但是运行速度比JDK动态代理要快。