I/O流
I/O 实际上是input和output,也就是输入和输出。而流其实是一种抽象 的概念,它表示的是数据的无结构化传递
- 在Java中I/O流操作的类很多,但是核心体系实际上就只有File、 InputStream、OutputStream、Reader、Writer。
- 字节流:操作的数据单元是8位的字节。InputStream、OutputStream 作为抽象基类。
- 字符流:操作的数据单元是字符。以Writer、Reader作为抽象基类。(utf8中:一个中文字符有3个字节,一个英文字符对应一个字节)
字节流可以处理所有数据文件、字符流只能处理文本数据。
IO流的分类:
管道流PipedInputStream:针对线程的操作
ByteArrayInputStream:针对内存的一个操作。需要转换成内存数组。
缓冲流:提供一个缓冲区避免每次和磁盘的交互,提升输入输出的执行效率。优点可以整行读取,readLine()方法。
对象流ObjectInputStream:Java将一个内存对象转换成一个可存储可传输的一个对象,就可以用到对象流。(序列化)需要实现序列化接口。
file文件流:用于文件或者目录的信息描述,创建文件目录、删除文件、判断文件路径等。
InputStreamReader:转换流:把字节流转换成字符流,字符流使用更加方便。并且可以指定编码格式。
案例:读取文件
用字节流读取包含中文的文件,会出现乱码现象。(因为一个中文字符包含3个字节,单个解析字节就会乱码)可以使用字符流,或指定编码格式的自节流
// 文件中内容为Hello World
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("E:/test.txt");
int i = 0;
// 终止符号:i=fileInputStream.read(),读取一个字节数据
// 该方法效率低,读取一个字符就要和磁盘进行交互。
while ( (i=fileInputStream.read()) != -1 ) {
System.out.print((char) i);// ASCII码
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
fileInputStream.close();
}
}
一、IO流数据来源及操作的API
IO流的来源
- 硬盘
- 内存
- 键盘
- 网络
// 1. 磁盘IO
FileInputStream fileInputStream = new FileInputStream("E:/test.txt");
// 2. 内存,字节数组
String str = "hello word";
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes());
// 3.键盘
// 4.网络(重点),不同的计算机之间进行通讯
Socket socket;
socket.getInputStream();
socket.getOutputStream();
二、本地磁盘文件操作(File)
File类是Java中为文件进行创建、删除、重命名、移动等操作而设计 的一个类。它是属于Java.io包下的类。
例子:
fileOutputStream.write(buffer, 0, len); 指定了一个写出的范围
0开始到i的位置
// 把字节流读取到一个缓存中,而没有使用缓冲流。
public static void main(String[] args) throws IOException{
File file = new File("E:/logo.png");
FileInputStream fileInputStream = new FileInputStream("E:/logo.png");
FileOutputStream fileOutputStream = new FileOutputStream("E:/logo_copy.png");
byte[] buffer = new byte[1024];// 占内存,不是越大越好
int len = 0;
while( (len=fileInputStream.read(buffer)) != -1 ) {
// 方法一:写道磁盘,读取一次和磁盘交互一次,效率低
fileOutputStream.write(len);
// 方法二:1024个字节交互一次,效率高
fileOutputStream.write(buffer, 0, len);
}
fileOutputStream.close();
fileInputStream.close();
}
自动关闭流,Java7的用法:将流放在try()中
流必须实现Closeable接口
OutputStream implements Closeable
public static void main(String[] args) throws IOException{
File file = new File("E:/logo.png");
try(
FileInputStream fileInputStream = new FileInputStream("E:/logo.png");
FileOutputStream fileOutputStream = new FileOutputStream("E:/logo_copy.png");
) {
byte[] buffer = new byte[1024];
int len = 0;
while( (len=fileInputStream.read(buffer)) != -1 ) {
// 方法一:写道磁盘,读取一次和磁盘交互一次,效率低
fileOutputStream.write(len);
// 方法二:1024个字节交互一次,效率高
fileOutputStream.write(buffer, 0, len);
}
}catch (Exception e) {
}
}
1 深入浅出read方法
fileInputStream.read():读取指定目录的字节,如果read方法没有输入,会阻塞。
三、基于缓冲流的输入输出流
缓冲流:
缓冲流是带缓冲区的处理流(默认8K),它会提供一个缓冲区,缓冲区的作用的主要 目的是:避免每次和硬盘打交道,能够提高输入/输出的执行效率。
如果输出字符大小,没有达到默认缓冲区大小。那么需要手动触发输出到磁盘操作。flush()
public static void main(String[] args){
// 将[文件输出流]包装成 -> 缓冲输出流
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("E:/zhukai.txt"));
bufferedOutputStream.write("Hello World".getBytes());
// bufferedOutputStream.flush(); // 刷盘操作
bufferedOutputStream.close(); // 流关闭的时候,也会触发一个刷盘操作
}
四、基于文件的字符输入输出流实践
// 和字节流的读取没有太大的区别
public static void main(String[] args){
try(FileReader reader = new FileReader("E:/mic.txt")) {
int i = 0;
char[] bf = char[1024];
while( (i=reader.read(bf)) != -1 ) {
System.out.println(new String(bf, 0, i));
}
}catch (Exception e) {
}
}
1. 字符转换流(比较重要)
是字节流 —> 字符流的一个桥梁。
InputStreamReader、OutputStreamWriter
背景:当本地文件的编码格式和程序的编码格式不一致时,使用IO读取的时候会出现乱码。字符转换流可以解决这样的问题。
- 字符转换流有一个参数可以指定编码格式的。
- 缓冲的字节输入流
- 缓冲的字节输入流 --> 字符输入流
- 字符输入流 --> 缓冲的字符reader
public static void main(String[] args){
try(InputStream is = new FileInputStream("E:/mic.txt")) {
InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");
BufferedReader BufferedReader = new BufferedReader(reader);
// 转换流特有的方法
BufferedReader.readLine();
}catch(Exception e) {
}
}
五、序列化和反序列化,网络传输中使用,socket。
序列化是把对象的状态信息转化为可存储或传输的形式过程,也就是把对 象转化为字节序列的过程称为对象的序列化。
反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列 恢复为对象的过程成为对象的反序列化。
将内存对象,保存在本地磁盘当中:
ObjectOutputStream: 将内存对象写入到磁盘,该对象需要实现序列化接口,serialable.
在socket网络通信中,会用到ObjectOutputStream对象输入输出流。
六、网络IO
Socket、ServerSocket:Java中提供的用来构建网络通信协议的一个套接字。
ServerSocket:服务于服务端,对外提供一个监听等待客户端的链接,对外暴露了一个服务地址和端口号。
Socket:服务于客户端的一个套接字,通过Socket来连接到一个指定的serverSocket
是一个双工通信可以双向传输数据。
例子:
// 启动一个服务端
public static void main(String[] args){
final int DEFAULT_PORT = 8080;
ServerSocket serverSocket = null;
// 绑定一个监听端口
try {
serverSocket = new ServerSocket(DEFAULT_PORT);
// 这里是一个阻塞操作,等待客户端的链接。
Socket socket = serverSocket.accept();
System.out.print("客户端:" + socket.getPort() + "已连接");
// 1. 字符转换流InputStreamReader,字节到字符的转换:new InputStreamReader(socket.getInputStream())
// 2. 将字符流构建成一个字符缓冲流(更加高效)
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));//
String str= bufferedReader.readLine();// 获取客户端输入的一行信息
System.out.println("收到客户端的请求信息:" + str);
// 服务端也可以写出信息到输入端
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamReader(socket.getOutputStream());
bufferedWriter.write("我已经收到了消息!\n");
bufferedWriter.flush();
}catch(IOException e){
e.printStackTrace();
}finally {
bufferedReader.close();
bufferedWriter.close();
socket.close();
}
}
// 客户端
public static void main(String[] args){
final int DEFAULT_PORT = 8080;
Socket socket = new Socket("localhost", DEFAULT_PORT);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamReader(socket.getOutputStream());
// 加\n是因为服务端要读一行数据,没有这个符号,服务端会认为一直在读一行,readLine方法会阻塞
bufferedWriter.write("我是客户端发送的消息!\n");
bufferedWriter.flush();
// 读取服务端的返回消息
BufferedReader BufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str= BufferedReader.readLine();
System.out.println("收到服务端的信息:" + str);
}
1. 详解网络通信的底层原理
上面一个例子只能一个一个处理请求,后面的请求要在等待前一个请求处理完之后执行。
这是一个BIO(block): 这是一个阻塞IO
第一个阻塞位置:accept()方法,连接阻塞
第二个阻塞位置InputStream(流的阻塞,输入流还没有输入完成之前)
解决方案:
让线程池作出处理,因为线程是一个异步的。
socket协议是一个双工协议,可以双向通信。
// 启动一个服务端
public static void main(String[] args){
final int DEFAULT_PORT = 8080;
ServerSocket serverSocket = null;
// 绑定一个监听端口
try {
serverSocket = new ServerSocket(DEFAULT_PORT);
ExecutorService executorService = Executors.newFixedThreadPool(4);
while(true) {
Socket socket = serverSocket.accept(); // 阻塞
executorService.submit(new ServerSocketThread(socket));// 异步的处理方式,线程池处理。
}
}catch(IOException e){
e.printStackTrace();
}finally {
流的关闭
}
}
public class ServerSocketThread implements Runnable{
Socket socket;
@Override
public void run() {
System.out.print("客户端:" + socket.getPort() + "已连接");
// 1. 转换流InputStreamReader,字节到字符的转换:new InputStreamReader(socket.getInputStream())
// 2. 将字符流构建成一个字符缓冲流(更加高效)
BufferedReader BufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));//
String str= BufferedReader.readLine();// 获取客户端输入的一行信息
System.out.println("收到客户端的请求信息:" + str);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamReader(socket.getOutputStream());
bufferedWriter.write("我已经收到了消息!\n");
bufferedWriter.flush();
}
}
上面这中也是低效的,所以会引出NIO,对于服务器来说线程的数量毕竟是有限的。
七、Socket实现RPC通讯框架
RPC(Remote Procedure Call) 远程过程调用:是一种通过网络从远程计算机 程序上请求服务,而不需要了解底层网络技术的协议。
一般用来实现部署在 不同机器上的系统之间的方法调用,使得程序能够像访问本地系统资源一样, 通过网络传输去访问远端系统资源。
简单地说,调用其他系统方法就像调用本地方法一样简单,不需要考虑调用细节。
RPC框架:Dubbo、webservice等。
7. 手写RPC框架:
1. 创建两个工程:
- rpc-server
- rpc-client
2. 工程rpc-server中创建两个模块:
右键工程rpc-server工程,new — module 创建模块
- rpc-server-api:是一个被公共依赖的包(接口要公共依赖)。
- rpc-server-provider:接口的实现,服务的实现可以部署在不同的计算机上面。
3. 整理工程目录
maven中分模块的构建方式:删除src目录
4. rpc-server-provider实现API的接口
- rpc-server-provider模块中,先引入rpc-server-api的依赖
- rpc-server-provider模块中,实现rpc-server-api的接口
5. server能够被client远程调用
- rpc-server-provider需要打包:
- rpc-server-provider:install到本地仓库
6. 工程rpc-client也需要依赖rpc-server-api
- rpc-client:引入接口依赖