Java常用API(三)
Easul Lv6

相关链接

网络模型

  • OSI:Open System Interconnection 开放系统互联参考模型
  • TCP/IP参考模型
    • 物理层:网线是物理层介质。
    • 数据链路层:将物理层接收的数据进行MAC地址封装与解封装。该层数据叫帧。设备是交换机,交换机每个口都有MAC地址。
    • 网络层:对数据链路层数据进行IP地址封装。该层数据是数据包。设备是路由器。
    • 传输层:定义传输协议和端口号。发送数据规则。TCP,UDP等。该层数据叫段。
    • 会话层:建立会话
    • 表示层:进行数据的解析,加密解密,压缩解压
    • 应用层:计算机的应用软件
      image

网络通讯要素

Java网络编程的包是net包

IP地址

折叠代码块YAML 复制代码
1
2
127.0.0.1: 本地回环地址。访问本机使用
192.168.1.100: 局域网内可以互相访问

端口号

  • 同一个计算机的不同应用程序标识。标识进程的逻辑地址
  • 端口号在0-65535.0-1024通常为保留端口(系统级端口),不要用。
  • 防火墙:禁用进来和出去的端口就无法进行数据交互了。

传输协议

  • 传输协议就是传输规则(传输层的协议)
  • 传输层的两种协议UDP(数据报文协议),TCP(传输控制协议)
    折叠代码块YAML 复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    UDP: 将数据,源地址,目的地址封装到数据包。
    - 不需要建立连接,速度快,不可靠协议,无法知道是否到达。
    - 数据包大小限制在64K。类似快递。
    - 对讲机,QQ,在线视频使用UDP,可能丢失一些数据,但是速度快。
    TCP: 建立连接后才数据通讯。
    - 通过三次握手完成连接。
    - 是可靠协议,对方断开不传输数据,不丢包。
    - 必须建立连接。效率低。
    - 类似打电话。下载用的就是TCP。
    比较:
    - UDP可能丢包,但是速度快。
    - TCP速度较慢,但是不会丢包。

网络相关知识

  • DNS:(Domain Name System)域名解析服务器,进行域名与IP对应关系的记录
    • 先请求域名,域名从DNS服务器中找到对应IP返回自己的主机,自己根据该IP访问域名所属网站。
    • 域名解析顺序:本地hosts->互联网
  • 局域网的某台机器装一个DNS服务器软件,配置好域名解析,然后本地DNS指向该机器的IP,访问网址的时候就会自动从这台DNS服务器进行域名查询。
  • C类地址,IP前三字段都是网络位,IP的第四个字段为0是网络位,相当于网段
  • ip地址主机位从1-254有效,255是广播字段,会把消息广播到网段中所有IP中。可用于群聊

InetAddress

特点

  • 表示网络的IP地址。在网际层。
  • 没有构造函数,使用静态方法返回对象。

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
InetAddress getLocalHost(): 返回本地主机ip地址对象
InetAddress getByName("DESKTOP-G8GJS0I"): 根据主机名获取ip地址对象
InetAddress getByName("192.168.120.124"): 根据IP获取ip地址对象
InetAddress getByName("www.baidu.com"): 根据域名获取ip地址对象
InetAddress[] getAllByName(): 获取主机的IP地址数组。有的主机IP地址不唯一。可能有服务器集群。
getHostAddress(): 获取主机地址
getHostName(): 获取主机名。找不到主机名就返回IP

UDP传输流程

  • DatagramSocket创建数据包套接字对象
  • DatagramPacket创建发送或接收数据包对象
  • 使用DatagramSocket发送或接收数据包
  • 关闭socket流

TCP传输流程

Socket

  • 实现客户端套接字
  • 术语叫做套接字,是为网络服务提供的一种机制
  • 可以理解为通讯的端点,数据在Socket中进行IO传递。需要通信两端都有Socket
  • TCP客户端创建的过程
    • 创建Socket对象,并指定要连接的IP与Port,明确目的主机
    • 如果连接建立成功,就形成了一个数据传输通道(socket流)。可以从该Socket对象获取输入输出流对象进行数据传输。
    • 使用输出流写出数据或使用输入流写入数据
    • 关闭Socket流(这里可以不用关闭,服务器端会统一关闭,但是需要给服务器端发送结束标记,否则服务器端也不会关。)

