举报网站建设运行汇报济南建设工程信息网站
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:34
当前位置: 首页 > news >正文
举报网站建设运行汇报,济南建设工程信息网站,网站自然排名工具,软文推广去哪个平台好IO 流简介 IO 即 Input/Output#xff0c;输入和输出。数据输入到计算机内存的过程即输入#xff0c;反之输出到外部存储#xff08;比如数据库#xff0c;文件#xff0c;远程主机#xff09;的过程即输出。数据传输过程类似于水流#xff0c;因此称为 IO 流。IO 流在… IO 流简介 IO 即 Input/Output输入和输出。数据输入到计算机内存的过程即输入反之输出到外部存储比如数据库文件远程主机的过程即输出。数据传输过程类似于水流因此称为 IO 流。IO 流在 Java 中分为输入流和输出流而根据数据的处理方式又分为字节流和字符流。 Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。 InputStream/Reader: 所有的输入流的基类前者是字节输入流后者是字符输入流。 OutputStream/Writer: 所有输出流的基类前者是字节输出流后者是字符输出流。 字节流 InputStream字节输入流 InputStream用于从源头通常是文件读取数据字节信息到内存中java.io.InputStream抽象类是所有字节输入流的父类。 InputStream 常用方法 read() 返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节则代码返回 -1 表示文件结束。 read(byte b[ ]) : 从输入流中读取一些字节存储到数组 b 中。如果数组 b 的长度为零则不读取。如果没有可用字节读取返回 -1。如果有可用字节读取则最多读取的字节数最多等于 b.length 返回读取的字节数。这个方法等价于 read(b, 0, b.length)。 read(byte b[], int off, int len) 在read(byte b[ ]) 方法的基础上增加了 off 参数偏移量和 len 参数要读取的最大字节数。 skip(long n) 忽略输入流中的 n 个字节 ,返回实际忽略的字节数。 available() 返回输入流中可以读取的字节数。 close() 关闭输入流释放相关的系统资源。 从 Java 9 开始InputStream 新增加了多个实用的方法 readAllBytes() 读取输入流中的所有字节返回字节数组。 readNBytes(byte[] b, int off, int len) 阻塞直到读取 len 个字节。 transferTo(OutputStream out) 将所有字节从一个输入流传递到一个输出流。 FileInputStream 是一个比较常用的字节输入流对象可直接指定文件路径可以直接读取单字节数据也可以读取至字节数组中。 FileInputStream 代码示例 try (InputStream fis new FileInputStream(input.txt)) {System.out.println(Number of remaining bytes: fis.available());int content;long skip fis.skip(2);System.out.println(The actual number of bytes skipped: skip);System.out.print(The content read from file:);while ((content fis.read()) ! -1) {System.out.print((char) content);} } catch (IOException e) {e.printStackTrace(); } input.txt 文件内容 输出 Number of remaining bytes:11 The actual number of bytes skipped:2 The content read from file:JavaGuide 不过一般我们是不会直接单独使用 FileInputStream 通常会配合 BufferedInputStream字节缓冲输入流后文会讲到来使用。 像下面这段代码在我们的项目中就比较常见我们通过 readAllBytes() 读取输入流所有字节并将其直接赋值给一个 String 对象。 // 新建一个 BufferedInputStream 对象 BufferedInputStream bufferedInputStream new BufferedInputStream(new FileInputStream(input.txt)); // 读取文件的内容并复制到 String 对象中 String result new String(bufferedInputStream.readAllBytes()); System.out.println(result); DataInputStream 用于读取指定类型数据不能单独使用必须结合 FileInputStream 。 FileInputStream fileInputStream new FileInputStream(input.txt); //必须将fileInputStream作为构造参数才能使用 DataInputStream dataInputStream new DataInputStream(fileInputStream); //可以读取任意具体的类型数据 dataInputStream.readBoolean(); dataInputStream.readInt(); dataInputStream.readUTF(); ObjectInputStream 用于从输入流中读取 Java 对象反序列化ObjectOutputStream 用于将对象写入到输出流(序列化)。 ObjectInputStream input new ObjectInputStream(new FileInputStream(object.data)); MyClass object (MyClass) input.readObject(); input.close(); 另外用于序列化和反序列化的类必须实现 Serializable 接口对象中如果有属性不想被序列化使用 transient 修饰。 OutputStream字节输出流 OutputStream用于将数据字节信息写入到目的地通常是文件java.io.OutputStream抽象类是所有字节输出流的父类。 OutputStream 常用方法 write(int b) 将特定字节写入输出流。 write(byte b[ ]) : 将数组b 写入到输出流等价于 write(b, 0, b.length) 。 write(byte[] b, int off, int len) : 在write(byte b[ ]) 方法的基础上增加了 off 参数偏移量和 len 参数要读取的最大字节数。 flush() 刷新此输出流并强制写出所有缓冲的输出字节。 close() 关闭输出流释放相关的系统资源。 FileOutputStream 是最常用的字节输出流对象可直接指定文件路径可以直接输出单字节数据也可以输出指定的字节数组。 FileOutputStream 代码示例 try (FileOutputStream output new FileOutputStream(output.txt)) {byte[] array JavaGuide.getBytes();output.write(array); } catch (IOException e) {e.printStackTrace(); } 运行结果 类似于 FileInputStreamFileOutputStream 通常也会配合 BufferedOutputStream字节缓冲输出流后文会讲到来使用。 FileOutputStream fileOutputStream new FileOutputStream(output.txt); BufferedOutputStream bos new BufferedOutputStream(fileOutputStream) DataOutputStream 用于写入指定类型数据不能单独使用必须结合 FileOutputStream // 输出流 FileOutputStream fileOutputStream new FileOutputStream(out.txt); DataOutputStream dataOutputStream new DataOutputStream(fileOutputStream); // 输出任意数据类型 dataOutputStream.writeBoolean(true); dataOutputStream.writeByte(1); ObjectInputStream 用于从输入流中读取 Java 对象ObjectInputStream,反序列化ObjectOutputStream将对象写入到输出流(ObjectOutputStream序列化)。 ObjectOutputStream output new ObjectOutputStream(new FileOutputStream(file.txt) Person person new Person(Guide哥, JavaGuide作者); output.writeObject(person); 字符流 不管是文件读写还是网络发送接收信息的最小存储单元都是字节。 那为什么 I/O 流操作要分为字节流操作和字符流操作呢 个人认为主要有两点原因 字符流是由 Java 虚拟机将字节转换得到的这个过程还算是比较耗时。 如果我们不知道编码类型就很容易出现乱码问题。 乱码问题这个很容易就可以复现我们只需要将上面提到的 FileInputStream 代码示例中的 input.txt 文件内容改为中文即可原代码不需要改动。 输出 Number of remaining bytes:9 The actual number of bytes skipped:2 The content read from file:§å®¶å¥½ 可以很明显地看到读取出来的内容已经变成了乱码。 因此I/O 流就干脆提供了一个直接操作字符的接口方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好如果涉及到字符的话使用字符流比较好。 字符流默认采用的是 Unicode 编码我们可以通过构造方法自定义编码。顺便分享一下之前遇到的笔试题常用字符编码所占字节数utf8 :英文占 1 字节中文占 3 字节unicode任何字符都占 2 个字节gbk英文占 1 字节中文占 2 字节。 Reader字符输入流 Reader用于从源头通常是文件读取数据字符信息到内存中java.io.Reader抽象类是所有字符输入流的父类。 Reader 用于读取文本 InputStream 用于读取原始字节。 Reader 常用方法 read() : 从输入流读取一个字符。 read(char[] cbuf) : 从输入流中读取一些字符并将它们存储到字符数组 cbuf中等价于 read(cbuf, 0, cbuf.length) 。 read(char[] cbuf, int off, int len) 在read(char[] cbuf) 方法的基础上增加了 off 参数偏移量和 len 参数要读取的最大字符数。 skip(long n) 忽略输入流中的 n 个字符 ,返回实际忽略的字符数。 close() : 关闭输入流并释放相关的系统资源。 InputStreamReader 是字节流转换为字符流的桥梁其子类 FileReader 是基于该基础上的封装可以直接操作字符文件。 // 字节流转换为字符流的桥梁 public class InputStreamReader extends Reader { } // 用于读取字符文件 public class FileReader extends InputStreamReader { } FileReader 代码示例 try (FileReader fileReader new FileReader(input.txt);) {int content;long skip fileReader.skip(3);System.out.println(The actual number of bytes skipped: skip);System.out.print(The content read from file:);while ((content fileReader.read()) ! -1) {System.out.print((char) content);} } catch (IOException e) {e.printStackTrace(); } input.txt 文件内容 输出 The actual number of bytes skipped:3 The content read from file:我是Guide。 Writer字符输出流 Writer用于将数据字符信息写入到目的地通常是文件java.io.Writer抽象类是所有字符输出流的父类。 Writer 常用方法 write(int c) : 写入单个字符。 write(char[] cbuf) 写入字符数组 cbuf等价于write(cbuf, 0, cbuf.length)。 write(char[] cbuf, int off, int len) 在write(char[] cbuf) 方法的基础上增加了 off 参数偏移量和 len 参数要读取的最大字符数。 write(String str) 写入字符串等价于 write(str, 0, str.length()) 。 write(String str, int off, int len) 在write(String str) 方法的基础上增加了 off 参数偏移量和 len 参数要读取的最大字符数。 append(CharSequence csq) 将指定的字符序列附加到指定的 Writer 对象并返回该 Writer 对象。 append(char c) 将指定的字符附加到指定的 Writer 对象并返回该 Writer 对象。 flush() 刷新此输出流并强制写出所有缓冲的输出字符。 close():关闭输出流释放相关的系统资源。 OutputStreamWriter 是字符流转换为字节流的桥梁其子类 FileWriter 是基于该基础上的封装可以直接将字符写入到文件。 // 字符流转换为字节流的桥梁 public class OutputStreamWriter extends Writer { } // 用于写入字符到文件 public class FileWriter extends OutputStreamWriter { } FileWriter 代码示例 try (Writer output new FileWriter(output.txt)) {output.write(你好我是Guide。); } catch (IOException e) {e.printStackTrace(); } 输出结果 字节缓冲流 IO 操作是很消耗性能的缓冲流将数据加载至缓冲区一次性读取/写入多个字节从而避免频繁的 IO 操作提高流的传输效率。 字节缓冲流这里采用了装饰器模式来增强 InputStream 和OutputStream子类对象的功能。 举个例子我们可以通过 BufferedInputStream字节缓冲输入流来增强 FileInputStream 的功能。 // 新建一个 BufferedInputStream 对象 BufferedInputStream bufferedInputStream new BufferedInputStream(new FileInputStream(input.txt)); 字节流和字节缓冲流的性能差别主要体现在我们使用两者的时候都是调用 write(int b) 和 read() 这两个一次只读取一个字节的方法的时候。由于字节缓冲流内部有缓冲区字节数组因此字节缓冲流会先将读取到的字节存放在缓存区大幅减少 IO 次数提高读取效率。 我使用 write(int b) 和 read() 方法分别通过字节流和字节缓冲流复制一个 524.9 mb 的 PDF 文件耗时对比如下 使用缓冲流复制PDF文件总耗时:15428 毫秒 使用普通字节流复制PDF文件总耗时:2555062 毫秒 两者耗时差别非常大缓冲流耗费的时间是字节流的 1/165。 测试代码如下: Test void copy_pdf_to_another_pdf_buffer_stream() {// 记录开始时间long start System.currentTimeMillis();try (BufferedInputStream bis new BufferedInputStream(new FileInputStream(深入理解计算机操作系统.pdf));BufferedOutputStream bos new BufferedOutputStream(new FileOutputStream(深入理解计算机操作系统-副本.pdf))) {int content;while ((content bis.read()) ! -1) {bos.write(content);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end System.currentTimeMillis();System.out.println(使用缓冲流复制PDF文件总耗时: (end - start) 毫秒); } Test void copy_pdf_to_another_pdf_stream() {// 记录开始时间long start System.currentTimeMillis();try (FileInputStream fis new FileInputStream(深入理解计算机操作系统.pdf);FileOutputStream fos new FileOutputStream(深入理解计算机操作系统-副本.pdf)) {int content;while ((content fis.read()) ! -1) {fos.write(content);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end System.currentTimeMillis();System.out.println(使用普通流复制PDF文件总耗时: (end - start) 毫秒); } 如果是调用 read(byte b[]) 和 write(byte b[], int off, int len) 这两个写入一个字节数组的方法的话只要字节数组的大小合适两者的性能差距其实不大基本可以忽略。 这次我们使用 read(byte b[]) 和 write(byte b[], int off, int len) 方法分别通过字节流和字节缓冲流复制一个 524.9 mb 的 PDF 文件耗时对比如下 使用缓冲流复制PDF文件总耗时:695 毫秒 使用普通字节流复制PDF文件总耗时:989 毫秒 两者耗时差别不是很大缓冲流的性能要略微好一点点。 测试代码如下 Test void copy_pdf_to_another_pdf_with_byte_array_buffer_stream() {// 记录开始时间long start System.currentTimeMillis();try (BufferedInputStream bis new BufferedInputStream(new FileInputStream(深入理解计算机操作系统.pdf));BufferedOutputStream bos new BufferedOutputStream(new FileOutputStream(深入理解计算机操作系统-副本.pdf))) {int len;byte[] bytes new byte[4 * 1024];while ((len bis.read(bytes)) ! -1) {bos.write(bytes, 0, len);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end System.currentTimeMillis();System.out.println(使用缓冲流复制PDF文件总耗时: (end - start) 毫秒); } Test void copy_pdf_to_another_pdf_with_byte_array_stream() {// 记录开始时间long start System.currentTimeMillis();try (FileInputStream fis new FileInputStream(深入理解计算机操作系统.pdf);FileOutputStream fos new FileOutputStream(深入理解计算机操作系统-副本.pdf)) {int len;byte[] bytes new byte[4 * 1024];while ((len fis.read(bytes)) ! -1) {fos.write(bytes, 0, len);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end System.currentTimeMillis();System.out.println(使用普通流复制PDF文件总耗时: (end - start) 毫秒); } BufferedInputStream字节缓冲输入流 BufferedInputStream 从源头通常是文件读取数据字节信息到内存的过程中不会一个字节一个字节的读取而是会先将读取到的字节存放在缓存区并从内部缓冲区中单独读取字节。这样大幅减少了 IO 次数提高了读取效率。 BufferedInputStream 内部维护了一个缓冲区这个缓冲区实际就是一个字节数组通过阅读 BufferedInputStream 源码即可得到这个结论。 public class BufferedInputStream extends FilterInputStream {// 内部缓冲区数组protected volatile byte buf[];// 缓冲区的默认大小private static int DEFAULT_BUFFER_SIZE 8192;// 使用默认的缓冲区大小public BufferedInputStream(InputStream in) {this(in, DEFAULT_BUFFER_SIZE);}// 自定义缓冲区大小public BufferedInputStream(InputStream in, int size) {super(in);if (size 0) {throw new IllegalArgumentException(Buffer size 0);}buf new byte[size];} } 缓冲区的大小默认为 8192 字节当然了你也可以通过 BufferedInputStream(InputStream in, int size) 这个构造方法来指定缓冲区的大小。 BufferedOutputStream字节缓冲输出流 BufferedOutputStream 将数据字节信息写入到目的地通常是文件的过程中不会一个字节一个字节的写入而是会先将要写入的字节存放在缓存区并从内部缓冲区中单独写入字节。这样大幅减少了 IO 次数提高了读取效率 try (BufferedOutputStream bos new BufferedOutputStream(new FileOutputStream(output.txt))) {byte[] array JavaGuide.getBytes();bos.write(array); } catch (IOException e) {e.printStackTrace(); } 类似于 BufferedInputStream BufferedOutputStream 内部也维护了一个缓冲区并且这个缓存区的大小也是 8192 字节。 字符缓冲流 BufferedReader 字符缓冲输入流和 BufferedWriter字符缓冲输出流类似于 BufferedInputStream字节缓冲输入流和BufferedOutputStream字节缓冲输入流内部都维护了一个字节数组作为缓冲区。不过前者主要是用来操作字符信息。 打印流 下面这段代码大家经常使用吧 System.out.print(Hello); System.out.println(Hello); System.out 实际是用于获取一个 PrintStream 对象print方法实际调用的是 PrintStream 对象的 write 方法。 PrintStream 属于字节打印流与之对应的是 PrintWriter 字符打印流。PrintStream 是 OutputStream 的子类PrintWriter 是 Writer 的子类。 public class PrintStream extends FilterOutputStreamimplements Appendable, Closeable { } public class PrintWriter extends Writer { } 随机访问流 这里要介绍的随机访问流指的是支持随意跳转到文件的任意位置进行读写的 RandomAccessFile 。 RandomAccessFile 的构造方法如下我们可以指定 mode读写模式。 // openAndDelete 参数默认为 false 表示打开文件并且这个文件不会被删除 public RandomAccessFile(File file, String mode)throws FileNotFoundException {this(file, mode, false); } // 私有方法 private RandomAccessFile(File file, String mode, boolean openAndDelete) throws FileNotFoundException{// 省略大部分代码 } 读写模式主要有下面四种 r : 只读模式。 rw: 读写模式 rws: 相对于 rwrws 同步更新对“文件的内容”或“元数据”的修改到外部存储设备。 rwd : 相对于 rwrwd 同步更新对“文件的内容”的修改到外部存储设备。 文件内容指的是文件中实际保存的数据元数据则是用来描述文件属性比如文件的大小信息、创建和修改时间。 RandomAccessFile 中有一个文件指针用来表示下一个将要被写入或者读取的字节所处的位置。我们可以通过 RandomAccessFile 的 seek(long pos) 方法来设置文件指针的偏移量距文件开头 pos 个字节处。如果想要获取文件指针当前的位置的话可以使用 getFilePointer() 方法。 RandomAccessFile 代码示例 RandomAccessFile randomAccessFile new RandomAccessFile(new File(input.txt), rw); System.out.println(读取之前的偏移量 randomAccessFile.getFilePointer() ,当前读取到的字符 (char) randomAccessFile.read() 读取之后的偏移量 randomAccessFile.getFilePointer()); // 指针当前偏移量为 6 randomAccessFile.seek(6); System.out.println(读取之前的偏移量 randomAccessFile.getFilePointer() ,当前读取到的字符 (char) randomAccessFile.read() 读取之后的偏移量 randomAccessFile.getFilePointer()); // 从偏移量 7 的位置开始往后写入字节数据 randomAccessFile.write(new byte[]{H, I, J, K}); // 指针当前偏移量为 0回到起始位置 randomAccessFile.seek(0); System.out.println(读取之前的偏移量 randomAccessFile.getFilePointer() ,当前读取到的字符 (char) randomAccessFile.read() 读取之后的偏移量 randomAccessFile.getFilePointer()); input.txt 文件内容 输出 读取之前的偏移量0,当前读取到的字符A读取之后的偏移量1 读取之前的偏移量6,当前读取到的字符G读取之后的偏移量7 读取之前的偏移量0,当前读取到的字符A读取之后的偏移量1 input.txt 文件内容变为 ABCDEFGHIJK 。 RandomAccessFile 的 write 方法在写入对象的时候如果对应的位置已经有数据的话会将其覆盖掉。 RandomAccessFile randomAccessFile new RandomAccessFile(new File(input.txt), rw); randomAccessFile.write(new byte[]{H, I, J, K}); 假设运行上面这段程序之前 input.txt 文件内容变为 ABCD 运行之后则变为 HIJK 。 RandomAccessFile 比较常见的一个应用就是实现大文件的 断点续传 。何谓断点续传简单来说就是上传文件中途暂停或失败比如遇到网络问题之后不需要重新上传只需要上传那些未成功上传的文件分片即可。分片先将文件切分成多个文件分片上传是断点续传的基础。 RandomAccessFile 可以帮助我们合并文件分片示例代码如下 我在《Java 面试指北》中详细介绍了大文件的上传问题。 RandomAccessFile 的实现依赖于 FileDescriptor (文件描述符) 和 FileChannel 内存映射文件。 这篇文章我们简单来看看我们从 IO 中能够学习到哪些设计模式的应用。 IO设计模式 装饰器模式 装饰器Decorator模式 可以在不改变原有对象的情况下拓展其功能。 装饰器模式通过组合替代继承来扩展原始类的功能在一些继承关系比较复杂的场景IO 这一场景各种类的继承关系就比较复杂更加实用。 对于字节流来说 FilterInputStream 对应输入流和FilterOutputStream对应输出流是装饰器模式的核心分别用于增强 InputStream 和OutputStream子类对象的功能。 我们常见的BufferedInputStream(字节缓冲输入流)、DataInputStream 等等都是FilterInputStream 的子类BufferedOutputStream字节缓冲输出流、DataOutputStream等等都是FilterOutputStream的子类。 举个例子我们可以通过 BufferedInputStream字节缓冲输入流来增强 FileInputStream 的功能。 BufferedInputStream 构造函数如下public BufferedInputStream(InputStream in) {this(in, DEFAULT_BUFFER_SIZE); } public BufferedInputStream(InputStream in, int size) {super(in);if (size 0) {throw new IllegalArgumentException(Buffer size 0);}buf new byte[size]; } 可以看出BufferedInputStream 的构造函数其中的一个参数就是 InputStream 。 BufferedInputStream 代码示例 try (BufferedInputStream bis new BufferedInputStream(new FileInputStream(input.txt))) {int content;long skip bis.skip(2);while ((content bis.read()) ! -1) {System.out.print((char) content);} } catch (IOException e) {e.printStackTrace(); } 这个时候你可以会想了为啥我们直接不弄一个BufferedFileInputStream字符缓冲文件输入流呢 BufferedFileInputStream bfis new BufferedFileInputStream(input.txt); 如果 InputStream的子类比较少的话这样做是没问题的。不过 InputStream的子类实在太多继承关系也太复杂了。如果我们为每一个子类都定制一个对应的缓冲输入流那岂不是太麻烦了。 如果你对 IO 流比较熟悉的话你会发现ZipInputStream 和ZipOutputStream 还可以分别增强 BufferedInputStream 和 BufferedOutputStream 的能力。 BufferedInputStream bis new BufferedInputStream(new FileInputStream(fileName)); ZipInputStream zis new ZipInputStream(bis); BufferedOutputStream bos new BufferedOutputStream(new FileOutputStream(fileName)); ZipOutputStream zipOut new ZipOutputStream(bos); ZipInputStream 和ZipOutputStream 分别继承自InflaterInputStream 和DeflaterOutputStream。 public class InflaterInputStream extends FilterInputStream { } public class DeflaterOutputStream extends FilterOutputStream { } 这也是装饰器模式很重要的一个特征那就是可以对原始类嵌套使用多个装饰器。 为了实现这一效果装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口。上面介绍到的这些 IO 相关的装饰类和原始类共同的父类是 InputStream 和OutputStream。 对于字符流来说BufferedReader 可以用来增加 Reader 字符输入流子类的功能BufferedWriter 可以用来增加 Writer 字符输出流子类的功能。 BufferedWriter bw new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), UTF-8)); IO 流中的装饰器模式应用的例子实在是太多了不需要特意记忆完全没必要哈搞清了装饰器模式的核心之后你在使用的时候自然就会知道哪些地方运用到了装饰器模式。 适配器模式 适配器Adapter Pattern模式 主要用于接口互不兼容的类的协调工作你可以将其联想到我们日常经常使用的电源适配器。 适配器模式中存在被适配的对象或者类称为 适配者(Adaptee) 作用于适配者的对象或者类称为适配器(Adapter) 。适配器分为对象适配器和类适配器。类适配器使用继承关系来实现对象适配器使用组合关系来实现。 IO 流中的字符流和字节流的接口不同它们之间可以协调工作就是基于适配器模式来做的更准确点来说是对象适配器。通过适配器我们可以将字节流对象适配成一个字符流对象这样我们可以直接通过字节流对象来读取或者写入字符数据。 InputStreamReader 和 OutputStreamWriter 就是两个适配器(Adapter) 同时它们两个也是字节流和字符流之间的桥梁。InputStreamReader 使用 StreamDecoder 流解码器对字节进行解码实现字节流到字符流的转换 OutputStreamWriter 使用StreamEncoder流编码器对字符进行编码实现字符流到字节流的转换。 InputStream 和 OutputStream 的子类是被适配者 InputStreamReader 和 OutputStreamWriter是适配器。 // InputStreamReader 是适配器FileInputStream 是被适配的类 InputStreamReader isr new InputStreamReader(new FileInputStream(fileName), UTF-8); // BufferedReader 增强 InputStreamReader 的功能装饰器模式 BufferedReader bufferedReader new BufferedReader(isr); java.io.InputStreamReader 部分源码 public class InputStreamReader extends Reader {//用于解码的对象private final StreamDecoder sd;public InputStreamReader(InputStream in) {super(in);try {// 获取 StreamDecoder 对象sd StreamDecoder.forInputStreamReader(in, this, (String)null);} catch (UnsupportedEncodingException e) {throw new Error(e);}}// 使用 StreamDecoder 对象做具体的读取工作public int read() throws IOException {return sd.read();} } java.io.OutputStreamWriter 部分源码 public class OutputStreamWriter extends Writer {// 用于编码的对象private final StreamEncoder se;public OutputStreamWriter(OutputStream out) {super(out);try {// 获取 StreamEncoder 对象se StreamEncoder.forOutputStreamWriter(out, this, (String)null);} catch (UnsupportedEncodingException e) {throw new Error(e);}}// 使用 StreamEncoder 对象做具体的写入工作public void write(int c) throws IOException {se.write©;} } 适配器模式和装饰器模式有什么区别呢 装饰器模式 更侧重于动态地增强原始类的功能装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口。并且装饰器模式支持对原始类嵌套使用多个装饰器。 适配器模式 更侧重于让接口不兼容而不能交互的类可以一起工作当我们调用适配器对应的方法时适配器内部会调用适配者类或者和适配类相关的类的方法这个过程透明的。就比如说 StreamDecoder 流解码器和StreamEncoder流编码器就是分别基于 InputStream 和 OutputStream 来获取 FileChannel对象并调用对应的 read 方法和 write 方法进行字节数据的读取和写入。 StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) {// 省略大部分代码// 根据 InputStream 对象获取 FileChannel 对象ch getChannel((FileInputStream)in); } 适配器和适配者两者不需要继承相同的抽象类或者实现相同的接口。 另外FutureTask 类使用了适配器模式Executors 的内部类 RunnableAdapter 实现属于适配器用于将 Runnable 适配成 Callable。 FutureTask参数包含 Runnable 的一个构造方法 public FutureTask(Runnable runnable, V result) {// 调用 Executors 类的 callable 方法this.callable Executors.callable(runnable, result);this.state NEW; } Executors中对应的方法和适配器 // 实际调用的是 Executors 的内部类 RunnableAdapter 的构造方法 public static T CallableT callable(Runnable task, T result) {if (task null)throw new NullPointerException();return new RunnableAdapterT(task, result); } // 适配器 static final class RunnableAdapterT implements CallableT {final Runnable task;final T result;RunnableAdapter(Runnable task, T result) {this.task task;this.result result;}public T call() {task.run();return result;} } 工厂模式 工厂模式用于创建对象NIO 中大量用到了工厂模式比如 Files 类的 newInputStream 方法用于创建 InputStream 对象静态工厂、 Paths 类的 get 方法创建 Path 对象静态工厂、ZipFileSystem 类sun.nio包下的类属于 java.nio 相关的一些内部实现的 getPath 的方法创建 Path 对象简单工厂。 InputStream is Files.newInputStream(Paths.get(generatorLogoPath)) 观察者模式 NIO 中的文件目录监听服务使用到了观察者模式。 NIO 中的文件目录监听服务基于 WatchService 接口和 Watchable 接口。WatchService 属于观察者Watchable 属于被观察者。 Watchable 接口定义了一个用于将对象注册到 WatchService监控服务 并绑定监听事件的方法 register 。 public interface Pathextends ComparablePath, IterablePath, Watchable{ } public interface Watchable {WatchKey register(WatchService watcher,WatchEvent.Kind?[] events,WatchEvent.Modifier… modifiers)throws IOException; } WatchService 用于监听文件目录的变化同一个 WatchService 对象能够监听多个文件目录。 // 创建 WatchService 对象 WatchService watchService FileSystems.getDefault().newWatchService(); // 初始化一个被监控文件夹的 Path 类: Path path Paths.get(workingDirectory); // 将这个 path 对象注册到 WatchService监控服务 中去 WatchKey watchKey path.register( watchService, StandardWatchEventKinds…); Path 类 register 方法的第二个参数 events 需要监听的事件为可变长参数也就是说我们可以同时监听多种事件。 WatchKey register(WatchService watcher,WatchEvent.Kind?… events)throws IOException; 常用的监听事件有 3 种 StandardWatchEventKinds.ENTRY_CREATE 文件创建。 StandardWatchEventKinds.ENTRY_DELETE : 文件删除。 StandardWatchEventKinds.ENTRY_MODIFY : 文件修改。 register 方法返回 WatchKey 对象通过WatchKey 对象可以获取事件的具体信息比如文件目录下是创建、删除还是修改了文件、创建、删除或者修改的文件的具体名称是什么。 WatchKey key; while ((key watchService.take()) ! null) {for (WatchEvent? event : key.pollEvents()) {// 可以调用 WatchEvent 对象的方法做一些事情比如输出事件的具体上下文信息}key.reset(); } WatchService 内部是通过一个 daemon thread守护线程采用定期轮询的方式来检测文件的变化简化后的源码如下所示。 class PollingWatchServiceextends AbstractWatchService {// 定义一个 daemon thread守护线程轮询检测文件变化private final ScheduledExecutorService scheduledExecutor; PollingWatchService() {scheduledExecutor Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {Overridepublic Thread newThread(Runnable r) {Thread t new Thread®;t.setDaemon(true);return t;}});} void enable(Set? extends WatchEvent.Kind? events, long period) {synchronized (this) {// 更新监听事件this.events events; // 开启定期轮询Runnable thunk new Runnable() { public void run() { poll(); }};this.poller scheduledExecutor.scheduleAtFixedRate(thunk, period, period, TimeUnit.SECONDS);}} } IO 模型这块确实挺难理解的需要太多计算机底层知识。写这篇文章用了挺久就非常希望能把我所知道的讲出来吧!希望朋友们能有收获为了写这篇文章还翻看了一下《UNIX 网络编程》这本书太难了我滴乖乖心痛~ 个人能力有限。如果文章有任何需要补充/完善/修改的地方欢迎在评论区指出共同进步 I/O模型 何为 I/O? I/OInput/Outpu 即输入输出 。 我们先从计算机结构的角度来解读一下 I/O。 根据冯.诺依曼结构计算机结构分为 5 大部分运算器、控制器、存储器、输入设备、输出设备。 输入设备比如键盘和输出设备比如显示器都属于外部设备。网卡、硬盘这种既可以属于输入设备也可以属于输出设备。 输入设备向计算机输入数据输出设备接收计算机输出的数据。 从计算机结构的视角来看的话 I/O 描述了计算机系统与外部设备之间通信的过程。 我们再先从应用程序的角度来解读一下 I/O。 根据大学里学到的操作系统相关的知识为了保证操作系统的稳定性和安全性一个进程的地址空间划分为 用户空间User space 和 内核空间Kernel space 。 像我们平常运行的应用程序都是运行在用户空间只有内核空间才能进行系统态级别的资源有关的操作比如文件管理、进程通信、内存管理等等。也就是说我们想要进行 IO 操作一定是要依赖内核空间的能力。 并且用户空间的程序不能直接访问内核空间。 当想要执行 IO 操作时由于没有执行这些操作的权限只能发起系统调用请求操作系统帮忙完成。 因此用户进程想要执行 IO 操作的话必须通过 系统调用 来间接访问内核空间 我们在平常开发过程中接触最多的就是 磁盘 IO读写文件 和 网络 IO网络请求和响应。 从应用程序的视角来看的话我们的应用程序对操作系统的内核发起 IO 调用系统调用操作系统负责的内核执行具体的 IO 操作。也就是说我们的应用程序实际上只是发起了 IO 操作的调用而已具体 IO 的执行是由操作系统的内核来完成的。 当应用程序发起 I/O 调用后会经历两个步骤 内核等待 I/O 设备准备好数据 内核将数据从内核空间拷贝到用户空间。 有哪些常见的 IO 模型? UNIX 系统下 IO 模型一共有 5 种 同步阻塞 I/O、同步非阻塞 I/O、I/O 多路复用、信号驱动 I/O 和异步 I/O。 这也是我们经常提到的 5 种 IO 模型。 Java 中 3 种常见 IO 模型 BIO (Blocking I/O) BIO 属于同步阻塞 IO 模型 。 同步阻塞 IO 模型中应用程序发起 read 调用后会一直阻塞直到内核把数据拷贝到用户空间。 在客户端连接数量不高的情况下是没问题的。但是当面对十万甚至百万级连接的时候传统的 BIO 模型是无能为力的。因此我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 NIO (Non-blocking/New I/O) Java 中的 NIO 于 Java 1.4 中引入对应 java.nio 包提供了 Channel , SelectorBuffer 等抽象。NIO 中的 N 可以理解为 Non-blocking不单纯是 New。它是支持面向缓冲的基于通道的 I/O 操作方法。 对于高负载、高并发的网络应用应使用 NIO 。 Java 中的 NIO 可以看作是 I/O 多路复用模型。也有很多人认为Java 中的 NIO 属于同步非阻塞 IO 模型。 跟着我的思路往下看看相信你会得到答案 我们先来看看 同步非阻塞 IO 模型。 同步非阻塞 IO 模型中应用程序会一直发起 read 调用等待数据从内核空间拷贝到用户空间的这段时间里线程依然是阻塞的直到在内核把数据拷贝到用户空间。 相比于同步阻塞 IO 模型同步非阻塞 IO 模型确实有了很大改进。通过轮询操作避免了一直阻塞。 但是这种 IO 模型同样存在问题应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。 这个时候I/O 多路复用模型 就上场了。 IO 多路复用模型中线程首先发起 select 调用询问内核数据是否准备就绪等内核把数据准备好了用户线程再发起 read 调用。read 调用的过程数据从内核空间 - 用户空间还是阻塞的。 目前支持 IO 多路复用的系统调用有 selectepoll 等等。select 系统调用目前几乎在所有的操作系统上都有支持。 select 调用 内核提供的系统调用它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。 epoll 调用 linux 2.6 内核属于 select 调用的增强版本优化了 IO 的执行效率。 IO 多路复用模型通过减少无效的系统调用减少了对 CPU 资源的消耗。 Java 中的 NIO 有一个非常重要的选择器 ( Selector ) 的概念也可以被称为 多路复用器。通过它只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后才会为其服务。 AIO (Asynchronous I/O) AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。 异步 IO 是基于事件和回调机制实现的也就是应用操作之后会直接返回不会堵塞在那里当后台处理完成操作系统会通知相应的线程进行后续的操作。 目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO不过又放弃了。这是因为Netty 使用了 AIO 之后在 Linux 系统上的性能并没有多少提升。 最后来一张图简单总结一下 Java 中的 BIO、NIO、AIO。 更多请添加博主微信或者公众号每星期会持续更新还会提供程序员接私活的一些思路 想要接私活请练习博主 chengxvyuanresheng 公共号会时不时发一些开源项目 请搜索 程序员人生 最近换面试的请加入星球问博主要一下
- 上一篇: 举报网站建设公司前台网站开发技术
- 下一篇: 巨省网站哈尔滨网站建设唯辛ls15227
相关文章
-
举报网站建设公司前台网站开发技术
举报网站建设公司前台网站开发技术
- 技术栈
- 2026年03月21日
-
莒南建设局网站wordpress免费主题下载
莒南建设局网站wordpress免费主题下载
- 技术栈
- 2026年03月21日
-
局域网中怎么访问自己做的网站太原网站建设培训
局域网中怎么访问自己做的网站太原网站建设培训
- 技术栈
- 2026年03月21日
-
巨省网站哈尔滨网站建设唯辛ls15227
巨省网站哈尔滨网站建设唯辛ls15227
- 技术栈
- 2026年03月21日
-
巨腾网站建设中国交通建设集团有限公司地址
巨腾网站建设中国交通建设集团有限公司地址
- 技术栈
- 2026年03月21日
-
句容网站定制关键词排名查询工具
句容网站定制关键词排名查询工具
- 技术栈
- 2026年03月21日
