diff --git a/.DS_Store b/.DS_Store index a3f2d40c..6b515f86 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Java基础教程/JavaIO与网络编程/01 Java 标准IO.md b/Java基础教程/JavaIO与网络编程/01 Java 标准IO.md new file mode 100644 index 00000000..1285e079 --- /dev/null +++ b/Java基础教程/JavaIO与网络编程/01 Java 标准IO.md @@ -0,0 +1,407 @@ +# Java 标准IO + +- [Java 标准IO](#java-标准io) + - [1 概览](#1-概览) + - [IO定义](#io定义) + - [虚拟内存](#虚拟内存) + - [IO的分类](#io的分类) + - [装饰者模式](#装饰者模式) + - [2 字节操作](#2-字节操作) + - [InputStream](#inputstream) + - [OutputStream](#outputstream) + - [3 字符操作](#3-字符操作) + - [编码与解码](#编码与解码) + - [String 的编码方式](#string-的编码方式) + - [字节字符流转换Reader 与 Writer](#字节字符流转换reader-与-writer) + - [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容) + - [Reader](#reader) + - [Writer](#writer) + - [参考文献](#参考文献) + + + +## 1 概览 +![](image/2022-07-12-11-35-43.png) + +本篇文章的范围应该是涵盖java.io包中的所有类。 + +### IO定义 +> 该理解方式非常有价值。为什么IO的同步和异步如此重要?因为IO操作是整个调用链路上的性能瓶颈。普通的A函数调用B函数,为什么不采用异步操作呢,因为函数都是计算任务,都在内存完成。所以所有的操作都可以分为两种:计算操作(非IO操作,内存中即可完成)和IO操作(从其他设备中读取、写入数据)。计算操作是使用CPU的,IO操作过程中CPU线程是挂起的,等待中。函数、调用可以分为两种,正常调用和IO调用。 + +缓冲区以及如何处理缓冲区是所有I / O的基础。 术语“输入/输出”仅意味着将数据移入和移出缓冲区。 只要时刻牢记这一点即可。 通常,进程通过请求操作系统从缓冲区中清空数据( write operation )或向缓冲区中填充数据( read operation )来执行I / O。 以上是I / O概念的全部摘要。 + + +![](image/2022-11-26-17-31-29.png) + + +上图显示了块数据如何从外部源(例如硬盘)移动到正在运行的进程(例如RAM)内部的存储区的简化“逻辑”图。 +1. 首先,该进程通过进行read()系统调用来请求填充其缓冲区。 +2. 此调用导致内核向磁盘控制器硬件发出命令以从磁盘获取数据。 磁盘控制器通过DMA将数据直接写入内核内存缓冲区,而无需主CPU的进一步协助。 +3. 磁盘控制器完成缓冲区填充后,当它请求read()操作时。内核将数据从内核空间中的临时缓冲区复制到进程指定的缓冲区中; +4. 需要注意的一件事是内核尝试缓存和/或预取数据,因此进程请求的数据可能已经在内核空间中可用。 如果是这样,则将过程所请求的数据复制出来。 如果数据不可用,则该过程将在内核将数据带入内存时挂起。 + +### 虚拟内存 + +虚拟内存具有两个重要优点: + +1)多个虚拟地址可以引用相同的物理内存位置。 +2)虚拟内存空间可以大于可用的实际硬件内存。 + +对于第一个特性,通过地址映射mmap,将内核空间逻辑地址与用户空间中虚拟地址映射到相同物理空间。DMA硬件(只能访问物理内存地址)可以填充一个缓冲区,该缓冲区同时对内核和用户空间进程可见。消除了内核空间和用户空间之间的副本, + +![](image/2022-11-26-18-03-55.png) + +对于第二个特性,进行虚拟内存分页(通常称为交换)。将虚拟内存空间的页面持久保存到外部磁盘存储中,从而在物理内存中为其他虚拟页面腾出空间。物理内存充当页面调度区域的缓存,当虚拟内存的不在物理内存中时,由物理内存从磁盘空间交换。 + + + +### IO的分类 +Java 的 I/O可以根据如下方式进行分类。 +![](image/2022-11-26-19-31-24.png) +![](image/2022-11-26-19-20-23.png) +![](image/2022-11-27-00-08-19.png) + +根据内容的不同可以分为: + +- 字节操作:InputStream 和 OutputStream +- 字符操作:Reader 和 Writer +- 输入流 InputStream、Reader +- 输出流 OutputStream、Writer +- 字节转字符:InputStreamReader/OutputStreamWriter + + +根据对象的不同可以分为: + +- 文件操作:File +- 管道操作:Piped +- 数组操作:ByteArray&CharArray +- 对象操作:Object +- 过滤操作:Filter添加额外特性 + - Bufferd + - Data + - PushBack + - LineNumber +- 网络操作:Socket + + +根据IO模型的不同可以分为: +- 同步阻塞IO:BIO +- 异步IO:NIO +- 异步IO:AIO + +根据IO的原理可以分为: +* Block IO 块IO +* Stream IO 流IO + + + + +### 装饰者模式 + +Java I/O 使用了装饰者模式来实现。以 InputStream 为例, + +- InputStream 是抽象组件; +- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作; +- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。 + +

+ +实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。 + +```java +FileInputStream fileInputStream = new FileInputStream(filePath); +BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); +``` + +DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。 + + +## 2 字节操作 +![](image/2022-11-26-19-40-09.png) + + +### InputStream + +* 基本的InputStream +``` +int read() +int read(byte[] b) +int read(byte[] b, int off, int len) +void close() +long skip(long n) +``` +* FileInputStream + +```java +FileInputStream(File file) +FileInputStream(String name) +``` + +* PipedInputStream + +```java +PipedInputStream(int pipeSize) +PipedInputStream(PipedOutputStream src) +PipedInputStream(PipedOutputStream src, int pipeSize) + +connect(PipedOutputStream src) +receive(int b) +``` + +* ByteArrayInputStream + +```java +ByteArrayInputStream(byte[] buf) +ByteArrayInputStream(byte[] buf, int offset, int length) +``` + +* ObjectInputStream 持久化对象。用ObjectOutputStream写出,就用这个读入 + +```java +ObjectInputStream(InputStream in) + +readBoolean()、Byte、Char、Double、Float、Int、Long +readFully() +readObject() +``` +* FilterInputStream->BufferedInputStream + +```java + +``` + +### OutputStream + +* 基本的OutputStream + +``` +close() +flush() +write(byte[] b) +write(byte[] b, int off, int len) +write(int b) +``` + +* FileOutputStream + +```java +FileOutputStream(File file) +FileOutputStream(File file, boolean append) +FileOutputStream(String name) +FileOutputStream(String name, boolean append) +``` +* PipedOutputStream + +```java +PipedOutputStream(PipedInputStream snk) +connect(PipedInputStream snk) +``` + +* ByteArrayOutputStream,自带一个字节缓冲区 + +```java +ByteArrayOutputStream(int size) + size() +``` + +* ObjectOutputStream + +```java +ObjectOutputStream(OutputStream out) + + +writeBoolean(boolean val) +writeByte(int val) +writeBytes(String str) +writeChar(int val) +writeChars(String str) +writeInt(int val) +writeLong(long val) +writeObject(Object obj) +``` + +* FilterOutputStream->BufferedOutputStream + +* PrintStream一个特殊的包装器类 +```java +print(基础类型和对象) +printf(String format, Object... args) +println(基础类型和对象) +``` +## 3 字符操作 +![](image/2022-11-26-20-24-47.png) + + +### 编码与解码 + +编码就是把字符转换为字节,而解码是把字节重新组合成字符。 + +如果编码和解码过程使用不同的编码方式那么就出现了乱码。 + +- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节; +- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节; +- UTF-16 be编码中,中文字符和英文字符都占 2 个字节。 + +UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。 + +Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。 + +### String 的编码方式 + +String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。 + +```java +String str1 = "中文"; +byte[] bytes = str1.getBytes("UTF-8"); +String str2 = new String(bytes, "UTF-8"); +System.out.println(str2); +``` + +在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。 + +```java +byte[] bytes = str1.getBytes(); +``` + +### 字节字符流转换Reader 与 Writer + +不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。 + +- InputStreamReader 实现从字节流解码成字符流; + +```java +InputStreamReader(InputStream in) +InputStreamReader(InputStream in, Charset cs) +InputStreamReader(InputStream in, CharsetDecoder dec) +InputStreamReader(InputStream in, String charsetName) +getEncoding() +``` +- OutputStreamWriter 实现字符流编码成为字节流。 + +```java +OutputStreamWriter(OutputStream out) +OutputStreamWriter(OutputStream out, Charset cs) +OutputStreamWriter(OutputStream out, CharsetEncoder enc) +OutputStreamWriter(OutputStream out, String charsetName) + +getEncoding() +``` + +### 实现逐行输出文本文件的内容 + +```java +public static void readFileContent(String filePath) throws IOException { + + FileReader fileReader = new FileReader(filePath); + BufferedReader bufferedReader = new BufferedReader(fileReader); + + String line; + while ((line = bufferedReader.readLine()) != null) { + System.out.println(line); + } + + // 装饰者模式使得 BufferedReader 组合了一个 Reader 对象 + // 在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法 + // 因此只要一个 close() 调用即可 + bufferedReader.close(); +} +``` + +### Reader + +* 基本的Reader + +```java +close() +read() +read(char[] cbuf) +read(char[] cbuf, int off, int len) +read(CharBuffer target) +ready() +skip(long n) +``` +* FileReader +```java +FileReader(File file) +FileReader(String fileName) +``` + +* PipedReader +```java +PipedReader(int pipeSize) +PipedReader(PipedWriter src) +PipedReader(PipedWriter src, int pipeSize) + +connect(PipedWriter src) +``` +* CharArrayReader +``` +CharArrayReader(char[] buf) +CharArrayReader(char[] buf, int offset, int length) +``` +* BufferedReader +``` +String readLine() +Stream lines() +``` + +### Writer + +* 基本的Writer +```java +append(char c) +Writer append(CharSequence csq) +Writer append(CharSequence csq, int start, int end) + +close() +flush() +write(char[] cbuf) +write(char[] cbuf, int off, int len) +write(int c) +write(String str) +write(String str, int off, int len) +``` +* FileWriter + +```java +OutputStreamWriter(OutputStream out) +OutputStreamWriter(OutputStream out, Charset cs) +OutputStreamWriter(OutputStream out, CharsetEncoder enc) +OutputStreamWriter(OutputStream out, String charsetName) +getEncoding() +``` +* PipedWriter +```java +PipedWriter(PipedReader snk) +connect(PipedReader snk) +``` +* CharArrayWriter +```java +size() +char[] toCharArray() +String toString() +``` +* BufferedWriter +``` +BufferedWriter(Writer out) +BufferedWriter(Writer out, int sz) +newLine() +``` + +* PrintWriter一个特殊的装饰器类,在write的基础上添加了许多print函数 +```java +print(基础类型和对象) +printf(String format, Object... args) +println(基础类型和对象) +``` +## 参考文献 +- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002. +- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html) +- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html) +- [Java NIO 浅析](https://tech.meituan.com/nio.html) +- [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html) +- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html) +- [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html) +- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499) +- [Decorator Design Pattern](http://stg-tud.github.io/sedc/Lecture/ws13-14/5.3-Decorator.html#mode=document) +- [Socket Multicast](http://labojava.blogspot.com/2012/12/socket-multicast.html) diff --git a/Java基础教程/JavaIO与网络编程/02 Java NIO.md b/Java基础教程/JavaIO与网络编程/02 Java NIO.md new file mode 100644 index 00000000..dd29df14 --- /dev/null +++ b/Java基础教程/JavaIO与网络编程/02 Java NIO.md @@ -0,0 +1,713 @@ + + +# NIO + +## 1 NIO概述 + +新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。 + +IO Stream 和 NIO Block。NIO将最耗时的IO活动(即填充和清空缓冲区)移回操作系统,从而极大地提高了速度。 + +阻塞 I/O(blocking I/O)是旧的输入/输出(old input/output,OIO)。被称为普通 I/O(plain I/O) + + +### 标准IO与NIO的区别一:流与块 + +I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。 + + +* 面向流的I / O系统一次处理一个或多个字节的数据。 输入流产生一个字节的数据,而输出流消耗一个字节的数据。为流数据创建过滤器非常容易。 将几个过滤器链接在一起也是相对简单的,这样每个过滤器都能发挥自己的作用,相当于一个单一的复杂处理机制。 重要的是字节不会在任何地方缓存。 此外,您不能在流中的数据中来回移动。 如果需要来回移动从流中读取的数据,则必须先将其缓存在缓冲区中。 + + +* 面向块的I / O系统按块处理数据。 每个操作一步就产生或消耗一个数据块。 通过块可以处理数据,比处理(流式传输)字节快得多。 您可以根据需要在缓冲区中来回移动。 这使您在处理过程中更具灵活性。 但是,您还需要检查缓冲区是否包含您需要的所有数据,以便对其进行完全处理。 并且,您需要确保在将更多数据读入缓冲区时,不要覆盖尚未处理的缓冲区中的数据。 但是面向块的I / O缺少面向流的I / O的一些优雅和简单性。 + + +I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。 + + +### 标准IO与NIO区别二:同步异步 + +标准IO和NIO第二个主要的区别是,同步和异步。同步程序通常不得不诉诸于轮询或创建许多线程来处理大量连接。 使用异步I / O,您可以在任意数量的通道上侦听I / O事件,而无需轮询且无需额外的线程。异步I / O中的中心对象称为选择器。 +* Java IO当线程调用read()或write()时,该线程将被阻塞,直到有一些数据要读取或数据被完全写入为止。因此引入多线程增加并发性。 +* 异步IO中,线程可以请求将某些数据写入通道,但不等待将其完全写入。 然后线程可以继续运行,同时执行其他操作。 单个线程现在可以管理输入和输出的多个通道。 + + +![](image/2022-11-27-10-06-38.png) + +## 2 Path + +Path是NIO的入口点。 + +### 绝对路径 +绝对路径始终包含根元素和查找文件所需的完整目录列表。 不再需要更多信息来访问文件或路径。 + +```java +//Starts with file store root or drive +Path absolutePath1 = Paths.get("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt"); +Path absolutePath2 = Paths.get("C:/Lokesh/Setup/workspace", "NIOExamples/src", "sample.txt"); +Path absolutePath3 = Paths.get("C:/Lokesh", "Setup/workspace", "NIOExamples/src", "sample.txt"); +``` + +### 相对路径 + +```java +Path relativePath1 = Paths.get("src", "sample.txt"); +``` + +### 通过Uri +将格式为“ file:///src/someFile.txt”的文件路径转换为NIO路径。 让我们来看看如何做。 + + +```java +//URI uri = URI.create("file:///c:/Lokesh/Setup/workspace/NIOExamples/src/sample.txt"); //OR +URI uri = URI.create("file:///Lokesh/Setup/workspace/NIOExamples/src/sample.txt"); + +String scheme = uri.getScheme(); +if (scheme == null) + throw new IllegalArgumentException("Missing scheme"); + +//Check for default provider to avoid loading of installed providers +if (scheme.equalsIgnoreCase("file")) +{ + System.out.println(FileSystems.getDefault().provider().getPath(uri).toAbsolutePath().toString()); +} + +//If you do not know scheme then use this code. This code check file scheme as well if available. +for (FileSystemProvider provider: FileSystemProvider.installedProviders()) { + if (provider.getScheme().equalsIgnoreCase(scheme)) { + System.out.println(provider.getPath(uri).toAbsolutePath().toString()); + break; + } +} +``` +## 3 Buffer缓冲区 + +### 缓冲区 +Buffer对象可以称为固定数量数据的容器。 它充当存储箱或临时暂存区,可以在其中存储数据并在以后检索。 + +通道是进行I / O传输的实际门户。 缓冲区是这些数据传输的源或目标。 + +发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。 + +缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。 + +缓冲区包括以下类型,能够包含所有的类型。 + +- ByteBuffer +- CharBuffer +- ShortBuffer +- IntBuffer +- LongBuffer +- FloatBuffer +- DoubleBuffer + + + +### 缓冲区状态变量 + + 所有缓冲区拥有的四个属性可提供有关所包含数据元素的信息。 这些是: + +* Capacity :缓冲区可以容纳的最大数据元素数。 容量是在创建缓冲区时设置的,无法更改。 +* Limit :不应读取或写入的缓冲区的第一个元素。 换句话说,缓冲区中活动元素的数量。 +* Position :下一个要读取或写入的元素的索引。 该位置由相对的get()和put()方法自动更新。 +* Mark :记忆中的位置。 调用mark()设置mark =位置。 调用reset()设置position =标记。 该标记在设置之前是不确定的。 + +> 0<= mark <= position <= limit <= capacity + +状态变量的改变过程举例: + +① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。 + +![](image/2022-11-27-00-33-15.png) + +② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。 + +![](image/2022-11-27-00-33-28.png) + +③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。 + +![](image/2022-11-27-00-33-35.png) + +④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。 + +![](image/2022-11-27-00-33-45.png) + +⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。 + +![](image/2022-11-27-00-33-59.png) + + +### 创建缓冲区 + +缓冲区类都不能直接实例化。 它们都是抽象类,但是每个都包含静态工厂方法来创建相应类的新实例。 + +```java +CharBuffer charBuffer = CharBuffer.allocate (100); + +//调用put()对缓冲区所做的更改将反映在数组中,而直接对数组所做的任何更改将对缓冲区对象可见。 +char [] myArray = new char [100]; +CharBuffer charbuffer = CharBuffer.wrap (myArray); +``` + + + +### 反转缓冲区:从写状态转换为读状态 + +```java +public abstract class ByteBuffer extends Buffer implements Comparable +{ + // This is a partial API listing + // get到当前元素,postion到下一个元素 + public abstract byte get(); + // get指定元素 + public abstract byte get (int index); + public abstract ByteBuffer put (byte b); + public abstract ByteBuffer put (int index, byte b); +} +``` + +* flip()方法将缓冲区从可以附加数据元素的填充状态翻转到耗尽状态,以准备读取元素 。 + +![](image/2022-11-27-00-48-14.png) + +```java +buffer.flip(); +buffer.limit( buffer.position() ).position(0); +``` +* clear()方法将缓冲区重置为空状态。 它不会更改缓冲区的任何数据元素,而只是将限制设置为容量并将位置设置回0。这使缓冲区可以再次填充。 + +```java +import java.nio.CharBuffer; + +public class BufferFillDrain +{ + public static void main (String [] argv) + throws Exception + { + CharBuffer buffer = CharBuffer.allocate (100); + + while (fillBuffer (buffer)) { + buffer.flip( ); + drainBuffer (buffer); + buffer.clear(); + } + } + + private static void drainBuffer (CharBuffer buffer) + { + while (buffer.hasRemaining()) { + System.out.print (buffer.get()); + } + + System.out.println(""); + } + + private static boolean fillBuffer (CharBuffer buffer) + { + if (index >= strings.length) { + return (false); + } + + String string = strings [index++]; + + for (int i = 0; i > string.length( ); i++) { + buffer.put (string.charAt (i)); + } + + return (true); + } + + private static int index = 0; + + private static String [] strings = { + "Some random string content 1", + "Some random string content 2", + "Some random string content 3", + "Some random string content 4", + "Some random string content 5", + "Some random string content 6", + }; +} +``` + +## 4 Channel通道 + +### Channel概念 +通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。 + +通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。 + +通道包括以下类型: + +- FileChannel:从文件中读写数据; +- DatagramChannel:通过 UDP 读写网络中数据; +- SocketChannel:通过 TCP 读写网络中数据; +- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。 + + + +### 创建channel + +* FileChannel:只能通过在打开的RandomAccessFile , FileInputStream或FileOutputStream对象上调用getChannel()方法来获取FileChannel对象。 您不能直接创建FileChannel对象。 + +```java +RandomAccessFile raf = new RandomAccessFile ("somefile", "r"); +FileChannel fc = raf.getChannel(); +``` + +* SocketChannel:套接字通道具有工厂方法来直接创建新的套接字通道。 + +```java +//How to open SocketChannel +SocketChannel sc = SocketChannel.open(); +sc.connect(new InetSocketAddress("somehost", someport)); + +//How to open ServerSocketChannel +ServerSocketChannel ssc = ServerSocketChannel.open(); +ssc.socket().bind (new InetSocketAddress (somelocalport)); + +//How to open DatagramChannel +DatagramChannel dc = DatagramChannel.open(); +``` + + +### 使用Channel +> 可以发现,通过Channel转换buffer上的数据,而不需要直接操作buffer。 + + +* 它通过实现不同的接口,表示其是双向或者单向的。 连接到只读文件的Channel实例无法写入。 + +* 快速复制文件 + +```java +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +public class ChannelCopyExample +{ + public static void main(String args[]) throws IOException + { + FileInputStream input = new FileInputStream ("testIn.txt"); + ReadableByteChannel source = input.getChannel(); + + FileOutputStream output = new FileOutputStream ("testOut.txt"); + WritableByteChannel dest = output.getChannel(); + + copyData(source, dest); + + source.close(); + dest.close(); + } + + private static void copyData(ReadableByteChannel src, WritableByteChannel dest) throws IOException + { + ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024); + + while (src.read(buffer) != -1) + { + // Prepare the buffer to be drained + buffer.flip(); + + // Make sure that the buffer was fully drained + while (buffer.hasRemaining()) + { + dest.write(buffer); + } + + // Make the buffer empty, ready for filling + buffer.clear(); + } + } +} +``` + + +### Vectored IO + +从通道读取的分散数据是将数据读取到多个缓冲区中的读取操作。 因此,通道将数据从通道“ scatters ”到多个缓冲区中。 收集到通道的写操作是一种写操作,它将来自多个缓冲区的数据写到单个通道中。 因此,通道gathers来自多个缓冲区的数据“ gathers ”到一个通道中。 在需要分别处理传输数据的各个部分的情况下,散布/收集可能非常有用。 + +在此示例中,我创建了两个缓冲区。 一个缓冲区将存储一个随机数,另一个缓冲区将存储一个随机字符串。 我将使用GatheringByteChannel读取写入文件通道中两个缓冲区中存储的数据。 然后,我将使用ScatteringByteChannel将文件中的数据读回到两个单独的缓冲区中,并在控制台中打印内容以验证存储和检索的数据是否匹配。 + +```java +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + +public class ScatteringAndGatheringIOExample +{ + public static void main(String params[]) + { + String data = "Scattering and Gathering example shown in howtodoinjava.com"; + + gatherBytes(data); + scatterBytes(); + } + + /* + * gatherBytes() reads bytes from different buffers and writes to file + * channel. Note that it uses a single write for both the buffers. + */ + public static void gatherBytes(String data) + { + //First Buffer holds a random number + ByteBuffer bufferOne = ByteBuffer.allocate(4); + + //Second Buffer holds data we want to write + ByteBuffer buffer2 = ByteBuffer.allocate(200); + + //Writing Data sets to Buffer + bufferOne.asIntBuffer().put(13); + buffer2.asCharBuffer().put(data); + + //Calls FileOutputStream(file).getChannel() + GatheringByteChannel gatherer = createChannelInstance("test.txt", true); + + //Write data to file + try + { + gatherer.write(new ByteBuffer[] { bufferOne, buffer2 }); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /* + * scatterBytes() read bytes from a file channel into a set of buffers. Note that + * it uses a single read for both the buffers. + */ + public static void scatterBytes() + { + //First Buffer holds a random number + ByteBuffer bufferOne = ByteBuffer.allocate(4); + + //Second Buffer holds data we want to write + ByteBuffer bufferTwo = ByteBuffer.allocate(200); + + //Calls FileInputStream(file).getChannel() + ScatteringByteChannel scatterer = createChannelInstance("test.txt", false); + + try + { + //Reading from the channel + scatterer.read(new ByteBuffer[] { bufferOne, bufferTwo }); + } + catch (Exception e) + { + e.printStackTrace(); + } + + + //Read the buffers seperately + bufferOne.rewind(); + bufferTwo.rewind(); + + int bufferOneContent = bufferOne.asIntBuffer().get(); + String bufferTwoContent = bufferTwo.asCharBuffer().toString(); + + //Verify the content + System.out.println(bufferOneContent); + System.out.println(bufferTwoContent); + } + + + public static FileChannel createChannelInstance(String file, boolean isOutput) + { + FileChannel fc = null; + try + { + if (isOutput) { + fc = new FileOutputStream(file).getChannel(); + } else { + fc = new FileInputStream(file).getChannel(); + } + } + catch (Exception e) { + e.printStackTrace(); + } + return fc; + } +} +``` + + +### 内存映射文件 + +内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。 + +向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。 + +下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。 + +```java +MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024); +``` + + +## 5 选择器 + +NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。 + +NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。 + +通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。 + +因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。 + +应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。 + +