ServerSocket

  • 实现服务端套接字
  • TCP服务端创建的过程
    • 创建ServerSocket对象
    • 给服务端提供一个监听端口,否则客户端无法连接
    • 获取连接过来的客户端Socket对象,并获取其输入流或输出流对象
    • 使用输出流写出数据或使用输入流写入数据
    • 关闭资源(需要关闭客户端,服务端如果需要一直进行服务提供则可以不用关闭。)

DatagramSocket

用于表示发送和接收数据报包的套接字

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
构造方法:
DatagramSocket(): 创建一个数据报包套接字对象。用于发送数据包,默认绑定任意可用端口
DatagramSocket(int): 创建一个带端口的数据报包套接字对象。用于接收数据包
常用方法:
close(): 关闭套接字
send(DatagramPacket): 发送数据报包
receive(DatagramPacket): 接收数据报包。阻塞式方法。接收到数据就可以走他下边的代码了。

DatagramPacket

特点

  • 表示数据报包,实现无连接的投递服务。不对包投递做出保证。
  • 包含包中的数据,目的地址,源地址。所以封装对象更方便。

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
构造函数:
DatagramPacket(byte[], int, InetAddress, int): 创建一个发送数据报包的套接字对象
- 需要指定字节数组,发送长度,IP和端口
- 发送包有目的地址,接收包不需要地址
常用方法:
getData(): 获取接收的数据,为byte数组
getPort(): 获取发送端端口,int类型
InetAddress getAddress(): 获取发送端IP对象
getLength(): 获取发送数据的长度,int类型

UDP传输示例

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 发送端代码
public void udpSend() throws IOException {
/**
* 1 创建UDP的socket
* 2 组包
* 3 发包
* 4 关服务
*/
System.out.println("发送端启动");
DatagramSocket ds = new DatagramSocket();
String sendData = "UDP传输演示";
byte[] sendDataBinary = sendData.getBytes();
InetAddress ip = InetAddress.getByName("127.0.0.1");
DatagramPacket dp = new DatagramPacket(sendDataBinary, sendDataBinary.length, ip, 10000);
ds.send(dp);
ds.close();
}

// 接收端代码
public void updReceive() throws IOException {
/**
* 1 建立UDP的socket
* 2 接收UDP的包。使用数据包接收
* 3 解包查看
* 4 关闭socket
*/
System.out.println("接收端启动");
DatagramSocket ds = new DatagramSocket(10000);
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
ds.receive(dp);
System.out.println(new String(dp.getData(), 0, dp.getLength()));
System.out.println(dp.getPort());
System.out.println(dp.getAddress().getHostAddress());
ds.close();
}

多线程聊天室

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 线程开启
public static void chatroomDemo() throws SocketException {
DatagramSocket senderSocket = new DatagramSocket(10001);
DatagramSocket receiverSocket = new DatagramSocket(10002);
ChatSender sender = new ChatSender(senderSocket);
ChatReceiver receiver = new ChatReceiver(receiverSocket);
new Thread(sender).start();
new Thread(receiver).start();
}

// ChatSender
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class ChatSender implements Runnable{
private DatagramSocket ds;

public ChatSender(DatagramSocket ds) {
this.ds = ds;
}

public void run() {
try {
System.out.println("发送端启动");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line = br.readLine()) != null) {
byte[] sendDataBinary = line.getBytes();
// InetAddress ip = InetAddress.getByName("127.0.0.255"); 255可以广播消息到该网段所有IP
InetAddress ip = InetAddress.getByName("127.0.0.1");
DatagramPacket dp = new DatagramPacket(sendDataBinary, sendDataBinary.length, ip, 10002);
ds.send(dp);
if ("over".equals(line))
break;
}
ds.close();
} catch (Exception e) {

}
}
}

//ChatReceiver
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ChatReceiver implements Runnable {
private DatagramSocket ds;

public ChatReceiver(DatagramSocket ds) {
this.ds = ds;
}
public void run() {
try {
System.out.println("接收端启动");
while (true) {
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress() ;
String port = "" + dp.getPort();
String message = new String(dp.getData(), 0, dp.getLength(), "UTF-8");
System.out.println(ip + ":" + port + ":" + message);
if (message.equals("over"))
System.out.println(ip + "退出聊天室");
}
} catch (Exception e) {

}
}
}

