前言
在上一篇的文章获取不错的浏览量后,继续加更的念头一直徘徊在心中,本来是想花段时间深入学习tomcat的,可是tomcat的源码中就有至关重要的NIO,于是得先整一下NIO,但是NIO的基础是BIO,于是这篇文章就先写IO流吧。
学习NIO(非阻塞IO),千万不能被IO阻塞住哇!
IO流在java中其实是很重要的一块知识点,难度还好,但是容易被忽视,因为工作中真正写IO的代码少之又少。
IO的难点在于
IO流api很多,各种基础的流,包装的流嵌套使用很难记忆
基本每个方法都要抛出非运行时异常
导致很多开发学过io流一段时间后,写不出一段正确的io流代码。
本文将从理论+代码的方式由浅入深的带大家学习IO流,通过图解的方式来记忆常用的IO流。
文末有IO总结的思维导图,很多博文采用的都是上来一张图,我觉得对于阅读者来说很容易陷进去,所以建议理清各个流后再去看思维导图。
File
在正式的介绍IO流之前,我觉得应该介绍一下File类,该类主要是对文件和目录的抽象表示,因为学习io流第一反应就是文件,该类提供了对文件的创建、删除、查找等操作。主要有以下特点
- java的世界万物皆对象,文件和目录就可抽象为File对象
- 对于File而言,封装的并不是真正的文件,封装的仅仅是一个路径名,磁盘文件本身可以存在,也可以不存在
- 文件的内容不能用File读取,而是通过流来读取,File对象可以作为流的来源地和目的地
File类的常用构造方法
如下示例,表示这几种文件和目录的代码
// pathnameFile liuBei = new File("D:/三国/刘备.jpg");// String parent, String childFile guanYu = new File("D:/三国", "关羽.jpg");// 目录File sanGuo = new File("D:/三国");// File parent, String childFile zhangFei = new File(sanGuo, "张飞.txt");// 可以声明不存在的文件File zhuGeLiang = new File(sanGuo, "诸葛亮.txt");
绝对路径和相对路径
绝对路径:从盘符开始的路径,表示一个完整的路径。(经常使用) 相对路径:相对于当前项目目录的路径
File f = new File("D:/bbb.java");// D:\bbb.javaSystem.out.println(f.getAbsolutePath());File f2 = new File("bbb.java");// F:\code\ad\bbb.javaSystem.out.println(f2.getAbsolutePath());
路径分隔符和换行符
路径分隔符
- windows的路径分隔符: \
linux的路径分隔符: /
java有常量separator表示路径分隔符
public static final String separator = "" + separatorChar;
换行符
- windows的换行符: \r\n
- linux的换行符 \n
File的常用方法
创建、删除
上述方法比较简单,其中需要注意的是
- 创建多级目录时,mkdir创建失败,返回false,mkdirs创建成功,返回true(推荐使用mkdirs)
- 删除目录时,目录内不为空时,删除失败,返回false, 即只能删除文件或者空目录
File shuiHu = new File("D:/四大名著/水浒传");// 返回false 创建失败boolean mkdir = shuiHu.mkdir();// 返回true 创建失败boolean mkdirs = shuiHu.mkdirs();File four = new File("D:/四大名著");// 返回false 删除目录时必须目录为空才能删除成功boolean delete = four.delete();File shuiHu = new File("D:/四大名著/水浒传");// true 正确删除了水浒传目录boolean delete1 = shuiHu.delete();File liuBei = new File("D:/三国/刘备.jpg");// 返回true 正确删除了刘备.jpg文件boolean delete2 = liuBei.delete();
文件检测
注意的是
- 文件或目录不存在时, isDirectory() 或 isFile() 返回false
- 可读、可写、可执行是对操作系统给文件赋予的权限
File xiYou = new File("D:/西游记");
// 文件或目录不存在时 返回false
System.out.println(xiYou.isDirectory());
文件获取
注意
- length() 返回的是文件的字节数,目录的 长度是0
- getPath()在用绝对路径表示的文件时相同,用相对路径表示的文件时不同
- listFiles和list方法的调用,必须是实际存在的目录,否则返回null
- listFiles和list 可以传入FilenameFilter的实现类,用于按照文件名称过过滤文件
File shuiHu = new File("D:/水浒传");// 0System.out.println(shuiHu.length());File liuBei = new File("D:/三国/刘备.jpg");// 24591System.out.println(liuBei.length());File f = new File("D:/bbb.java"); // D:\bbb.javaSystem.out.println(f.getPath());File f2 = new File("bbb.java");// bbb.javaSystem.out.println(f2.getPath());File sanGuo2 = new File("D:/三国2");// 该目录不存在,返回nullString[] list = sanGuo2.list();
过滤文件的接口
@FunctionalInterfacepublic interface FilenameFilter { // 参数为目录和指定过滤名称 boolean accept(File dir, String name);}
扩展(由读者自己实现)
读取目录下所有的文件以及目录,包括子目录下所有的文件及目录
IO流
上一章节学习了使用File类创建、查找、删除文件,但是无法读取、传输文件中的内容。
IO流主要是读取、传输、写入数据内容的。
I: input,O:output
这里的主体说的都是程序(即内存),从外部设备中读取数据到程序中 即为输入流,从程序中写出到外部程序中即为输出流
IO的分类
- 本地IO和网络IO
本地IO主要是操作本地文件,例如在windows上复制粘贴操作文件,都可以使用java的io来操作
网络IO主要是通过网络发送数据,或者通过网络上传、下载文件,我们每天上网无时无刻不在体验着IO的传输
按流向分,输入流和输出流
按数据类型分:字节流和字符流
按功能分:节点流和处理流
��ɳ��Ӱ,�ܴ��ܿ�- 程序直接操作目标设备的类称为节点流
- 对节点流进行装饰,功能、性能进行增强,称为处理流
IO流主要的入口是数据源,下面列举常见的源设备和目的设备
源设备
- 硬盘(文件)
- 内存(字节数组、字符串等)
- 网络(Socket)
键盘(System.in)
目的设备- 硬盘(文件)
- 内存(字节数组、字符串等)
- 网络(Socket)
- 控制台(System.out)
本文先探讨本地IO的字节流和字符流,先列举字节流和字符流的公共方法
注意:
- 程序中打开的IO资源不属于内存资源,垃圾回收机制无法回收该资源,需要显式的关闭文件资源
- 下面的代码示例中就不显示的调用close方法,也不会处理IOException,只是为了代码的简洁,方便阅读
字节流
一切皆为字节
一切文件数据(文本、图片、视频等)在存储时,都是以二进制的形式保存,都可以通过使用字节流传输。
InputStream是字节输入流的顶层抽象
// Closeable有close()方法public abstract class InputStream implements Closeable {}
核心方法如下
OutputStream是字节输出流的顶层抽象
// Flushable里面有flush()方法public abstract class OutputStream implements Closeable, Flushable {}
核心方法如下
文件节点流
InputStream有很多的实现类,先介绍下文件节点流,即目标设备是文件,输入流和输出流对应的是
FileInputStream和FileOutputStream
FileInputStream主要从磁盘文件中读取数据,常用构造方法如下
public FileInputStream(File file) throws FileNotFoundException{}public FileInputStream(String name) throws FileNotFoundException{};
当传入的文件不存在时,运行时会抛出FileNotFoundException异常
1.read()方法读取
File file = new File("D:/三国/诸葛亮.txt");FileInputStream fileInputStream = new FileInputStream(file);// 核心代码int b;while ((b = fileInputStream.read()) != -1 ){ System.out.print((char) b);}// 输出结果abcde
2.read(byte[])读取
File file = new File("D:/三国/诸葛亮.txt");FileInputStream fileInputStream = new FileInputStream(file);// 核心代码byte[] data = new byte[2];while (fileInputStream.read(data) != -1) { System.out.println(new String(data));}// 输出结果abcded
上述代码由于最后一次读取时,只读取一个字节 e ,数组中还是上次的数据cd,只替换了e,所以最后输出了ed
下面是使用FileInputStream读取的正确姿势
File file = new File("D:/三国/诸葛亮.txt");FileInputStream fileInputStream = new FileInputStream(file);// 核心代码byte[] data = new byte[2];int len;while ((len = fileInputStream.read(data)) != -1) { // len 为每次读取的有效的字节个数 System.out.println(new String(data, 0, len));}// 输出结果abcde
注意:使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了效率,建议使用
源码解析
public int read() throws IOException { return read0();}private native int read0() throws IOException;public int read(byte b[]) throws IOException { return readBytes(b, 0, b.length);}private native int readBytes(byte b[], int off, int len) throws IOException;
上面列了read()和read(byte[])的源码,可见都是调用native的方法,涉及底层的系统调用。
如果用read()读取文件,每读取一个字节就要访问一次硬盘,这种效率较低。
- 如果用read(byte[])读取文件,一次读取多个字节,当文件很大时,也会频繁访问硬盘。如果一次读取超多字节,效率也不会高。
FileOutputStream主要是向磁盘文件中写出数据,常用构造方法如下
注意:
上述构造方法执行后,如果file不存在,会自动创建该文件
如果file存在,append没有传或者传了false,会清空文件的数据
- 如果file存在,append传了true,不会清空文件的数据
File file = new File("D:/三国/赵云.txt");FileOutputStream fos = new FileOutputStream(file);FileOutputStream fos1 = new FileOutputStream("D:/三国/司马懿.txt");// 上述代码中执行完后,赵云.txt和司马懿.txt都会自动创建出来
向文件写数据
FileOutputStream fos = new FileOutputStream("D:/三国/司马懿.txt");fos.write(96);fos.write(97);fos.write(98);// 文件内容为 abcFileOutputStream fos = new FileOutputStream("D:/三国/赵云.txt");fos.write("三国赵云".getBytes());// 文件内容为 三国赵云
上述代码每执行一次,文件里的内容就会被覆盖,有时候这不是我们想要的场景,我们一般是想追加文件
FileOutputStream fos = new FileOutputStream("D:/三国/赵云.txt", true);fos.write("有万夫不当之勇".getBytes());fos.close();// 文件内容为 三国赵云有万夫不当之勇
应用场景
开发中涉及文件的上传、下载、传输都是用的这个节点流,会结合装饰后的处理流一起使用,在缓冲流部分有介绍。
扩展(由读者自己实现)
利用文件节点流实现文件的复制
内存节点流
ByteArrayInputStream是从内存的字节数组中读取数据
public ByteArrayInputStream(byte buf[]) {}
注意:不需要close数据源和抛出IOException,因为不涉及底层的系统调用
ByteArrayOutputStream是向内存字节数组中写数据,内部维护了一个数组
public ByteArrayOutputStream() { // 内部维护了一个可变的字节数组 // protected byte buf[]; this(32);}
内存节点流代码示例
ByteArrayInputStream bis = new ByteArrayInputStream("data".getBytes());ByteArrayOutputStream bos = new ByteArrayOutputStream();int len = 0;while ((len = bis.read()) != -1){ bos.write(len);}// 输出dataSystem.out.println(new String(bos.toByteArray()));
应用场景
- 内存操作流一般在一些生成临时信息时会被使用,如果临时信息保存着文件中,代码执行完还要删除文件比较麻烦
- 结合对象流,可以实现对象和字节数组的互转
字符流
字符流封装了更加适合操作文本字符的方法
Reader用于读取文本字符
public abstract class Reader implements Readable, Closeable {}
核心方法
Writer用于写出文本字符
public abstract class Writer implements Appendable, Closeable, Flushable {}
核心方法
文件节点流
字符流操作纯文本字符的文件是最合适的,主要有FileReader和FileWriter
FileReader主要是向磁盘文件中写出数据,常用构造方法如下
public FileReader(String fileName) throws FileNotFoundException{}public FileReader(File file) throws FileNotFoundException {}
注意:当读取的文件不存在时,会抛出FileNotFoundException,这点和FileInputStream一致
- read()循环读取文件
FileReader fileReader = new FileReader("D:/三国/赵云.txt");int b;while ((b = fileReader.read()) != -1) { System.out.println((char) b);}
2.read(char[]) 读取文件
FileReader fileReader = new FileReader("D:/三国/赵云.txt");int len;char[] data = new char[2];while ((len = fileReader.read(data)) != -1) { System.out.println(new String(data, 0, len));}// 两个字符两个字符依次读取
FileWriter构造方法如下,和FileOutStream构造方法类似,和FileOutputStream类似。
public FileWriter(String fileName) throws IOException {}public FileWriter(String fileName, boolean append) throws IOException {}public FileWriter(File file) throws IOException{}public FileWriter(File file, boolean append) throws IOException {}
常用的写数据进文件的方法
FileWriter fileWriter = new FileWriter("D:/三国/孙权.txt");fileWriter.write(97); fileWriter.write('b'); fileWriter.write('C'); fileWriter.write("权"); fileWriter.append("力");
注意:
* 如果不执行close()或者flush()方法,数据只是保存到缓冲区,不会保存到文件。这点和与FileOutputStream不同,原因见 字节流和字符流的共同点章节
应用场景
纯文本文件的io操作,配合处理流一起实现。
内存节点流
字符流也有对应的内存节点流,常用的有StringWriter和CharArrayWriter
StringWriter是向内部的StringBuffer对象写数据。
// 定义public class StringWriter extends Writer { private StringBuffer buf; public StringWriter() { buf = new StringBuffer(); lock = buf; }}// 应用StringWriter sw = new StringWriter();sw.write("hello");StringBuffer buffer = sw.getBuffer();// 输出helloSystem.out.println(buffer.toString());
CharArrayWriter是向内部的char数组写数据
// 定义public class CharArrayWriter extends Writer { protected char buf[];}// 应用 CharArrayWriter caw = new CharArrayWriter();caw.write("hello");char[] chars = caw.toCharArray();for (char c : chars) { // 输出了h e l l o System.out.println(c);}
四种常用节点流的使用总结
字节流和字符流的共同点
注意到,OutputStream、Reader、Writer都实现了Flushable接口,Flushable接口有flush()方法
flush():强制刷新缓冲区的数据到目的地,刷新后流对象还可以继续使用
close(): 强制刷新缓冲区后关闭资源,关闭后流对象不可以继续使用
缓冲区:可以理解为内存区域,程序频繁操作资源(如文件)时,性能较低,因为读写内存较快,利用内存缓冲一部分数据,不要频繁的访问系统资源,是提高效率的一种方式
具体的流只要内部有维护了缓冲区,必须要close()或者flush(),不然不会真正的输出到文件中
处理流
上面的章节介绍了字节流和字符流的常用节点流,但是真正开发中都是使用更为强大的处理流
处理流是对节点流在功能上、性能上的增强
字节流的处理流的基类是FilterInputStream和FilterOutputStream
缓冲流(重点)
前面说了节点流,都是直接使用操作系统底层方法读取硬盘中的数据,缓冲流是处理流的一种实现,增强了节点流的性能,为了提高效率,缓冲流类在初始化对象的时候,内部有一个缓冲数组,一次性从底层流中读取数据到数组中,程序中执行read()或者read(byte[])的时候,就直接从内存数组中读取数据。
分类
字节缓冲流:BufferedInputStream , BufferedOutputStream
字符缓冲流:BufferedReader , BufferedWriter
字节缓冲流
可见构造方法传入的是节点流,是对节点流的装饰
// 内部默认8192 =8*1024 即8M的缓冲区public BufferedInputStream(InputStream in) { // 8192 // 内部维护了下面这样的字节数组 // protected volatile byte buf[]; this(in, DEFAULT_BUFFER_SIZE);}public BufferedOutputStream(OutputStream out) { this(out, 8192);}
这里使用复制一部1G的电影来感受缓冲流的强大
使用基本的流读取数据(一次传输一个字节)long start = System.currentTimeMillis();FileInputStream fis = new FileInputStream("D:/三国/视频.mp4");FileOutputStream fos = new FileOutputStream("D:/三国/拷贝.mp4");int data;while ((data = fis.read()) != -1) { fos.write(data);}log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start);// 五分钟还没拷好,关闭程序了...
使用基本的流读取数据(一次传输一个8M的字节数组)
long start = System.currentTimeMillis();FileInputStream fis = new FileInputStream("D:/三国/视频.mp4");FileOutputStream fos = new FileOutputStream("D:/三国/拷贝.mp4");int len;byte[] data = new byte[1024 * 1024 * 1024];while ((len = fis.read(data)) != -1) { fos.write(data, 0, len);}log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start);// 拷贝电影耗时:4651ms
使用缓冲流读取数据(一次传输一个字节)
long start = System.currentTimeMillis();BufferedInputStream fis = new BufferedInputStream(new FileInputStream("D:/三国/视频.mp4"));BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("D:/三国/拷贝.mp4"));int data;while ((data = fis.read()) != -1) { fos.write(data);}log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start);// 拷贝电影耗时:39033ms
使用缓冲流读取数据(一次传输一个8M的字节数组)(最常使用)
long start = System.currentTimeMillis();BufferedInputStream fis = new BufferedInputStream(new FileInputStream("D:/三国/视频.mp4"));BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("D:/三国/拷贝.mp4"));int len;byte[] data = new byte[8 * 1024];while ((len = fis.read(data)) != -1) { fos.write(data, 0, len);}
log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start);
// 拷贝电影耗时:1946ms
由上述四个例子可以得出结论,缓冲流读取数据比普通流读取数据快很多!
注意:采用边读边写的方式,一次传输几兆的数据效率比较高,如果采用先把文件的数据都读入内存,在进行写出,这样读写的次数是较小,但是占用太大的内存空间,一次读太大的数据也严重影响效率!
字符缓冲流
对字符节点流的装饰,下面是字符缓冲流的构造方法
public BufferedReader(Reader in) { // private static int defaultCharBufferSize = 8192; // 内部维护了一个字符数组 // private char cb[]; this(in, defaultCharBufferSize);}public BufferedWriter(Writer out) { this(out, defaultCharBufferSize);}
字符缓冲流的特有方法
// 创建流对象BufferedReader br = new BufferedReader(new FileReader("D:/三国/赵云.txt"));BufferedWriter bw = new BufferedWriter(new FileWriter("D:/三国/赵子龙.txt"));String line = null;while ((line = br.readLine())!=null) { System.out.println(line); bw.write(line); bw.newLine();}// 结果我乃常山赵子龙于万军从中,取上将首级
缓冲流的正确姿势
缓冲流是IO流中最重要的知识点,下面通过代码实现正确用IO流的姿势
BufferedInputStream bis = null;BufferedOutputStream bos = null;try { bis = new BufferedInputStream(new FileInputStream(new File("D:/三国/视频.mp4"))); bos = new BufferedOutputStream(new FileOutputStream(new File("D:/三国/拷贝.mp4"))); int len; // 一次传输8M的文件,实际测试这里传输的大小并不影响传输的速度 byte[] data = new byte[8 * 1024]; while ((len = bis.read(data)) != -1) { bos.write(data, 0, len); }} catch (IOException e) { log.error("error", e);} finally { // finally块中关闭流,确保资源一定被关闭 if (bis != null) { try { bis.close(); } catch (IOException e) { log.error("error", e); } } if (bos != null) { try { bos.close(); } catch (IOException e) { log.error("error", e); } }}
转换流
字符编码与字符集
字符编码
计算机存储的数据都是二进制的,而我们在电脑上看到的数字、英文、汉字等都是二进制转换的结果
将字符转换成二进制,为编码
- 将二进制转换为字符,为解码
字符编码 就是 自然语言和二进制的对应规则
字符集
就是一个编码表,常见的字符集有ASCII字符集、GBK字符集、Unicode字符集等,具体各个编码的介绍在这里就不介绍了。
图片
IDEA中
IDEA中,使用 FileReader 读取项目中的文本文件。IDEA可以设置为GBK 编码,当读取Windows系统中创建的默认的UTF8文本文件时,就会出现乱码 。
如下例
idea的字符集设置 默认是UTF-8,这里修改为GBK
运行的代码及结果
FileReader fileReader = new FileReader("D:/sanguo/utf8.txt");int read;while ((read = fileReader.read()) != -1) { System.out.print((char)read);}// 浣犲ソ
InputStreamReader
Reader的子类,读取字节,并使用指定的字符集将其解码为字符。字符集可以自己指定,也可以使用平台的默认字符集。
构造方法如下
// 使用平台默认字符集public InputStreamReader(InputStream in) {}// 指定字符集public InputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException{}
读取文件的“你好",文件默认的字符集是UTF8
// 创建流对象,默认UTF8编码InputStreamReader isr = new InputStreamReader(new FileInputStream("D:/三国/utf8.txt"));// 创建流对象,指定GBK编码InputStreamReader isr2 = new InputStreamReader(new FileInputStream("D:/三国/utf8.txt"), "GBK");int read;while ((read = isr.read()) != -1) { System.out.println((char) read);}while ((read = isr2.read()) != -1) { System.out.println((char) read);}// 输出结果你好浣犲ソ
OutputStreamWriter
Writer的子类,使用指定的字符集将字符编码为字节。字符集可以自己指定,也可以使用平台的默认字符集。
构造方法如下
// 使用平台默认字符集public OutputStreamWriter(OutputStream out) {}// 使用平台默认字符集public OutputStreamWriter(OutputStream out, String charsetName)throws UnsupportedEncodingException{}
如下面的代码,将你好写入文件。写入后两个文件的字符集不一样,文件大小也不同
// 创建流对象,默认UTF8编码OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:/三国/黄忠.txt"));osw.write("你好"); // 保存为6个字节// 创建流对象,指定GBK编码OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("D:/三国/马超.txt"),"GBK");osw2.write("你好");// 保存为4个字节
对象流
序列化
jdk提供了对象序列化的方式,该序列化机制将对象转为二进制流,二进制流主要包括对象的数据、对象的类型、对象的属性。可以将java对象转为二进制流写入文件中。文件会持久保存了对象的信息。
同理,从文件中读出对象的信息为反序列化的过程
对象想序列化,满足的条件:
- 该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口(没有任何抽象方法),不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException 。
- 该类的所有属性必须是可序列化的,如果有一个属性不需要可序列化的,则该属性使用transient 关键字修饰
ObjectOutputStream
该类实现将对象序列化后写出到外部设备,如硬盘文件
public ObjectOutputStream(OutputStream out) throws IOException{}
常用方法
如下代码,将User对象写入文件中
public class User implements Serializable { private static final long serialVersionUID = 8289102797441171947L; private String name; private Integer age;}// 下面是将对象输出到文件的核心代码User user = new User("马超",20);// 创建序列化流对象ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/三国/马超.txt"));// 写出对象out.writeObject(user);
注意:
1.实现了Serializable的实体一定要加一个serialVersionUID变量,这也是习惯问题,idea可以设置一下。
2.serialVersionUID生成后不要改变,避免反序列化失败,改变后会抛出InvalidClassException异常
生成的文件内容如下
ObjectInputStream
该类将ObjectOutputStream写出的对象反序列化成java对象
public ObjectInputStream(InputStream in) throws IOException
常用方法
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/三国/马超.txt"));// 强转为userUser user = (User) in.readObject();System.out.println(user);// 输出内容User(name=马超, age=20)
对象和字节数组的转换
利用对象流和字节数组流结合 ,可以实现java对象和byte[]之间的互转
// 将对象转为byte[]public static <T> byte[] t1(T t) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(t); return bos.toByteArray();}// 将byte[]转为对象public static <T> T t2(byte[] data) throws IOException, ClassNotFoundException { ByteArrayInputStream bos = new ByteArrayInputStream(data); ObjectInputStream oos = new ObjectInputStream(bos); return (T) oos.readObject();}
管道流(了解)
管道流主要用于两个线程间的通信,即一个线程通过管道流给另一个线程发数据
注意:线程的通信一般使用wait()/notify(),使用流也可以达到通信的效果,并且可以传递数据
使用的类是如下
- PipedInputStream和PipedOutStream
- PipedReader和PipedWriter
这里使用字节流为例
class Sender implements Runnable {
private PipedOutputStream pos;
private String msg;
public Sender(String msg) { this.pos = new PipedOutputStream(); this.msg = msg;}@Overridepublic void run() { pos.write(msg.getBytes());}public PipedOutputStream getPos() { return pos;}
}
class Receiver implements Runnable {
private PipedInputStream pis;
public Receiver() { this.pis = new PipedInputStream();}@Overridepublic void run() { byte[] data = new byte[1024]; int len; while ((len = pis.read(data)) != -1) { System.out.println(new String(data, 0, len)); }}
}
Sender sender = new Sender("hello");
Receiver receiver = new Receiver();
receiver.getPis().connect(sender.getPos());
new Thread(sender).start();
new Thread(receiver).start();
// 控制台输出 hello
**输入与输出流(了解)**System.in和System.out代表了系统标准的输入、输出设备默认输入设备是键盘,默认输出设备是控制台可以使用System类的setIn,setOut方法对默认设备进行改变我们开发中经常使用的输出到控制台上的内容的方法。
System.out.println("a");
System.out.print("b");
class System{
public final static InputStream in = null;
public final static PrintStream out = null;
}
public PrintStream(String fileName) throws FileNotFoundException{}
**数据流(了解)**主要方便读取Java基本类型以及String的数据,有DataInputStream 和 DataOutputStream两个实现类
DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:/三国/周瑜.txt"));
dos.writeUTF("周瑜");
dos.writeBoolean(false);
dos.writeLong(1234567890L);
DataInputStream dis = new DataInputStream(new FileInputStream("D:/三国/周瑜.txt"));
String s = dis.readUTF();
System.out.println(s);
boolean b = dis.readBoolean();
System.out.println(b);
// 输出
周瑜
false
**IO流总结**以上各个章节详细介绍了各个流,可见流的种类比较多,记忆确实增加了困难。但是可以通过思维导图的方式整理出来,方便记忆。字节流的导图![](https://s4.51cto.com/images/blog/202105/05/7be91e46c2a4598dc18992d50385f9f1.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)字符流的导图![](https://s4.51cto.com/images/blog/202105/05/1161ed20623045327ecdfa42ccef53ab.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)按照功能划分![](https://s4.51cto.com/images/blog/202105/05/189c51e97776b8f9985df610e2554321.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)输入、输出对应关系![](https://s4.51cto.com/images/blog/202105/05/9ea492325909a2dee0763c834bdf0521.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)**结语**短期的加更计划1.NIO2.tomcat系列源码解析文章篇幅较长,给看到这里的小伙伴点个大大的赞!由于作者水平有限,文章中难免会有错误之处,欢迎小伙伴们反馈指正。如果觉得文章对你有帮助,麻烦 点赞、评论、转发、在看 、关注 走起你的支持是我加更最大的动力!!!另琐碎时间想看一些技术文章,可以去公众号菜单栏翻一翻我分类好的内容,应该对部分童鞋有帮助。同时看的过程中发现问题欢迎留言指出,不胜感谢~。另外,有想多了解哪些方面内容的可以留言(什么时候,哪篇文章下留言都行),附菜单栏截图(PS:很多人不知道公众号菜单栏是什么)![](https://s4.51cto.com/images/blog/202105/05/99a2296945e08ff078233d26f265d9e7.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)