+ +### 1. 创建选择器 + +```java +Selector selector = Selector.open(); +``` + +### 2. 将通道注册到选择器上 + +```java +ServerSocketChannel ssChannel = ServerSocketChannel.open(); +ssChannel.configureBlocking(false); +ssChannel.register(selector, SelectionKey.OP_ACCEPT); +``` + +通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。 + +在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类: + +- SelectionKey.OP_CONNECT +- SelectionKey.OP_ACCEPT +- SelectionKey.OP_READ +- SelectionKey.OP_WRITE + +它们在 SelectionKey 的定义如下: + +```java +public static final int OP_READ = 1 << 0; +public static final int OP_WRITE = 1 << 2; +public static final int OP_CONNECT = 1 << 3; +public static final int OP_ACCEPT = 1 << 4; +``` + +可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如: + +```java +int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; +``` + +### 3. 监听事件 + +```java +int num = selector.select(); +``` + +使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。 + +### 4. 获取到达的事件 + +```java +Set keys = selector.selectedKeys(); +Iterator keyIterator = keys.iterator(); +while (keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + if (key.isAcceptable()) { + // ... + } else if (key.isReadable()) { + // ... + } + keyIterator.remove(); +} +``` + +### 5. 事件循环 + +因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。 + +```java +while (true) { + int num = selector.select(); + Set keys = selector.selectedKeys(); + Iterator keyIterator = keys.iterator(); + while (keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + if (key.isAcceptable()) { + // ... + } else if (key.isReadable()) { + // ... + } + keyIterator.remove(); + } +} +``` + +## 6 Socket套接字 NIO 实例 + +```java +public class NIOServer { + + public static void main(String[] args) throws IOException { + + Selector selector = Selector.open(); + + ServerSocketChannel ssChannel = ServerSocketChannel.open(); + ssChannel.configureBlocking(false); + ssChannel.register(selector, SelectionKey.OP_ACCEPT); + + ServerSocket serverSocket = ssChannel.socket(); + InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888); + serverSocket.bind(address); + + while (true) { + + selector.select(); + Set keys = selector.selectedKeys(); + Iterator keyIterator = keys.iterator(); + + while (keyIterator.hasNext()) { + + SelectionKey key = keyIterator.next(); + + if (key.isAcceptable()) { + + ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel(); + + // 服务器会为每个新连接创建一个 SocketChannel + SocketChannel sChannel = ssChannel1.accept(); + sChannel.configureBlocking(false); + + // 这个新连接主要用于从客户端读取数据 + sChannel.register(selector, SelectionKey.OP_READ); + + } else if (key.isReadable()) { + + SocketChannel sChannel = (SocketChannel) key.channel(); + System.out.println(readDataFromSocketChannel(sChannel)); + sChannel.close(); + } + + keyIterator.remove(); + } + } + } + + private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException { + + ByteBuffer buffer = ByteBuffer.allocate(1024); + StringBuilder data = new StringBuilder(); + + while (true) { + + buffer.clear(); + int n = sChannel.read(buffer); + if (n == -1) { + break; + } + buffer.flip(); + int limit = buffer.limit(); + char[] dst = new char[limit]; + for (int i = 0; i < limit; i++) { + dst[i] = (char) buffer.get(i); + } + data.append(dst); + buffer.clear(); + } + return data.toString(); + } +} +``` + +```java +public class NIOClient { + + public static void main(String[] args) throws IOException { + Socket socket = new Socket("127.0.0.1", 8888); + OutputStream out = socket.getOutputStream(); + String s = "hello world"; + out.write(s.getBytes()); + out.close(); + } +} +``` + + +## 7 文件IO + +### 使用标准IO的示例代码 + +```java +import java.io.FileReader; +import java.io.IOException; + +public class WithoutNIOExample +{ + public static void main(String[] args) + { + BufferedReader br = null; + String sCurrentLine = null; + try + { + br = new BufferedReader( + new FileReader("test.txt")); + while ((sCurrentLine = br.readLine()) != null) + { + System.out.println(sCurrentLine); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + try + { + if (br != null) + br.close(); + } catch (IOException ex) + { + ex.printStackTrace(); + } + } + } +} +``` + +### NIO 读取文件 + +```java +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +public class ReadFileWithFixedSizeBuffer +{ + public static void main(String[] args) throws IOException + { + RandomAccessFile aFile = new RandomAccessFile + ("test.txt", "r"); + FileChannel inChannel = aFile.getChannel(); + ByteBuffer buffer = ByteBuffer.allocate(1024); + while(inChannel.read(buffer) > 0) + { + buffer.flip(); + for (int i = 0; i < buffer.limit(); i++) + { + System.out.print((char) buffer.get()); + } + buffer.clear(); // do something with the data and clear/compact it. + } + inChannel.close(); + aFile.close(); + } +} +``` \ No newline at end of file diff --git a/Java基础教程/JavaIO与网络编程/03 Java IO文件.md b/Java基础教程/JavaIO与网络编程/03 Java IO文件.md new file mode 100644 index 00000000..380fff08 --- /dev/null +++ b/Java基础教程/JavaIO与网络编程/03 Java IO文件.md @@ -0,0 +1,973 @@ + +- [File IO](#file-io) + - [0 File对象](#0-file对象) + - [File](#file) + - [RandomAccessFile](#randomaccessfile) + - [NIO:Files](#niofiles) + - [1 列出:list](#1-列出list) + - [BIO](#bio) + - [2 复制:copy](#2-复制copy) + - [BIO](#bio-1) + - [NIO](#nio) + - [common-util](#common-util) + - [3 删除:delete](#3-删除delete) + - [NIO](#nio-1) + - [apache commons-io](#apache-commons-io) + - [4 创建create](#4-创建create) + - [BIO:createNewFile](#biocreatenewfile) + - [BIO:FileOutputStream](#biofileoutputstream) + - [NIO](#nio-2) + - [5 写入:wrirte\&append](#5-写入wrirteappend) + - [BIO:BufferedWriter](#biobufferedwriter) + - [BIO:PrintWriter](#bioprintwriter) + - [BIO:FileOutputStream](#biofileoutputstream-1) + - [BIO:DataOutputStream](#biodataoutputstream) + - [NIO:FileChannel](#niofilechannel) + - [NIO:Files静态方法](#niofiles静态方法) + - [6 读取:read](#6-读取read) + - [BIO:BufferedReader按行读](#biobufferedreader按行读) + - [BIO:FileInputStream 读取字节](#biofileinputstream-读取字节) + - [NIO:Files按行读](#niofiles按行读) + - [NIO:读取所有字节](#nio读取所有字节) + - [commons-io](#commons-io) + - [7 Properties](#7-properties) + - [补充:静态内部类与懒汉式单例模式](#补充静态内部类与懒汉式单例模式) + - [8 Resource File](#8-resource-file) + - [在spring中可以这样](#在spring中可以这样) + - [10 读写utf8数据](#10-读写utf8数据) + - [11 从控制台读取输入](#11-从控制台读取输入) + - [console对象](#console对象) + - [System.in封装](#systemin封装) + - [更加复杂的Scanner](#更加复杂的scanner) + - [12 将String转换成输入流](#12-将string转换成输入流) + - [ByteArrayInputStream](#bytearrayinputstream) + - [apach.IOUtils](#apachioutils) + + + + +# File IO + + + +## 0 File对象 + +### File +java.io.File + +File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。有大量相关的方法 + +```java +File(String pathname) +File(URI uri) + +createNewFile() +createTempFile(String prefix, String suffix) +delete() +deleteOnExit() +exists() +getAbsoluteFile() +getAbsolutePath() +getName() +getPath() +isFile() +isDirectory() +list() +listFiles() +mkdir() +mkdirs() +setReadOnly() +setExecutable(boolean executable) +setWritable(boolean writable) +toPath() +toURI() + +``` + +### RandomAccessFile +java.io.RandomAccessFile + +RandomAccessFile支持"随机访问"的方式,程序可以直接跳转到文件的任意地方来读写数据。 + +* RandomAccessFile可以自由访问文件的任意位置。 +* RandomAccessFile允许自由定位文件记录指针。 +* RandomAccessFile只能读写文件而不是流。 + +```java +RandomAccessFile(String name, String mode): +RandomAccessFile(File file, String mode) + +read*() +write*() +long getFilePointer():返回文件记录指针的当前位置。(native方法) +void seek(long pos):将文件记录指针定位到pos位置。(调用本地方法seek0) +``` + +* 使用randomaccessfile插入内容。RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输出的内容会覆盖文件中原有的内容。如果需要向指定位置插入内容,程序需要先把插入点后面的内容读入缓冲区,等把需要插入的数据写入文件后,再将缓冲区的内容追加到文件后面。 + +```java + /** + * 向指定文件的指定位置插入指定的内容 + * + * @param fileName 指定文件名 + * @param pos 指定文件的指定位置 + * @param insertContent 指定文件的指定位置要插入的指定内容 + */ + public static void insert(String fileName, long pos, + String insertContent) throws IOException { + RandomAccessFile raf = null; + //创建一个临时文件来保存插入点后的数据 + File tmp = File.createTempFile("tmp", null); + FileOutputStream tmpOut = null; + FileInputStream tmpIn = null; + tmp.deleteOnExit(); + try { + raf = new RandomAccessFile(fileName, "rw"); + tmpOut = new FileOutputStream(tmp); + tmpIn = new FileInputStream(tmp); + raf.seek(pos); + //--------下面代码将插入点后的内容读入临时文件中保存--------- + byte[] bbuf = new byte[64]; + //用于保存实际读取的字节数 + int hasRead = 0; + //使用循环方式读取插入点后的数据 + while ((hasRead = raf.read(bbuf)) > 0) { + //将读取的数据写入临时文件 + tmpOut.write(bbuf, 0, hasRead); + } + //----------下面代码插入内容---------- + //把文件记录指针重新定位到pos位置 + raf.seek(pos); + //追加需要插入的内容 + raf.write(insertContent.getBytes()); + //追加临时文件中的内容 + while ((hasRead = tmpIn.read(bbuf)) > 0) { + raf.write(bbuf, 0, hasRead); + } + } finally { + if (raf != null) { + raf.close(); + } + } + } +``` + +### NIO:Files + +```java +Files.exists(path); //true + + + +``` + +## 1 列出:list + +### BIO +递归地列出一个目录下所有文件: + +```java +public static void listAllFiles(File dir) { + if (dir == null || !dir.exists()) { + return; + } + if (dir.isFile()) { + System.out.println(dir.getName()); + return; + } + for (File file : dir.listFiles()) { + listAllFiles(file); + } +} +``` + +从 Java7 开始,可以使用 Paths 和 Files 代替 File。 + + +## 2 复制:copy +### BIO + +```java +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * 利用文件输入流与输出流实现文件的复制操作 + */ +public class CopyDemo { + public static void main(String[] args) throws IOException { + //用文件输入流读取待复制的文件 + FileInputStream fis = new FileInputStream("01.rmvb"); + //用文件输出流向复制文件中写入复制的数据 + FileOutputStream fos = new FileOutputStream("01_cp.rmvb"); + int d;//先定义一个变量,用于记录每次读取到的数据 + long start = System.currentTimeMillis();//获取当前系统时间 + while ((d = fis.read()) != -1) { + fos.write(d); + } + long end = System.currentTimeMillis(); + System.out.println("复制完毕!耗时:" + (end - start) + "ms"); + fis.close(); + fos.close(); + } +} +//带缓冲区的 +```java +public static void copyFile(String src, String dist) throws IOException { + FileInputStream in = new FileInputStream(src); + FileOutputStream out = new FileOutputStream(dist); + + byte[] buffer = new byte[20 * 1024]; + int cnt; + + // read() 最多读取 buffer.length 个字节 + // 返回的是实际读取的个数 + // 返回 -1 的时候表示读到 eof,即文件尾 + while ((cnt = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, cnt); + } + + in.close(); + out.close(); +} +``` +### NIO +```java +package com.howtodoinjava.examples.io; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +public class DirectoryCopyExample +{ + public static void main(String[] args) throws IOException + { + //Source directory which you want to copy to new location + File sourceFolder = new File("c:\\temp"); + + //Target directory where files should be copied + File destinationFolder = new File("c:\\tempNew"); + + //Call Copy function + copyFolder(sourceFolder, destinationFolder); + } + /** + * This function recursively copy all the sub folder and files from sourceFolder to destinationFolder + * */ + private static void copyFolder(File sourceFolder, File destinationFolder) throws IOException + { + //Check if sourceFolder is a directory or file + //If sourceFolder is file; then copy the file directly to new location + if (sourceFolder.isDirectory()) + { + //Verify if destinationFolder is already present; If not then create it + if (!destinationFolder.exists()) + { + destinationFolder.mkdir(); + System.out.println("Directory created :: " + destinationFolder); + } + + //Get all files from source directory + String files[] = sourceFolder.list(); + + //Iterate over all files and copy them to destinationFolder one by one + for (String file : files) + { + File srcFile = new File(sourceFolder, file); + File destFile = new File(destinationFolder, file); + + //Recursive function call + copyFolder(srcFile, destFile); + } + } + else + { + //Copy the file content from one place to another + Files.copy(sourceFolder.toPath(), destinationFolder.toPath(), StandardCopyOption.REPLACE_EXISTING); + System.out.println("File copied :: " + destinationFolder); + } + } +} + +Output: + +Directory created :: c:\tempNew +File copied :: c:\tempNew\testcopied.txt +File copied :: c:\tempNew\testoriginal.txt +File copied :: c:\tempNew\testOut.txt +``` + +### common-util + +```java +private static void fileCopyUsingApacheCommons() throws IOException +{ + File fileToCopy = new File("c:/temp/testoriginal.txt"); + File newFile = new File("c:/temp/testcopied.txt"); + + FileUtils.copyFile(fileToCopy, newFile); + + // OR + + IOUtils.copy(new FileInputStream(fileToCopy), new FileOutputStream(newFile)); +} +``` + +## 3 删除:delete + +### NIO + +```java +public class DeleteDirectoryNIOWithStream +{ + public static void main(String[] args) + { + Path dir = Paths.get("c:/temp/innerDir"); + + Files.walk(dir) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } +} +``` + +### apache commons-io + +``` +public class DeleteDirectoryNIOWithStream +{ + public static void main(String[] args) + { + Path dir = Paths.get("c:/temp/innerDir"); + + Files.walk(dir) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } +} +``` + +## 4 创建create + +### BIO:createNewFile +File.createNewFile()方法创建新文件。 此方法返回布尔值– +* 如果文件创建成功,则返回true 。 +* 如果文件已经存在或操作由于某种原因失败,则返回false 。 +* 此方法不会像文件中写任何数据 + +```java +File file = new File("c://temp//testFile1.txt"); + +//Create the file +if (file.createNewFile()) +{ + System.out.println("File is created!"); +} else { + System.out.println("File already exists."); +} + +//Write Content +FileWriter writer = new FileWriter(file); +writer.write("Test data"); +writer.close(); +``` +### BIO:FileOutputStream + +FileOutputStream.write()方法自动创建一个新文件并向其中写入内容 。 +```java +String data = "Test data"; + +FileOutputStream out = new FileOutputStream("c://temp//testFile2.txt"); + +out.write(data.getBytes()); +out.close(); +``` + +### NIO + +Files.write()是创建文件的最佳方法,如果您尚未使用它,则应该是将来的首选方法。 + +此方法将文本行写入文件 。 每行都是一个char序列,并按顺序写入文件,每行由平台的line separator终止。 + +```java +String data = "Test data"; +Files.write(Paths.get("c://temp//testFile3.txt"), data.getBytes()); + +//or + +List lines = Arrays.asList("1st line", "2nd line"); + +Files.write(Paths.get("file6.txt"), + lines, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND); +``` +## 5 写入:wrirte&append +* append模式,使用BufferedWritter , PrintWriter , FileOutputStream和Files类将内容追加到java中的 Files 。 在所有示例中,在打开要写入的文件时,您都传递了第二个参数true ,表示该文件以append mode打开 + +### BIO:BufferedWriter + +通过BufferedWriter,进行更少的IO操作,提高了性能。 + +```java +public static void usingBufferedWritter() throws IOException +{ + String fileContent = "Hello Learner !! Welcome to howtodoinjava.com."; + + BufferedWriter writer = new BufferedWriter(new FileWriter("c:/temp/samplefile1.txt")); + writer.write(fileContent); + writer.close(); +} +``` + + +### BIO:PrintWriter +使用PrintWriter将格式化的文本写入文件。 +```java +public static void usingPrintWriter() throws IOException +{ + String fileContent = "Hello Learner !! Welcome to howtodoinjava.com."; + + FileWriter fileWriter = new FileWriter("c:/temp/samplefile3.txt"); + PrintWriter printWriter = new PrintWriter(fileWriter); + printWriter.print(fileContent); + printWriter.printf("Blog name is %s", "howtodoinjava.com"); + printWriter.close(); +} +``` + +### BIO:FileOutputStream + +使用FileOutputStream 将二进制数据写入文件 。 FileOutputStream用于写入原始字节流,例如图像数据。 要编写字符流,请考虑使用FileWriter 。 +```java +public static void usingFileOutputStream() throws IOException +{ + String fileContent = "Hello Learner !! Welcome to howtodoinjava.com."; + + FileOutputStream outputStream = new FileOutputStream("c:/temp/samplefile4.txt"); + byte[] strToBytes = fileContent.getBytes(); + outputStream.write(strToBytes); + + outputStream.close(); +} +``` +### BIO:DataOutputStream + +DataOutputStream允许应用程序以可移植的方式将原始Java数据类型写入输出流。 然后,应用程序可以使用数据输入流来读回数据。 + +```java +public static void usingDataOutputStream() throws IOException +{ + String fileContent = "Hello Learner !! Welcome to howtodoinjava.com."; + + FileOutputStream outputStream = new FileOutputStream("c:/temp/samplefile5.txt"); + DataOutputStream dataOutStream = new DataOutputStream(new BufferedOutputStream(outputStream)); + dataOutStream.writeUTF(fileContent); + + dataOutStream.close(); +} +``` + +### NIO:FileChannel + +FileChannel可用于读取,写入,映射和操作文件。 如果要处理大文件,则FileChannel可能比标准IO快。 + +文件通道可以安全地供多个并发线程使用。 + +```java +public static void usingFileChannel() throws IOException +{ + String fileContent = "Hello Learner !! Welcome to howtodoinjava.com."; + + RandomAccessFile stream = new RandomAccessFile("c:/temp/samplefile6.txt", "rw"); + FileChannel channel = stream.getChannel(); + byte[] strBytes = fileContent.getBytes(); + ByteBuffer buffer = ByteBuffer.allocate(strBytes.length); + buffer.put(strBytes); + buffer.flip(); + channel.write(buffer); + stream.close(); + channel.close(); +} +``` + + +### NIO:Files静态方法 + +```java +public static void usingPath() throws IOException +{ + String fileContent = "Hello Learner !! Welcome to howtodoinjava.com."; + + Path path = Paths.get("c:/temp/samplefile7.txt"); + + Files.write(path, fileContent.getBytes()); +} + +public static void usingPath() throws IOException +{ + String textToAppend = "\r\n Happy Learning !!"; //new line in content + + Path path = Paths.get("c:/temp/samplefile.txt"); + + Files.write(path, textToAppend.getBytes(), StandardOpenOption.APPEND); //Append mode +} +``` + + +## 6 读取:read +### BIO:BufferedReader按行读 + +```java +//Using BufferedReader and FileReader - Below Java 7 + +private static String usingBufferedReader(String filePath) +{ + StringBuilder contentBuilder = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new FileReader(filePath))) + { + + String sCurrentLine; + while ((sCurrentLine = br.readLine()) != null) + { + contentBuilder.append(sCurrentLine).append("\n"); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + return contentBuilder.toString(); +``` + +### BIO:FileInputStream 读取字节 + +```java +import java.io.File; +import java.io.FileInputStream; + +public class ContentToByteArrayExample +{ + public static void main(String[] args) + { + + File file = new File("C:/temp/test.txt"); + + readContentIntoByteArray(file); + } + + private static byte[] readContentIntoByteArray(File file) + { + FileInputStream fileInputStream = null; + byte[] bFile = new byte[(int) file.length()]; + try + { + //convert file into array of bytes + fileInputStream = new FileInputStream(file); + fileInputStream.read(bFile); + fileInputStream.close(); + for (int i = 0; i < bFile.length; i++) + { + System.out.print((char) bFile[i]); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + return bFile; + } +} +``` +### NIO:Files按行读 + +lines()方法从文件中读取所有行以进行流传输,并在stream被消耗时延迟填充。 使用指定的字符集将文件中的字节解码为字符。 +readAllBytes()方法reads all the bytes from a file 。 该方法可确保在读取所有字节或引发I / O错误或其他运行时异常时关闭文件。 +```java +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Stream; + +public class ReadFileToString +{ + public static void main(String[] args) + { + String filePath = "c:/temp/data.txt"; + + System.out.println( readLineByLineJava8( filePath ) ); + } + + + //Read file content into string with - Files.lines(Path path, Charset cs) + + private static String readLineByLineJava8(String filePath) + { + StringBuilder contentBuilder = new StringBuilder(); + + try (Stream stream = Files.lines( Paths.get(filePath), StandardCharsets.UTF_8)) + { + stream.forEach(s -> contentBuilder.append(s).append("\n")); + } + catch (IOException e) + { + e.printStackTrace(); + } + + return contentBuilder.toString(); + } +} +``` + +### NIO:读取所有字节 + + +读取所有字节后,我们将这些字节传递给String类构造函数以创建一个字符串 +```java +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class ReadFileToString +{ + public static void main(String[] args) + { + String filePath = "c:/temp/data.txt"; + + System.out.println( readAllBytesJava7( filePath ) ); + } + + //Read file content into string with - Files.readAllBytes(Path path) + + private static String readAllBytesJava7(String filePath) + { + String content = ""; + + try + { + content = new String ( Files.readAllBytes( Paths.get(filePath) ) ); + } + catch (IOException e) + { + e.printStackTrace(); + } + + return content; + } +} +``` + +### commons-io + +```java +//Using FileUtils.readFileToByteArray() +byte[] org.apache.commons.io.FileUtils.readFileToByteArray(File file) + +//Using IOUtils.toByteArray +byte[] org.apache.commons.io.IOUtils.toByteArray(InputStream input) +``` + +## 7 Properties + +任何复杂的应用程序都需要某种配置。 有时我们需要将此配置为只读(通常在应用程序启动时读取),有时(或很少)我们需要写回或更新这些属性配置文件上的内容。 + +在这个简单易用的教程中,学习使用Properties.load()方法读取Java中的Properties.load() 文件 。 然后,我们将使用Properties.setProperty()方法将新属性写入file 。 + +* 创建单实例的属性文件 + + + +```java +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.Set; + +public class PropertiesCache +{ + private final Properties configProp = new Properties(); + + private PropertiesCache() + { + //Private constructor to restrict new instances + InputStream in = this.getClass().getClassLoader().getResourceAsStream("app.properties"); + System.out.println("Read all properties from file"); + try { + configProp.load(in); + } catch (IOException e) { + e.printStackTrace(); + } + } + + //Bill Pugh Solution for singleton pattern + private static class LazyHolder + { + private static final PropertiesCache INSTANCE = new PropertiesCache(); + } + + public static PropertiesCache getInstance() + { + return LazyHolder.INSTANCE; + } + + public String getProperty(String key){ + return configProp.getProperty(key); + } + + public Set getAllPropertyNames(){ + return configProp.stringPropertyNames(); + } + + public boolean containsKey(String key){ + return configProp.containsKey(key); + } +} +``` + +### 补充:静态内部类与懒汉式单例模式 + +这种方式是当被调用getInstance()时才去加载静态内部类LazyHolder,LazyHolder在加载过程中会实例化一个静态的Singleton,因为利用了classloader的机制来保证初始化instance时只有一个线程,所以Singleton肯定只有一个,是线程安全的,这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。 + +```java +public class Singleton { + private static class LazyHolder { + private static final Singleton INSTANCE = new Singleton(); + } + private Singleton (){} + public static final Singleton getInstance() { + return LazyHolder.INSTANCE; + } +} +``` + +## 8 Resource File + +ClassLoader引用从应用程序的资源包中读取文件。加载上下文环境中的文件。 + +```java +package com.howtodoinjava.demo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class ReadResourceFileDemo +{ + public static void main(String[] args) throws IOException + { + String fileName = "config/sample.txt"; + ClassLoader classLoader = new ReadResourceFileDemo().getClass().getClassLoader(); + + File file = new File(classLoader.getResource(fileName).getFile()); + + //File is found + System.out.println("File Found : " + file.exists()); + + //Read File Content + String content = new String(Files.readAllBytes(file.toPath())); + System.out.println(content); + } +} +``` + +### 在spring中可以这样 + +```java +File file = ResourceUtils.getFile("classpath:config/sample.txt") + +//File is found +System.out.println("File Found : " + file.exists()); + +//Read File Content +String content = new String(Files.readAllBytes(file.toPath())); +System.out.println(content); + +``` + + +## 10 读写utf8数据 + +```java +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +public class WriteUTF8Data +{ + public static void main(String[] args) + { + try + { + File fileDir = new File("c:\\temp\\test.txt"); + Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileDir), "UTF8")); + out.append("Howtodoinjava.com").append("\r\n"); + out.append("UTF-8 Demo").append("\r\n"); + out.append("क्षेत्रफल = लंबाई * चौड़ाई").append("\r\n"); + out.flush(); + out.close(); + } catch (UnsupportedEncodingException e) + { + System.out.println(e.getMessage()); + } catch (IOException e) + { + System.out.println(e.getMessage()); + } catch (Exception e) + { + System.out.println(e.getMessage()); + } + } +} +``` + +```java +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; + +public class ReadUTF8Data +{ + public static void main(String[] args) + { + try { + File fileDir = new File("c:\\temp\\test.txt"); + + BufferedReader in = new BufferedReader( + new InputStreamReader( + new FileInputStream(fileDir), "UTF8")); + + String str; + + while ((str = in.readLine()) != null) { + System.out.println(str); + } + + in.close(); + } + catch (UnsupportedEncodingException e) + { + System.out.println(e.getMessage()); + } + catch (IOException e) + { + System.out.println(e.getMessage()); + } + catch (Exception e) + { + System.out.println(e.getMessage()); + } + } +} +``` + + +## 11 从控制台读取输入 + +### console对象 +```java +private static void usingConsoleReader() +{ + Console console = null; + String inputString = null; + try + { + // creates a console object + console = System.console(); + // if console is not null + if (console != null) + { + // read line from the user input + inputString = console.readLine("Name: "); + // prints + System.out.println("Name entered : " + inputString); + } + } catch (Exception ex) + { + ex.printStackTrace(); + } +} +``` + +### System.in封装 + +```java +private static void usingBufferedReader() +{ + System.out.println("Name: "); + try{ + BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in)); + String inputString = bufferRead.readLine(); + + System.out.println("Name entered : " + inputString); + } + catch(IOException ex) + { + ex.printStackTrace(); + } +} +``` + +### 更加复杂的Scanner +```java +private static void usingScanner() +{ + System.out.println("Name: "); + + Scanner scanIn = new Scanner(System.in); + String inputString = scanIn.nextLine(); + + scanIn.close(); + System.out.println("Name entered : " + inputString); +} +``` + + +## 12 将String转换成输入流 + +### ByteArrayInputStream +```java +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +public class ConvertStringToInputStreamExample +{ + public static void main(String[] args) + { + String sampleString = "howtodoinjava.com"; + + //Here converting string to input stream + InputStream stream = new ByteArrayInputStream(sampleString.getBytes()); + } +} +``` + +### apach.IOUtils + +```java +import java.io.InputStream; +import org.apache.commons.io.IOUtils; + +public class ConvertStringToInputStreamExample +{ + public static void main(String[] args) + { + String sampleString = "howtodoinjava.com"; + + //Here converting string to input stream + InputStream stream = IOUtils.toInputStream(sampleString); + } +} +``` \ No newline at end of file diff --git a/Java基础教程/JavaIO与网络编程/04 Java IO对象.md b/Java基础教程/JavaIO与网络编程/04 Java IO对象.md new file mode 100644 index 00000000..55bada01 --- /dev/null +++ b/Java基础教程/JavaIO与网络编程/04 Java IO对象.md @@ -0,0 +1,59 @@ +# 对象操作 + +## 基本操作 +### 序列化 + +序列化就是将一个对象转换成字节序列,方便存储和传输。 + +- 序列化:ObjectOutputStream.writeObject() +- 反序列化:ObjectInputStream.readObject() + +不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。 + +### Serializable + +序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。 + +```java +public static void main(String[] args) throws IOException, ClassNotFoundException { + + A a1 = new A(123, "abc"); + String objectFile = "file/a1"; + + ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile)); + objectOutputStream.writeObject(a1); + objectOutputStream.close(); + + ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(objectFile)); + A a2 = (A) objectInputStream.readObject(); + objectInputStream.close(); + System.out.println(a2); +} + +private static class A implements Serializable { + + private int x; + private String y; + + A(int x, String y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return "x = " + x + " " + "y = " + y; + } +} +``` + + +### transient + +transient 关键字可以使一些属性不会被序列化。 + +ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 + +```java +private transient Object[] elementData; +``` \ No newline at end of file diff --git a/Java基础教程/JavaIO与网络编程/05 Java IO网络.md b/Java基础教程/JavaIO与网络编程/05 Java IO网络.md new file mode 100644 index 00000000..033b96a5 --- /dev/null +++ b/Java基础教程/JavaIO与网络编程/05 Java IO网络.md @@ -0,0 +1,191 @@ +# 网络操作 + + +## 1 网络编程基础 +Java 中的网络支持: + +- InetAddress:用于表示网络上的硬件资源,即 IP 地址; +- URL:统一资源定位符; +- Sockets:使用 TCP 协议实现网络通信; +- Datagram:使用 UDP 协议实现网络通信。 + +### InetAddress + +没有公有的构造函数,只能通过静态方法来创建实例。 + +```java +InetAddress.getByName(String host); +InetAddress.getByAddress(byte[] address); +``` + +### URL + +可以直接从 URL 中读取字节流数据。 + +```java +public static void main(String[] args) throws IOException { + + URL url = new URL("http://www.baidu.com"); + + /* 字节流 */ + InputStream is = url.openStream(); + + /* 字符流 */ + InputStreamReader isr = new InputStreamReader(is, "utf-8"); + + /* 提供缓存功能 */ + BufferedReader br = new BufferedReader(isr); + + String line; + while ((line = br.readLine()) != null) { + System.out.println(line); + } + + br.close(); +} +``` + +### Sockets + +- ServerSocket:服务器端类 +- Socket:客户端类 +- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。 + +