Socket

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
构造方法: 
Socket(): 创建一个客户端未连接的套接字。可以使用connect方法连接
Socket(InetAddress, port): 创建一个流套接字并连接到指定IP和端口号
Socket(String, port): 使用字符串来指定IP
常用方法:
connect(): 连接该socket到一个套接字地址
getInputStream(): 获取输入流对象,用于读数据
getOutputStream(): 获取输出流对象,用于写数据
shutdownInput(): 给套接字的输入流置一个结束标记
shutdownOutput(): 给套接字的输出流置一个结束标记

tcp客户端实例

客户端用于连接服务端,发送和接收数据均可

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
Socket socket = new Socket("127.0.0.1", 10001);
OutputStream out = socket.getOutputStream();
out.write("TCP演示".getBytes());

InputStream input = socket.getInputStream();
byte[] buf = new byte[1024];
int len = input.read(buf);
String text = new String(buf, 0, len);
System.out.println(text);
// 这里的关闭链接就相当于断开socket流连接。不关的话服务器关掉也行
socket.close();

SocketAddress

封装了IP + Port

ServerSocket

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
构造方法:
ServerSocket(): 创建一个服务端未连接的套接字
ServerSocket(port): 创建服务端套接字的时候初始化监听端口
常用方法:
accept(): 获取客户端连接的Socket对象

tcp服务端实例

服务端用于接收客户端信息,发送和接收数据均可

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ServerSocket ss = new ServerSocket(10001);
// 阻塞式方法。如果没有进行Socket连接,这里一直等待
Socket socket = ss.accept();
InputStream input = socket.getInputStream();
byte[] buf = new byte[1024];
int len = input.read(buf);
String ip = socket.getInetAddress().getHostAddress();
String text = new String(buf, 0, len);
System.out.println(ip + ":" + text);

OutputStream out = socket.getOutputStream();
out.write("收到".getBytes());

socket.close();
ss.close();

TCP传输问题

传输过程两端都在等,接收不到数据

  • 有阻塞式方法。数据留在了缓冲区,没有发出去。
    • 需要进行数据刷新
  • 没有读取到数据结束标记
    • 例如服务端使用readLine()读取客户端发送的数据,没有发送换行符,所以一直进行数据等待

英文大写转换服务器

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// TransClient
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;


public class TransClient {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket("127.0.0.1", 10001);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
// 用PrintWriter直接输出字符流,加true自动刷新
PrintWriter pw = new PrintWriter(socket.getOutputStream());
BufferedReader result = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while ((line = br.readLine()) != null) {
if ("over".equals(line))
break;
// 将字节流输出,并加上换行
pw.print(line);
// 读入服务器返回的一行数据
System.out.println(result.readLine());
}
// 客户端进行了close,就会在流中植入结束标记-1,所以服务端判断没有数据,也会自动关闭
// 最好给服务端发送一个结束标记,让服务端来关闭。
socket.close();
}
}

// TransServer
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;


public class TransServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10001);
Socket socket = ss.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream());
String line = null;
while((line = br.readLine()) != null) {
System.out.println(line);
String newLine = line.toUpperCase();
pw.print(newLine);
}
socket.close();
ss.close();
}
}

上传文本文件

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// UploadClient
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;


public class UploadClient {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("client start");
Socket socket = new Socket("127.0.0.1", 10001);
BufferedReader br = new BufferedReader(new FileReader("Worker.java"));
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
String line = null;
// 客户端可以发送时间戳来当作结束标记。先发一个时间戳,服务端接收到存储好。然后客户端发送完再发相同的时间戳即可。
// 也可以使用Socket的shutdownInput或shutdownOutput方法
while ((line = br.readLine()) != null) {
pw.println(line);
}
// 传输给服务端结束标记
socket.shutdownOutput();

pw.println("over");
BufferedReader result = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println(line = result.readLine());
br.close();
socket.close();
}
}

// UploadServer
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;


