TCP 三次握手和四次挥手
三次握手
- 第一次
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。 - 第二次
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; - 第三次
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。 - 完成三次握手,客户端与服务器开始传送数据,在上述过程中,还有一些重要的概念:未连接队列。
在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于 Syn_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。
四次挥手
- TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
- 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
- 服务器关闭客户端的连接,发送一个FIN给客户端。
- 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
关于为什么要三次握手和四次分手
== 和 equals 的区别是什么?
- == 的作用
- 对于基本类型(比如 int、flaot、double),比较的是值是否相同。
- 对于引用类型(比如 String、Integer),比较的是引用是否相同。
- equals 的作用
- 一般情况下比较的都是引用相同,但是对于 String、Integer等类,其重写了 equals() 方法,使之成为了值比较。
- 测试代码 1
public class Test {
private static class Demo{
private int val;
private Demo(int v){
val = v;
}
}
public static void main(String [] args){
int i1 = 5;
int i2 = 5;
System.out.println(i1 == i2);
// int 不是引用类型的实例(对象),所以没有 equals
Demo d1 = new Demo(5);
Demo d2 = new Demo(5);
System.out.println(d1 == d2);
System.out.println(d1.equals(d2));
// equals() 默认是引用比较。
String s1 = new String("666");
String s2 = new String("666");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
// 但是 String 重写了 equals方法,使之成为了值比较。
}
}
测试 1 的输出为:
true
false
false
false
true
- 测试代码2
public class Test {
public static void main(String [] args){
String info = "b";
String A = "abc";
String B = "a"+info+"c";
String C = "a"+"b"+"c";
// == 在引用类型下比较的是引用是否相同
System.out.println(A==B);
System.out.println(A==C);
}
}
其输出如下:
false
true
最开始,我对这样的结果也很讶异,我预测的是两个输出都是 false, 因为 String 的 + 语法糖底层是通过 new StringBuffer(),然后调用 append() 和 toString() 实现的,明显引用不一样了,应该都输出 false,但是这里还有更深层次的知识。
对象 info 和 A 均使用的字符串字面量,说白话点,就是直接把字符串写死,在编译期间,这种字面量会直接放入 class 文件的常量池中,从而实现复用。C 虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String C = "a" + "b" + "c";在class文件中被优化成String C = "abc",A、C指向的是同一个内存地址所以A == C成立,但是 B 的拼接过程中除了拼接字符串字面量,还有 info 这个对象的参与,因此无法在编译期间做优化,B 指向了和 A 不同的内存地址。
类修饰符
修饰符 | 类别 | 含义 |
---|---|---|
public | 访问控制符 | 将一个类声明为公共类,它可以被任何对象访问;每个 Java 程序有且只有一个类是 public,它被称为主类 ,其他外部类无访问控制修饰符,具有包访问性。 |
缺省 | 访问控制符 | 缺省修饰符时,这个类只能被同一个包中的类访问或引用;这一访问特性又称为包访问性。 |
abstract | 非访问控制符 | 将一个类声明为抽象类,没有任何实现方法,需要子类提供方法的实现,所以不能创建该类的实例。 |
final | 非访问控制符 | 将一个类声明为非继承类,则表示它不能被其他类继承。 |
上面所说的修饰符应该是针对外部类来说的,一个类的内部类可以被其他访问控制修饰符 protected、default、private 和非访问控制符 static、final等修饰。总的来说,内部类在修饰符上的使用上,类似于类的成员(内部类可用的修饰符只是类成员可用的修饰符的一个真子集;而且具体访问权限还有细微的差别,因此只能说类似)。
(常用)成员变量/方法修饰符
修饰符 | 类别 | 含义 |
---|---|---|
public | 访问控制符 | 指定该变量/方法为公共的,它可以被任何对象访问。 |
private | 访问控制符 | 指定该变量/方法只允许自己类的方法访问,其他任何类(包括子类)中的方法均不能访问此变量/方法。 |
protected | 访问控制符 | 指定该变量/方法只可以被它自己的类及其子类或同一包中的其他类访问。在子类中可以覆盖此变量。 |
缺省 | 访问控制符 | 表示同一包中的其他类可以访问此成员变量/方法,其它包则不能。 |
final | 非访问控制符 | 指定该变量不能改变/该方法不能被重载 |
static | 非访问控制符 | 指定该变量被所有实例共享/指定不需要实例化一个对象就能调用该方法 |
注意,无论什么修饰符,某类的所有成员总是对该类的方法和实例可见!
接口和抽象类的联系与区别
联系
接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
区别
接口里只能包含抽象方法,静态方法和默认方法,不能为普通方法提供方法实现,抽象类则完全可以包含普通方法。
接口里只能定义静态常量,不能定义普通成员变量,抽象类里则既可以定义普通成员变量,也可以定义静态常量。
接口不能包含构造器,抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
接口里不能包含初始化块,但抽象类里完全可以包含初始化块。
一个类最多只能有一个直接父类,包括抽象类,但一个类可以直接实现多个接口,通过实现多个接口可以弥补 Java 单继承的不足。
String、StringBuilder、StringBuffer的区别
String 创建的是不可变的对象,修改字符串需要 JVM 创建、回收 String 对象,比较耗时,适用于少量操作字符串的场景。
StringBuilder 可以在原有对象上进行操作,但却是非线程安全的,适用于单线程下频繁操作字符串的场景。
StringBuffer 在 StringBuilder 的基础上对某些方法新增了synchronized锁原语,所以是线程安全的,也正是这个原因,StringBuffer 的速度要慢于 StringBuilder;适用于多线程下频繁操作字符串的场景。