+ +### Datagram + +- DatagramSocket:通信类 +- DatagramPacket:数据包类 + + +## 2 BIO Socket编程 + +```java +public class PlainOioServer { + + public void serve(int port) throws IOException { + final ServerSocket socket = new ServerSocket(port); //1 + try { + for (;;) { + final Socket clientSocket = socket.accept(); //2 + System.out.println("Accepted connection from " + clientSocket); + + new Thread(new Runnable() { //3 + @Override + public void run() { + OutputStream out; + try { + out = clientSocket.getOutputStream(); + out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8"))); //4 + out.flush(); + clientSocket.close(); //5 + + } catch (IOException e) { + e.printStackTrace(); + try { + clientSocket.close(); + } catch (IOException ex) { + // ignore on close + } + } + } + }).start(); //6 + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} +``` + + +## 3 NIO Socket编程 + + +```java +public class NIOServer { + + public static void main(String[] args) throws IOException { + + Selector selector = Selector.open(); + + ServerSocketChannel ssChannel = ServerSocketChannel.open(); + ssChannel.configureBlocking(false); + ssChannel.register(selector, SelectionKey.OP_ACCEPT); + + ServerSocket serverSocket = ssChannel.socket(); + InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888); + serverSocket.bind(address); + + while (true) { + + selector.select(); + Set keys = selector.selectedKeys(); + Iterator keyIterator = keys.iterator(); + + while (keyIterator.hasNext()) { + + SelectionKey key = keyIterator.next(); + + if (key.isAcceptable()) { + + ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel(); + + // 服务器会为每个新连接创建一个 SocketChannel + SocketChannel sChannel = ssChannel1.accept(); + sChannel.configureBlocking(false); + + // 这个新连接主要用于从客户端读取数据 + sChannel.register(selector, SelectionKey.OP_READ); + + } else if (key.isReadable()) { + + SocketChannel sChannel = (SocketChannel) key.channel(); + System.out.println(readDataFromSocketChannel(sChannel)); + sChannel.close(); + } + + keyIterator.remove(); + } + } + } + + private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException { + + ByteBuffer buffer = ByteBuffer.allocate(1024); + StringBuilder data = new StringBuilder(); + + while (true) { + + buffer.clear(); + int n = sChannel.read(buffer); + if (n == -1) { + break; + } + buffer.flip(); + int limit = buffer.limit(); + char[] dst = new char[limit]; + for (int i = 0; i < limit; i++) { + dst[i] = (char) buffer.get(i); + } + data.append(dst); + buffer.clear(); + } + return data.toString(); + } +} +``` + +```java +public class NIOClient { + + public static void main(String[] args) throws IOException { + Socket socket = new Socket("127.0.0.1", 8888); + OutputStream out = socket.getOutputStream(); + String s = "hello world"; + out.write(s.getBytes()); + out.close(); + } +} +``` diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-08-10-51-54.png b/Java基础教程/JavaIO与网络编程/image/2022-11-08-10-51-54.png new file mode 100644 index 00000000..05c795c4 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-08-10-51-54.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-08-10-54-19.png b/Java基础教程/JavaIO与网络编程/image/2022-11-08-10-54-19.png new file mode 100644 index 00000000..67cfddec Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-08-10-54-19.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-26-17-31-29.png b/Java基础教程/JavaIO与网络编程/image/2022-11-26-17-31-29.png new file mode 100644 index 00000000..0d5b5649 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-26-17-31-29.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-26-18-03-55.png b/Java基础教程/JavaIO与网络编程/image/2022-11-26-18-03-55.png new file mode 100644 index 00000000..776044ae Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-26-18-03-55.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-20-23.png b/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-20-23.png new file mode 100644 index 00000000..5fc9e6a8 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-20-23.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-31-24.png b/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-31-24.png new file mode 100644 index 00000000..5d4e8271 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-31-24.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-40-09.png b/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-40-09.png new file mode 100644 index 00000000..25796457 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-40-09.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-40-59.png b/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-40-59.png new file mode 100644 index 00000000..17042c78 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-26-19-40-59.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-26-20-24-47.png b/Java基础教程/JavaIO与网络编程/image/2022-11-26-20-24-47.png new file mode 100644 index 00000000..17042c78 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-26-20-24-47.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-08-19.png b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-08-19.png new file mode 100644 index 00000000..cb7c445f Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-08-19.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-15.png b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-15.png new file mode 100644 index 00000000..d19e8add Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-15.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-28.png b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-28.png new file mode 100644 index 00000000..796defbd Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-28.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-35.png b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-35.png new file mode 100644 index 00000000..03f4ae48 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-35.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-45.png b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-45.png new file mode 100644 index 00000000..e2d409de Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-45.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-59.png b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-59.png new file mode 100644 index 00000000..651bc3ba Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-59.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-48-14.png b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-48-14.png new file mode 100644 index 00000000..62d763c4 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-27-00-48-14.png differ diff --git a/Java基础教程/JavaIO与网络编程/image/2022-11-27-10-06-38.png b/Java基础教程/JavaIO与网络编程/image/2022-11-27-10-06-38.png new file mode 100644 index 00000000..f4c29dd4 Binary files /dev/null and b/Java基础教程/JavaIO与网络编程/image/2022-11-27-10-06-38.png differ diff --git a/Java基础教程/Java网络编程/media/007ab35fabcc3bbba6b8e4defa4a37b1.png b/Java基础教程/JavaIO与网络编程/media/007ab35fabcc3bbba6b8e4defa4a37b1.png similarity index 100% rename from Java基础教程/Java网络编程/media/007ab35fabcc3bbba6b8e4defa4a37b1.png rename to Java基础教程/JavaIO与网络编程/media/007ab35fabcc3bbba6b8e4defa4a37b1.png diff --git a/Java基础教程/Java网络编程/media/028bef0da4c4d8440e13915a126df9ca.png b/Java基础教程/JavaIO与网络编程/media/028bef0da4c4d8440e13915a126df9ca.png similarity index 100% rename from Java基础教程/Java网络编程/media/028bef0da4c4d8440e13915a126df9ca.png rename to Java基础教程/JavaIO与网络编程/media/028bef0da4c4d8440e13915a126df9ca.png diff --git a/Java基础教程/Java网络编程/media/041cbc2e6105e1571f8c6570b651ab08.png b/Java基础教程/JavaIO与网络编程/media/041cbc2e6105e1571f8c6570b651ab08.png similarity index 100% rename from Java基础教程/Java网络编程/media/041cbc2e6105e1571f8c6570b651ab08.png rename to Java基础教程/JavaIO与网络编程/media/041cbc2e6105e1571f8c6570b651ab08.png diff --git a/Java基础教程/Java网络编程/media/065a723dabe70a4f55b1e194d9bbf4d2.png b/Java基础教程/JavaIO与网络编程/media/065a723dabe70a4f55b1e194d9bbf4d2.png similarity index 100% rename from Java基础教程/Java网络编程/media/065a723dabe70a4f55b1e194d9bbf4d2.png rename to Java基础教程/JavaIO与网络编程/media/065a723dabe70a4f55b1e194d9bbf4d2.png diff --git a/Java基础教程/Java网络编程/media/06e9b6a8836c9bea9aa0f49fc2b497e1.png b/Java基础教程/JavaIO与网络编程/media/06e9b6a8836c9bea9aa0f49fc2b497e1.png similarity index 100% rename from Java基础教程/Java网络编程/media/06e9b6a8836c9bea9aa0f49fc2b497e1.png rename to Java基础教程/JavaIO与网络编程/media/06e9b6a8836c9bea9aa0f49fc2b497e1.png diff --git a/Java基础教程/Java网络编程/media/0795fe05778f83e679a5f7fe70dc292d.png b/Java基础教程/JavaIO与网络编程/media/0795fe05778f83e679a5f7fe70dc292d.png similarity index 100% rename from Java基础教程/Java网络编程/media/0795fe05778f83e679a5f7fe70dc292d.png rename to Java基础教程/JavaIO与网络编程/media/0795fe05778f83e679a5f7fe70dc292d.png diff --git a/Java基础教程/Java网络编程/media/0a98a46c93fbd1041bdc163970fcad1f.png b/Java基础教程/JavaIO与网络编程/media/0a98a46c93fbd1041bdc163970fcad1f.png similarity index 100% rename from Java基础教程/Java网络编程/media/0a98a46c93fbd1041bdc163970fcad1f.png rename to Java基础教程/JavaIO与网络编程/media/0a98a46c93fbd1041bdc163970fcad1f.png diff --git a/Java基础教程/Java网络编程/media/0ed020222f07f3d0d0b405b20c9e4199.png b/Java基础教程/JavaIO与网络编程/media/0ed020222f07f3d0d0b405b20c9e4199.png similarity index 100% rename from Java基础教程/Java网络编程/media/0ed020222f07f3d0d0b405b20c9e4199.png rename to Java基础教程/JavaIO与网络编程/media/0ed020222f07f3d0d0b405b20c9e4199.png diff --git a/Java基础教程/Java网络编程/media/12ae308e42698e5ea0ffac38c42a3484.png b/Java基础教程/JavaIO与网络编程/media/12ae308e42698e5ea0ffac38c42a3484.png similarity index 100% rename from Java基础教程/Java网络编程/media/12ae308e42698e5ea0ffac38c42a3484.png rename to Java基础教程/JavaIO与网络编程/media/12ae308e42698e5ea0ffac38c42a3484.png diff --git a/Java基础教程/Java网络编程/media/135097b6a32d7be79d3a02754eb5017a.png b/Java基础教程/JavaIO与网络编程/media/135097b6a32d7be79d3a02754eb5017a.png similarity index 100% rename from Java基础教程/Java网络编程/media/135097b6a32d7be79d3a02754eb5017a.png rename to Java基础教程/JavaIO与网络编程/media/135097b6a32d7be79d3a02754eb5017a.png diff --git a/Java基础教程/Java网络编程/media/13ba18c5d137570035bb5ad25946599b.png b/Java基础教程/JavaIO与网络编程/media/13ba18c5d137570035bb5ad25946599b.png similarity index 100% rename from Java基础教程/Java网络编程/media/13ba18c5d137570035bb5ad25946599b.png rename to Java基础教程/JavaIO与网络编程/media/13ba18c5d137570035bb5ad25946599b.png diff --git a/Java基础教程/Java网络编程/media/1e605723a237772c2404d1bf6d5248c3.gif b/Java基础教程/JavaIO与网络编程/media/1e605723a237772c2404d1bf6d5248c3.gif similarity index 100% rename from Java基础教程/Java网络编程/media/1e605723a237772c2404d1bf6d5248c3.gif rename to Java基础教程/JavaIO与网络编程/media/1e605723a237772c2404d1bf6d5248c3.gif diff --git a/Java基础教程/Java网络编程/media/207fbeca9fd31ed3ab75a2b0b9803b4c.png b/Java基础教程/JavaIO与网络编程/media/207fbeca9fd31ed3ab75a2b0b9803b4c.png similarity index 100% rename from Java基础教程/Java网络编程/media/207fbeca9fd31ed3ab75a2b0b9803b4c.png rename to Java基础教程/JavaIO与网络编程/media/207fbeca9fd31ed3ab75a2b0b9803b4c.png diff --git a/Java基础教程/Java网络编程/media/21202143ea74374015361d25b0455b5f.png b/Java基础教程/JavaIO与网络编程/media/21202143ea74374015361d25b0455b5f.png similarity index 100% rename from Java基础教程/Java网络编程/media/21202143ea74374015361d25b0455b5f.png rename to Java基础教程/JavaIO与网络编程/media/21202143ea74374015361d25b0455b5f.png diff --git a/Java基础教程/Java网络编程/media/23275a1f62680918e7891389fa9a149f.png b/Java基础教程/JavaIO与网络编程/media/23275a1f62680918e7891389fa9a149f.png similarity index 100% rename from Java基础教程/Java网络编程/media/23275a1f62680918e7891389fa9a149f.png rename to Java基础教程/JavaIO与网络编程/media/23275a1f62680918e7891389fa9a149f.png diff --git a/Java基础教程/Java网络编程/media/23aa409dbf14d3a002a46df7d8ea5c77.png b/Java基础教程/JavaIO与网络编程/media/23aa409dbf14d3a002a46df7d8ea5c77.png similarity index 100% rename from Java基础教程/Java网络编程/media/23aa409dbf14d3a002a46df7d8ea5c77.png rename to Java基础教程/JavaIO与网络编程/media/23aa409dbf14d3a002a46df7d8ea5c77.png diff --git a/Java基础教程/Java网络编程/media/26005b0c83224fe160d6cb7658367c2c.png b/Java基础教程/JavaIO与网络编程/media/26005b0c83224fe160d6cb7658367c2c.png similarity index 100% rename from Java基础教程/Java网络编程/media/26005b0c83224fe160d6cb7658367c2c.png rename to Java基础教程/JavaIO与网络编程/media/26005b0c83224fe160d6cb7658367c2c.png diff --git a/Java基础教程/Java网络编程/media/2602720f7f65ca5d4a9d8af2abcf3569.png b/Java基础教程/JavaIO与网络编程/media/2602720f7f65ca5d4a9d8af2abcf3569.png similarity index 100% rename from Java基础教程/Java网络编程/media/2602720f7f65ca5d4a9d8af2abcf3569.png rename to Java基础教程/JavaIO与网络编程/media/2602720f7f65ca5d4a9d8af2abcf3569.png diff --git a/Java基础教程/Java网络编程/media/288b81f4a8937847c3e775b86b07eaa3.png b/Java基础教程/JavaIO与网络编程/media/288b81f4a8937847c3e775b86b07eaa3.png similarity index 100% rename from Java基础教程/Java网络编程/media/288b81f4a8937847c3e775b86b07eaa3.png rename to Java基础教程/JavaIO与网络编程/media/288b81f4a8937847c3e775b86b07eaa3.png diff --git a/Java基础教程/Java网络编程/media/29a9dcc45fcd9a687b53ae6ff8d0e7c2.png b/Java基础教程/JavaIO与网络编程/media/29a9dcc45fcd9a687b53ae6ff8d0e7c2.png similarity index 100% rename from Java基础教程/Java网络编程/media/29a9dcc45fcd9a687b53ae6ff8d0e7c2.png rename to Java基础教程/JavaIO与网络编程/media/29a9dcc45fcd9a687b53ae6ff8d0e7c2.png diff --git a/Java基础教程/Java网络编程/media/2ba3630488f0f2874f32bc5bd8a236dd.png b/Java基础教程/JavaIO与网络编程/media/2ba3630488f0f2874f32bc5bd8a236dd.png similarity index 100% rename from Java基础教程/Java网络编程/media/2ba3630488f0f2874f32bc5bd8a236dd.png rename to Java基础教程/JavaIO与网络编程/media/2ba3630488f0f2874f32bc5bd8a236dd.png diff --git a/Java基础教程/Java网络编程/media/310c3e09c44d270fe52c0e360aab83b5.png b/Java基础教程/JavaIO与网络编程/media/310c3e09c44d270fe52c0e360aab83b5.png similarity index 100% rename from Java基础教程/Java网络编程/media/310c3e09c44d270fe52c0e360aab83b5.png rename to Java基础教程/JavaIO与网络编程/media/310c3e09c44d270fe52c0e360aab83b5.png diff --git a/Java基础教程/Java网络编程/media/32695a79080b5d070d180cd90dc6a60f.png b/Java基础教程/JavaIO与网络编程/media/32695a79080b5d070d180cd90dc6a60f.png similarity index 100% rename from Java基础教程/Java网络编程/media/32695a79080b5d070d180cd90dc6a60f.png rename to Java基础教程/JavaIO与网络编程/media/32695a79080b5d070d180cd90dc6a60f.png diff --git a/Java基础教程/Java网络编程/media/339890a404ce80ccaad34f1f9f8761ab.png b/Java基础教程/JavaIO与网络编程/media/339890a404ce80ccaad34f1f9f8761ab.png similarity index 100% rename from Java基础教程/Java网络编程/media/339890a404ce80ccaad34f1f9f8761ab.png rename to Java基础教程/JavaIO与网络编程/media/339890a404ce80ccaad34f1f9f8761ab.png diff --git a/Java基础教程/Java网络编程/media/352644fe8e44bed2bcc5029e19f36a2d.png b/Java基础教程/JavaIO与网络编程/media/352644fe8e44bed2bcc5029e19f36a2d.png similarity index 100% rename from Java基础教程/Java网络编程/media/352644fe8e44bed2bcc5029e19f36a2d.png rename to Java基础教程/JavaIO与网络编程/media/352644fe8e44bed2bcc5029e19f36a2d.png diff --git a/Java基础教程/Java网络编程/media/3be95dd9e28fc013f1872e48c76e7e72.png b/Java基础教程/JavaIO与网络编程/media/3be95dd9e28fc013f1872e48c76e7e72.png similarity index 100% rename from Java基础教程/Java网络编程/media/3be95dd9e28fc013f1872e48c76e7e72.png rename to Java基础教程/JavaIO与网络编程/media/3be95dd9e28fc013f1872e48c76e7e72.png diff --git a/Java基础教程/Java网络编程/media/3c2c1fd4f17e7976e8a31c089251f780.png b/Java基础教程/JavaIO与网络编程/media/3c2c1fd4f17e7976e8a31c089251f780.png similarity index 100% rename from Java基础教程/Java网络编程/media/3c2c1fd4f17e7976e8a31c089251f780.png rename to Java基础教程/JavaIO与网络编程/media/3c2c1fd4f17e7976e8a31c089251f780.png diff --git a/Java基础教程/Java网络编程/media/3d25120fdc157d7eb22f3a1cf36a7885.png b/Java基础教程/JavaIO与网络编程/media/3d25120fdc157d7eb22f3a1cf36a7885.png similarity index 100% rename from Java基础教程/Java网络编程/media/3d25120fdc157d7eb22f3a1cf36a7885.png rename to Java基础教程/JavaIO与网络编程/media/3d25120fdc157d7eb22f3a1cf36a7885.png diff --git a/Java基础教程/Java网络编程/media/410d4cf19c8273fcf391b4bbcfa7b391.png b/Java基础教程/JavaIO与网络编程/media/410d4cf19c8273fcf391b4bbcfa7b391.png similarity index 100% rename from Java基础教程/Java网络编程/media/410d4cf19c8273fcf391b4bbcfa7b391.png rename to Java基础教程/JavaIO与网络编程/media/410d4cf19c8273fcf391b4bbcfa7b391.png diff --git a/Java基础教程/Java网络编程/media/41fd9830599e374b63260ae7dece858e.png b/Java基础教程/JavaIO与网络编程/media/41fd9830599e374b63260ae7dece858e.png similarity index 100% rename from Java基础教程/Java网络编程/media/41fd9830599e374b63260ae7dece858e.png rename to Java基础教程/JavaIO与网络编程/media/41fd9830599e374b63260ae7dece858e.png diff --git a/Java基础教程/Java网络编程/media/421fd9e102576846483b67c53d23f170.png b/Java基础教程/JavaIO与网络编程/media/421fd9e102576846483b67c53d23f170.png similarity index 100% rename from Java基础教程/Java网络编程/media/421fd9e102576846483b67c53d23f170.png rename to Java基础教程/JavaIO与网络编程/media/421fd9e102576846483b67c53d23f170.png diff --git a/Java基础教程/Java网络编程/media/46764bdc5a287633a25d30b52cf0ae1e.png b/Java基础教程/JavaIO与网络编程/media/46764bdc5a287633a25d30b52cf0ae1e.png similarity index 100% rename from Java基础教程/Java网络编程/media/46764bdc5a287633a25d30b52cf0ae1e.png rename to Java基础教程/JavaIO与网络编程/media/46764bdc5a287633a25d30b52cf0ae1e.png diff --git a/Java基础教程/Java网络编程/media/46851e91409fb2dce25933f725a3e1ce.png b/Java基础教程/JavaIO与网络编程/media/46851e91409fb2dce25933f725a3e1ce.png similarity index 100% rename from Java基础教程/Java网络编程/media/46851e91409fb2dce25933f725a3e1ce.png rename to Java基础教程/JavaIO与网络编程/media/46851e91409fb2dce25933f725a3e1ce.png diff --git a/Java基础教程/Java网络编程/media/4f027230571687bae1d7d5fd6c887262.png b/Java基础教程/JavaIO与网络编程/media/4f027230571687bae1d7d5fd6c887262.png similarity index 100% rename from Java基础教程/Java网络编程/media/4f027230571687bae1d7d5fd6c887262.png rename to Java基础教程/JavaIO与网络编程/media/4f027230571687bae1d7d5fd6c887262.png diff --git a/Java基础教程/Java网络编程/media/51e409b11aa51c150090697429a953ed.gif b/Java基础教程/JavaIO与网络编程/media/51e409b11aa51c150090697429a953ed.gif similarity index 100% rename from Java基础教程/Java网络编程/media/51e409b11aa51c150090697429a953ed.gif rename to Java基础教程/JavaIO与网络编程/media/51e409b11aa51c150090697429a953ed.gif diff --git a/Java基础教程/Java网络编程/media/53c99757ef646fb19c968c270261f997.png b/Java基础教程/JavaIO与网络编程/media/53c99757ef646fb19c968c270261f997.png similarity index 100% rename from Java基础教程/Java网络编程/media/53c99757ef646fb19c968c270261f997.png rename to Java基础教程/JavaIO与网络编程/media/53c99757ef646fb19c968c270261f997.png diff --git a/Java基础教程/Java网络编程/media/554c75f84f8e53347e13bc5bfae5c2ed.png b/Java基础教程/JavaIO与网络编程/media/554c75f84f8e53347e13bc5bfae5c2ed.png similarity index 100% rename from Java基础教程/Java网络编程/media/554c75f84f8e53347e13bc5bfae5c2ed.png rename to Java基础教程/JavaIO与网络编程/media/554c75f84f8e53347e13bc5bfae5c2ed.png diff --git a/Java基础教程/Java网络编程/media/5662bedb45e733ee84d631c05aa45a1b.png b/Java基础教程/JavaIO与网络编程/media/5662bedb45e733ee84d631c05aa45a1b.png similarity index 100% rename from Java基础教程/Java网络编程/media/5662bedb45e733ee84d631c05aa45a1b.png rename to Java基础教程/JavaIO与网络编程/media/5662bedb45e733ee84d631c05aa45a1b.png diff --git a/Java基础教程/Java网络编程/media/5920ee3aa726049e1d5a905a51614daa.png b/Java基础教程/JavaIO与网络编程/media/5920ee3aa726049e1d5a905a51614daa.png similarity index 100% rename from Java基础教程/Java网络编程/media/5920ee3aa726049e1d5a905a51614daa.png rename to Java基础教程/JavaIO与网络编程/media/5920ee3aa726049e1d5a905a51614daa.png diff --git a/Java基础教程/Java网络编程/media/5a5e5697905e0cbc15ff7a0e76a0dbfc.png b/Java基础教程/JavaIO与网络编程/media/5a5e5697905e0cbc15ff7a0e76a0dbfc.png similarity index 100% rename from Java基础教程/Java网络编程/media/5a5e5697905e0cbc15ff7a0e76a0dbfc.png rename to Java基础教程/JavaIO与网络编程/media/5a5e5697905e0cbc15ff7a0e76a0dbfc.png diff --git a/Java基础教程/Java网络编程/media/5c81759ac65a3e108718d187ceb1e24b.png b/Java基础教程/JavaIO与网络编程/media/5c81759ac65a3e108718d187ceb1e24b.png similarity index 100% rename from Java基础教程/Java网络编程/media/5c81759ac65a3e108718d187ceb1e24b.png rename to Java基础教程/JavaIO与网络编程/media/5c81759ac65a3e108718d187ceb1e24b.png diff --git a/Java基础教程/Java网络编程/media/5cf5dde716475024008c7505ed210def.png b/Java基础教程/JavaIO与网络编程/media/5cf5dde716475024008c7505ed210def.png similarity index 100% rename from Java基础教程/Java网络编程/media/5cf5dde716475024008c7505ed210def.png rename to Java基础教程/JavaIO与网络编程/media/5cf5dde716475024008c7505ed210def.png diff --git a/Java基础教程/Java网络编程/media/5dbaec9196d3822fbfe811ee7dc3dbd5.png b/Java基础教程/JavaIO与网络编程/media/5dbaec9196d3822fbfe811ee7dc3dbd5.png similarity index 100% rename from Java基础教程/Java网络编程/media/5dbaec9196d3822fbfe811ee7dc3dbd5.png rename to Java基础教程/JavaIO与网络编程/media/5dbaec9196d3822fbfe811ee7dc3dbd5.png diff --git a/Java基础教程/Java网络编程/media/5e148d9d5712418811886d86b891e823.png b/Java基础教程/JavaIO与网络编程/media/5e148d9d5712418811886d86b891e823.png similarity index 100% rename from Java基础教程/Java网络编程/media/5e148d9d5712418811886d86b891e823.png rename to Java基础教程/JavaIO与网络编程/media/5e148d9d5712418811886d86b891e823.png diff --git a/Java基础教程/Java网络编程/media/5f4ea984a41d2eb6cd5e2bdb760a0ad9.png b/Java基础教程/JavaIO与网络编程/media/5f4ea984a41d2eb6cd5e2bdb760a0ad9.png similarity index 100% rename from Java基础教程/Java网络编程/media/5f4ea984a41d2eb6cd5e2bdb760a0ad9.png rename to Java基础教程/JavaIO与网络编程/media/5f4ea984a41d2eb6cd5e2bdb760a0ad9.png diff --git a/Java基础教程/Java网络编程/media/64c795a7d1f06eb3d76e02ae2a301db9.png b/Java基础教程/JavaIO与网络编程/media/64c795a7d1f06eb3d76e02ae2a301db9.png similarity index 100% rename from Java基础教程/Java网络编程/media/64c795a7d1f06eb3d76e02ae2a301db9.png rename to Java基础教程/JavaIO与网络编程/media/64c795a7d1f06eb3d76e02ae2a301db9.png diff --git a/Java基础教程/Java网络编程/media/661d914f0508cf83bd6b3344bf5d1ad1.png b/Java基础教程/JavaIO与网络编程/media/661d914f0508cf83bd6b3344bf5d1ad1.png similarity index 100% rename from Java基础教程/Java网络编程/media/661d914f0508cf83bd6b3344bf5d1ad1.png rename to Java基础教程/JavaIO与网络编程/media/661d914f0508cf83bd6b3344bf5d1ad1.png diff --git a/Java基础教程/Java网络编程/media/682155b3d9bb95cf9e0764d21aa398df.png b/Java基础教程/JavaIO与网络编程/media/682155b3d9bb95cf9e0764d21aa398df.png similarity index 100% rename from Java基础教程/Java网络编程/media/682155b3d9bb95cf9e0764d21aa398df.png rename to Java基础教程/JavaIO与网络编程/media/682155b3d9bb95cf9e0764d21aa398df.png diff --git a/Java基础教程/Java网络编程/media/72d35439798aa9e9411cc9f85e65412d.png b/Java基础教程/JavaIO与网络编程/media/72d35439798aa9e9411cc9f85e65412d.png similarity index 100% rename from Java基础教程/Java网络编程/media/72d35439798aa9e9411cc9f85e65412d.png rename to Java基础教程/JavaIO与网络编程/media/72d35439798aa9e9411cc9f85e65412d.png diff --git a/Java基础教程/Java网络编程/media/7343332168b1cd6d2398995412c4739b.png b/Java基础教程/JavaIO与网络编程/media/7343332168b1cd6d2398995412c4739b.png similarity index 100% rename from Java基础教程/Java网络编程/media/7343332168b1cd6d2398995412c4739b.png rename to Java基础教程/JavaIO与网络编程/media/7343332168b1cd6d2398995412c4739b.png diff --git a/Java基础教程/Java网络编程/media/74d89c3525a5fb85f03cbb3fdea9ac0b.png b/Java基础教程/JavaIO与网络编程/media/74d89c3525a5fb85f03cbb3fdea9ac0b.png similarity index 100% rename from Java基础教程/Java网络编程/media/74d89c3525a5fb85f03cbb3fdea9ac0b.png rename to Java基础教程/JavaIO与网络编程/media/74d89c3525a5fb85f03cbb3fdea9ac0b.png diff --git a/Java基础教程/Java网络编程/media/7f69ee69093961598550e28f7e25719a.png b/Java基础教程/JavaIO与网络编程/media/7f69ee69093961598550e28f7e25719a.png similarity index 100% rename from Java基础教程/Java网络编程/media/7f69ee69093961598550e28f7e25719a.png rename to Java基础教程/JavaIO与网络编程/media/7f69ee69093961598550e28f7e25719a.png diff --git a/Java基础教程/Java网络编程/media/8b63587275597554721b193b36793bdd.png b/Java基础教程/JavaIO与网络编程/media/8b63587275597554721b193b36793bdd.png similarity index 100% rename from Java基础教程/Java网络编程/media/8b63587275597554721b193b36793bdd.png rename to Java基础教程/JavaIO与网络编程/media/8b63587275597554721b193b36793bdd.png diff --git a/Java基础教程/Java网络编程/media/946ac67c66320b025c977140449f2726.png b/Java基础教程/JavaIO与网络编程/media/946ac67c66320b025c977140449f2726.png similarity index 100% rename from Java基础教程/Java网络编程/media/946ac67c66320b025c977140449f2726.png rename to Java基础教程/JavaIO与网络编程/media/946ac67c66320b025c977140449f2726.png diff --git a/Java基础教程/Java网络编程/media/999fffbe3d46bc883b98ea7b5769a7da.png b/Java基础教程/JavaIO与网络编程/media/999fffbe3d46bc883b98ea7b5769a7da.png similarity index 100% rename from Java基础教程/Java网络编程/media/999fffbe3d46bc883b98ea7b5769a7da.png rename to Java基础教程/JavaIO与网络编程/media/999fffbe3d46bc883b98ea7b5769a7da.png diff --git a/Java基础教程/Java网络编程/media/a332ee101f4e360193d878fcf2bdb926.png b/Java基础教程/JavaIO与网络编程/media/a332ee101f4e360193d878fcf2bdb926.png similarity index 100% rename from Java基础教程/Java网络编程/media/a332ee101f4e360193d878fcf2bdb926.png rename to Java基础教程/JavaIO与网络编程/media/a332ee101f4e360193d878fcf2bdb926.png diff --git a/Java基础教程/Java网络编程/media/a984c00d0f7846158558aa25291e6a83.png b/Java基础教程/JavaIO与网络编程/media/a984c00d0f7846158558aa25291e6a83.png similarity index 100% rename from Java基础教程/Java网络编程/media/a984c00d0f7846158558aa25291e6a83.png rename to Java基础教程/JavaIO与网络编程/media/a984c00d0f7846158558aa25291e6a83.png diff --git a/Java基础教程/Java网络编程/media/ab38369f8043d690b448fe8e8004a755.png b/Java基础教程/JavaIO与网络编程/media/ab38369f8043d690b448fe8e8004a755.png similarity index 100% rename from Java基础教程/Java网络编程/media/ab38369f8043d690b448fe8e8004a755.png rename to Java基础教程/JavaIO与网络编程/media/ab38369f8043d690b448fe8e8004a755.png diff --git a/Java基础教程/Java网络编程/media/ac2af137ab9c52245d2e72ceafffee76.png b/Java基础教程/JavaIO与网络编程/media/ac2af137ab9c52245d2e72ceafffee76.png similarity index 100% rename from Java基础教程/Java网络编程/media/ac2af137ab9c52245d2e72ceafffee76.png rename to Java基础教程/JavaIO与网络编程/media/ac2af137ab9c52245d2e72ceafffee76.png diff --git a/Java基础教程/Java网络编程/media/b24ac0390788e8547c3f60daee9eaacf.jpeg b/Java基础教程/JavaIO与网络编程/media/b24ac0390788e8547c3f60daee9eaacf.jpeg similarity index 100% rename from Java基础教程/Java网络编程/media/b24ac0390788e8547c3f60daee9eaacf.jpeg rename to Java基础教程/JavaIO与网络编程/media/b24ac0390788e8547c3f60daee9eaacf.jpeg diff --git a/Java基础教程/Java网络编程/media/b2c4e7c54fc6c246e437b80f8752b75d.png b/Java基础教程/JavaIO与网络编程/media/b2c4e7c54fc6c246e437b80f8752b75d.png similarity index 100% rename from Java基础教程/Java网络编程/media/b2c4e7c54fc6c246e437b80f8752b75d.png rename to Java基础教程/JavaIO与网络编程/media/b2c4e7c54fc6c246e437b80f8752b75d.png diff --git a/Java基础教程/Java网络编程/media/b8e04854836dbd4d12d08351c1e70332.png b/Java基础教程/JavaIO与网络编程/media/b8e04854836dbd4d12d08351c1e70332.png similarity index 100% rename from Java基础教程/Java网络编程/media/b8e04854836dbd4d12d08351c1e70332.png rename to Java基础教程/JavaIO与网络编程/media/b8e04854836dbd4d12d08351c1e70332.png diff --git a/Java基础教程/Java网络编程/media/b9541e310983abc3936b385e852c23b0.png b/Java基础教程/JavaIO与网络编程/media/b9541e310983abc3936b385e852c23b0.png similarity index 100% rename from Java基础教程/Java网络编程/media/b9541e310983abc3936b385e852c23b0.png rename to Java基础教程/JavaIO与网络编程/media/b9541e310983abc3936b385e852c23b0.png diff --git a/Java基础教程/Java网络编程/media/bf6e89db6228034cd1d1f820a54d9332.png b/Java基础教程/JavaIO与网络编程/media/bf6e89db6228034cd1d1f820a54d9332.png similarity index 100% rename from Java基础教程/Java网络编程/media/bf6e89db6228034cd1d1f820a54d9332.png rename to Java基础教程/JavaIO与网络编程/media/bf6e89db6228034cd1d1f820a54d9332.png diff --git a/Java基础教程/Java网络编程/media/c0987f0a7d6c303402b2dceb1d8d6f13.png b/Java基础教程/JavaIO与网络编程/media/c0987f0a7d6c303402b2dceb1d8d6f13.png similarity index 100% rename from Java基础教程/Java网络编程/media/c0987f0a7d6c303402b2dceb1d8d6f13.png rename to Java基础教程/JavaIO与网络编程/media/c0987f0a7d6c303402b2dceb1d8d6f13.png diff --git a/Java基础教程/Java网络编程/media/dca5742acd83b6d3f86f5b3af9cf1fcb.png b/Java基础教程/JavaIO与网络编程/media/dca5742acd83b6d3f86f5b3af9cf1fcb.png similarity index 100% rename from Java基础教程/Java网络编程/media/dca5742acd83b6d3f86f5b3af9cf1fcb.png rename to Java基础教程/JavaIO与网络编程/media/dca5742acd83b6d3f86f5b3af9cf1fcb.png diff --git a/Java基础教程/Java网络编程/media/e9ef17fc1494294fd805ec482f8fe2cc.png b/Java基础教程/JavaIO与网络编程/media/e9ef17fc1494294fd805ec482f8fe2cc.png similarity index 100% rename from Java基础教程/Java网络编程/media/e9ef17fc1494294fd805ec482f8fe2cc.png rename to Java基础教程/JavaIO与网络编程/media/e9ef17fc1494294fd805ec482f8fe2cc.png diff --git a/Java基础教程/Java网络编程/media/eaac8b5cec8dde91bba8c2d0ebf4df12.png b/Java基础教程/JavaIO与网络编程/media/eaac8b5cec8dde91bba8c2d0ebf4df12.png similarity index 100% rename from Java基础教程/Java网络编程/media/eaac8b5cec8dde91bba8c2d0ebf4df12.png rename to Java基础教程/JavaIO与网络编程/media/eaac8b5cec8dde91bba8c2d0ebf4df12.png diff --git a/Java基础教程/Java网络编程/media/f28b2b1dc1614e898cb4255e41d37d00.png b/Java基础教程/JavaIO与网络编程/media/f28b2b1dc1614e898cb4255e41d37d00.png similarity index 100% rename from Java基础教程/Java网络编程/media/f28b2b1dc1614e898cb4255e41d37d00.png rename to Java基础教程/JavaIO与网络编程/media/f28b2b1dc1614e898cb4255e41d37d00.png diff --git a/Java基础教程/Java网络编程/media/f2f880588756d88cbf5083a94fbd41ae.jpeg b/Java基础教程/JavaIO与网络编程/media/f2f880588756d88cbf5083a94fbd41ae.jpeg similarity index 100% rename from Java基础教程/Java网络编程/media/f2f880588756d88cbf5083a94fbd41ae.jpeg rename to Java基础教程/JavaIO与网络编程/media/f2f880588756d88cbf5083a94fbd41ae.jpeg diff --git a/Java基础教程/Java网络编程/media/f4495b0ffb8c7fa60db31e6bfad35328.png b/Java基础教程/JavaIO与网络编程/media/f4495b0ffb8c7fa60db31e6bfad35328.png similarity index 100% rename from Java基础教程/Java网络编程/media/f4495b0ffb8c7fa60db31e6bfad35328.png rename to Java基础教程/JavaIO与网络编程/media/f4495b0ffb8c7fa60db31e6bfad35328.png diff --git a/Java基础教程/Java网络编程/media/f5953ff98bf276f97b08461d9460e10b.png b/Java基础教程/JavaIO与网络编程/media/f5953ff98bf276f97b08461d9460e10b.png similarity index 100% rename from Java基础教程/Java网络编程/media/f5953ff98bf276f97b08461d9460e10b.png rename to Java基础教程/JavaIO与网络编程/media/f5953ff98bf276f97b08461d9460e10b.png diff --git a/Java基础教程/Java网络编程/media/f658002b86035d44ed08b7e2dbd4fffc.png b/Java基础教程/JavaIO与网络编程/media/f658002b86035d44ed08b7e2dbd4fffc.png similarity index 100% rename from Java基础教程/Java网络编程/media/f658002b86035d44ed08b7e2dbd4fffc.png rename to Java基础教程/JavaIO与网络编程/media/f658002b86035d44ed08b7e2dbd4fffc.png diff --git a/Java基础教程/Java网络编程/media/f837ce408bd2404325047c0762c206db.png b/Java基础教程/JavaIO与网络编程/media/f837ce408bd2404325047c0762c206db.png similarity index 100% rename from Java基础教程/Java网络编程/media/f837ce408bd2404325047c0762c206db.png rename to Java基础教程/JavaIO与网络编程/media/f837ce408bd2404325047c0762c206db.png diff --git a/Java基础教程/Java网络编程/media/f8f1212105e495ccfca7866fb6baaf26.png b/Java基础教程/JavaIO与网络编程/media/f8f1212105e495ccfca7866fb6baaf26.png similarity index 100% rename from Java基础教程/Java网络编程/media/f8f1212105e495ccfca7866fb6baaf26.png rename to Java基础教程/JavaIO与网络编程/media/f8f1212105e495ccfca7866fb6baaf26.png diff --git a/Java基础教程/Java网络编程/media/f9577fd8236c564d81c08514aaa46e3c.png b/Java基础教程/JavaIO与网络编程/media/f9577fd8236c564d81c08514aaa46e3c.png similarity index 100% rename from Java基础教程/Java网络编程/media/f9577fd8236c564d81c08514aaa46e3c.png rename to Java基础教程/JavaIO与网络编程/media/f9577fd8236c564d81c08514aaa46e3c.png diff --git a/Java基础教程/Java网络编程/media/fb087dd562da5b0cebc1224cb5c0e3ac.png b/Java基础教程/JavaIO与网络编程/media/fb087dd562da5b0cebc1224cb5c0e3ac.png similarity index 100% rename from Java基础教程/Java网络编程/media/fb087dd562da5b0cebc1224cb5c0e3ac.png rename to Java基础教程/JavaIO与网络编程/media/fb087dd562da5b0cebc1224cb5c0e3ac.png diff --git a/Java基础教程/Java网络编程/media/fde4f0671aec53278e9e0cdf7c6f9b6d.png b/Java基础教程/JavaIO与网络编程/media/fde4f0671aec53278e9e0cdf7c6f9b6d.png similarity index 100% rename from Java基础教程/Java网络编程/media/fde4f0671aec53278e9e0cdf7c6f9b6d.png rename to Java基础教程/JavaIO与网络编程/media/fde4f0671aec53278e9e0cdf7c6f9b6d.png diff --git a/Java基础教程/Java网络编程/media/fdff5669c899369c5ac4faa37e6a2df3.png b/Java基础教程/JavaIO与网络编程/media/fdff5669c899369c5ac4faa37e6a2df3.png similarity index 100% rename from Java基础教程/Java网络编程/media/fdff5669c899369c5ac4faa37e6a2df3.png rename to Java基础教程/JavaIO与网络编程/media/fdff5669c899369c5ac4faa37e6a2df3.png diff --git a/Java基础教程/Java网络编程/理论课复习提纲 (1).md b/Java基础教程/JavaIO与网络编程/附录1:分布式计算.md similarity index 99% rename from Java基础教程/Java网络编程/理论课复习提纲 (1).md rename to Java基础教程/JavaIO与网络编程/附录1:分布式计算.md index 4f5b8b5a..0b3538b8 100644 --- a/Java基础教程/Java网络编程/理论课复习提纲 (1).md +++ b/Java基础教程/JavaIO与网络编程/附录1:分布式计算.md @@ -1,3 +1,5 @@ +## 分布式计算的问答 + 第一章 概论 分布计算的定义与挑战 diff --git a/Java基础教程/Java网络编程/java 输入输出流问题.md b/Java基础教程/JavaIO与网络编程/附录2:流的选择.md similarity index 60% rename from Java基础教程/Java网络编程/java 输入输出流问题.md rename to Java基础教程/JavaIO与网络编程/附录2:流的选择.md index ec6111d6..7a920273 100644 --- a/Java基础教程/Java网络编程/java 输入输出流问题.md +++ b/Java基础教程/JavaIO与网络编程/附录2:流的选择.md @@ -1,16 +1,18 @@ -1)确定是数据源和数据目的(输入还是输出) +## 如何选择流对象 -源:输入流 InputStream Reader +### 1)确定是数据源和数据目的(输入还是输出) -目的:输出流 OutputStream Writer +* 源:输入流 InputStream Reader -2)明确操作的数据对象是否是纯文本 +* 目的:输出流 OutputStream Writer + +### 2)明确操作的数据对象是否是纯文本 是:字符流Reader,Writer 否:字节流InputStream,OutputStream -3)明确具体的设备。 +### 3)明确具体的设备。 是硬盘文件:File++: @@ -31,34 +33,17 @@ StringReader, StringWriter 是键盘:用System.in(是一个InputStream对象)读取,用System.out(是一个OutoutStream对象)打印 -3)是否需要转换流 +### 3)是否需要转换流 是,就使用转换流,从Stream转化为Reader,Writer:InputStreamReader,OutputStreamWriter -4)是否需要缓冲提高效率 +### 4)是否需要缓冲提高效率 是就加上Buffered:BufferedInputStream, BufferedOuputStream, BuffereaReader, BufferedWriter -5)是否需要格式化输出 +### 5)是否需要格式化输出 -例:将一个文本文件中数据存储到另一个文件中。 - -1)数据源和数据目的:读取流,InputStream Reader 输出:OutputStream Writer - -2)是否纯文本:是!这时就可以选择Reader Writer。 - -3)设备:是硬盘文件。Reader体系中可以操作文件的对象是 FileReader FileWriter。 - -FileReader fr = new FileReader("a.txt"); - -FileWriter fw = new FileWriter("b.txt"); - -4)是否需要提高效率:是,加Buffer - -BufferedReader bfr = new BufferedReader(new FileReader("a.txt"); ); - -BufferedWriter bfw = new BufferedWriter(new FileWriter("b.txt"); ); PrintStream diff --git a/Java基础教程/JavaIO与网络编程/附录3:IO、网络编程与web开发的关系.md b/Java基础教程/JavaIO与网络编程/附录3:IO、网络编程与web开发的关系.md new file mode 100644 index 00000000..aac63d79 --- /dev/null +++ b/Java基础教程/JavaIO与网络编程/附录3:IO、网络编程与web开发的关系.md @@ -0,0 +1,23 @@ +最近接触到的通信框架有点多,需要从头多学习一点 + + +### IO方式 +以下是IO方式。包括网络IO、文件IO等各种IO场景,不只是网络通信。说的是内存数据加载的方式,专注于一端。对应java中的java.io和java.nio两个包。 + +1. Java BIO 阻塞IO +2. Java NIO-Netty 非阻塞IO +3. Java AIO 异步IO + +### 网络编程 +以下是网络通信框架。提供了客户端和服务端必须一一对应。无应用层协议,是传输层的封装。包括两端,每一端都通过IO模型加载和写入数据。 + +1. socket 是传统的端到端通信模型,最基本的网络通信框架。对端也必须是socket协议的服务端。其底层基于不同的JavaIO方式。 +2. sofabolt 是alibaba的网络通信框架。对端也必须是sofabolt协议的服务端。 +3. Netty 是开源的网络通信框架。 + +### web开发 +以下三个是Http服务实现。技能够提供http协议的服务端,也能实现http协议的客户端。是有协议的。 +1. Servlet 最基本的http服务实现方法。采用多线程阻塞的方式。包括服务端和HttpClient,可以独立使用 +2. Java ws.rs - Resteasy 基于标准接口的http服务的实现方法。包括服务端和ResteasyClient,可以独立使用 +3. Spring MVC 基于Servlet的http服务的封装和实现方法。包括服务端和RestTemplate,可以独立使用 +4. Spring Flux 基于Netty的http服务的封装和实现方法。包括服务端和WebClient,可以独立使用 \ No newline at end of file diff --git a/Java基础教程/Java标准库/10 Java容器.md b/Java基础教程/Java容器/10 Java容器.md similarity index 100% rename from Java基础教程/Java标准库/10 Java容器.md rename to Java基础教程/Java容器/10 Java容器.md diff --git a/Java基础教程/Java标准库/11 JavaList.md b/Java基础教程/Java容器/11 JavaList.md similarity index 100% rename from Java基础教程/Java标准库/11 JavaList.md rename to Java基础教程/Java容器/11 JavaList.md diff --git a/Java基础教程/Java标准库/12 JavaQueue.md b/Java基础教程/Java容器/12 JavaQueue.md similarity index 100% rename from Java基础教程/Java标准库/12 JavaQueue.md rename to Java基础教程/Java容器/12 JavaQueue.md diff --git a/Java基础教程/Java标准库/13 JavaSet.md b/Java基础教程/Java容器/13 JavaSet.md similarity index 100% rename from Java基础教程/Java标准库/13 JavaSet.md rename to Java基础教程/Java容器/13 JavaSet.md diff --git a/Java基础教程/Java标准库/14 JavaMap.md b/Java基础教程/Java容器/14 JavaMap.md similarity index 100% rename from Java基础教程/Java标准库/14 JavaMap.md rename to Java基础教程/Java容器/14 JavaMap.md diff --git a/Java基础教程/Java标准库/15 Java容器底层结构.md b/Java基础教程/Java容器/15 Java容器底层结构.md similarity index 100% rename from Java基础教程/Java标准库/15 Java容器底层结构.md rename to Java基础教程/Java容器/15 Java容器底层结构.md diff --git a/Java基础教程/Java标准库/09 Java IO.md b/Java基础教程/Java标准库/09 Java IO.md deleted file mode 100644 index e9e0a663..00000000 --- a/Java基础教程/Java标准库/09 Java IO.md +++ /dev/null @@ -1,631 +0,0 @@ -# Java IO - -- [Java IO](#java-io) - - [一、概览](#一概览) - - [二、磁盘操作](#二磁盘操作) - - [三、字节操作](#三字节操作) - - [实现文件复制](#实现文件复制) - - [装饰者模式](#装饰者模式) - - [四、字符操作](#四字符操作) - - [编码与解码](#编码与解码) - - [String 的编码方式](#string-的编码方式) - - [Reader 与 Writer](#reader-与-writer) - - [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容) - - [五、对象操作](#五对象操作) - - [序列化](#序列化) - - [Serializable](#serializable) - - [transient](#transient) - - [六、网络操作](#六网络操作) - - [InetAddress](#inetaddress) - - [URL](#url) - - [Sockets](#sockets) - - [Datagram](#datagram) - - [七、NIO](#七nio) - - [流与块](#流与块) - - [通道与缓冲区](#通道与缓冲区) - - [1. 通道](#1-通道) - - [2. 缓冲区](#2-缓冲区) - - [缓冲区状态变量](#缓冲区状态变量) - - [文件 NIO 实例](#文件-nio-实例) - - [选择器](#选择器) - - [1. 创建选择器](#1-创建选择器) - - [2. 将通道注册到选择器上](#2-将通道注册到选择器上) - - [3. 监听事件](#3-监听事件) - - [4. 获取到达的事件](#4-获取到达的事件) - - [5. 事件循环](#5-事件循环) - - [套接字 NIO 实例](#套接字-nio-实例) - - [内存映射文件](#内存映射文件) - - [对比](#对比) - - [八、参考资料](#八参考资料) - - -十二、Java 字节流Java IO 流简介Java InputStreamJava OutputStreamJava FileInputStreamJava FileOutputStreamJava ByteArrayInputStreamJava ByteArrayOutputStreamJava ObjectInputStreamJava ObjectOutputStreamJava BufferedInputStreamJava BufferedOutputStreamJava PrintStream十三、Java 字符流Java ReaderJava WriterJava InputStreamReaderJava OutputStreamWriterJava FileReaderJava FileWriterJava BufferedReaderJava BufferedWriterJava StringWriterJava PrintWriter - -## 一、概览 -![](image/2022-07-12-11-35-43.png) - -Java 的 I/O 大概可以分成以下几类: - -- 磁盘操作:File -- 字节操作:InputStream 和 OutputStream -- 字符操作:Reader 和 Writer -- 对象操作:Serializable -- 网络操作:Socket -- 新的输入/输出:NIO - -## 二、磁盘操作 - -File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。 - -递归地列出一个目录下所有文件: - -```java -public static void listAllFiles(File dir) { - if (dir == null || !dir.exists()) { - return; - } - if (dir.isFile()) { - System.out.println(dir.getName()); - return; - } - for (File file : dir.listFiles()) { - listAllFiles(file); - } -} -``` - -从 Java7 开始,可以使用 Paths 和 Files 代替 File。 - -## 三、字节操作 - -### 实现文件复制 - -```java -public static void copyFile(String src, String dist) throws IOException { - FileInputStream in = new FileInputStream(src); - FileOutputStream out = new FileOutputStream(dist); - - byte[] buffer = new byte[20 * 1024]; - int cnt; - - // read() 最多读取 buffer.length 个字节 - // 返回的是实际读取的个数 - // 返回 -1 的时候表示读到 eof,即文件尾 - while ((cnt = in.read(buffer, 0, buffer.length)) != -1) { - out.write(buffer, 0, cnt); - } - - in.close(); - out.close(); -} -``` - -### 装饰者模式 - -Java I/O 使用了装饰者模式来实现。以 InputStream 为例, - -- InputStream 是抽象组件; -- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作; -- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。 - -