public class UploadServer {
public static void main(String[] args) throws IOException {
System.out.println("server start");
ServerSocket ss = new ServerSocket(10001);
Socket socket = ss.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(new FileWriter("server.txt"), true);
String line = null;
// 服务端需要使用结束标记来判断接收数据结束。
// 或者客户端使用Socket的shutdownInput或shutdownOutput方法
while ((line = br.readLine()) != null) {
pw.println(line);
}
PrintWriter returnStr = new PrintWriter(socket.getOutputStream());
returnStr.println("success");
System.out.println("server success");
returnStr.flush();
pw.close();
socket.close();
ss.close();
}
}

服务器多线程上传图片

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// UploadPicClient
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class UploadPicClient {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket("127.0.0.1", 10001);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.png"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] buf = new byte[1024];
int len = 0;
System.out.println("client start");
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
bos.flush();
}
socket.shutdownOutput();
InputStream is = socket.getInputStream();
byte[] result = new byte[1024];
int resultLength = is.read(result);
System.out.println(new String(result, 0, resultLength));
System.out.println("client finish");
bis.close();
socket.close();
}
}

// UploadPicServer
// 使用多线程是为了解决客户端请求服务端需要排队的问题。如果请求时间过长,会超时。
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class UploadPicServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10001);
System.out.println("server start");

while (true) {
Socket socket = ss.accept();
new Thread(new UploadTask(socket)).start();;
}

// ss.close();
}
}


// UploadTask
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;

public class UploadTask implements Runnable {
private Socket socket;

public UploadTask(Socket socket) {
this.socket = socket;
}

public void run() {
BufferedOutputStream bos = null;
int count = 0;
try {
File dir = new File("C:\\pic");
if (!dir.exists()) {
dir.mkdirs();
}
File pic = new File(dir, socket.getInetAddress().getHostAddress() + ".png");

while (pic.exists()) {
pic = new File(dir, socket.getInetAddress().getHostAddress() + "(" + (++count) + ").png");
}

BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
bos = new BufferedOutputStream(new FileOutputStream(pic));

byte[] buf = new byte[1024];
int len = 0;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
bos.flush();
}

PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.print("success");
pw.flush();
System.out.println("server finish");
} catch (IOException e) {

} finally {
try {
bos.close();
} catch (IOException e) {
}
try {
socket.close();
} catch (IOException e) {
}
}
}

}

客户端和服务端常识

  • 服务端的无限循环不要怕,主要是因为循环中有阻塞方法,资源没进来就进行等待。不耗费CPU
  • 如果判断一次,使用if,如果判断多次,使用while
  • HTTP是应用层规则。是一种通讯协议,通讯规则。
  • 192.168.1.100(自己的主机地址)和127.0.0.1都可以访问本地主机
  • C/S需要做客户端和服务端
    • 开发成本较高,维护比较麻烦。
    • 可以在本地分担一部分运算。例如杀毒就不适合使用B/S
    • 客户端游戏速度更快,效果更好。
  • B/S不需要做客户端,直接有浏览器就行
    • 开发成本较低,维护较简单。
    • 所有运算都在浏览器

常见客户端

如浏览器

  • 早期浏览器都是IE内核,改了下外观。
  • IE是单窗口的,一个页面一个窗口。遨游将IE改为单窗口,多标签形式,切换更方便
  • 基于IE内核可能收费,所以后期基于webkit(开源免费)

常见服务端

如服务器:可以对外提供服务的机器。

  • Oracle:数据库服务器
  • Tomcat:Web资源访问服务器。处理请求并给予应答。
    折叠代码块YAML 复制代码
    1
    2
    Servlet: 服务端的处理都需要实现Servlet(Server Applet)接口
    webapp: 用于访问web应用
  • 网络硬盘:存储服务器

服务端多线程

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
// 服务端多线程伪代码
ServerSocket ss = new ServerSocket();
while (true) {
// 服务器端接收到一个客户端
Socket socket = ss.accept();
// 将客户端封装为一个线程执行任务
new Thread(new Task(socket));
// 主线程任务执行完,返回while重新执行,重新进行accept()获取。这样就可以并发处理多个客户端请求了。
// 如果是单线程,原客户端任务没有执行完,新的客户端无法进行accept()获取,只能等待
}

浏览器的服务端

