目录
一、新建项目
二、IO流
1. 什么是IO流?
2. IO的流向说明图解
3. IO 流的分类
4. 字节流
输出流
字节输出流的细节
输入流
字节输入流的细节
5. 练习
6. 字符流
输入流
字符流读取的细节
字符输入流原理解析
字符输出流原理解析
三、网络编程
1. UDP通信程序
UDP 发送数据
UDP 接收数据
2. TCP 通信程序
TCP发送端
TCP 服务器端
3. 解读代码
Socket 套接字
传输数据的类型
accept 方法
read 方法
返回的 -1
作业
1. 实现在线聊天室
2. 预习
一、新建项目
二、IO流
1. 什么是IO流?
I 是 Input,O 是Output,也就是输入输出流。
当我们操作数据时,以内存为基准,把数据读取到内存中是输入,把数据从内存写入到硬盘是输出
2. IO的流向说明图解
图片来自哔哩哔哩黑马程序员
3. IO 流的分类
根据数据的流向可以分为:输入流和输出流
根据数据的类型可以分为:字节流和字符流
操作文件类型
字节流:以字节为单位,可以操作所有文件类型
字符流:以字符为单位,只能输入输出纯文本文件
4. 字节流
输出流
//1. 创建 FileOutputStream 对象,指定写入的文件路径。
FileOutputStream fos = new FileOutputStream("文件路径");
//2. 将字符串转换为字节数组并写入文件。
fos.write("hello".getBytes());
//3. 关闭输出流
fos.close();
字节输出流的细节
1.创建字节输出流的对象
- 参数是字符串表示的路径或者File文件对象都是可以的
- 如果文件不存在,就会创建一个新的文件,不过需要父级路径是正确的
- 如果文件已经存在,会覆盖原文件
2.写数据
是根据字节对应的ASCII码表
3.关闭数据流
追加
在创建字节输出流的对象的时候,第二个参数是打开追加
输入流
//1. 创建 FileInputStream 对象,指定读取的文件路径。
FileInputStream fis = new FileInputStream("文件路径");
//每次只会读取一个字节,读取出来的是int型,需要转成Char类型
System.out.println((char)fis.read());
//释放资源
fis.close();
字节输入流的细节
1. 创建对象
如果指定的路径文件不存在,会字节报错
2. 读取内容
一次只会读取一个字节,读取的是ASCII码表对应的数值
当读取指针指到末尾没有内容了,会返回-1
3. 释放资源
5. 练习
拷贝文件
当使用一个字节一个字节来读取数据,再写入数据的方式,速度比较慢
所以,可以定义一个数组,一次读取尽量把数组装满,一次写入整个数组的数量
FileInputStream fis = new FileInputStream(要读取的文件路径);
FileOutputStream fos = new FileOutputStream(要写入的文件路径);
//创建一个数组,用来一次读取多少字节
byte [] buf = new byte[1024*1024*10];
int b;
while ((b = fis.read(buf)) != -1) {
fos.write(buf, 0, b); //第一个参数表示将buf数组里的数据写入到文件里
} //第二个参数是从 0 索引开始,写入的字节数是 b
fis.close();
fos.close();
解读代码
fis.read(buf)用于从输入流中读取数据到缓冲区buf,返回值b表示实际读取到的数据字节数。当读取到的数据为-1时,表示输入流已经到达末尾,循环结束。
fos.write(buf, 0, b)则用于将缓冲区中的数据写入到输出流中,写入的字节数为b。
6. 字符流
输入流
//创建字符流对象
FileReader fr = new FileReader("要读取的文件路径");
int ch;
//字符流的底层也是字节流,默认也是一次读取一个字节
while ( (ch = fr.read() ) != -1){
System.out.print( (char) ch); //读取出来后会自动解码然后转成十进制
} //所以再转成字符就可以了
fr.close();
字符流读取的细节
- 字符流的底层也是字节流,默认也是按照一个字节一个字节读取的
- 遇到中文就会一次读取多个字节,这样可以避免乱码,编码不同,一次读取的字节也不同
- 在读取之后,底层会自动解码然后转成十进制数,然后把十进制数返回
- 返回的十进制数也是表示字符集上的数值, 如果需要看到汉字,那么就需要再转成字符就行了
字符输入流原理解析
1. 创建字符输入流对象
底层会关联文件,并且在内存里创建一个byte数组作为缓冲区,长度为8192字节的数组
2. 读取数据
底层: 1.判断缓冲区有没有数据可以读取
2.缓冲区没有数据:从文件里读取数据到缓冲区,尽量把缓冲区装满
如果文件里没有数据了,返回-1
3.缓冲区有数据:从缓冲区获取数据。
空参的read方法:一次读取一个字节,遇到中文一次读取多个字节(根据字符集),把字节解码后并且转成十进制数字返回
有参的read方法:把读取字节,解码,转成文字合并了,然后把文字放在数组里
字符输出流原理解析
创建字符输出流的对象的时候,在内存会创建一个缓冲区
有三种情况会把缓冲区的数据写入到文件里
- 当缓冲区存放满了的时候
- 手动刷新,flush刷新之后还可以继续写数据
- 释放资源
要学习 Tomcat ,就需要先了解什么是网络编程
创建两个类 一个作为客户端,一个作为服务器端
三、网络编程
1. UDP通信程序
- Java中的UDP通信
- UDP协议是一种不可靠的无连接的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
- Java提供了DatagramSocket类作为基于UDP协议的Socket
- 构造方法
方法名 | 说明 |
DatagramSocket() | 创建数据报套接字并将其绑定到本机地址上的任何可用端口 |
DatagramPacket(byte[] buf,int len,InetAddress add,int port) | 创建数据包,发送长度为len的数据包到指定主机的指定端口 |
- 相关方法
方法名 | 说明 |
void send(DatagramPacket p) | 发送数据报包 |
void close() | 关闭数据报套接字 |
void receive(DatagramPacket p) | 从此套接字接受数据报包 |
UDP 发送数据
//创建发送端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
//创建数据,并把数据打包
//构造一个数据包,发送长度为 length 的数据包到指定IP上的指定端口号。
byte[] bys = "你好".getBytes();
DatagramPacket dp = new DatagramPacket(bys,bys.length,InetAddress.getByName("127.0.0.1"),8888);
//调用DatagramSocket对象的 send 方法发送数据
ds.send(dp);
//关闭通信通道
ds.close();
UDP 接收数据
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws IOException {
// 创建一个DatagramSocket实例,绑定到8888端口,用于接收UDP数据包。
DatagramSocket ds = new DatagramSocket(8888);
// 创建一个大小为1024字节的字节数组,用于存储接收到的数据。
byte[] bytes = new byte[1024];
// 该实例用于接收UDP数据包。
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
// receive方法接收来自任何发送者的数据包,并将其存储在dp中。
// 这个方法是阻塞的,直到接收到一个数据包。
ds.receive(dp);
// 获取接收到的数据包中的数据部分和长度。
byte[] data = dp.getData();
int length = dp.getLength();
// 将接收到的字节数据转换为字符串并打印。
// 字符串的起始位置是0,长度是数据包的实际长度。
// 这样可以确保只打印接收到的有效数据部分。
System.out.println(new String(data,0, length));
// 释放资源
ds.close();
}
}
测试
2. TCP 通信程序
- Java中的TCP通信
- Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
- Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
TCP发送端
import java.io.IOException;
import java.io.OutputStream;
import java.net.*;
public class Client {
public static void main(String[] args) throws IOException {
//1.创建Socket对象
//细节:在创建对象的同时会连接服务端
// 如果连接不上,代码会报错
Socket socket = new Socket("127.0.0.1",8080);
//2.可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
//写出数据
os.write("hello".getBytes());
//3.释放资源
os.close();
socket.close();
}
}
TCP 服务器端
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
// 创建一个ServerSocket对象,监听8888端口,等待客户端连接
ServerSocket ss = new ServerSocket(8080);
// 调用accept()方法阻塞等待,直到有客户端连接到服务器,返回一个Socket对象用于与该客户端通信
Socket socket = ss.accept();
System.out.println("客户端连接成功");
// 获取Socket对象的输入流,用于读取客户端发送的数据
InputStream is = socket.getInputStream();
// 定义一个整型变量b,用于存储读取到的字节
int b;
// 循环读取输入流中的数据,read()方法返回-1表示流已结束
while ((b = is.read()) != -1){
// 将读取到的字节转换为字符并输出
System.out.print((char)b);
}
// 关闭与客户端的连接
socket.close();
// 关闭连接通道
ss.close();
}
}
3. 解读代码
TCP 服务器端
accept
方法是阻塞的,作用就是等待客户端连接- 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
- 针对客户端来讲,是往外写的,所以是输出流,针对服务器来讲,是往里读的,所以是输入流
read
方法也是阻塞的- 客户端在关流的时候,还多了一个往服务器写结束标记的动作,结束标记 -1
- 最后一步断开连接,通过四次挥手协议保证连接终止
Socket 套接字
Socket 套接字,作用就是用来实现网络上不同主机的应用程序进行双向通信。
套接字包括 IP 地址和端口号两个部分。
Socket 被用于应用层和传输层之间的通信。
Java 提供的 Socket 的类的核心功能就是 建立连接,发送数据,接收数据
Socket(InetAddress address,int port) 建立连接
getoutputStram() 发送数据
getInputStream() 接收数据
传输数据的类型
在网络中,数据传输的都是字节 byte,然后把字节放到字节数组里发送,这就是为什么在发送数据前要把数据转成字节
accept 方法
accept
方法用于接收来自客户端的连接请求。这个方法是阻塞的,也就是它会一直等待,直到有一个新的客户端连接才会继续往下执行
read 方法
read 方法是从当前建立的连接通道中读取字节,一次读取一个字节,所以使用循环读取数据。
read 方法也是阻塞的,这意味着它会一直等待直到有足够的数据可读,或者直到连接关闭。
那数据读取完了怎么结束等待呢?判断数据是否读取完,如果读取完,就停止循环
返回的 -1
当客户端主动关闭连接后会发送一个结束标志,服务端的 read 方法就会返回 -1 ,表示客户端关闭了连接
当读完数据时,read 方法会返回 -1 ,表示数据读取完毕
作业
1. 实现在线聊天室
可以使用TCP建立连接
张三发送:你好
李四发送:你好呀
2. 预习
网络通信三要素:IP、端口、协议
TCP、UDP,它们之间的区别是什么?
三次握手、四次挥手
HTTP 传输协议:请求协议,响应协议
进程和线程的关系