- -实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。 - -```java -FileInputStream fileInputStream = new FileInputStream(filePath); -BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); -``` - -DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。 - -## 四、字符操作 - -### 编码与解码 - -编码就是把字符转换为字节,而解码是把字节重新组合成字符。 - -如果编码和解码过程使用不同的编码方式那么就出现了乱码。 - -- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节; -- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节; -- UTF-16be 编码中,中文字符和英文字符都占 2 个字节。 - -UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。 - -Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。 - -### String 的编码方式 - -String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。 - -```java -String str1 = "中文"; -byte[] bytes = str1.getBytes("UTF-8"); -String str2 = new String(bytes, "UTF-8"); -System.out.println(str2); -``` - -在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。 - -```java -byte[] bytes = str1.getBytes(); -``` - -### Reader 与 Writer - -不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。 - -- InputStreamReader 实现从字节流解码成字符流; -- OutputStreamWriter 实现字符流编码成为字节流。 - -### 实现逐行输出文本文件的内容 - -```java -public static void readFileContent(String filePath) throws IOException { - - FileReader fileReader = new FileReader(filePath); - BufferedReader bufferedReader = new BufferedReader(fileReader); - - String line; - while ((line = bufferedReader.readLine()) != null) { - System.out.println(line); - } - - // 装饰者模式使得 BufferedReader 组合了一个 Reader 对象 - // 在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法 - // 因此只要一个 close() 调用即可 - bufferedReader.close(); -} -``` - -## 五、对象操作 - -### 序列化 - -序列化就是将一个对象转换成字节序列,方便存储和传输。 - -- 序列化:ObjectOutputStream.writeObject() -- 反序列化:ObjectInputStream.readObject() - -不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。 - -### Serializable - -序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。 - -```java -public static void main(String[] args) throws IOException, ClassNotFoundException { - - A a1 = new A(123, "abc"); - String objectFile = "file/a1"; - - ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile)); - objectOutputStream.writeObject(a1); - objectOutputStream.close(); - - ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(objectFile)); - A a2 = (A) objectInputStream.readObject(); - objectInputStream.close(); - System.out.println(a2); -} - -private static class A implements Serializable { - - private int x; - private String y; - - A(int x, String y) { - this.x = x; - this.y = y; - } - - @Override - public String toString() { - return "x = " + x + " " + "y = " + y; - } -} -``` - -### transient - -transient 关键字可以使一些属性不会被序列化。 - -ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 - -```java -private transient Object[] elementData; -``` - -## 六、网络操作 - -Java 中的网络支持: - -- InetAddress:用于表示网络上的硬件资源,即 IP 地址; -- URL:统一资源定位符; -- Sockets:使用 TCP 协议实现网络通信; -- Datagram:使用 UDP 协议实现网络通信。 - -### InetAddress - -没有公有的构造函数,只能通过静态方法来创建实例。 - -```java -InetAddress.getByName(String host); -InetAddress.getByAddress(byte[] address); -``` - -### URL - -可以直接从 URL 中读取字节流数据。 - -```java -public static void main(String[] args) throws IOException { - - URL url = new URL("http://www.baidu.com"); - - /* 字节流 */ - InputStream is = url.openStream(); - - /* 字符流 */ - InputStreamReader isr = new InputStreamReader(is, "utf-8"); - - /* 提供缓存功能 */ - BufferedReader br = new BufferedReader(isr); - - String line; - while ((line = br.readLine()) != null) { - System.out.println(line); - } - - br.close(); -} -``` - -### Sockets - -- ServerSocket:服务器端类 -- Socket:客户端类 -- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。 - -