可以使用ServerSocket写一个服务端,浏览器访问该服务端,接收返回信息并解析。

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ServerSocket ss = new ServerSocket(10001);
Socket socket = ss.accept();
String ip = socket.getInetAddress().getHostAddress();
System.out.println(ip);
InputStream is = socket.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
System.out.println(new String(buf, 0, len));
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
// 需要加上响应行,浏览器才可以解析
pw.println("HTTP/1.1 200 OK");
pw.println();
// 响应体前边加一个空行
pw.println("hello KnowServer");
socket.close();
ss.close();

应答消息的含义

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200  OK: 应答行:HTTP协议版本 应答状态码 应答状态描述信息
Accept-Ranges: bytes,(从这里开始都是应答消息头,键值对形式)
ETag: W/"5-1597909615505"
Last-Modified: Thu, 20 Aug 2020 07:46:55 GMT,资源最后修改时间。
- 如果本地有缓存,请求的时候带上缓存中的这个键值对,
- 服务端判断时间一致,返回一个状态码,不再返回响应体。速度会更快。
Content-Type: text/html,返回数据类型。
Content-Length: 5,返回数据的字节长度
Date: Thu, 20 Aug 2020 07:47:54 GMT
Connection: close,连接关闭

hello: 应答体和应答消息头之间也需要有空行

常用状态码

折叠代码块YAML 复制代码
1
2
200: OK 请求成功
404: not found 资源没有找到(输入不存在的网页也找不到)

浏览器的客户端

使用Socket写一个客户端,使其访问Tomcat,接收返回信息并解析。

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Socket socket = new Socket("127.0.0.1", 8080);
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
// 添加请求行
pw.println("GET / HTTP/1.1");
// 添加请求头
pw.println("Accept: */*");
pw.println("Connection: close");
pw.println("Host: 127.0.0.1:8080");
// 这里没有请求体,有的话下边的换行是必须的
pw.println();
pw.println();

InputStream is = socket.getInputStream();
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) != -1) {
System.out.println(new String(buf, 0, len));
}

socket.close();

浏览器发送的请求消息的含义

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET / HTTP/1.1: 请求行:请求方式 请求路径(这里是根目录) http协议版本。请求行是最主要的。
- GET请求的请求参数会在请求行获取到 /?user=qwe&pass=123
- POST请求的请求参数会在请求体中获取到 user=qwe&pass=123
Host: 127.0.0.1:10001,获取主机名
- 从这里向下都是请求消息头,格式都是键值对。
- 用于告诉服务器都允许什么应用程序解析
Connection: keep-alive,连接状态:保持存活
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36,用来获取浏览器版本
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9,表示浏览器都支持解析什么数据。
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br,表示浏览器支持的压缩方式。
- 如果网页比较大,服务端会将网页压缩再发给浏览器。用于提高传输效率。
Accept-Language: zh-CN,zh;q=0.9,表示浏览器都支持什么语言

页面数据: 浏览器发送的请求体,请求体和请求头由空行隔开。
- 是HTTP请求规则。

GET提交和POST提交的区别

  • GET
    • 数据封装在请求行
    • 提交大量数据,地址栏有限,放不下
  • POST
    • 数据封装在请求体
    • 可以提交大量数据

URL

特点

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
构造方法:
URL(String): 创建一个带URL地址的URL对象
URL(String protocol, String host, int port, String file): 指定协议,主机,端口,文件路径来创建URL对象
常用方法:
getProtocol(): 获取协议名称
getHost(): 获取主机名称
getPort(): 获取端口,返回int数据
getPath(): 获取路径名
getFile(): 获取文件名,会有参数信息
getQuery(): 获取查询部分
openStream(): 打开URL连接,返回从连接读入的InputStream,
- 获取服务端返回的数据。只返回应答体。
- 连上就相当于建立了Socket。
- 底层调用了openConnection.getInputStream();
openConnection(): 获得URL连接器对象。返回URLConnection
- 可以连到统一资源定位符指向的资源。

URI

特点

URLConnection

特点

  • Java中内置的可以解析具体协议的对象+Socket
  • 只能获取该对象,不能进行实现。

常用方法

