一、初步认识IO流

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读取的时候会出现乱码。字符转换流可以解决这样的问题。

  • 字符转换流有一个参数可以指定编码格式的。
  1. 缓冲的字节输入流
  2. 缓冲的字节输入流 --> 字符输入流
  3. 字符输入流 --> 缓冲的字符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:引入接口依赖

在这里插入图片描述


7. socket实现远程通信

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值