- -### Datagram - -- DatagramSocket:通信类 -- DatagramPacket:数据包类 - -## 七、NIO - -新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。 - -### 流与块 - -I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。 - -面向流的 I/O 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。 - -面向块的 I/O 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。 - -I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。 - -### 通道与缓冲区 - -#### 1. 通道 - -通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。 - -通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。 - -通道包括以下类型: - -- FileChannel:从文件中读写数据; -- DatagramChannel:通过 UDP 读写网络中数据; -- SocketChannel:通过 TCP 读写网络中数据; -- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。 - -#### 2. 缓冲区 - -发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。 - -缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。 - -缓冲区包括以下类型: - -- ByteBuffer -- CharBuffer -- ShortBuffer -- IntBuffer -- LongBuffer -- FloatBuffer -- DoubleBuffer - -### 缓冲区状态变量 - -- capacity:最大容量; -- position:当前已经读写的字节数; -- limit:还可以读写的字节数。 - -状态变量的改变过程举例: - -① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。 - -

- -② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。 - -

- -③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。 - -

- -④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。 - -

- -⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。 - -

- -### 文件 NIO 实例 - -以下展示了使用 NIO 快速复制文件的实例: - -```java -public static void fastCopy(String src, String dist) throws IOException { - - /* 获得源文件的输入字节流 */ - FileInputStream fin = new FileInputStream(src); - - /* 获取输入字节流的文件通道 */ - FileChannel fcin = fin.getChannel(); - - /* 获取目标文件的输出字节流 */ - FileOutputStream fout = new FileOutputStream(dist); - - /* 获取输出字节流的文件通道 */ - FileChannel fcout = fout.getChannel(); - - /* 为缓冲区分配 1024 个字节 */ - ByteBuffer buffer = ByteBuffer.allocateDirect(1024); - - while (true) { - - /* 从输入通道中读取数据到缓冲区中 */ - int r = fcin.read(buffer); - - /* read() 返回 -1 表示 EOF */ - if (r == -1) { - break; - } - - /* 切换读写 */ - buffer.flip(); - - /* 把缓冲区的内容写入输出文件中 */ - fcout.write(buffer); - - /* 清空缓冲区 */ - buffer.clear(); - } -} -``` - -### 选择器 - -NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。 - -NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。 - -通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。 - -因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。 - -应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。 - -