折叠代码块YAML 复制代码
1
2
3
getHeaderField(String): 用键获取消息头对应键的值
getInputStream(): 获取输入流,返回InputStream
getOutputStream(): 获取输出流,返回OutputStream

反射

含义

  • 运行状态中,对任意一个类(class文件)都能知道这个类的所有属性和方法
  • 对任意一个对象,都能够调用它的任意一个方法和属性
  • 这种动态获取的信息及动态调用对象的方法的功能叫做java的反射机制。
  • 可以理解为对类的解剖。
  • 反射所有的对象都在java.lang.reflect包下(反射包),用于解析类成员对象

应用场景

  • 对指定名称的字节码文件加载并获取其内容进行调用
    • 例如Tomcat中自己实现了Servlet,需要Tomcat运行自己的程序,只需要把自己的程序配置到web.xml,Tomcat就可以执行了。Tomcat就动态的获取了这个类的信息。这里用了反射。
    • 用户不用创建对象,把自己的类放到配置文件里就可以运行了

作用

大大提高了程序扩展性

Class

含义

描述字节码文件的类,用来获取字节码文件的内容(名称,字段,构造函数,一般函数)

作用

用于获取字节码文件的所有内容。反射就是依靠该类完成的。

获取字节码的方式

  • Object类的getClass方法。用的时候需要明确具体的类并创建对象
    折叠代码块JAVA 复制代码
    1
    2
    Worker worker = new Worker("name", 12);
    Class clazz = worker.getClass();
  • 使用任意类的class静态属性
    折叠代码块JAVA 复制代码
    1
    Class clazz = Worker.class;
  • 通过给定类的字符串名称获取类,更具扩展性。
    使用Class类的方法,是反射主要方式,知道类名字即可。
    折叠代码块JAVA 复制代码
    1
    2
    3
    // 从当前根目录找,需要写类的全名
    Class clazz = Class.forName("ml.lightly.Worker");
    Object obj = clazz.newInstance();

早期和现在创建对象比较

  • 早期:new的时候,根据类名称找到该类的字节码文件,加载到内存,创建该字节码文件对象,然后根据字节码文件对象创建该类对象
  • 现在:forName()会找到类文件,并加载到内存,产生Class对象,然后newInstance(),创建该类对象

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class forName(name): 获取某个类名的Class对象
Object newInstance(): 创建获取的类的空参对象。没有空参构造函数抛出异常。
- 一般被反射的类都有空参构造函数。
Constructor<?>[] getDeclaredConstructors(): 获取所有构造函数,包括私有
Constructor<?>[] getConstructors(): 获取所有公共构造函数
Constructor<?> getConstructor(参数列表): 获取指定参数列表的公共构造函数。
- 任何数据类型都可以被.class描述
- getConstructor(String.class, int.class)
Field[] getFields(): 获取所有的公共字段。
- 因为字段有字段修饰符,类型,字段的值,所以被封装为Field对象
Field getFields(String): 获取指定键的公共字段
Field[] getDeclaredFields(): 获取所有字段,包括私有字段
Field getDeclaredField(String): 获取指定键的字段,包括私有字段
Method[] getMethods(): 获取某类的所有公有方法,包括继承的
Method[] getDeclaredMethods(): 只获取属于该类的所有方法,包括私有方法
Method getMethod(方法名, 参数列表): 获取某个方法
- 需要传入参数列表,没有参数列表传入null,有的话传入xx.class

电脑运行

新设备实现接口,并添加配置文件,就可以被源设备使用。
适用于不修改代码,但是可以运行新的设备
定义好规则,后期设备通过该规则扩充,前期设备只操作这个规则即可。

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// pci.properties
pci0=SoundCard

// 反射Demo
import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

public class ReflectDemo {
public static void main(String[] args) throws Exception {
MainBoard mb = new MainBoard();
// 主板运行
mb.run();
File config = new File("pci.properties");
Properties prop = new Properties();
// 读取配置文件
FileInputStream fis = new FileInputStream(config);
prop.load(fis);
for (int i = 0; i < prop.size(); i++) {
// 获取配置文件中的设备
String pciName = "ml.lightly." + prop.getProperty("pci" + i);
// 获取该设备
Class clazz = Class.forName(pciName);
// 因为设备有统一的接口,所以可以同该接口进行统一操作
PCI p = (PCI)clazz.newInstance();
// 运行该设备
mb.usePCI(p);
}
}
}

