1、hashCode 和 equals
- Object 中没有重写 hashCode 方法,默认使用对象的 jvm 内存地址进行 hashCode;重写了 equals 方法,直接判断的是两个对象的地址是否相等
- 如果 hashCode 相等,equals 不一定相等;如果 equals 相等,那么 hashCode 一定相等。也就是说使用 equals 比较是绝对可靠的, 但是为了效率,可以先比较 hashCode,如果相等,再比较 equals 方法;如果 hashCode 不相等,那么肯定不想动。
- 若重写 equals 方法,一定要重写 hashCode 方法
package com.vim.test;
public class User{
private String name;
public User(String name){
this.name = name;
}
// 重写 equals 方法
public boolean equals(Object arg0){
if(!(arg0 instanceof User)){
return false;
}
User user = (User)arg0;
//如果名字相同,则表示属于同一个对象。
if (user.getName().equals(this.getName())){
return true;
}else{
return false;
}
}
// 重写 hashCode 方法,返回名字的哈希码
public int hashCode() {
return name.hashCode();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
User u1 = new User("张三");
User u2 = new User("李四");
User u3 = new User("张三");
System.out.println(u1.equals(u3));
}
}
该图为 Set 集合放入元素时判断重复的逻辑流程。
2、ceil、floor、round
- floor 返回不大于的最大整数;ceil 则是不小于他的最小整数
- round 则是4舍5入的计算,如果是负数,可以先取绝对值;正数小数点后大于5则进位;负数小数点后小于以及等于5都舍去,大于5的则进位
3、StringBuffer、StringBuilder
- 两种都是 final 修饰的类,底层都是采用字符数组来实现的;减少了字符串拼接的开销
- StringBuffer 是线程安全的
4、抽象类特点
- 抽象类不一定非要有抽象方法,还可以有构造函数和main方法,并且可以运行
- 抽象类不能直接实例化
- 抽象类不能使用 final 修饰
- 抽象类中的方法可以使用任意修饰符
5、集合知识点
- Collection 和 Map 两大类,Collection 下又分为 List 和 Set;常用的集合类有 ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet 和 HashMap、LinkedHashMap、TreeMap
- List、Set 主要区别在于重不重复和是否有序,List:有序、可重复; Set:无序(也可有序),重复
- HashMap 和 Hashtable 区别,两者都是在内部通过单链表解决冲突问题,容量不足(超过了阀值)时,会自动增长;而 HashMap 是线程不安全的,而且可以存储 null 值;基于 hash 算法存储元素,当计算出的 hash 相同(冲突)时,采用链表和红黑树的方式进行存储
- ArrayList 和 LinkedList 两种实现的方式不同:前一个使用数组结构,后一个使用双向链表结构;所以随机访问效率、增加和删除操作,两种各有优势
6、线程有哪些状态
- 新建(new)、就绪(Runnable)、运行(run)、阻塞(blocked)、死亡(dead)
- 新建:实现 Runnable 接口或继承 Thread 的类,使用 new 关键字创建
- 就绪:调用 start 方法、sleep 结束、join 结束、时间片用完、调用 yield
- 运行:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态
- 阻塞:调用 sleep 方法
7、wait 和 notify 流程原理
- 调用了 wait 方法的线程会进入等待队列
- 调用了 notify 方法的线程,会从等待队列中选择一个线程进入锁池;调用了 notifyAll 方法的线程,会从等待队列中选择所有的线程进入锁池,然后共同去竞争对象锁
8、sleep 和 wait 的区别
- sleep 来自于 Thread 类,wait 属于 Object 类
- wait 会释放锁,如果sleep放在同步方法中,并不会释放锁,只是会让出 cpu
- wait 必须运行在同步方法中,sleep不需要
9、ThreadLocal
- 为每一个线程提供了独立的变量副本,如 数据库连接、session 管理
10、synchronized 和 volatile 的区别
- synchronized 会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性; volatile 关键字解决的是内存可见性的问题,会使得所有对
volatile
变量的读写都会直接刷到主存,即保证了变量的可见性 - volatile 仅能够使用在变量级别,而 synchronized 可以使用在变量、方法、类上面
- volatile 不会造成线程的阻塞,但是 synchronized 会造成线程的阻塞
11、深拷贝和浅拷贝
- 将一个对象复制给另一个对象,有三种方式: 直接复制、浅拷贝、深拷贝,其中直接复制,如果是引用对象,会导致两者一起变化
- 浅拷贝:使用 Object 类的 clone 方法,创建一个新对象,将当前对象的非静态字段复制到新对象,如果字段是基本类型,直接复制,如果是引用类型,复制引用而不复制引用的对象
public class Resume implements Cloneable{
private String name; //姓名
private String sex; //性别
public Resume(String name, String sex) {
this.name = name;
this.sex = sex;
}
public Object clone() {
try {
return (Resume)super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
- 深拷贝示例,使用该工具类的对象必须要实现Serializable接口
public class CloneUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
12、session 和 cookie 的区别
- 存储位置:session 在服务端,cookie 在客户端
- cookie 出现的原因是为了解决 http 协议的无状态问题,通过在客户端记录信息来确定用户的信息,而 session 则是通过在服务端记录信息来解决这个问题
- session 携带的 sessionId 需要通过 cookie 机制保存到客户端浏览器中;也可以将 sessionId 放入链接参数的方式来使用
13、常见的异常类有哪些
- NullPointerException、ClassNotFoundException、IndexOutOfBoundsException、ClassCastException
- NumberFormatException、FileNotFoundException、IOException
14、tcp 和 udp
- tcp 是面向连接的,udp 是面向无连接的
- tcp 是面向字节流的,udp 是面向数据包的;由于缓冲区的存在,tcp 的读写不需要一一匹配,可以分多次读取和写入,而 udp 针对报文原样发送,不会拆分,也不会合并。
- tcp 保证数据正确性,udp 可能丢包
- 三次握手:保证通信双方全都有来有回
初始阶段,客户端和服务端都处于 CLOSED 阶段,服务端监听某个端口,处于 LISTEN 状态
客户端主动发送 SYN 连接请求,并置发送序号为 x,之后处于 SYN-SENT 状态
服务端收到连接请求之后,返回返回SYN + ACK,并且发送序号为 y,并确认序号为 x+1
客户端收到服务端的 SYN+ACK 之后,进入 ESTABLISHED 状态,然后回复 ACK,并置发送序号为 x+1,确认序号 y+1
服务端收到 ACK 之后处于 ESTABLISHED 状态,
5、四次挥手:
客户端发送 FIN 数据包来主动断开,进入 FIN-WAIT-1 状态(此时可以接受、应答数据,无法发送数据)
服务端收到 FIN 后,进入 CLOSED-WAIT 状态,并回复 ACK
客户端收到 ACK 之后,进入 FIN-WAIT-2 状态,该状态是等待对方的 FIN,默认60s,超过就本地关闭,不会进入下一个状态
服务端发送 FIN+ACK 给客户端,进入 LAST-ACK 阶段
当客户端收到了服务端发来的 FIN 之后,回复 ACK ,进入 TIME-WAIT 状态,此时内核会设定一个时间长度为 2MSL 的定时器,当到时间后,内核会将该连接关闭,MSL即报文最大生存时间
15、粘包产生原因
- 发送方:发送端需要等缓冲区满才发送出去
- 接收方:不及时接收缓存区数据,造成多个包接收
16、事务介绍
- 四大特征:原子性、一致性、隔离性、持久性(ACID)
- 原子性:事务包含的所有操作要么全部成功,要么全部失败; 一致性:事务操作必须使数据库从一个一致性状态切换到另一个一致性状态,如转账前后,两个用户钱的总和不变;隔离性:多个用户并发访问数据库,为每一个用户开启的事务不能被其他事务干扰;持久性:一个事务一旦被提交了,那么对数据的改变就是永久性的。
- 隔离级别:读未提交、读已提交、可重复读、可串行化
- 读未提交:事务A未提交的数据,事务B可以读到,此时读到的是脏数据;
- 读已提交:事务A提交的数据,事务B可以立即读到,可以避免脏读,但是不可重复读;
- 可重复读(mysql默认级别):事务A和事务B开启事务之后,事务A更新了数据,进行事务提交后,事务B读取的仍然是之前的数据,在事务B提交之前,这条数据都是可重复读的;
- 串行化:事务A在操作数据时,事务B只能等待。
17、Mybatis 中 #{} 和 ${} 的区别
#{} 是预编译处理,${} 是字符替换; #{} 会将SQL 中的 #{} 替换为 ?,然后配合 PreparedStatement 的 Set 方法进行赋值,这样可以有效地防止 SQL 注入,保证安全。
18、Mybatis 的一级和二级缓存