Java基础之IO基本流学习
简介
-
概念
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。
-
作用
主要解决设备与设备之间的数据传输问题。 内存—>硬盘 硬盘—>内存
-
IO流选用标准
-
明确要操作的数据是数据源还是数据目的(也就是要读还是要写)
-
明确要操作的设备上的数据是字节还是文本
-
明确数据所在的具体设备
-
明确是否需要额外功能(比如是否需要转换流、高效流等)
-
分类
划分方式
-
根据处理数据类型划分为:字符流和字节流
字节流:以字节为单位,读写数据的流。
字符流:以字符为单位,读写数据的流。字符流 = 字节流 + 解码
-
根据数据流向划分为:输入流和输出流
可以看做是一种数据的流动,按照流动的方向,以内存为基准,即流向内存是输入流,流出内存的输出流
序列化:将一个具体的对象的数据转换为一堆字节数据的过程。
反序列化:将一堆字节数据转换为一个具体对象实例的过程。
-
命名规范
由这四个类的子类名称基本都是以其父类名作为子类名的后缀。
如:InputStream的子类FileInputStream。
如:Reader的子类FileReader。 -
类层次图
-
跨服务传输流向过程分析
介绍
字节流OutputStream与InputStream
-
OutputStream与InputStream类图
在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
字节输出流OutputStream
-
定义
java.io.OutputStream 抽象类是表示字节输出流的所有类的超类(父类),将指定的字节信息写出到目的地。
-
共性方法
public void close() // 关闭此输出流并释放与此流相关联的任何系统资源。 public void flush() // 刷新此输出流并强制任何缓冲的输出字节被写出。 public void write(byte[] b) // 将 b.length个字节从指定的字节数组写入此输出流。 public void write(byte[] b, int off, int len) // 从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。 也就是说从off个字节数开始读取一直到len个字节结束 public abstract void write(int b) // 将指定的字节输出流。
文件输出流FileOutputStream
-
定义
OutputStream有很多子类,我们从最简单的一个子类FileOutputStream开始。用于将数据写出到文件。
-
构造方法
只要是对象,就从构造方法开始学。
public FileOutputStream(File file) // 根据File对象为参数创建对象。 // 推荐使用 public FileOutputStream(String name) // 根据名称字符串为参数创建对象。 public FileOutputStream(File file, boolean append) public FileOutputStream(String name, boolean append)
-
注意事项
// 创建字节输出流主要做了3件事: // 1. 调用系统功能去创建文件(输出流对象才会自动创建) // 2. 创建OutputStream对象 // 3. 把outputStream对象指向这个文件 FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt");
在指定路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据
-
FileOutputStream写出字节数据
使用FileOutputStream写出字节数据主要通过Write方法,而write方法分如下三种
public void write(int b) public void write(byte[] b) public void write(byte[] b,int off,int len) //从`off`索引开始,`len`个字节 /** * @Author charlesYan * @Description //FileOutputStream写出字节数据 * @Date 17:24 2020/10/23 * @Param [] * @return void **/ @Test public void testFileOutputStreamWriteByte() throws Exception{ // 创建字节输出流主要做了3件事: // 1. 调用系统功能去创建文件(输出流对象才会自动创建) // 2. 创建OutputStream对象 // 3. 把outputStream对象指向这个文件 FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt"); out.write(98); out.write(99); out.close(); } /** * @Author charlesYan * @Description //FileOutputStream写出字节数组 * @Date 17:57 2020/10/23 * @Param [] * @return void **/ @Test public void testFileOutputStreamWriteByteArray() throws Exception { FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt"); // 字符串转换为字节数组 byte[] bytes = "加班使我快乐".getBytes(); // 写出字节数组 out.write(bytes); //关闭资源 out.close(); } /** * @Author charlesYan * @Description //写出指定长度数组 * @Date 18:08 2020/10/23 * @Param [] * @return void **/ @Test public void testFileOutputStreamWriteByteArrayLength() throws Exception{ FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt"); // 字符串转换为字节数组 byte[] bytes = "加班使我快乐".getBytes(); // 写出指定长度数组:从索引0开始,3个字节,也就是 加 out.write(bytes,0,3); out.close(); }
-
FileOutputStream追加续写数据
回车符\r和换行符\n :
回车符:回到一行的开头(return)。 换行符:下一行(newline)。
系统中的换行:
Windows系统里,每行结尾是 回车+换行 ,即\r\n; Unix系统里,每行结尾只有 换行 ,即\n; Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一
/** * @Author charlesYan * @Description //FileOutputStream实现数据追加续写 * @Date 18:15 2020/10/23 * @Param [] * @return void **/ @Test public void testFileOutputStreamWriteAppend() throws Exception{ FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt",true); // 字符串转换为字节数组 byte[] bytes = "加班使我快乐".getBytes(); // 写出指定长度数组:从索引3开始,3个字节,也就是 班 out.write(bytes,3,3); out.close(); } /** * @Author charlesYan * @Description //FileOutputStream实现数据追加续写换行 * @Date 18:29 2020/10/23 * @Param [] * @return void **/ @Test public void testFileOutputStreamWriteLineFeed() throws Exception{ FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt",true); // 字符串转换为字节数组 byte[] bytes = "\r\n".getBytes(); // 写出指定长度数组:从索引3开始,3个字节,也就是 班 byte[] contentBytes = "加班使我快乐".getBytes(); out.write(bytes); out.write(contentBytes,6,3); out.close(); }
字节输入流InputStream
-
定义
java.io.InputStream 抽象类是表示字节输入流的所有类的超类(父类),可以读取字节信息到内存中。
-
共性方法
public void close() // 关闭此输入流并释放与此流相关联的任何系统资源。 public abstract int read() // 从输入流读取数据的下一个字节。 public int read(byte[] b) // 该方法返回的int值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1
文件输入流FileInputStream
-
定义
java.io.FileInputStream类是文件输入流,从文件中读取字节。
-
构造方法
FileInputStream(File file) // 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。 // 推荐使用 FileInputStream(String name) // 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名name命名。
-
注意事项
当创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件会抛出FileNotFoundException。
-
FileInputStream读取字节数据
/** * @Author charlesYan * @Description //FileInputSteam读取字节:read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1 * @Date 10:23 2020/10/26 * @Param [] * @return void **/ @Test public void testFileInputStreamReadByte() throws Exception { FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt"); // 读取数据,返回一个字节 int read = in.read(); System.out.println("读取原数据内容:" + read); System.out.println("转换原数据内容:" + (char)read); in.close(); } /** * @Author charlesYan * @Description // 循环改进读取方式 * @Date 16:25 2020/11/12 * @Param [] * @return void **/ @Test public void testFileInputStreamCycleReadByte() throws Exception { // 使用文件名称创建流对象 FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt"); // 定义变量,保存数据 int b; // 循环读取,读取中文时,打印乱码(字节流读取中文字符时,可能不会显示完整的字符,那是因为一个中文字符占用多个字节存储。) while ((b = in.read()) != -1) { System.out.println((char)b); } // 关闭资源 in.close(); } /** * @Author charlesYan * @Description //使用字节数组读取:read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1 * @Date 16:42 2020/11/12 * @Param [] * @return void **/ @Test public void testFileInputStreamCycleReadByteArray() throws Exception { // 使用文件名称创建流对象 FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt"); // 定义变量,作为有效个数 int len; // 定义字节数组,作为装字节数据的容器 byte[] b = new byte[2]; // 循环读取 while (((len = in.read(b)) != -1)) { // 每次读取后,把数组变成字符串打印 System.out.println(new String(b)); } // 关闭资源 in.close(); } // 注意事项:由于202011121615.txt文件中内容为abc123e,而错误数据3,是由于最后一次读取时,只读取一个字节e,数组中,上次读取的数据没有被完全替换 /** * @Author charlesYan * @Description //优化使用字节数组读取,只读取数组的有效字节部分 * @Date 17:20 2020/11/12 * @Param [] * @return void **/ @Test public void testFileInputStreamCycleReadByteArrayByOptimization() throws Exception { // 使用文件名称创建流对象 FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt"); // 定义变量,作为有效个数 int len; // 定义字符数组,用于存读取字节数据 byte[] b = new byte[2]; // 循环读取 while ((len = in.read(b)) != -1) { // 只获取每次读取的有效长度 System.out.println(new String(b,0,len)); } // 关闭资源 in.close(); } /** * @Author charlesYan * @Description //实际工作中强烈推荐使用数组读取文件,jdk1.7以后捕获异常关流写法 * @Date 18:01 2020/11/12 * @Param [] * @return void **/ @Test public void testFileInputStreamCycleReadByteByWork() { // 使用文件名创建流对象,在java1.7以后版本中try try (FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt")) { // 定义变量,作为有效个数 int len; // 定义字符数组,用于存储读取的字节数据 byte[] b = new byte[1024]; // 循环读取 while ((len = in.read(b)) != -1) { // 只获取每次读取的长度 System.out.println(new String(b,0,len)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
-
FileInputStream读取字节数据
/** * @Author charlesYan * @Description //复制文件 * @Date 18:17 2020/11/12 * @Param [] * @return void **/ @Test public void testCopyImage() throws Exception { // 1.创建流对象 // 1.1 指定数据源 FileInputStream fis = new FileInputStream("E:\\Blog\\202011121615.txt"); // 1.2 指定目的地 FileOutputStream fos = new FileOutputStream("E:\\Blog\\202011121615_copy.txt"); // 2.读写数据 // 2.1 定义数组 byte[] b = new byte[1024]; // 2.2 定义长度 int len; // 2.3 循环读取 while ((len = fis.read(b)) != -1) { fos.write(b,0,len); } // 3.关闭流 fos.close();// 先关闭输出流 fis.close(); }
字符流Reader和Writer
-
字符流Reader和Writer继承类图
-
字符流起源
因为数据编码的不同,因而有了对字符进行高效操作的流对象,字符流本质其实就是基于字节流读取时,去查了指定的码表,而字节流直接读取数据会有乱码的问题(读中文会乱码)
字符流 = 字节流 + 编码表
-
字节流读取中文
/** * @Author charlesYan * @Description //测试文件输入流读取中文 * @Date 9:34 2020/11/13 * @Param [] * @return void **/ @Test public void testFileInputStreamCycleReadChinese() throws Exception{ // 创建流对象 FileInputStream fis = new FileInputStream("E:\\Blog\\202011121615_copy.txt"); // 初始化字节数组用于存读取数据 byte[] b = new byte[1024]; // 读取长度 int len; // 循环读取 while ((len = fis.read(b)) != -1) { System.out.println(new String(b,0,len)); } fis.close(); }
-
分析中文正常读取原因
读取的文件的编码格式为UTF-8,IDEA中设置的项目的编码格式也是UTF-8
System.out.println(Charset.defaultCharset()); // IDEA中设置项目的编码格式,tomcat设置的编码格式,是配置的语言编码 System.out.println(System.getProperty("file.encoding")); // file.encoding是唯一的,与main入口函数所在的java类文件编码保持一致 // 使用String的有参构造方法 String str = new String("hhhh ty智障%shfu摸淑芬十分uif内服NSF黑"); // 1.以GBK编码方式获取str的字节数组,再用String有参构造函数构造字符串 System.out.println(new String(str.getBytes("GBK"))); // 2.以UTF-8编码方式获取str的字节数组,再以默认编码构造字符串 System.out.println(new String(str.getBytes("UTF-8"))); // new String源码分析:底层new String()的源码,String构造方法有解码功能,并且默认编码通过获取配置的编码,如果为空则是ISO-8859-1 this.value = StringCoding.decode(bytes, offset, length); String csn = Charset.defaultCharset().name();
-
选用场景
尽管字节流也能有办法决绝乱码问题,但是还是比较麻烦,于是java就有了字符流,字符为单位读写数据,字符流专门用于处理文本文件。如果处理纯文本的数据优先考虑字符流,其他情况就只能用字节流了(图片、视频、等等只文本例外)。
字符输入流Reader
-
定义
java.io.Reader抽象类是字符输入流的所有类的超类(父类),可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
-
共性方法
public void close() :关闭此流并释放与此流相关联的任何系统资源。 public int read(): 从输入流读取一个字符。 public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中
FileReader类
-
定义
java.io.FileReader类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区
-
构造方法
FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。 FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的字符串名称。
-
FileReader读取字符数据
/** * @Author charlesYan * @Description //字符输入流读取字符 - 读取字符:read方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取 * @Date 11:28 2020/11/13 * @Param [] * @return void **/ @Test public void testFileReaderReadCharacter() throws Exception{ // 使用文件名创建字符输入流对象 FileReader reader = new FileReader("E:\\Blog\\202011121615.txt"); // 定义变量,存储读取的数据 int b; // 循环读取 while ((b = reader.read()) != -1) { System.out.println((char)b); } // 关闭资源 reader.close(); }
字符输出流Writer
-
定义
java.io.Writer抽象类是字符输出流的所有类的超类(父类),将指定的字符信息写出到目的地。
-
共性方法
void write(int c) 写入单个字符。 void write(char[] cbuf)写入字符数组。 abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。 void write(String str)写入字符串。 void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。 void flush()刷新该流的缓冲。 void close() 关闭此流,但要先刷新它。
FileWriter类
-
定义
java.io.FileWriter类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
-
构造方法
FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。 FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。
-
注意事项
- 关闭close和刷新flush
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。
flush:刷新缓冲区,流对象可以继续使用。 close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
- 字符流,只能操作文本文件,不能操作图片,视频等非文本文件。当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流
-
FileWriter写出字符数据
/** * @Author charlesYan * @Description //字符输出流写出字符 * @Date 15:22 2020/11/13 * @Param [] * @return void **/ @Test public void testFileWriterWriteCharacter() throws Exception{ // 使用文件名创建流对象 FileWriter writer = new FileWriter("E:\\Blog\\202011131518.txt"); // 写出数据 writer.write(97); // 写出第1个字符 writer.write("晏"); // 写出第2个字符 // 关闭资源时,与FileOutputStream不同。如果不关闭,数据只是保存到缓冲区,并非保存到文件。 writer.close(); } /** * @Author charlesYan * @Description //字符输出流写出字符 * @Date 15:22 2020/11/13 * @Param [] * @return void **/ @Test public void testFileWriterWriteCharacter() throws Exception{ // 使用文件名创建流对象 FileWriter writer = new FileWriter("E:\\Blog\\202011131518.txt"); // 写出数据 writer.write(97); // 写出第1个字符 writer.write("晏"); // 写出第2个字符 // 关闭资源时,与FileOutputStream不同。如果不关闭,数据只是保存到缓冲区,并非保存到文件。 writer.close(); } /** * @Author charlesYan * @Description // 使用字符流进行文本文件复制 * @Date 15:43 2020/11/13 * @Param [] * @return void **/ @Test public void testCharacterFileCopy() throws Exception{ // 根据文件名创建字符输入输出流 FileReader reader = new FileReader("E:\\Blog\\202011131518.txt"); /*创建输出流做的工作: * 1、调用系统资源创建了一个文件 * 2、创建输出流对象 * 3、把输出流对象指向文件 * */ FileWriter writer = new FileWriter("E:\\Blog\\202011131518_copy.txt"); //一次读取一个字符进行文本复制 4毫秒 copyMethodByCharacter(reader,writer); //一次读取一个字符数组 1毫秒 copyMethodByCharacterArray(reader,writer); reader.close(); writer.close(); } private void copyMethodByCharacterArray(FileReader reader, FileWriter writer) throws Exception { System.out.println("start time:" + System.currentTimeMillis()); // 定义字符数组 char[] chs = new char[1024]; // 定义长度 int len; while ((len = reader.read(chs)) != -1) { writer.write(chs,0,len); } writer.flush(); System.out.println("end time:" + System.currentTimeMillis()); } private void copyMethodByCharacter(FileReader reader, FileWriter writer) throws Exception { System.out.println("start time:" + System.currentTimeMillis()); int len; while ((len = reader.read()) != -1) { writer.write(len); } writer.flush(); System.out.println("end time:" + System.currentTimeMillis()); }
区别
-
实现方式
字符流本质上就是基于字节流,读取时查询相应的码表。
-
读写单位不同
字节流以字节为单位;字符流以字符为单位,一次可能读多个字节。
-
处理对象不同
字节流能处理所有类型的数据(图片、视频等),而字符流只能处理字符类型的数据(文本数据优先考虑字符流,其他情况都用字节流)。
参考链接
-
史上最骚最全最详细的IO流教程,没有之一!
https://www.cnblogs.com/yichunguo/p/11775270.html
-
IO流
https://www.jianshu.com/p/b274fe091048
-
Java 常用IO流操作详解
https://blog.csdn.net/MAGIC_JSS/article/details/51475923