// 主板
class MainBoard {
public void run() {
System.out.println("main board run");
}
// 用于接口操作
public void usePCI(PCI p) {
if (p != null) {
p.open();
p.close();
}
}
}

// 统一规则
interface PCI {
public void open();
public void close();
}

// 后期添加设备
class SoundCard implements PCI{
public void open() {
System.out.println("SoundCard open");
}
public void close() {
System.out.println("SoundCard close");
}
}

Constructor

特点

是获取的Class的构造器组成的对象

常用方法

折叠代码块YAML 复制代码
1
newInstance(参数列表): 使用该构造器创建对象。传入相应的参数。返回Object

Field

特点

是获取的Class的字段组成的对象

常用方法

折叠代码块YAML 复制代码
1
2
3
setAccessible(boolean): 对私有字段的访问取消权限检查。暴力访问
Object get(Object): 获取传入对象的该字段的值。私有字段需要设置setAccessible(true)
set(Object, Object): 给某个对象的该字段设置值。

Method

特点

是获取的Class的方法组成的对象

常用方法

折叠代码块YAML 复制代码
1
2
invoke(Object, Object): 运行某方法,需要传入运行对象和参数列表。返回Object
- 没有参数列表传null,有参数列表传相应的参数值。

正则

作用

主要操作字符串

特点

  • 通过特定符号体现
  • 简化了,阅读性变差。

正则表达式常用符号

  • 大括号控制次数,中括号控制数据范围,小括号用于符号封装
    • 会对加了小括号的每个封装体从1进行编号,没有加括号就是第0组
  • (.)\\1+, 在java里,括号封装了一个匹配模式,这是第一组模式
    • 正则中用1来标识,但不是普通1,所以加转义符号来标识第一组
    • 然后+就是用来对第一组进行操作的符号
  • ^,非,作用于后边的第一个控制范围
  • -,用于数据范围
  • &&,与,用于交集
折叠代码块YAML 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
字符: 
x: 表示随意地一个字符
\\: 反斜杠
字符类:
[abc]: 某一位上只能是a或b或c
[^abc]: 某一位上是除了a或b或c的任意字符
[a-zA-Z]: 所有字母
[a-z[A-Z]]: 所有字母,可以多加一个中括号
[a-d&&c-e]: 获取交集cd
[a-z&&[^bc]]: 获取a-d里边不包含bc的,等同[ad-z]
预定义字符类(已经定义好的字符含义):
.: 该位可以是任意字符
\d: 表示数字,等同[0-9]
\D: 表示非数字,等同[^0-9]
\s: 表示空白字符,等同[ \t\n\x0b\f\r]
\S: 表示非空白字符,等同[^\s]
\w: 表示数字字母下划线,等同[0-9a-z_A-Z]
\W: 表示非数字字母下划线,等同[^\w]
边界匹配:
^: 放在最开始,表示行的开头
$: 放在最后,表示行的结尾
\b: 表示单词边界,每个单词中间都有空格,空格就是边界
数量词:
x?: 随意字符跟一个问号,表示这个字符出现0或1次
x*: 随意字符跟一个星号,表示这个字符出现0或多次
x+: 随意字符跟一个加号,表示这个字符出现1或多次
x{n}: 随意字符跟一个大括号和次数,表示这个字符出现n次
x{n,}: 随意字符跟一个大括号和次数和逗号,表示这个字符出现至少n次
x{n,m}: 随意字符跟一个大括号和次数,逗号,次数,表示这个字符出现至少n次,至多出现m次
逻辑运算符:
xy: x字符后边跟一个y字符
x|y: x或y
(x): 作为捕获组,第一个左括号是第一组,第二个左括号是第二组,用数字标识
- 数字不是普通的数字字符,所以需要转义
- 组0代表整个表达式
- (x)\\1+ 第一组的匹配模式出现1次或多次
引用:
\组号: 用于匹配捕获组

字符串正则

匹配

匹配手机号