- -#### 1. 创建选择器 - -```java -Selector selector = Selector.open(); -``` - -#### 2. 将通道注册到选择器上 - -```java -ServerSocketChannel ssChannel = ServerSocketChannel.open(); -ssChannel.configureBlocking(false); -ssChannel.register(selector, SelectionKey.OP_ACCEPT); -``` - -通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。 - -在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类: - -- SelectionKey.OP_CONNECT -- SelectionKey.OP_ACCEPT -- SelectionKey.OP_READ -- SelectionKey.OP_WRITE - -它们在 SelectionKey 的定义如下: - -```java -public static final int OP_READ = 1 << 0; -public static final int OP_WRITE = 1 << 2; -public static final int OP_CONNECT = 1 << 3; -public static final int OP_ACCEPT = 1 << 4; -``` - -可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如: - -```java -int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; -``` - -#### 3. 监听事件 - -```java -int num = selector.select(); -``` - -使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。 - -#### 4. 获取到达的事件 - -```java -Set keys = selector.selectedKeys(); -Iterator keyIterator = keys.iterator(); -while (keyIterator.hasNext()) { - SelectionKey key = keyIterator.next(); - if (key.isAcceptable()) { - // ... - } else if (key.isReadable()) { - // ... - } - keyIterator.remove(); -} -``` - -#### 5. 事件循环 - -因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。 - -```java -while (true) { - int num = selector.select(); - Set keys = selector.selectedKeys(); - Iterator keyIterator = keys.iterator(); - while (keyIterator.hasNext()) { - SelectionKey key = keyIterator.next(); - if (key.isAcceptable()) { - // ... - } else if (key.isReadable()) { - // ... - } - keyIterator.remove(); - } -} -``` - -### 套接字 NIO 实例 - -```java -public class NIOServer { - - public static void main(String[] args) throws IOException { - - Selector selector = Selector.open(); - - ServerSocketChannel ssChannel = ServerSocketChannel.open(); - ssChannel.configureBlocking(false); - ssChannel.register(selector, SelectionKey.OP_ACCEPT); - - ServerSocket serverSocket = ssChannel.socket(); - InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888); - serverSocket.bind(address); - - while (true) { - - selector.select(); - Set keys = selector.selectedKeys(); - Iterator keyIterator = keys.iterator(); - - while (keyIterator.hasNext()) { - - SelectionKey key = keyIterator.next(); - - if (key.isAcceptable()) { - - ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel(); - - // 服务器会为每个新连接创建一个 SocketChannel - SocketChannel sChannel = ssChannel1.accept(); - sChannel.configureBlocking(false); - - // 这个新连接主要用于从客户端读取数据 - sChannel.register(selector, SelectionKey.OP_READ); - - } else if (key.isReadable()) { - - SocketChannel sChannel = (SocketChannel) key.channel(); - System.out.println(readDataFromSocketChannel(sChannel)); - sChannel.close(); - } - - keyIterator.remove(); - } - } - } - - private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException { - - ByteBuffer buffer = ByteBuffer.allocate(1024); - StringBuilder data = new StringBuilder(); - - while (true) { - - buffer.clear(); - int n = sChannel.read(buffer); - if (n == -1) { - break; - } - buffer.flip(); - int limit = buffer.limit(); - char[] dst = new char[limit]; - for (int i = 0; i < limit; i++) { - dst[i] = (char) buffer.get(i); - } - data.append(dst); - buffer.clear(); - } - return data.toString(); - } -} -``` - -```java -public class NIOClient { - - public static void main(String[] args) throws IOException { - Socket socket = new Socket("127.0.0.1", 8888); - OutputStream out = socket.getOutputStream(); - String s = "hello world"; - out.write(s.getBytes()); - out.close(); - } -} -``` - -### 内存映射文件 - -内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。 - -向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。 - -下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。 - -```java -MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024); -``` - -### 对比 - -NIO 与普通 I/O 的区别主要有以下两点: - -- NIO 是非阻塞的; -- NIO 面向块,I/O 面向流。 - -## 八、参考资料 - -- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002. -- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html) -- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html) -- [Java NIO 浅析](https://tech.meituan.com/nio.html) -- [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html) -- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html) -- [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html) -- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499) -- [Decorator Design Pattern](http://stg-tud.github.io/sedc/Lecture/ws13-14/5.3-Decorator.html#mode=document) -- [Socket Multicast](http://labojava.blogspot.com/2012/12/socket-multicast.html) diff --git a/Java基础教程/Java标准库/30 Java网络编程.md b/Java基础教程/Java标准库/30 Java网络编程.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Java基础教程/Java标准库/40 Java网络编程.md b/Java基础教程/Java标准库/40 Java网络编程.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Java基础教程/Java网络编程/StringBuffer.md b/Java基础教程/Java标准库/StringBuffer.md similarity index 100% rename from Java基础教程/Java网络编程/StringBuffer.md rename to Java基础教程/Java标准库/StringBuffer.md diff --git a/Java基础教程/Java标准库/image/2022-11-26-17-31-29.png b/Java基础教程/Java标准库/image/2022-11-26-17-31-29.png new file mode 100644 index 00000000..0d5b5649 Binary files /dev/null and b/Java基础教程/Java标准库/image/2022-11-26-17-31-29.png differ diff --git a/Java基础教程/Java标准库/image/2022-11-26-18-03-55.png b/Java基础教程/Java标准库/image/2022-11-26-18-03-55.png new file mode 100644 index 00000000..776044ae Binary files /dev/null and b/Java基础教程/Java标准库/image/2022-11-26-18-03-55.png differ diff --git a/Java基础教程/Java标准库/image/2022-11-26-19-20-23.png b/Java基础教程/Java标准库/image/2022-11-26-19-20-23.png new file mode 100644 index 00000000..5fc9e6a8 Binary files /dev/null and b/Java基础教程/Java标准库/image/2022-11-26-19-20-23.png differ diff --git a/Java基础教程/Java标准库/image/2022-11-26-19-31-24.png b/Java基础教程/Java标准库/image/2022-11-26-19-31-24.png new file mode 100644 index 00000000..5d4e8271 Binary files /dev/null and b/Java基础教程/Java标准库/image/2022-11-26-19-31-24.png differ diff --git a/Java基础教程/Java标准库/image/2022-11-26-19-40-09.png b/Java基础教程/Java标准库/image/2022-11-26-19-40-09.png new file mode 100644 index 00000000..25796457 Binary files /dev/null and b/Java基础教程/Java标准库/image/2022-11-26-19-40-09.png differ diff --git a/Java基础教程/Java标准库/image/2022-11-26-19-40-59.png b/Java基础教程/Java标准库/image/2022-11-26-19-40-59.png new file mode 100644 index 00000000..17042c78 Binary files /dev/null and b/Java基础教程/Java标准库/image/2022-11-26-19-40-59.png differ diff --git a/Java基础教程/Java标准库/image/2022-11-26-20-24-47.png b/Java基础教程/Java标准库/image/2022-11-26-20-24-47.png new file mode 100644 index 00000000..17042c78 Binary files /dev/null and b/Java基础教程/Java标准库/image/2022-11-26-20-24-47.png differ diff --git a/Java基础教程/Java网络编程/RMI相关知识.md b/Java基础教程/Java网络编程/RMI相关知识.md deleted file mode 100644 index 6cca5484..00000000 --- a/Java基础教程/Java网络编程/RMI相关知识.md +++ /dev/null @@ -1,159 +0,0 @@ -RMI的定义 - -RPC (Remote Procedure -Call):远程方法调用,用于一个进程调用另一个进程中的过程,从而提供了过程的分布能力。 - -RMI(Remote Method -Invocation):远程方法调用,即在RPC的基础上有向前迈进了一步,提供分布式对象间的通讯。允许运行在一个java -虚拟机的对象调用运行在另一个java虚拟机上对象的方法。这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。 - -RMI的作用 - -RMI的全称宗旨就是尽量简化远程接口对象的调用。 - -RMI大大增强了java开发分布式应用的能力,例如可以将计算方法复杂的程序放在其他的服务器上,主服务器只需要去调用,而真正的运算是在其他服务器上进行,最后将运算结果返回给主服务器,这样就减轻了主服务器的负担,提高了效率(但是也有其他的开销)。 - -RMI网络模型 - -在设计初始阶段,我们真正想要的是这样一种机制,客户端程序员以常规方式进行方法调用,而无需操心将数据发送到网络上或者解析响应之类的问题。所以才有了如下的网络模型:在客户端为远程对象安装一个代理。代理是位于客户端虚拟机中的一个对象,它对于客户端程序来说,就像是要访问的远程对象一样。客户端调用此代理时,只需进行常规的方法调用。而客户端代理则负责使用网络协议与服务器进行联系。 - -![8-343985609.jpeg](media/b24ac0390788e8547c3f60daee9eaacf.jpeg) - -现在的问题在于代理之间是如何进行通信的?通常有三种方法: - -1、CORBA:通过对象请求代理架构,支持任何编程语言编写的对象之间的方法调用。 - -2、SOAP - -3、RMI:JAVA的远程方法调用技术,支持java的分布式对象之间的方法调用。 - -其中CORBA与SOAP都是完全独立于言语的,可以使用C、C++、JAVA来编写,而RMI只适用于JAVA。 - -RMI的工作原理 - -**术语介绍** - -> 1、存根:当客户端要调用远程对象的一个方法时,实际上调用的是代理对象上的一个普通方法,**我们称此代理对象为存根(stub)。**存根位于客户端机器上,而非服务器上。 - -> 2、参数编组:存根会将**远程方法所需的参数打包成一组字节**,对参数编码的过程就称为参数编组。参数编组的目的是将参数转换成适合在虚拟机之间进行传递的格式,在RMI协议中,对象是使用序列化机制进行编码的。 - -编程模型 - -为了介绍RMI的编程模型,我下面会编写一个DEMO。远程对象表示的是一个仓库,而客户端程序向仓库询问某个产品的价格。 - -**接口定义** - -远程对象的能力是由在客户端和服务器之间共享的接口所表示的: - -![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif) - -复制代码 - -package rmi; import java.rmi.Remote; import java.rmi.RemoteException; public -interface Warehouse extends Remote { double getPrice(String description) throws -RemoteException; } - -远程对象的接口必须扩展Remote接口,它位于java.rmi包中。接口中所有的方法必须声明抛出RemoteException异常。这是因为远程方法总是存在失败的可能,所以java编程语言要求每一次远程方法的调用都必须捕获RemoteException,并且指明当调用不成功时应执行的相应处理操作。 - -**接口的实现** - -![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif) - -复制代码 - -package rmi; import java.rmi.RemoteException; import -java.rmi.server.UnicastRemoteObject; import java.util.HashMap; import -java.util.Map; public class WarehouseImpl extends UnicastRemoteObject implements -Warehouse { private static final long serialVersionUID = 1L; private -Map\ prices; protected WarehouseImpl() throws RemoteException { -prices = new HashMap\(); prices.put("mate7",3700.00); } public -double getPrice(String description) throws RemoteException { Double price = -prices.get(description); return price == null? 0 : price; }} - -你可以看出这个类是远程方法调用的目标,因为它扩展自UnicastRemoteObject,这个类的构造器使得它的对象可供远程访问。 - -**RMI注册表:通过JNDI发布RMI服务** - -1、要访问服务器上的一个远程对象时,客户端必须先得到一个本地的存根对象,也就是客户端机器上的代理对象。那么问题来了,如何才能得到这个存根呢? - -2、为此,JDK提供了自举注册服务(bootstrap registry -service),服务器程序应该使用自举注册服务来注册至少一个远程对象。 - -3、而要注册一个远程对象,需要一个RMI URL和一个对实现对象的引用。 - -4、RMI -的URL以rmi:开头,后接域名或IP地址(host),紧接着是端口号(port),最后是服务名(service)。 - -如:rmi://regserver.mycompany.cmo:99/central_warehouse - -如果我们是在本地发布RMI服务,那么host就是“localhost”,此外RMI默认的端口号是“1099”,当然我们也可以自行设置,只要不与其他端口重复即可。service实际上是基于同一个host与port下唯一的服务名。 - -发布RMI服务: - -1 package rmi; 2 3 import java.net.MalformedURLException; 4 import -java.rmi.AlreadyBoundException; 5 import java.rmi.Naming; 6 import -java.rmi.RemoteException; 7 import java.rmi.registry.LocateRegistry; 8 9 import -javax.naming.NamingException; 10 11 12 public class WarehouseServer 13 { 14 -public static void main(String[] args) throws RemoteException, NamingException, -MalformedURLException, AlreadyBoundException 15 { 16 -System.out.println("Constructing server implementation"); 17 WarehouseImpl -centralWarehouse = new WarehouseImpl(); 18 19 System.out.println("Binding server -implementation to registry"); 20 LocateRegistry.createRegistry(1099); 21 -Naming.bind("rmi://localhost:1099/central_warehoues",centralWarehouse); 22 23 -System.out.println("Waiting for invocations from clients ..."); 24 } 25 } - -运行结果: - -Constructing server implementation Binding server implementation to registry -Waiting for invocations from clients ... - -1、第20行只需提供一个port,就在JNDI中创建了一个注册表。 - -2、第21行通过bind方法绑定了RMI地址与RMI服务实现类。 - -3、执行这个方法后,相当于自动发布了RMI服务。接下来要做的事情就是写一个RM客户端调用已发布的RMI服务。 - -**客户端调用RMI服务** - -1 package rmi; 2 3 import java.net.MalformedURLException; 4 import -java.rmi.Naming; 5 import java.rmi.NotBoundException; 6 import -java.rmi.RemoteException; 7 import javax.naming.NamingException; 8 9 public -class WarehouseClient 10 { 11 public static void main(String[] args) throws -NamingException, RemoteException, MalformedURLException, NotBoundException 12 { -13 System.out.println("RMI registry binding:"); 14 String url = -"rmi://localhost:1099/central_warehoues"; 15 Warehouse centralWarehouse = -(Warehouse) Naming.lookup(url); 16 String descr = "mate7"; 17 double price = -centralWarehouse.getPrice(descr); 18 System.out.println(descr + ":" + price); 19 -} 20 } - -运行结果: - -RMI registry binding: mate7:3700.0 - -补充说明: - -> 服务调用只需要知道两个东西:1、RMI请求路径;2、RMI接口名 - -> 第15行,这里用的是接口名Warehouse,而不是实现类。一定不能RMI接口的实现类,否则就是本地调用了。 - -> 查看运行结果,我们知道这次DEMO展示的远程调用成功了。 - -**下面我们来看下RMI的网络示意图:** - -![3-765464905.jpeg](media/f2f880588756d88cbf5083a94fbd41ae.jpeg) - -1、借助JNDI这个所谓的命名与目录服务,我们成功地发布并调用了RMI服务。实际上,JNDI就是一个注册表,服务端将服务对象放入到注册表中,客户端从注册表中获取服务对象。 - -2、在服务端我们发布了RMI服务,并在JNDI中进行了注册,此时就在服务端创建了一个Skeleton(骨架),当客户端第一次成功连接JNDI并获取远程服务对象后,立马在本地创建了一个Stub(存根)。 - -3、远程通信实际是通过Skeleton与Stub来完成的,数据是基于TCP/IP协议,在“传输层”上发送的。 - -4、毋庸置疑,理论上RMI一定比WebService要快,毕竟WebService是基于http协议的,而http所携带的数据是通过“应用层”来传输的。传输层较应用层更为底层,越底层越快。 - -RMI的局限性 - -1、只能实现JAVA系统之间的调用,而WebService可以实现跨语言实现系统之间的调用。 - -2、RMI使用了JAVA默认的序列化方式,对于性能要求比较高的系统,可能需要其他的序列化方案来解决。 - -3、RMI服务在运行时难免会存在故障,例如,如果RMI服务无法连接了,就会导致客户端无法响应的现象。 diff --git a/Java基础教程/Java网络编程/XML语言学习.md b/Java基础教程/Java网络编程/XML语言学习.md deleted file mode 100644 index 72c4a9e5..00000000 --- a/Java基础教程/Java网络编程/XML语言学习.md +++ /dev/null @@ -1,189 +0,0 @@ -简介 - -**作用:** - -用来传输和存储数据 - -**定义:** - -可扩展标记语言。 - -**特点:** - -自行定义标签,具有自我描述性。 - -能够跨越各种平台 - -**具体应用:** - -用于描述可用的web服务的WSDL - -树结构 - -**范例** - -\\\Tove\\Jani\\Reminder\\Don't -forget me this weekend!\\ - -说明:第一行是XML文档声明,定义了版本号和文档的编码方式。 - -**树结构:** - -![clipboard.png](media/ab38369f8043d690b448fe8e8004a755.png) - -**元素:** - -起始标记、结束标记、内容(可以是字符文本、其他元素、混合物) - -**属性:** - -属性名=属性值 - -**语法规则:** - -标签闭合,正确嵌套 - -有一个根元素 - -可以有树枝也可以由实体引用 - -XML声明可选,放在第一行 - -XML标签对大小写敏感 - -属性值必须加引号,应当尽量用子元素代替属性。数据的数据存储为属性,而数据本身应当存储为元素。 - -id属性可以用来标识XML元素 - -注释方式\ Buffer 对象) + ByteBuf buf = (ByteBuf) msg; + //定义byte数组 + byte[] req = new byte[buf.readableBytes()]; + // 从缓冲区获取数据到 req + buf.readBytes(req); + //读取到的数据转换为字符串 + String body = new String(req, "utf-8"); + System.out.println("服务端读取到数据:" + body); + + //响应给客户端的数据 + ctx.writeAndFlush(Unpooled.copiedBuffer("netty server response data".getBytes())); + // 添加 addListener 可以触发关闭通道监听事件(客户端短连接场景使用) + // .addListener(ChannelFutureListener.CLOSE); + }catch (Exception e){ + e.printStackTrace(); + }finally { + //释放数据 + ReferenceCountUtil.release(msg); + } + + } + + /** + * 当我们读取完成数据的时候触发的监听 + * + * @param ctx + * @throws Exception + */ + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + System.out.println("--------服务端数据读取完毕---------"); + } + + /** + * 当我们读取数据异常的时候触发的监听 + * + * @param ctx + * @param cause + * @throws Exception + */ + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + System.out.println("--------服务端数据读取异常---------"); + ctx.close(); + } +} +``` +### 客户端 +```java +package com.example.netty.io; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +/** + * netty 客户端 + * @author lanx + * @date 2022/3/20 + */ +public class Client { + + public static void main(String[] args) throws InterruptedException { + + //线程工作组 + EventLoopGroup workerGroup = new NioEventLoopGroup(); + //辅助类 帮我我们创建netty服务 + Bootstrap b = new Bootstrap(); + b.group( workerGroup)//绑定两个工作组 + .channel(NioSocketChannel.class)//设置NIO模式 + + //初始化绑定服务通道 + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel sc) throws Exception { + //为通道进行初始化:数据传输过来的时候会进行拦截和执行 (可以有多个拦截器) + sc.pipeline().addLast(new ClientHandler()); + } + }); + ChannelFuture cf = b.connect("127.0.0.1",8765).syncUninterruptibly(); + cf.channel().writeAndFlush(Unpooled.copiedBuffer("netty client request data".getBytes())); + //释放连接 + cf.channel().closeFuture().sync(); + workerGroup.shutdownGracefully(); + } +} +``` + +```java +package com.example.netty.io; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.ReferenceCountUtil; + +/** + * 客户端 监听器 + * + * @author lanx + * @date 2022/3/20 + */ +public class ClientHandler extends ChannelInboundHandlerAdapter { + + /** + * 当我们的通道被激活的时候触发的监听 + * + * @param ctx + * @throws Exception + */ + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + System.out.println("--------客户端通道激活---------"); + } + + /** + * 当我们通道里有数据进行读取的时候触发的监听 + * + * @param ctx netty服务上下文 + * @param msg 实际传输的数据 + * @throws Exception + */ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + try { + // NIO 通信 (传输的数据是什么? ---------> Buffer 对象) + ByteBuf buf = (ByteBuf) msg; + //定义byte数组 + byte[] req = new byte[buf.readableBytes()]; + // 从缓冲区获取数据到 req + buf.readBytes(req); + //读取到的数据转换为字符串 + String body = new String(req, "utf-8"); + System.out.println("客户端读取到数据:" + body); + } catch (Exception e) { + e.printStackTrace(); + } finally { + //释放数据 (如果你读取数据后又写出去数据就不需要调用此方法,因为底层代码帮忙实现额释放数据) + ReferenceCountUtil.release(msg); + } + } + + /** + * 当我们读取完成数据的时候触发的监听 + * + * @param ctx + * @throws Exception + */ + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + System.out.println("--------客户端数据读取完毕---------"); + } + + /** + * 当我们读取数据异常的时候触发的监听 + * + * @param ctx + * @param cause + * @throws Exception + */ + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + System.out.println("--------客户端数据读取异常---------"); + ctx.close(); + } +} +``` \ No newline at end of file diff --git a/Netty/04 Reactor与Netty.md b/Netty/04 Reactor与Netty.md new file mode 100644 index 00000000..85b56731 --- /dev/null +++ b/Netty/04 Reactor与Netty.md @@ -0,0 +1,57 @@ + + +## Reactor与Netty + +### Reactor概念 + +Reactor模型中定义了三种角色: + +* Reactor:负责监听和分配事件,将I/O事件分派给对应的Handler。新的时间包含连接建立就绪、读就绪、写就绪等。 +* Acceptor:处理客户端新连接,并分派请求到处理器链中。 +* Handler:将自身与事件绑定,执行费阻塞读/写人物,完成channel的读入,完成处理业务逻辑后,负责将结果写出channel。 + + +### 单Reactor - 单线程模型 +NIO下Reactor单线程,所有的接受连接,处理数据的相关操作都在一个线程中完成,性能上有瓶颈。 + +![](image/2022-11-27-14-00-48.png) + +### 单Reactor - 多线程模型 +把比较消耗时的数据的编解码运算操作放入线程池中执行,虽然提升了性能但是还不是最好的方式。 + +![](image/2022-11-27-14-05-04.png) + +### 主从Reactor - 多线程 +主从多线程,对于服务器来说,接收客户端的连接是比较重要的,因此将这部分操作单独用线程去操作。 + +![](image/2022-11-27-14-06-17.png) + +这种模式的基本工作流程为: + +1. Reactor主线程MainReactor对象通过select监听客户端连接事件,收到事件后,通过Acceptor处理客户端连接事件。 +2. 当Acceptor处理完客户端连接事件之后(与客户端建立号socket连接),MainReactor将连接分配给subReactor。(即:MainReactor只负责监听客户端的请求,和客户端建立连接后将连接交给subReactor监听后面的IO事件。) +3. subReactor将连接加入到自己的连接队列进行监听,并创建Handler对各种事件进行处理。 +4. 当连接上有新事件发生的时候,subReactor将会调用对应的Handler处理。 +5. Handler通过read从连接上读取请求数据,将请求数据分发给Worker线程池进行业务处理。 +6. worker线程池会分配独立的线程来完成真正的业务处理,并将处理结果返回给handler。Handler通过send向客户端发送响应数据。 +7. 一个MainReactor可以对应多个subReactor,即一个MainReactor线程可以对应多个subReactor线程。 + +### 主从Reactor优势 +这种模式的优势如下: + +1. MainReactor 线程与SubReactor 线程的数据交互简单职责明确,MainReactor 线程只需要接受新连接,SubReactor 线程完成后续的业务处理。 +2. MainReactor 线程与SubReactor 线程的数据交互简单,MainReactor 线程只需要把新连接传给SubReactor 线程,SubReactor 线程无需返回数据。 +3. 多个SubReactor 线程能够应对更高的并发请求。 + +这种模式的缺点是编程复杂度高。但是由于优点明显,在许多项目中被广泛使用,包括Nginx、Memcachend、Netty等。 + +这种模式也被叫做服务器的 1+M+N线程模式,即使用改模式开发的服务器包含一个(或多个,1只是表示相对较少)连接建立线程 + M 个IO 线程 + N 个业务处理线程。这是业界成熟的服务器程序设计模式。 + +### Netty中的Reactor实现 +1. Netty抽象出两组线程池:Boss EventLoopGroup和Worker EventLoopGroup,每个线程池中都有 EventLoopGroup 线程(可以是OIO,NIO,AIO)。Boss EventLoopGroup中的线程专门负责处理和客户端建立连接,Worker EventLoopGroup 中的线程专门负责处理连接上的读写,EventLoopGroup 相当于一个事件循环组,这个组中含有锁多个事件循环。 +2. EventLoop 表示一个不断循环的执行事件处理的线程,每个EventLoop 都含有一个selector,用于监听注册在其上的socket 网络连接(channel)。 +3. 每一个Boss EventLoopGroup 中循环执行以下三个步骤:a、select:轮询注册在其上的serverSocketChannel 的 accept 事件(OP_ACCEPT 事件);b、processSeleckendKeys:处理 accept 事件,与客户端建立连接,生产一个socketChannel,并将其注册到Worker EventLoop 上的某个selector 上;c、runAllTasks:再去以此循环处理任务队列中的其他任务。 +3. 每个Worker EventLoop 中循环执行这三个步骤:a、select:轮询注册在其上的socketChannel 的read / write 事件(OP_READ / OP_WRITE 事件);b、processSeleckendKeys:在对应的socketChannel 上处理read / write 事件;c、runAllTasks:再去以此循环处理任务队列中的其他任务。 +4. 在以上两个processSeleckendKeys步骤中,会使用Pipeline(管道),Pipeline 中引用了channel,即通过Pipeline 可以获取对应的Channel,Pipeline 中维护了很多的处理器(拦截处理器、过滤处理器、自定义处理器等)。 + +![](image/2022-11-27-13-57-48.png) \ No newline at end of file diff --git a/Netty/image/2022-11-25-23-13-40.png b/Netty/image/2022-11-25-23-13-40.png new file mode 100644 index 00000000..0c5e9f13 Binary files /dev/null and b/Netty/image/2022-11-25-23-13-40.png differ diff --git a/Netty/image/2022-11-25-23-13-51.png b/Netty/image/2022-11-25-23-13-51.png new file mode 100644 index 00000000..f4aaddce Binary files /dev/null and b/Netty/image/2022-11-25-23-13-51.png differ diff --git a/Netty/image/2022-11-26-00-06-12.png b/Netty/image/2022-11-26-00-06-12.png new file mode 100644 index 00000000..22137bd2 Binary files /dev/null and b/Netty/image/2022-11-26-00-06-12.png differ diff --git a/Netty/image/2022-11-26-00-06-19.png b/Netty/image/2022-11-26-00-06-19.png new file mode 100644 index 00000000..61704a23 Binary files /dev/null and b/Netty/image/2022-11-26-00-06-19.png differ diff --git a/Netty/image/2022-11-27-09-15-21.png b/Netty/image/2022-11-27-09-15-21.png new file mode 100644 index 00000000..147c6ff2 Binary files /dev/null and b/Netty/image/2022-11-27-09-15-21.png differ diff --git a/Netty/image/2022-11-27-10-10-22.png b/Netty/image/2022-11-27-10-10-22.png new file mode 100644 index 00000000..60f05bd8 Binary files /dev/null and b/Netty/image/2022-11-27-10-10-22.png differ diff --git a/Netty/image/2022-11-27-10-15-20.png b/Netty/image/2022-11-27-10-15-20.png new file mode 100644 index 00000000..34542280 Binary files /dev/null and b/Netty/image/2022-11-27-10-15-20.png differ diff --git a/Netty/image/2022-11-27-11-15-49.png b/Netty/image/2022-11-27-11-15-49.png new file mode 100644 index 00000000..faa53e66 Binary files /dev/null and b/Netty/image/2022-11-27-11-15-49.png differ diff --git a/Netty/image/2022-11-27-11-24-16.png b/Netty/image/2022-11-27-11-24-16.png new file mode 100644 index 00000000..bce07214 Binary files /dev/null and b/Netty/image/2022-11-27-11-24-16.png differ diff --git a/Netty/image/2022-11-27-13-29-57.png b/Netty/image/2022-11-27-13-29-57.png new file mode 100644 index 00000000..b6c1a3c0 Binary files /dev/null and b/Netty/image/2022-11-27-13-29-57.png differ diff --git a/Netty/image/2022-11-27-13-36-53.png b/Netty/image/2022-11-27-13-36-53.png new file mode 100644 index 00000000..5d483499 Binary files /dev/null and b/Netty/image/2022-11-27-13-36-53.png differ diff --git a/Netty/image/2022-11-27-13-44-43.png b/Netty/image/2022-11-27-13-44-43.png new file mode 100644 index 00000000..c702dbc0 Binary files /dev/null and b/Netty/image/2022-11-27-13-44-43.png differ diff --git a/Netty/image/2022-11-27-13-49-01.png b/Netty/image/2022-11-27-13-49-01.png new file mode 100644 index 00000000..86ed2888 Binary files /dev/null and b/Netty/image/2022-11-27-13-49-01.png differ diff --git a/Netty/image/2022-11-27-13-57-48.png b/Netty/image/2022-11-27-13-57-48.png new file mode 100644 index 00000000..5ac57666 Binary files /dev/null and b/Netty/image/2022-11-27-13-57-48.png differ diff --git a/Netty/image/2022-11-27-14-00-48.png b/Netty/image/2022-11-27-14-00-48.png new file mode 100644 index 00000000..d1f42055 Binary files /dev/null and b/Netty/image/2022-11-27-14-00-48.png differ diff --git a/Netty/image/2022-11-27-14-05-04.png b/Netty/image/2022-11-27-14-05-04.png new file mode 100644 index 00000000..08e4d063 Binary files /dev/null and b/Netty/image/2022-11-27-14-05-04.png differ diff --git a/Netty/image/2022-11-27-14-06-17.png b/Netty/image/2022-11-27-14-06-17.png new file mode 100644 index 00000000..f1def5e9 Binary files /dev/null and b/Netty/image/2022-11-27-14-06-17.png differ diff --git a/Netty/image/2022-11-27-14-15-29.png b/Netty/image/2022-11-27-14-15-29.png new file mode 100644 index 00000000..520094c2 Binary files /dev/null and b/Netty/image/2022-11-27-14-15-29.png differ diff --git a/Netty/image/2022-11-27-14-17-35.png b/Netty/image/2022-11-27-14-17-35.png new file mode 100644 index 00000000..16ef497f Binary files /dev/null and b/Netty/image/2022-11-27-14-17-35.png differ diff --git a/Netty/image/2022-11-27-14-18-49.png b/Netty/image/2022-11-27-14-18-49.png new file mode 100644 index 00000000..d68ea2f8 Binary files /dev/null and b/Netty/image/2022-11-27-14-18-49.png differ diff --git a/Netty实战.pdf b/Netty实战.pdf new file mode 100644 index 00000000..f984f480 Binary files /dev/null and b/Netty实战.pdf differ diff --git a/Spring/Spring5/06 WebFlux01Flux&Mono.md b/Spring/Spring5/06 WebFlux01Flux&Mono.md new file mode 100644 index 00000000..a4ccebdb --- /dev/null +++ b/Spring/Spring5/06 WebFlux01Flux&Mono.md @@ -0,0 +1,347 @@ +# WebFlux +> https://blog.csdn.net/crazymakercircle/article/details/124120506 +> https://zhuanlan.zhihu.com/p/378136040 +## 1 WebFlux简介 +> 第一轮:就是看视频教程学会所有技术的原理和基本使用方法 +> 第二轮:阅读官方的文档,尤其是Spring、Java、Maven等,掌握编程的细节。 + + +### 简介 +Spring5添加的新模块。用于web开发的,功能与SpringMVC类似。Webflux使用与当前比较流行的响应式编程出现的框架。 + +传统的Web框架,比如SpringMVC,基于Servlet容器,Webflux是一种异步非阻塞的框架。(异步非阻塞的框架在Servlet3.1后才支持) + +其和虚拟式基于Reactor的相关API实现的。 + + +### 异步非阻塞 + +我更喜欢反过来理解这里。同步异步是针对被调用者的,阻塞和非阻塞是针对调用者的。 + +* **阻塞和非阻塞针对调用者**。阻塞和非阻塞指的是调用者(程序)在等待返回结果(或输入)时的状态。阻塞时,在调用结果返回前,当前线程会被挂起,并在得到结果之后返回。非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前线程。因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。 + +* **异步和同步是针对被调用者**,同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。 + +### 与SpringMVC对比 + +![](image/2022-10-12-19-28-38.png) + +* 异步非阻塞,在有限的资源下,能够处理更多的请求,提高系统地吞吐量。 +* 函数式编程。(Java最基本的编程模式)。能够使用Java函数式编程的特点。 +* 两个框架都可以使用注解方式运行,都可以运行在Tomcat等Servlet容器中。但SpringMVC采用命令式编程,WebFlux使用响应式编程。 + + +### 使用场景:网关 +* 需要处理大量的请求。所有客户调用网关,网关负责调用其他的组件。可以使用异步的方式。 + +## 2 响应式编程 + +### 响应式编程定义 +响应式编程是一种面向数据流和变化产波的编程范式。 + +意味着可以在编程语言很方便地表达静态或者动态的数据流, + +![](image/2022-10-12-19-35-03.png) +一个响应式编程的典型例子。D1=B1+C1。当B1的值修改后,D1的值也会修改。B1的数据变化,流向了D1。 + +其基本的模型如下 +* 可观察对象、观察者。(观察者模式) +* 发布者、订阅者。(发布订阅机制) +* 事件、响应。(事件驱动、事件监听机制、响应式编程) + +### Java8响应式编程 + +是要使用观察者模式,实现了响应式编程。使用响应式编程Observer,Observable实现。 +```java +/** + * Alipay.com Inc. + * Copyright (c) 2004-2022 All Rights Reserved. + */ +package com.ykl.shangguigu08.reactor; + +import java.util.Observable; + +/** + * @author yinkanglong + * @version : ObserverDemo, v 0.1 2022-10-12 19:47 yinkanglong Exp $ + */ +public class ObserverDemo extends Observable { + + /** + * 通过Java8中的类实现响应式编程。 + * 简单来说,就是观察值模式。 + * @param args + */ + @Test + public void testObserver() { + //其中observerDemo是一个可观察对象,Observer添加了匿名的观察者。 + //二者通过addObserver建立联系。 + //observable对象通过setChanged和notifyObservers,发送通知、事件。进行相应。 + ObserverDemo observerDemo = new ObserverDemo(); + observerDemo.addObserver((o,arg)->{ + System.out.println("发生变化"); + }); + + observerDemo.addObserver((o,arg)->{ + System.out.println("准备改变"); + }); + + observerDemo.addObserver(new Observer() { + @Override + public void update(Observable o, Object arg) { + System.out.println(o); + System.out.println(arg); + } + }); + + observerDemo.setA(11); + observerDemo.setChanged(); + System.out.println(observerDemo.hasChanged()); + observerDemo.notifyObservers(); + observerDemo.notifyObservers("hello world"); + } +} +``` +### java9响应式编程 +主要通过Flow类的sub和sub订阅消息,实现响应式编程。 +> 感觉这个响应式编程和awt控件的点击相应式操作很相似。但是不是启动新的线程。 + +```java + Observable observable=Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + for(int i=0;i<5;i++){ + subscriber.onNext(i); + } + subscriber.onCompleted(); + } + }); + //Observable.subscribe(Observer),Observer订阅了Observable + Subscription subscribe = observable.subscribe(new Observer() { + @Override + public void onCompleted() { + Log.e(TAG, "完成"); + } + + @Override + public void onError(Throwable e) { + Log.e(TAG, "异常"); + } + + @Override + public void onNext(Integer integer) { + Log.e(TAG, "接收Obsverable中发射的值:" + integer); + } + }); + +输出: + +接收Obsverable中发射的值:0 +接收Obsverable中发射的值:1 +接收Obsverable中发射的值:2 +接收Obsverable中发射的值:3 +接收Obsverable中发射的值:4 +``` +### Flux&Mono响应式编程 + +* 响应式编程操作,Reactor是满足Reactive规范框架 +* Reactor有两个核心类,Mono和Flux,这两个类实现接口Publisher,提供丰富操作符号. + * Flux对象实现发布,返回N个元素。 + * Mono实现发布者,返回0或者1个元素。把 Mono 用于在异步任务完成时发出通知。 +* Flux和Mono都是数据流的发布者。能够发出三种信号 + * 元素值 + * 完成信号。一种终止信号。订阅者数据流已经结束了。 + * 错误信号。一种终止信号。终止数据流并把错误信息传递给订阅者。 + +![](image/2022-10-13-10-22-26.png) + +三种信号的特点 +* 错误信号和完成信号都是终止信号不能共存。 +* 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示空数据流 +* 如果没有错误信号,没有完成信号,表示无限数据流。 + +### 实例:Flux&Mono + +引入相关的依赖 +```xml + + io.projectreactor + reactor-core + 3.1.5.RELEASE + +``` + +进行发布者发布内容 +* just等发布方法只是声明了数据流。只有声明了订阅者才会触发数据流,不订阅,就不会触发。 +```java +package com.ykl.shangguigu08.reactor; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +/** + * @author yinkanglong + * @version : TestReactor, v 0.1 2022-10-13 10:25 yinkanglong Exp $ + */ +public class TestReactor { + + @Test + public void testFlux(){ + //其中Flux/Mono就是一个可观察对象/发布者。Consumer是一个观察者/消费者。 + //二者通过subscriber建立联系。 + //直接发送通知。不需要调用特定的函数。 + Flux justInteger = Flux.just(1, 2, 3, 4); + justInteger.subscribe(System.out::println); + + + Integer[] array = {1,2,3,4}; + Flux.fromArray(array).subscribe(System.out::println); + + List list = Arrays.asList(array); + Flux.fromIterable(list).subscribe(System.out::println); + + Stream stream = list.stream(); + Flux.fromStream(stream).subscribe(System.out::println); + + Flux.just("hello","world").subscribe(new Consumer() { + @Override + public void accept(String s) { + System.out.println(s); + } + }); + + } +} +``` +```java +public Mono currentUser () { + return isAuthenticated ? Mono.just(new ClientUser("felord.cn", "reactive")) + : Mono.empty(); +} +``` + + + +## 4 WebFlux执行流程和核心API + +### Netty的基本原理 +SpringWebflux基于Reactor,默认使用容器Netty,Netty是高性能的NIO框架,异步非阻塞框架。 + +1. BIO阻塞 + +![](image/2022-10-13-11-44-28.png) + +2. NIO非阻塞 + +![](image/2022-10-13-11-44-49.png) + +3. webflux + + +![](image/2022-11-25-20-26-34.png) + +### SpringWebFlux + +* SpringWebflux核心控制器DispatchHandler,实现接口WebHandler + +![](image/2022-10-13-11-48-44.png) + +DispatcherHandler负责请求处理。有三个核心类。 +![](image/2022-10-13-11-51-20.png) + + +* HandlerMapping(reactor反应器):请求查询到处理方法。 +* HandlerAdapter:真正负责请求处理(processor部分) +* HandlerResultHandler:对结果进行处理 + +### 函数式编程 +两个核心接口。 +* RouterFunction 路由处理 +* HandlerFunction处理函数 + + +常用函数编程示例 +* Consumer 一个输入 无输出 +```java +Product product=new Product(); +//类名+静态方法 一个输入T 没有输出 +Consumer consumer1 = Product->Product.nameOf(product);//lambda +consumer1.accept(product); +Consumer consumer = Product::nameOf;//方法引用 +consumer.accept(product); +``` +* Funtion 一个输入 一个输出 +```java +//对象+方法 一个输入T 一个输出R +Function function = product::reduceStock; +System.out.println("剩余库存:" + function.apply(10)); +//带参数的构造函数 +Function function1=Product::new; +System.out.println("新对象:" +function1.apply(200)); +``` + +* Predicate 一个输入T, 一个输出 Boolean +```java +//Predicate 一个输入T 一个输出Boolean +Predicate predicate= i -> product.isEnough(i);//lambda +System.out.println("库存是否足够:"+predicate.test(100)); +Predicate predicate1= product::isEnough;//方法引用 +System.out.println("库存是否足够:"+predicate1.test(100)); +``` +* UnaryOperator 一元操作符 输入输出都是T +```java +//一元操作符 输入和输出T +UnaryOperator integerUnaryOperator =product::reduceStock; +System.out.println("剩余库存:" + integerUnaryOperator.apply(20)); +IntUnaryOperator intUnaryOperator = product::reduceStock; +System.out.println("剩余库存:" + intUnaryOperator.applyAsInt(30)); +``` + +* Supplier 没有输入 只有输出 +```java +//无参数构造函数 +Supplier supplier = Product::new; +System.out.println("创建新对象:" + supplier.get()); + +Supplier supplier1=()->product.getStock(); +System.out.println("剩余库存:" + supplier1.get()); +``` + +* BiFunction 二元操作符 两个输入 一个输出 + +```java +//类名+方法 +BiFunction binaryOperator = Product::reduceStock; +System.out.println(" 剩余库存(BiFunction):" + binaryOperator.apply(product, 10)); +``` + + +* BinaryOperator 二元操作符 ,二个输入 一个输出 +```java +//BinaryOperator binaryOperator1=(x,y)->product.reduceStock(x,y); +BinaryOperator binaryOperator1=product::reduceStock; +System.out.println(" 剩余库存(BinaryOperator):" +binaryOperator1.apply(product.getStock(),10)); +``` +### 流式编程 +1. 是Java新支持的一种变成方法,与面向对象编程、函数式编程等类似,流式编程以来流式编程的API,是一种编程的模式,本身并不影响代码的功能。也算是语法糖的一种。 + +2. Flux和Mono只是采用了流式编程思想。不是一种特殊的编程思想。 +```java +public Stream allUsers() { + return Stream.of(new ClientUser("felord.cn", "reactive"), + new ClientUser("Felordcn", "Reactor")); +} +``` + +对数据流进行一道道操作,成为操作符,比如工厂流水线。 +* 操作符map。将元素映射为新的元素。 +* 操作符flatmap。元素映射为流。 + +![](image/2022-10-13-10-38-26.png) + +![](image/2022-10-13-10-41-45.png) diff --git a/Spring/Spring5/06 WebFlux02对象分析.md b/Spring/Spring5/06 WebFlux02对象分析.md new file mode 100644 index 00000000..f8dd4470 --- /dev/null +++ b/Spring/Spring5/06 WebFlux02对象分析.md @@ -0,0 +1,314 @@ +## 1 Flux类中的静态方法 +### 简单的创建方法 + +**just()** + + +可以指定序列中包含的全部元素。创建出来的Flux序列在发布这些元素之后会自动结束 + +**fromArray(),fromIterable(),fromStream():** + +可以从一个数组,Iterable对象或Stream对象中穿件Flux对象 + +**empty()** + +创建一个不包含任何元素,只发布结束消息的序列 + +**error(Throwable error):** + +创建一个只包含错误消息的序列 + +**never():** + +传建一个不包含任务消息通知的序列 + +**range(int start, int count):** + +创建包含从start起始的count个数量的Integer对象的序列 + +**interval(Duration period)和interval(Duration delay, Duration period):** + +创建一个包含了从0开始递增的Long对象的序列。其中包含的元素按照指定的间隔来发布。除了间隔时间之外,还可以指定起始元素发布之前的延迟时间 + +**intervalMillis(long period)和intervalMillis(long delay, long period):** + +与interval()方法相同,但该方法通过毫秒数来指定时间间隔和延迟时间 + +例子 +``` +Flux.just("Hello", "World").subscribe(System.out::println); +Flux.fromArray(new Integer[]{1, 2, 3}).subscribe(System.out::println); +Flux.empty().subscribe(System.out::println); +Flux.range(1, 10).subscribe(System.out::println); +Flux.interval(Duration.of(10, ChronoUnit.SECONDS)).subscribe(System.out::println); +Flux.intervalMillis(1000).subscirbe(System.out::println); +``` + +### 复杂的序列创建 generate() +当序列的生成需要复杂的逻辑时,则应该使用generate()或create()方法。 + +* generate()方法通过同步和逐一的方式来产生Flux序列。 + +* 序列的产生是通过调用所提供的的SynchronousSink对象的next(),complete()和error(Throwable)方法来完成的。 + +* 逐一生成的含义是在具体的生成逻辑中,next()方法只能最多被调用一次。 + +* 在某些情况下,序列的生成可能是有状态的,需要用到某些状态对象,此时可以使用 + +``` +generate(Callable stateSupplier, BiFunction, S> generator), +``` +其中stateSupplier用来提供初始的状态对象。 + +在进行序列生成时,状态对象会作为generator使用的第一个参数传入,可以在对应的逻辑中对改状态对象进行修改以供下一次生成时使用。 +```java +Flux.generate(sink -> { + sink.next("Hello"); + sink.complete(); +}).subscribe(System.out::println); + + +final Random random = new Random(); +Flux.generate(ArrayList::new, (list, sink) -> { + int value = random.nextInt(100); + list.add(value); + sink.next(value); + if( list.size() ==10 ) + sink.complete(); + return list; +}).subscribe(System.out::println); +``` + +### 复杂的序列创建 create() +create()方法与generate()方法的不同之处在于所使用的是FluxSink对象。 + +FluxSink支持同步和异步的消息产生,并且可以在一次调用中产生多个元素。 +```· +Flux.create(sink -> { + for(int i = 0; i < 10; i ++) + sink.next(i); + sink.complete(); +}).subscribe(System.out::println); +``` + +## 2 Mono静态方法 +Mono类包含了与Flux类中相同的静态方法:just(),empty()和never()等。 + +除此之外,Mono还有一些独有的静态方法: + + +* fromCallable(),fromCompletionStage(),fromFuture(),fromRunnable()和fromSupplier():分别从Callable,CompletionStage,CompletableFuture,Runnable和Supplier中创建Mono + +* delay(Duration duration)和delayMillis(long duration):创建一个Mono序列,在指定的延迟时间之后,产生数字0作为唯一值 + +* ignoreElements(Publisher source):创建一个Mono序列,忽略作为源的Publisher中的所有元素,只产生消息 + +* justOrEmpty(Optional data)和justOrEmpty(T data):从一个Optional对象或可能为null的对象中创建Mono。只有Optional对象中包含之或对象不为null时,Mono序列才产生对应的元素 + + +```java +Mono.fromSupplier(() -> "Hello").subscribe(System.out::println); +Mono.justOrEmpty(Optional.of("Hello")).subscribe(System.out::println); +Mono.create(sink -> sink.success("Hello")).subscribte(System.out::println); +``` + +## 3 操作符 +### 操作符buffer和bufferTimeout +这两个操作符的作用是把当前流中的元素收集到集合中,并把集合对象作为流中的新元素。 + +在进行收集时可以指定不同的条件:所包含的元素的最大数量或收集的时间间隔。方法buffer()仅使用一个条件,而bufferTimeout()可以同时指定两个条件。 + +指定时间间隔时可以使用Duration对象或毫秒数,即使用bufferMillis()或bufferTimeoutMillis()两个方法。 + +除了元素数量和时间间隔外,还可以通过bufferUntil和bufferWhile操作符来进行收集。这两个操作符的参数时表示每个集合中的元素索要满足的条件的Predicate对象。 + +bufferUntil会一直收集直到Predicate返回true。 + +使得Predicate返回true的那个元素可以选择添加到当前集合或下一个集合中;bufferWhile则只有当Predicate返回true时才会收集。一旦为false,会立即开始下一次收集。 + +```java +Flux.range(1, 100).buffer(20).subscribe(System.out::println); +Flux.intervalMillis(100).bufferMillis(1001).take(2).toStream().forEach(System.out::println); +Flux.range(1, 10).bufferUntil(i -> i%2 == 0).subscribe(System.out::println); +Flux.range(1, 10).bufferWhile(i -> i%2 == 0).subscribe(System.out::println); +``` + +### 操作符Filter +对流中包含的元素进行过滤,只留下满足Predicate指定条件的元素。 +``` +Flux.range(1, 10).filter(i -> i%2 == 0).subscribe(System.out::println); +``` + +### 操作符zipWith +zipWith操作符把当前流中的元素与另一个流中的元素按照一对一的方式进行合并。在合并时可以不做任何处理,由此得到的是一个元素类型为Tuple2的流;也可以通过一个BiFunction函数对合并的元素进行处理,所得到的流的元素类型为该函数的返回值。 +```java +Flux.just("a", "b").zipWith(Flux.just("c", "d")).subscribe(System.out::println); +Flux.just("a", "b").zipWith(Flux.just("c", "d"), (s1, s2) -> String.format("%s-%s", s1, s2)).subscribe(System.out::println); +``` + +### 操作符take +take系列操作符用来从当前流中提取元素。提取方式如下: + +* take(long n),take(Duration timespan)和takeMillis(long timespan):按照指定的数量或时间间隔来提取 + +* takeLast(long n):提取流中的最后N个元素 + +* takeUntil(Predicate predicate) :提取元素直到Predicate返回true + +* takeWhile(Predicate continuePredicate):当Predicate返回true时才进行提取 + +* takeUntilOther(Publisher other):提取元素知道另外一个流开始产生元素 + +```java +Flux.range(1, 1000).take(10).subscribe(System.out::println); +Flux.range(1, 1000).takeLast(10).subscribe(System.out::println); +Flux.range(1, 1000).takeWhile(i -> i < 10).subscribe(System.out::println); +Flux.range(1, 1000).takeUntil(i -> i == 10).subscribe(System.out::println); +``` + +### 操作符reduce和reduceWith +reduce和reduceWith操作符对流中包含的所有元素进行累计操作,得到一个包含计算结果的Mono序列。累计操作是通过一个BiFunction来表示的。在操作时可以指定一个初始值。若没有初始值,则序列的第一个元素作为初始值。 + +``` +Flux.range(1, 100).reduce((x, y) -> x + y).subscribe(System.out::println); +Flux.range(1, 100).reduceWith(() -> 100, (x + y) -> x + y).subscribe(System.out::println); +``` + +### 操作符merge和mergeSequential +merge和mergeSequential操作符用来把多个流合并成一个Flux序列。merge按照所有流中元素的实际产生序列来合并,而mergeSequential按照所有流被订阅的顺序,以流为单位进行合并。 +```java +Flux.merge(Flux.intervalMillis(0, 100).take(5), Flux.intervalMillis(50, 100).take(5)).toStream().forEach(System.out::println); +Flux.mergeSequential(Flux.intervalMillis(0, 100).take(5), Flux.intervalMillis(50, 100).take(5)).toStream().forEach(System.out::println); +``` +### 操作符flatMap和flatMapSequential +flatMap和flatMapSequential操作符把流中的每个元素转换成一个流,再把所有流中的元素进行合并。flatMapSequential和flatMap之间的区别与mergeSequential和merge是一样的。 + +```java +Flux.just(5, 10).flatMap(x -> Flux.intervalMillis(x * 10, 100).take(x)).toStream().forEach(System.out::println); +``` +### 操作符concatMap +concatMap操作符的作用也是把流中的每个元素转换成一个流,再把所有流进行合并。concatMap会根据原始流中的元素顺序依次把转换之后的流进行合并,并且concatMap堆转换之后的流的订阅是动态进行的,而flatMapSequential在合并之前就已经订阅了所有的流。 +``` +Flux.just(5, 10).concatMap(x -> Flux.intervalMillis(x * 10, 100).take(x)).toStream().forEach(System.out::println); +``` +### 操作符combineLatest +combineLatest操作符把所有流中的最新产生的元素合并成一个新的元素,作为返回结果流中的元素。只要其中任何一个流中产生了新的元素,合并操作就会被执行一次,结果流中就会产生新的元素。 +``` +Flux.combineLatest(Arrays::toString, Flux.intervalMillis(100).take(5), Flux.intervalMillis(50, 100).take(5)).toStream().forEach(System.out::println); +``` + + +## 4 消息处理 +当需要处理Flux或Mono中的消息时,可以通过subscribe方法来添加相应的订阅逻辑。 + +* 在调用subscribe方法时可以指定需要处理的消息类型。 +```java +Flux.just(1, 2).concatWith(Mono.error(new IllegalStateException())).subscribe(System.out::println, System.err::println); + +Flux.just(1, 2).concatWith(Mono.error(new IllegalStateException())).onErrorReturn(0).subscribe(System.out::println); +``` + +* 第2种可以通过switchOnError()方法来使用另外的流来产生元素。 +```java +Flux.just(1, 2).concatWith(Mono.error(new IllegalStateException())).switchOnError(Mono.just(0)).subscribe(System.out::println); +``` + +* 第三种是通过onErrorResumeWith()方法来根据不同的异常类型来选择要使用的产生元素的流。 +```java +Flux.just(1, 2).concatWith(Mono.error(new IllegalArgumentException())).onErrorResumeWith(e -> { + if(e instanceof IllegalStateException) + return Mono.just(0); + else if(e instanceof IllegalArgumentException) + return Mono.just(-1); + return Mono.epmty(); +}).subscribe(System,.out::println); +``` +* 当出现错误时还可以使用retry操作符来进行重试。重试的动作是通过重新订阅序列来实现的。在使用retry操作时还可以指定重试的次数。 + +```java + Flux.just(1, 2).concatWith(Mono.error(new IllegalStateException())).retry(1).subscrible(System.out::println); +``` + +## 5 调度器Scheduler +通过调度器可以指定操作执行的方式和所在的线程。有以下几种不同的调度器实现 + +* 当前线程,通过Schedulers.immediate()方法来创建 + +* 单一的可复用的线程,通过Schedulers.single()方法来创建 + +* 弹性的线程池,通过Schedulers.elastic()方法来创建。线程池中的线程是可以复用的。当所需要时,新的线程会被创建。若一个线程闲置时间太长,则会被销毁。该调度器适用于I/O操作相关的流的处理 + +* 并行操作优化的线程池,通过Schedulers.parallel()方法来创建。其中的线程数量取决于CPU的核的数量。该调度器适用于计算密集型的流的处理 + +* 使用支持任务调度的调度器,通过Schedulers.timer()方法来创建 + +* 从已有的ExecutorService对象中创建调度器,通过Schedulers.fromExecutorService()方法来创建 + +通过publishOn()和subscribeOn()方法可以切换执行操作调度器。publishOn()方法切换的是操作符的执行方式,而subscribeOn()方法切换的是产生流中元素时的执行方式 + +```java +Flux.create(sink -> { + sink.next(Thread.currentThread().getName()); + sink.complete(); +}).publishOn(Schedulers.single()) +.map(x -> String.format("[%s] %s", Thread.currentThread().getName(), x)) +.publishOn(Schedulers.elastic()) +.map(x -> String.format("[%s] %s", Thread.currentThread().getName(), x)) +.subscribeOn(Schedulers.parallel()) +.toStream() +.forEach(System.out::println); +``` + +## 6 测试 +StepVerifier的作用是可以对序列中包含的元素进行逐一验证。通过StepVerifier.create()方法对一个流进行包装之后再进行验证。expectNext()方法用来声明测试时所期待的流中的下一个元素的值,而verifyComplete()方法则验证流是否正常结束。verifyError()来验证流由于错误而终止。 + +``` +StepVerifier.create(Flux.just(a, b)).expectNext("a").expectNext("b").verifyComplete(); +``` +使用StepVerifier.withVirtualTime()方法可以创建出使用虚拟时钟的SteoVerifier。通过thenAwait(Duration)方法可以让虚拟时钟前进。 +``` +StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofHours(4), Duration.ofDays(1)).take(2)) + .expectSubscription() + .expectNoEvent(Duration.ofHours(4)) + .expectNext(0L) + .thenAwait(Duration.ofDays(1)) + .expectNext(1L) + .verifyComplete(); +``` +TestPublisher的作用在于可以控制流中元素的产生,甚至是违反反应流规范的情况。通过create()方法创建一个新的TestPublisher对象,然后使用next()方法来产生元素,使用complete()方法来结束流。 +``` +final TestPublisher testPublisher = TestPublisher.creater(); +testPublisher.next("a"); +testPublisher.next("b"); +testPublisher.complete(); + +StepVerifier.create(testPublisher) + .expectNext("a") + .expectNext("b") + .expectComplete(); +``` +## 7 调试 +在调试模式启用之后,所有的操作符在执行时都会保存额外的与执行链相关的信息。当出现错误时,这些信息会被作为异常堆栈信息的一部分输出。 +``` +Hooks.onOperator(providedHook -> providedHook.operatorStacktrace()); +``` +也可以通过checkpoint操作符来对特定的流处理链来启用调试模式。 +``` +Flux.just(1, 0).map(x -> 1/x).checkpoint("test").subscribe(System.out::println); +``` + +## 8 日志记录 +可以通过添加log操作把流相关的事件记录在日志中, +``` +Flux.range(1, 2).log("Range").subscribe(System.out::println); +``` +## 9 冷热序列 +冷序列的含义是不论订阅者在何时订阅该序列,总是能收到序列中产生的全部消息。热序列是在持续不断的产生消息,订阅者只能获取到在其订阅之后产生的消息。 +```java +final Flux source = Flux.intervalMillis(1000).take(10).publish.autoConnect(); +source.subscribe(); +Thread.sleep(5000); +source.toStream().forEach(System.out::println); +``` + + diff --git a/Spring/Spring5/06 WebFlux03Server.md b/Spring/Spring5/06 WebFlux03Server.md new file mode 100644 index 00000000..254995f6 --- /dev/null +++ b/Spring/Spring5/06 WebFlux03Server.md @@ -0,0 +1,243 @@ + + +## 1 WebFlux基于注解的编程的实现 + + +### 创建WebFlux项目 +1. 创建Springboot项目,引入webflux的依赖 +```xml +pom.xml + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.1.RELEASE + + + com.example + shangguigu09 + 0.0.1-SNAPSHOT + shangguigu09 + shangguigu09 + + 1.8 + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +2. 在配置文件中,设置启动端口号8081 +```yaml +server.port =8081 +``` +3. 从上到下设计代码:创建接口和实现类 + +```java +@Service +public class UserServiceImpl implements UserService { + private final Map users = new HashMap<>(); + + public UserServiceImpl() { + + this.users.put(1,new User("lucy","nan",10)); + this.users.put(2,new User("mary","nv",38)); + this.users.put(3,new User("jack","nv",32)); + + } + + @Override + public Mono getUserById(int id) { + return Mono.justOrEmpty(this.users.get(id)); + } + + @Override + public Flux getAllUser() { + return Flux.fromIterable(this.users.values()); + } + + @Override + public Mono savaUserInfo(Mono userMono) { + return userMono.doOnNext(person->{ + int id = users.size() + 1; + users.put(id,person); + }).thenEmpty(Mono.empty()); + } +} +``` + +4. 从下到上实现代码:实现业务逻辑 +```java + +@RestController +public class UserController { + @Autowired + private UserService userService; + + //id + @GetMapping("/user/{id}") + public Mono getUserById(@PathVariable int id){ + return userService.getUserById(id); + } + + //all + @GetMapping("/user") + public Flux getAllUser(){ + return userService.getAllUser(); + } + //tianjian + @GetMapping("/saveuser") + public Mono saveUser(@RequestBody User user){ + Mono userMono = Mono.just(user); + return userService.savaUserInfo(userMono); + } + + @GetMapping("/hello/{latency}") + public Mono hello(@PathVariable long latency) { + System.out.println("Start:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))); + System.out.println("Page count:" + COUNT.incrementAndGet()); + Mono res = Mono.just("welcome to Spring Webflux").delayElement(Duration.ofSeconds(latency));//阻塞latency秒,模拟处理耗时 + System.out.println("End: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))); + return res; + } +} +``` + + +### 实现说明 +* SpringMVC范式,同步阻塞方式,基于SpringMVC+Servlet+Tomcat +* SpringWebflux方式,异步非阻塞方式,基于SpringMVCWebflux+Reactor+Netty + + +## 2 WebFlux基于函数的编程的实现 + +### 简要说明 +> bio,nio,aio +在使用函数式编程,需要自己初始化服务器 + +基于函数式编程模型的时候,有两个核心接口。 +* RouterFunction 实现路由功能,请求转发给对应的handler +* HandlerFunction 处理请求生成响应函数。 + +核心任务定义两个函数式接口的实现,并启动需要的服务器。 + +SpringWebFlux的请求和响应是 +* ServerRequest +* ServerResponse + + +### 实现流程 +1. 从上到下实现业务bean +2. 创建handler实现Mono方法 + +```java + +public class UserHandler { + + private final UserService userService; + public UserHandler(UserService userService){ + this.userService = userService; + } + + //根据id + public Mono getUserById(ServerRequest request){ + //获取id值 + int userid = Integer.valueOf( request.pathVariable("id")); + Mono notFound = ServerResponse.notFound().build(); + //调用service方法取得数据 + Mono userMono = this.userService.getUserById(userid); + + //UserMono进行转换返回。Reactor操作符 + return userMono.flatMap(person->ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) + .body(fromObject(person))) + .switchIfEmpty(notFound); + + } + + //所有用户 + public Mono getAllUsers(){ + Flux users = this.userService.getAllUser(); + return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class); + + } + + + //添加 + public Mono saveUser(ServerRequest request){ + Mono userMono = request.bodyToMono(User.class); + return ServerResponse.ok().build(this.userService.savaUserInfo(userMono)); + } +} +``` + +3. 创建并初始化服务器,设置路由和handler + +```java +public class Server { + //创建路由 + public RouterFunction route(){ + UserService userService = new UserServiceImpl(); + UserHandler handler = new UserHandler(userService); + + return RouterFunctions.route(GET("/users/{id}").and(accept(MediaType.APPLICATION_JSON)),handler::getUserById); +// .andRoute(GET("users").and(accept(MediaType.APPLICATION_JSON)),handler::getAllUsers) +// .andRoute(GET("saveuser").and(accept(MediaType.APPLICATION_JSON)),handler::saveUser); + + } + + public void createReactorServer(){ + RouterFunction route = route(); + HttpHandler httpHandler = toHttpHandler(route); + + ReactorHttpHandlerAdapter reactorHttpHandlerAdapter = new ReactorHttpHandlerAdapter(httpHandler); + + HttpServer httpServer = HttpServer.create(); + httpServer.handle(reactorHttpHandlerAdapter).bindNow(); + } + + public static void main(String[] args) throws Exception{ + Server server = new Server(); + server.createReactorServer(); + System.out.println("enter to exit"); + System.in.read(); + } +} +``` + +## 3 WebClient调用 + + +```java +public class Client { + + public static void main(String[] args) { + WebClient webClient = WebClient.create("http://127.0.0.1:62418"); + User userMono = webClient.get().uri("/users/{id}", "1").accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block(); + System.out.println(userMono.getName()); + } +} + +``` \ No newline at end of file diff --git a/Spring/Spring5/06 WebFlux04WebClient.md b/Spring/Spring5/06 WebFlux04WebClient.md new file mode 100644 index 00000000..c2e0ddc2 --- /dev/null +++ b/Spring/Spring5/06 WebFlux04WebClient.md @@ -0,0 +1,229 @@ + +> 参考文件https://blog.csdn.net/hellozpc/article/details/122441522 +> https://blog.csdn.net/kerongao/article/details/109746190 +> https://www.cnblogs.com/chaosmoor/p/1670308e.html +> https://www.jianshu.com/p/cc3a99614476 + + +## 1 概述 + +### 简介 + +它是Spring5中引入的响应式web客户端类库,最大特点是支持异步调用;我们还将学习WebTestClient,用于单元测试。 + +简单地说,WebClient是一个接口,执行web请求的主要入口点。 + +它是Spring Web Reactive模块的一部分,并且取代经典的RestTemplate而生。此外,新的客户端是一个响应式的、非阻塞的技术方案,可以在HTTP/1.1协议上工作。 + + + +## 2 使用 + +### 步骤 + +使用WebClient我们需要按照如下几步来操作 + +1. 创建WebClient实例 +2. 执行请求 +3. 处理返回数据 + +### 创建WebClient实例 + +构建WebClient有三种方法: + +第一种,使用默认配置构建WebClient +```java +WebClient client1 = WebClient.create(); +``` +第二种,使用base URI参数构建WebClient +```java +WebClient client2 = WebClient.create("http://localhost:8080"); +``` +第三种,使用DefaultWebClientBuilder构造WebClient。构造器流式编程接口创建。 +```java +WebClient client3 = WebClient + .builder() + .baseUrl("http://localhost:8080") + .defaultCookie("cookieKey", "cookieValue") + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080")) + .build(); +``` + + +### WebClient设置Timeouts +通常,默认HTTP的超时为30秒,对于实际业务来说时间太长,所以让我们看看如何为我们的WebClient实例配置它们。 + +可以通过TcpClient来设置超时时间,超时时间分为链接超时时间和读/写超时时间,为了保证达到预期效果这两个值都需要设置。 + +我们可以通过ChannelOption.CONNECT_TIMEOUT_MILLIS设置连接超时,我们还可以使用ReadTimeoutHandler和WriteTimeoutHandler分别设置读和写超时 +```java +TcpClient tcpClient = TcpClient + .create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) + .doOnConnected(connection -> { + connection.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS)); + connection.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)); + }); + +WebClient client = WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient))) + .build(); +``` +> 虽然我们也可以在WebClient客户端请求上调用超时,但这是一个信号超时,而不是HTTP连接或读/写超时;这是Mono/Flux发布者的超时 + +### 准备请求数据 + +**第一步**,我们需要通过method(HttpMethod method) 或调yonWebClient来指定请求的HTTP方法get, post,delete: +```java +WebClient.UriSpec request1 = client3.method(HttpMethod.POST); +WebClient.UriSpec request2 = client3.post(); +``` + +**第二步**,设置请求访问URL +```java +WebClient.RequestBodySpec uri1 = client3 + .method(HttpMethod.POST) + .uri("/resource"); + +WebClient.RequestBodySpec uri2 = client3 + .post() + .uri(URI.create("/resource")); +``` +路径参数:我们使用uriBuilder构建包含路径参数的uri,也可以直接使用占位符。 /events/{id} 访问点并构建相应的 URI: +```java +this.webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/events/{id}") + .build(2)) + .retrieve(); +verifyCalledUrl("/events/2"); + + +String url = "http://localhost:8080/user/{id}/{name}"; +String id = "123"; +String name = "Boss"; +Mono mono = WebClient.create() + .method(HttpMethod.POST) + .uri(url, id, name) + .retrieve() + .bodyToMono(String.class); +String result = mono.block(); + +``` +查询参数:采用 /events?name=[name]&startDate=[startDate]访问点。要设置查询参数,我们需要调用 UriBuilder 接口的 queryParam()方法: +```java +this.webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/events") + .queryParam("name", "InitFailed") + .queryParam("startDate", "13/02/2021") + .build()) + .retrieve(); +verifyCalledUrl("/events?name=InitFailed&startDate=13/02/2021") +``` + + +**第三步**,设置request body、content type、 length、cookies、headers 等请求参数 + +例如,如果我们想要设置一个request body,有两种可用的方法:用一个BodyInserter,或者把这个工作委托给Publisher +```java +WebClient.RequestHeadersSpec requestSpec1 = WebClient + .create() + .method(HttpMethod.POST) + .uri("/resource") + .body(BodyInserters.fromPublisher(Mono.just("data")), String.class); + +WebClient.RequestHeadersSpec requestSpec2 = WebClient + .create("http://localhost:8080") + .post() + .uri(URI.create("/resource")) + .body(BodyInserters.fromObject("data")); +``` +BodyInserter 是一个接口,负责向request body中插入请求的body值 + +我们还可以设置MultiValueMap数据作为request body值 +```java +LinkedMultiValueMap map = new LinkedMultiValueMap(); + +map.add("key1", "value1"); +map.add("key2", "value2"); + +BodyInserter inserter2 + = BodyInserters.fromMultipartData(map); +``` +或者插入一个对象 +```java +BodyInserter inserter3 + = BodyInserters.fromObject(new Object()); +``` +在设置request body之后,我们可以设置content type、 length、cookies、headers + +此外,它还支持最常用的头文件,如“If-None-Match”、“If-Modified-Since”、“Accept”和“Accept- charset”。 +```java +WebClient.ResponseSpec response1 = uri1 + .body(inserter3) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) + .acceptCharset(Charset.forName("UTF-8")) + .ifNoneMatch("*") + .ifModifiedSince(ZonedDateTime.now()) + .retrieve(); +``` + +### 获取Response +最后一个步骤是发送请求和接收响应,这可以通过exchange或retrieve方法来完成 +* exchange方法提供了ClientResponse以及它的status和header +* 而retrieve方法是直接获取body的内容: +```java +String response2 = request1.exchange() + .block() + .bodyToMono(String.class) + .block(); +String response3 = request2 + .retrieve() + .bodyToMono(String.class) + .block(); +``` +需要注意的是bodyToMono方法,如果状态代码是4xx(客户端错误)或5xx(服务器错误),它将抛出一个WebClientException。 + +* Monos.block()方法来订阅和检索与响应一起发送的实际数据 +* Monos.subscribe()非阻塞式获取响应结果 +```java +@Test +public void testSubscribe() { + Mono mono = WebClient + .create() + .method(HttpMethod.GET) + .uri("http://localhost:8080/hello") + .retrieve() + .bodyToMono(String.class); + mono.subscribe(WebClientTest::handleMonoResp); +} +//响应回调 +private static void handleMonoResp(String monoResp) { + System.out.println("请求结果为:" + monoResp); +} +``` + +注意!在使用新版的spring boot 2.4.0,可能会出现如下错误: +```java +org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144 +``` +可以通过修改application.yaml配置文件,增加缓冲区大小(测试发现不起作用) +```java +spring.codec.max-in-memory-size: 5MB +``` +通过代码配置缓冲区大小 +```java + WebClient client = WebClient.builder() + .exchangeStrategies(ExchangeStrategies.builder() + .codecs(configurer -> { + configurer.defaultCodecs() + .maxInMemorySize(16 * 1024 * 1024) ; } + ) + .build()) + .build(); + +``` +设置缓冲区大小为16MB diff --git a/Spring/Spring5/06 Webflux.md b/Spring/Spring5/06 Webflux.md deleted file mode 100644 index de004955..00000000 --- a/Spring/Spring5/06 Webflux.md +++ /dev/null @@ -1,435 +0,0 @@ -# WebFlux -> 这一块了解就行,不用掌握。 -## 1 WebFlux简介 -> 第一轮:就是看视频教程学会所有技术的原理和基本使用方法 -> 第二轮:阅读官方的文档,尤其是Spring、Java、Maven等,掌握编程的细节。 - - -### 简介 -Spring5添加的新模块。用于web开发的,功能与SpringMVC类似。Webflux使用与当前比较流行的响应式编程出现的框架。 - -传统的Web框架,比如SpringMVC,基于Servlet容器,Webflux是一种异步非阻塞的框架。(异步非阻塞的框架在Servlet3.1后才支持) - -其和虚拟式基于Reactor的相关API实现的。 - - -### 异步非阻塞 - -我更喜欢反过来理解这里。同步异步是针对被调用者的,阻塞和非阻塞是针对调用者的。 - -* **阻塞和非阻塞针对调用者**。阻塞和非阻塞指的是调用者(程序)在等待返回结果(或输入)时的状态。阻塞时,在调用结果返回前,当前线程会被挂起,并在得到结果之后返回。非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前线程。因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。。 - -* **异步和同步是针对被调用者**,同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。 - -### 与SpringMVC对比 - -![](image/2022-10-12-19-28-38.png) - -* 异步非阻塞,在有限的资源下,能够处理更多的请求,提高系统地吞吐量。 -* 函数式编程。(Java最基本的编程模式)。能够使用Java函数式编程的特点。 -* 两个框架都可以使用注解方式运行,都可以运行在Tomcat等Servlet容器中。但SpringMVC采用命令式编程,WebFlux使用响应式编程。 - - -### 使用场景:网关 -* 需要处理大量的请求。所有客户调用网关,网关负责调用其他的组件。可以使用异步的方式。 - -## 2 响应式编程 - -### 响应式编程定义 -响应式编程是一种面向数据流和变化产波的编程范式。 - -意味着可以在编程语言很方便地表达静态或者动态的数据流, - -![](image/2022-10-12-19-35-03.png) -一个响应式编程的典型例子。D1=B1+C1。当B1的值修改后,D1的值也会修改。B1的数据变化,流向了D1。 - - -### Java8响应式编程 - -是要使用观察者模式,实现了响应式编程。使用响应式编程Observer,Observable实现。 -```java -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package com.ykl.shangguigu08.reactor; - -import java.util.Observable; - -/** - * @author yinkanglong - * @version : ObserverDemo, v 0.1 2022-10-12 19:47 yinkanglong Exp $ - */ -public class ObserverDemo extends Observable { - - /** - * 通过Java8中的类实现响应式编程。 - * 简单来说,就是观察值模式。 - * @param args - */ - public static void main(String[] args) { - ObserverDemo observerDemo = new ObserverDemo(); - - observerDemo.addObserver((o,arg)->{ - System.out.println("发生变化"); - }); - - observerDemo.addObserver((o,arg)->{ - System.out.println("准备改变"); - }); - - observerDemo.setChanged(); - observerDemo.notifyObservers(); - } -} -``` -### java9响应式编程 -主要通过Flow类的sub和sub订阅消息,实现响应式编程。 -> 感觉这个响应式编程和awt控件的点击相应式操作很相似。但是不是启动新的线程。 - -``` -``` -### 响应式编程(Reator实现) - -* 响应式编程操作,Reactor是满足Reactive规范框架 -* Reactor有两个核心类,Mono和Flux,这两个类实现接口Publisher,提供丰富操作符号,Flux对象实现发布,返回N个元素。Mono实现发布者,返回0或者1个元素。 -* Flux和Mono都是数据流的发布者。能够发出三种信号 - * 元素值 - * 完成信号。一种终止信号。订阅者数据流已经结束了。 - * 错误信号。一种终止信号。终止数据流并把错误信息传递给订阅者。 - -![](image/2022-10-13-10-22-26.png) - -三种信号的特点 -* 错误信号和完成信号都是终止信号不能共存。 -* 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示空数据流 -* 如果没有错误信号,没有完成信号,表示无限数据流。 - -### 实例:Flux&Mono - -引入相关的依赖 -``` - - io.projectreactor - reactor-core - 3.1.5.RELEASE - -``` - -进行发布者发布内容 -```java -package com.ykl.shangguigu08.reactor; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Stream; - -/** - * @author yinkanglong - * @version : TestReactor, v 0.1 2022-10-13 10:25 yinkanglong Exp $ - */ -public class TestReactor { - - public static void main(String[] args) { - //reactor中的核心语法 - Flux.just(1,2,3,4); - Mono.just(1); - - //其他方法 - Integer[] array = {1,2,3,4}; - Flux.fromArray(array); - - List list = Arrays.asList(array); - Flux.fromIterable(list); - - Stream stream = list.stream(); - Flux.fromStream(stream); - } -} -``` -* just等发布方法只是声明了数据流。只有声明了订阅者才会触发数据流,不订阅,就不会触发。 - -``` -Flux.just(1,2,3,4).subscribe(System.out::print); -Mono.just(1).subscribe(System.out::print); -``` -### 操作符 -对数据流进行一道道操作,成为操作符,比如工厂流水线。 -* 操作符map。将元素映射为新的元素。 -* 操作符flatmap。元素映射为流。 - -![](image/2022-10-13-10-38-26.png) - -![](image/2022-10-13-10-41-45.png) - -## 4 WebFlux执行流程和核心API - -### Netty的基本原理 -SpringWebflux基于Reactor,默认使用容器Netty,Netty是高性能的NIO框架,异步非阻塞框架。 - -1. BIO阻塞 - -![](image/2022-10-13-11-44-28.png) - -2. NIO非阻塞 - -![](image/2022-10-13-11-44-49.png) - - -### SpringWebFlux - -* SpringWebflux核心控制器DispatchHandler,实现接口WebHandler - -![](image/2022-10-13-11-48-44.png) - -### 关键类 -DispatcherHandler负责请求处理。有三个核心类。 -![](image/2022-10-13-11-51-20.png) - - -* HandlerMapping(reactor反应器):请求查询到处理方法。 -* HandlerAdapter:真正负责请求处理(processor部分) -* HandlerResultHandler:对结果进行处理 - -### 函数式编程实现 -两个核心接口。 -* RouterFunction 路由处理 -* HandlerFunction处理函数 - - -## 5 WebFlux基于注解的编程的实现 - - -### 创建WebFlux项目 -1. 创建Springboot项目,引入webflux的依赖 -```xml -pom.xml - - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.2.1.RELEASE - - - com.example - shangguigu09 - 0.0.1-SNAPSHOT - shangguigu09 - shangguigu09 - - 1.8 - - - - org.springframework.boot - spring-boot-starter-webflux - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - -``` -2. 在配置文件中,设置启动端口号8081 -```yaml -server.port =8081 -``` -3. 从上到下设计代码:创建接口和实现类 - -```java -@Service -public class UserServiceImpl implements UserService { - private final Map users = new HashMap<>(); - - public UserServiceImpl() { - - this.users.put(1,new User("lucy","nan",10)); - this.users.put(2,new User("mary","nv",38)); - this.users.put(3,new User("jack","nv",32)); - - } - - @Override - public Mono getUserById(int id) { - return Mono.justOrEmpty(this.users.get(id)); - } - - @Override - public Flux getAllUser() { - return Flux.fromIterable(this.users.values()); - } - - @Override - public Mono savaUserInfo(Mono userMono) { - return userMono.doOnNext(person->{ - int id = users.size() + 1; - users.put(id,person); - }).thenEmpty(Mono.empty()); - } -} -``` - -4. 从下到上实现代码:实现业务逻辑 -```java - -@RestController -public class UserController { - @Autowired - private UserService userService; - - //id - @GetMapping("/user/{id}") - public Mono getUserById(@PathVariable int id){ - return userService.getUserById(id); - } - - //all - @GetMapping("/user") - public Flux getAllUser(){ - return userService.getAllUser(); - } - //tianjian - @GetMapping("/saveuser") - public Mono saveUser(@RequestBody User user){ - Mono userMono = Mono.just(user); - return userService.savaUserInfo(userMono); - } -} -``` - - -### 实现说明 -* SpringMVC范式,同步阻塞方式,基于SpringMVC+Servlet+Tomcat -* SpringWebflux方式,异步非阻塞方式,基于SpringMVCWebflux+Reactor+Netty - - -## 6 WebFlux基于函数的编程的实现 - -### 简要说明 -> bio,nio,aio -在使用函数式编程,需要自己初始化服务器 - -基于函数式编程模型的时候,有两个核心接口。 -* RouterFunction 实现路由功能,请求转发给对应的handler -* HandlerFunction 处理请求生成响应函数。 - -核心任务定义两个函数式接口的实现,并启动需要的服务器。 - -SpringWebFlux的请求和响应是 -* ServerRequest -* ServerResponse - - -### 实现流程 -1. 从上到下实现业务bean -2. 创建handler实现Mono方法 - -```java - -public class UserHandler { - - private final UserService userService; - public UserHandler(UserService userService){ - this.userService = userService; - } - - //根据id - public Mono getUserById(ServerRequest request){ - //获取id值 - int userid = Integer.valueOf( request.pathVariable("id")); - Mono notFound = ServerResponse.notFound().build(); - //调用service方法取得数据 - Mono userMono = this.userService.getUserById(userid); - - //UserMono进行转换返回。Reactor操作符 - return userMono.flatMap(person->ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) - .body(fromObject(person))) - .switchIfEmpty(notFound); - - } - - //所有用户 - public Mono getAllUsers(){ - Flux users = this.userService.getAllUser(); - return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class); - - } - - - //添加 - public Mono saveUser(ServerRequest request){ - Mono userMono = request.bodyToMono(User.class); - return ServerResponse.ok().build(this.userService.savaUserInfo(userMono)); - } -} -``` - -3. 创建并初始化服务器,设置路由和handler - -```java -public class Server { - //创建路由 - public RouterFunction route(){ - UserService userService = new UserServiceImpl(); - UserHandler handler = new UserHandler(userService); - - return RouterFunctions.route(GET("/users/{id}").and(accept(MediaType.APPLICATION_JSON)),handler::getUserById); -// .andRoute(GET("users").and(accept(MediaType.APPLICATION_JSON)),handler::getAllUsers) -// .andRoute(GET("saveuser").and(accept(MediaType.APPLICATION_JSON)),handler::saveUser); - - } - - public void createReactorServer(){ - RouterFunction route = route(); - HttpHandler httpHandler = toHttpHandler(route); - - ReactorHttpHandlerAdapter reactorHttpHandlerAdapter = new ReactorHttpHandlerAdapter(httpHandler); - - HttpServer httpServer = HttpServer.create(); - httpServer.handle(reactorHttpHandlerAdapter).bindNow(); - } - - public static void main(String[] args) throws Exception{ - Server server = new Server(); - server.createReactorServer(); - System.out.println("enter to exit"); - System.in.read(); - } -} -``` - -### WebClient调用 - - -```java -public class Client { - - public static void main(String[] args) { - WebClient webClient = WebClient.create("http://127.0.0.1:62418"); - User userMono = webClient.get().uri("/users/{id}", "1").accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block(); - System.out.println(userMono.getName()); - } -} - -``` \ No newline at end of file diff --git a/Spring/Spring5/07 事件监听模型.md b/Spring/Spring5/07 事件监听模型.md index f8a266a6..7a4d1e00 100644 --- a/Spring/Spring5/07 事件监听模型.md +++ b/Spring/Spring5/07 事件监听模型.md @@ -1,5 +1,8 @@ ## 1 原理说明 - +类似概念 +* 可观察对象、观察者。(观察者模式) +* 发布者、订阅者。(发布订阅机制) +* 事件、响应。(事件驱动、事件监听机制、响应式编程) ### 事件驱动模型和观察者模式 一下名称具有相同的含义,都是观察着模式在不同场景下的实现。 * Spring的事件驱动模型 diff --git a/Spring/Spring5/image/2022-11-25-20-26-34.png b/Spring/Spring5/image/2022-11-25-20-26-34.png new file mode 100644 index 00000000..58455efc Binary files /dev/null and b/Spring/Spring5/image/2022-11-25-20-26-34.png differ diff --git a/Java基础教程/Java网络编程/浅析TCP字节流与UDP数据报的区别.md b/计算机网络/浅析TCP字节流与UDP数据报的区别.md similarity index 100% rename from Java基础教程/Java网络编程/浅析TCP字节流与UDP数据报的区别.md rename to 计算机网络/浅析TCP字节流与UDP数据报的区别.md