折叠代码块JAVA 复制代码
1
2
3
4
5
6
// 匹配手机号码
String tel = "18024726182";
// 第一位固定为1,第二位固定为[3586],剩下的都是[0-9]总长度为11
// [0-9]可以用\\d代表。java的\d是转移字符,这里需要普通符号,所以用\\d
String regex = "1[3456789]\\d{9}";
System.out.println(tel.matches(regex));

切割

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 切割空格字符
String nameStr = "easul xiaoqiang zhaoliu";
String regex = "\\s+";
String[] results = nameStr.split(regex);
for (String result :results)
System.out.println(result);

// 用.进行切割
nameStr = "easul.xiaoqiang.zhaoliu";
// 正则中.表示任意字符,这里如果要当作普通字符,需要进行转义
regex = "\\.";
results = nameStr.split(regex);
for (String result :results)
System.out.println(result);

// 切叠词
nameStr = "easultttttxiaoqiangnnnnnnnnzhaoliu";
regex = "(.)\\1+";
results = nameStr.split(regex);
for (String result :results)
System.out.println(result);

替换

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 将叠词替换为一个字符
String str = "asdffffffffasdfafdasdf";
String regex = "(.)\\1+";
str = str.replaceAll(regex, "#");
System.out.println(str);

// 让第二个参数使用第一个参数的正则,使用$符号.$n就是匹配第几组
// 这里输出后第二个参数每次只获取第一个参数的一个字符
String str = "asdvvvgggggasdfafdasdf";
// 这个括号表示匹配任意一个字符,后边表示要匹配第一组出现1次或多次的情况
// $1获取的是括号内的数据,所以只有一个字符
String regex = "(.)\\1+";
str = str.replaceAll(regex, "$1");
System.out.println(str);

// 手机号隐藏中间四位.正则分组,用$n在替换的字符串中代表第几组
String tel = "15878783928";
String regex = "(\\d{3})\\d{4}(\\d{4})";
tel = tel.replaceAll(regex, "$1****$2");
System.out.println(tel);

获取

折叠代码块JAVA 复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 正则获取的常规操作.只能通过Pattern和Matcher获取
*
* 将正则规则进行对象封装
* 通过正则对象的matcher方法与字符串关联,获取字符串操作匹配对象Matcher
* 通过Matcher匹配器对象对字符串操作
*/
// 获取三个字母组成的单词
String str = "da jia hao, ming tian bu fang jia";
String regex = "\\b[a-z]{3}\\b";
// 将正则规则进行对象封装
Pattern p = Pattern.compile(regex);
// 通过正则对象的matcher方法与字符串关联,获取字符串操作匹配对象Matcher
Matcher m = p.matcher(str);
// 通过Matcher匹配器对象对字符串操作
while (m.find()) {
System.out.println(m.group());
}

String的正则方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
matches(String regex): 检查字符串是否匹配该正则,返回boolean
split(String regex): 将字符串按正则切割为字符串数组,返回String[]
replaceAll(String regex, String replacement): 使用replacement替换符合正则的部分,返回字符串
- 第二个参数想要用第一个参数的正则内容,可以用$表示
- replaceAll("(.)\\1+", "$1") $n就是匹配第n组
- 这里就可以在第二个参数获取第一个参数匹配的一个字符了.

Pattern

含义

正则表达式的对象形式.

作用

用于正则表达式的封装,存在于java.util.regex包中.

常用方法

折叠代码块YAML 复制代码
1
2
Pattern complie(regex): 将正则封装为正则对象
Matcher matcher(String): 将正则与要操作的字符串关联,返回匹配器对象

Matcher

含义

匹配器

作用

  • 使用正则对象的匹配器对象操作字符串
  • 匹配结果留在匹配器中

常用方法

折叠代码块YAML 复制代码
1
2
3
4
5
6
7
find(): 查找是否有与该模式匹配的字符串的下一个子序列.查找一次,匹配一次.返回
matches(): 判断正则与整个字符串是否存在匹配
group(): 返回匹配的子序列.需要先find才能获取
group(int): 返回指定组匹配的子序列.需要先find才能获取
replaceAll(String replacement): 对正则在字符串中匹配的地方修改为replacement,返回修改字符串
start(): 返回匹配的子序列的第一个字符下标
end(): 返回匹配的子序列的最后一个字符的后一个下标
 评论
来发评论吧~
Powered By Valine
v1.5.2