IO流
IO流的概念
- IO就是Input和Output的简写,也就是输入和输出的含义(读和写的含义)。IO就是用来进行读和写的。
- IO流就是指读写数据时像流水一样从一端流到另外一端,因此得名为“流"。
基本分类
-
按照读写数据的基本单位不同,分为 字节流 和 字符流。
- 其中字节流主要指以字节为单位进行数据读写的流,可以读写任意类型的文件。
- 其中字符流主要指以字符(2个字节)为单位进行数据读写的流,只能读写文本文件。 即:文件中的内容只能是文本内容,是文字,不能有图片视频音频之类的。.java后缀的文件也是文本文件。
- 字符的本质是2个字节,为什么不直接使用字节流来读还要在字节流的基础上提出字符流的原因:一个汉字是占2个字节,如果使用字节流来读带有汉字的文本的话,一次只能读出来半个汉字,半个汉字翻译成图案,翻译不明白就乱码了。所以我们读取汉字等等其它的特殊语言的这种文字的时候,我们建议用字符流,因为使用字符流读取一次会读取出一个完整的汉字,这也就是字符流的意义。
-
按照读写数据的方向不同,分为 输入流 和 输出流(站在程序的角度)。
- 其中输入流主要指从文件中读取数据内容输入到程序中,也就是读文件。
- 其中输出流主要指将程序中的数据内容输出到文件中,也就是写文件。
-
-
按照流的角色不同分为节点流和处理流。(看是否跟文件直接关联)
-
其中节点流主要指直接和输入输出源对接的流。和文件直接关联。
-
其中处理流主要指需要建立在节点流的基础之上的流。和文件间接关联,在节点流上面套了一个流。
体系结构
相关流的详解
-
**FileWriter类(重点)**节点流,可直接与文件关联
-
基本概念
- java.io.FileWriter类主要用于将文本内容写入到文本文件。
-
常用方法
方法声明 功能介绍 FileWriter(String fileName) 根据参数指定的文件名构造对象 FileWriter(String fileName, boolean append) 以追加(把数据往末尾写入)的方式根据参数指定的文件名来构造对象 void write(int c) 写入单个字符 void write(char[] cbuf, int off, int len) 将指定字符数组中从偏移量(下标)off开始的len个字符写入此 文件输出流 void write(char[] cbuf) 将cbuf.length个字符从指定字符数组写入此文件输出 流中 void flush() 刷新流 void close() 关闭流对象并释放 package com.lagou.module05.task02; import java.io.FileWriter; import java.io.IOException; public class FileWriterTest { public static void main(String[] args) { // 选中代码后可以使用 ctrl+alt+t 来生成异常的捕获代码等Surround With FileWriter fw = null; try { // 1.构造FileWrite类型的对象与d:/a.txt文件关联 // 若文件不存在,该流会自动创建新的空文件 // 若文件存在,该流会清空文件中的原有内容 // 相当于文件是水桶,流对象是水管,下面这个行代码的意思相当于把水管直接插入到水桶中去 fw = new FileWriter("d:/a.txt"); // 在new对象的时候发生异常,对象就可能new不出来,也就是这个引用所指向的是null // 以追加的方式创建对象去关联文件 // 若文件不存在则自动创建新的空文件,若文件存在则保留原有数据内容在后面追加新的内容 //fw = new FileWriter("d:/a.txt", true); // 2.通过流对象写入数据内容 每当写入一个字符后则文件中的读写位置向后移动一位 // 下面这行代码的意思相当于通过水管fw对象往水桶(文件)中灌水,水通过管道流入水桶中 // 也就是通过输出流,数据最终写入到文件中 fw.write('a'); // 准备一个字符数组 char[] cArr = new char[]{'h', 'e', 'l', 'l', 'o'}; // 将字符数组中的一部分内容写入进去,既包括1,又包括3 fw.write(cArr, 1, 3); // ell // 将整个字符数组写进去 fw.write(cArr); // hello // 刷新流 // 我们往水桶里灌水的时候需要通过水管,在水管内部可能会残留一部分水分 // flush的作用也就是刷新流,可以理解为把水管中的水分都控干净,就相当于把它全部都刷新出去,全部都扔到水桶里面 // 有了close方法还需要flush的原因: // 在以后的开发中可能进行大量反复的读写,反复读写的时候流可能一时半会关不了,这个时候就可以使用flush去刷新流,只是暂时我们不需要而已 fw.flush(); System.out.println("写入数据成功!"); } catch (IOException e) { e.printStackTrace(); } finally { // 为了确保无论是否发生异常都会关闭流 // 3.关闭流对象并释放有关的资源 // 我们知道这里有可能会发生空指针异常,此处我们可以使用避免异常的方式 if (null != fw) { try { // 相当于把水管抽走,断开与水桶的关联 // 也就是让流对象与文件解除关联 fw.close(); // close自带刷新功能flush } catch (IOException e) { e.printStackTrace(); } } } } }
-
-
**FileReader类(重点)**节点流,可直接与文件关联
-
基本概念
- java.io.FileReader类主要用于从文本文件读取文本数据内容。
-
常用的方法
方法声明 功能介绍 FileReader(String fileName) 根据参数指定的文件名构造对象 int read() 读取单个字符的数据并返回,返回-1表示读取到末尾 int read(char[] cbuf, int offset, int length) 从输入流中将最多(可能还读不到)len个字符的数据读入一个字符数组中,返回读取 到的字符个数,返回-1表示读取到末尾,从输入流中读取length个字符放到数组cbuf中下标从offset开始的位置依次往下找。 int read(char[] cbuf) 从此输入流中将最多 cbuf.length 个字符的数据读入字符数组中,返 回读取到的字符个数,返回-1表示读取到末尾,把数组读满 void close() 关闭流对象并释放有关的资源 package com.lagou.module05.task02; import java.io.FileReader; import java.io.IOException; public class FileReaderTest { public static void main(String[] args) { FileReader fr = null; try { // 1.构造FileReader类型的对象与d:/a.txt文件关联 //fr = new FileReader("d:/a.txt"); fr = new FileReader("d:/b.txt"); // 2.读取数据内容并打印 /* int res = fr.read(); System.out.println("读取到的单个字符是:" + (char)res); // 'a' */ int res = 0; while ((res = fr.read()) != -1) { // 读取、赋值、判断 // 要打印字符本身需要强转,因为返回的是一个int类型的值,如果不强转打印出来的只是ASCII码值 System.out.println("读取到的单个字符是:" + (char)res + ",对应的编号是:" + res); } // 上方通过while循环读取完文件中的所有内容之后,如果下方再读,将啥也读不到,因为光标已经到达文件的末尾了。、 // 准备一个字符数组来保存读取到的数据内容,希望读满字符数组的一部分,希望读满整个字符数组 // char[] cArr = new char[5]; // 期望读满字符数组中的一部分空间,也就是读取3个字符放入数组cArr中下标从1开始的位置上 /*int res = fr.read(cArr, 1, 3); System.out.println("实际读取到的字符个数是:" + res); // 3 for (char cv : cArr) { System.out.println("读取到的单个字符是:" + (char)cv); // 啥也没有 a e l 啥也没有 }*/ // 期望读满整个字符数组 /*int res = fr.read(cArr); System.out.println("实际读取到的字符个数是:" + res); // 5 for (char cv : cArr) { System.out.println("读取到的单个字符是:" + (char)cv); // a e l l h }*/ } catch (IOException e) { e.printStackTrace(); } finally { // 3.关闭流对象并释放有关的资源 if (null != fr) { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
案例题目:文件字符流实现文件的拷贝(注:只能拷贝文本类型的文件)
package com.lagou.module05.task02; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class FileCharCopyTest { public static void main(String[] args) { FileReader fr = null; FileWriter fw = null; try { // 1.创建FileReader类型的对象与d:/a.txt文件关联 fr = new FileReader("d:/a.txt"); //fr = new FileReader("d:/03 IO流的框架图.png"); // 2.创建FileWriter类型的对象与d:/b.txt文件关联 fw = new FileWriter("d:/b.txt"); //fw = new FileWriter("d:/IO流的框架图.png"); 拷贝图片文件失败!!! // 使用字符流拷贝图片文件,拷贝出来的文件会损坏,无法打开 // 3.不断地从输入流中读取数据内容并写入到输出流中 System.out.println("正在玩命地拷贝..."); int res = 0; while ((res = fr.read()) != -1) { fw.write(res); } System.out.println("拷贝文件成功!"); } catch (IOException e) { e.printStackTrace(); } finally { // 4.关闭流对象并释放有关的资源 // 潜规则:先创建的后关闭,后创建的先关闭 if (null != fw) { try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != fr) { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
-
FileOutputStream类(重点)
-
基本概念
- java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中。
- 用法和FileWriter大同小异,只是调用的时候把字符参数换成了字节参数
-
常用的方法
方法声明 功能介绍 FileOutputStream(String name) 根据参数指定的文件名来构造对象 FileOutputStream(String name, boolean append) 以追加的方式根据参数指定的文件名来构造对象 void write(int b) 将指定字节写入此文件输出流 void write(byte[] b, int off, int len) 将指定字节数组中从偏移量off开始的len个字节写入 此文件输出流 void write(byte[] b) 将 b.length 个字节从指定字节数组写入此文件输出 流中 void flush() 刷新此输出流并强制写出任何缓冲的输出字节 void close() 关闭流对象并释放有关的资源
-
-
FileInputStream类(重点)
-
基本概念
- java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等。
- 用法和FileReader大同小异,只是调用的时候把字符参数换成了字节参数
-
常用的方法
方法声明 功能介绍 FileInputStream(String name) 根据参数指定的文件路径名来构造对象 int read() 从输入流中读取单个字节的数据并返回,返回-1表示读取到末尾 int read(byte[] b, int off, int len) 从此输入流中将最多len个字节的数据读入字节数组中,返回读取到的 字节个数,返回-1表示读取到末尾 int read(byte[] b) 从此输入流中将最多 b.length 个字节的数据读入字节数组中,返回读 取到的字节个数,返回-1表示读取到末尾 void close() 关闭流对象并释放有关的资源 int available() 获取输入流所关联文件的大小,拷贝文件方式二需要用到的方法,我们用该方法来知道文件有多大,这样我们就能准备一个和文件一样大的缓冲区。 -
注意:把图片拷贝成视频,拷贝的文件会受损,无法打开。不同的文件格式不同。mp4的视频文件没拷贝完无法打开,但是有些格式的视频文件没有拷贝完也可以打开,可以看,但是不能拖拽(说明文件下载的不完整)。
-
拷贝文件方式一及缺点:以单个字节为单位进行拷贝,也就是每次读取一个字节后再写入一个字节,缺点是:当文件稍大时,拷贝的效率很低。就相当于买50个鸡蛋,一个鸡蛋一个鸡蛋的买。
-
拷贝文件方式二的缺点:准备一个和文件大小一样的缓冲区,一次性将文件中的所有内容取出到缓冲区然后一次性写入进去。就相当于我们去买50个鸡蛋,拿一个能装满50个鸡蛋的大篮子区,一次性买回家。缺点是:若文件过大时,无法申请和文件大小一样的缓冲区,真实物理内存不足。我买鸡蛋再小区出名了,邻居都让我帮忙捎鸡蛋,初略估计一下,需要带500000个鸡蛋,我总不能准备一个能装下500000个鸡蛋的篮子,这样的篮子很难找,找到了我们也抗不动。
-
拷贝文件方式三:准备一个相对适当(不要太大也不要太小)的缓冲区(一般我们给1024的整数倍数即可,因为我们计算机中的进制是1024),分多次将文件拷贝完成。 方式一、方式二虽然有缺点,但是在特殊情况下我们也能使用。方式三相对更合理更安全,效率也更高一些,不涉及到上面的缺陷。
-
案例题目:文件字节流实现文件的拷贝。
package com.lagou.module05.task02; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class FileByteCopyTest { public static void main(String[] args) { // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数 long g1 = System.currentTimeMillis(); FileInputStream fis = null; FileOutputStream fos = null; try { // 1.创建FileInputStream类型的对象与d:/03 IO流的框架图.png文件关联 //fis = new FileInputStream("d:/03 IO流的框架图.png"); fis = new FileInputStream("d:/02_IO流的框架结构.mp4"); // 2.创建FileOutputStream类型的对象与d:/IO流的框架图.png文件关联 //fos = new FileOutputStream("d:/IO流的框架图.png"); fos = new FileOutputStream("d:/IO流的框架结构.mp4"); // 3.不断地从输入流中读取数据内容并写入到输出流中 System.out.println("正在玩命地拷贝..."); // 方式一:以单个字节为单位进行拷贝,也就是每次读取一个字节后再写入一个字节 // 缺点:文件稍大时,拷贝的效率很低 /*int res = 0; while ((res = fis.read()) != -1) { fos.write(res); }*/ // 方式二:准备一个和文件大小一样的缓冲区,一次性将文件中的所有内容取出到缓冲区然后一次性写入进去 // 缺点:若文件过大时,无法申请和文件大小一样的缓冲区,真实物理内存不足 /*int len = fis.available(); System.out.println("获取到的文件大小是:" + len); byte[] bArr = new byte[len]; int res = fis.read(bArr); System.out.println("实际读取到的文件大小是:" + res); fos.write(bArr);*/ // 方式三:准备一个相对适当的缓冲区,分多次将文件拷贝完成 byte[] bArr = new byte[1024]; int res = 0; while ((res = fis.read(bArr)) != -1) { // res是实际读取到的字节个数 // 不能直接fos.write(bArr);因为如果文件大小不是刚好1024字节的话,我们还写入1024个字节文件可能会损坏 // 我们直接使用下面这种方式:每次写入的东西都是实际所读取到的字节个数的字节 fos.write(bArr, 0, res); } System.out.println("拷贝文件成功!"); } catch (IOException e) { e.printStackTrace(); } finally { // 4.关闭流对象并释放有关的资源 if (null != fos) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != fis) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } long g2 = System.currentTimeMillis(); System.out.println("使用文件流拷贝视频文件消耗的时间为:" + (g2-g1)); // 165 } }
-
-
BufferedOutputStream类(重点)
-
基本概念
- java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层 系统,不缓冲我们可能写一个字节就需要与磁盘交互一次,缓冲了之后就减少了和磁盘IO的交互。内部已经帮我们封装好了缓冲区,目的还是为了提高效率。
-
常用的方法
方法声明 功能介绍 BufferedOutputStream(OutputStream out) 根据参数指定的引用来构造对象(OutputStream是个抽象类) BufferedOutputStream(OutputStream out, int size) 根据参数指定的引用和缓冲区大小来构造 对象,一般使用默认的缓冲区大小 void write(int b) 写入单个字节 void write(byte[] b, int off, int len) 写入字节数组中的一部分数据 void write(byte[] b) 写入参数指定的整个字节数组 void flush() 刷新流 void close() 关闭流对象并释放有关的资源 -
BufferedInputStream类(重点)
-
基本概念
- java.io.BufferedInputStream类主要用于描述缓冲输入流。内部已经帮我们封装好了缓冲区。
-
常用的方法
方法声明 功能介绍 BufferedInputStream(InputStream in) 根据参数指定的引用构造对象 BufferedInputStream(InputStream in, int size) 根据参数指定的引用和缓冲区大小构造对象 int read() 读取单个字节 int read(byte[] b, int off, int len) 读取len个字节 int read(byte[] b) 读取b.length个字节 void close() 关闭流对象并释放有关的资源 -
问:为什么第1行代码的BufferedReader缓冲流中已经定义了一个大小为8192的char[]数组,而第3行代码中还要定义一个大小为1024大小的char[]数组作为缓冲区?
答:BufferedReader中8192大小的char[]数组和自定义的1024大小的char[]数组不是在同一个环节作用的。
举个生活中的例子说明:BufferedReader中8192大小的char[]数组相当于快递站,自定义的1024大小的char[]数组相当于快递小哥骑的三轮车。快递小哥每次取一个快递后并不是立即往快递站送,而是先放到三轮车中,等到三轮车装满了,再一次性往快递站送;等到快递小哥送了8次,把快递站也装满了,再一次性把快递站中的货物全部送走。不知上面的解释有没有说清楚。
-
**案例题目:**缓冲字节流拷贝文件,此处测性能
package com.lagou.module05.task02; import java.io.*; public class BufferedByteCopyTest { public static void main(String[] args) { // 以后开发中使用这种自带缓冲区的流,因为自带了缓冲区,我们再额外提供一个缓冲区,效率也更快 // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数 long g1 = System.currentTimeMillis(); BufferedInputStream bis = null; BufferedOutputStream bos = null; try { // 1.创建BufferedInputStream类型的对象与d:/02_IO流的框架结构.mp4文件关联 // 流套在流之上,叫做处理流 /** * public BufferedInputStream(InputStream in) { * this(in, DEFAULT_BUFFER_SIZE); * } * * private static int DEFAULT_BUFFER_SIZE = 8192; 1024 x 8 Java 官方认为1024是合适的缓冲区 * * public BufferedInputStream(InputStream in, int size) { * super(in); * if (size <= 0) { * throw new IllegalArgumentException("Buffer size <= 0"); * } * buf = new byte[size]; * } */ bis = new BufferedInputStream(new FileInputStream("d:/02_IO流的框架结构.mp4")); // 2.创建BufferedOuputStream类型的对象与d:/IO流的框架结构.mp4文件关联 bos = new BufferedOutputStream(new FileOutputStream("d:/IO流的框架结构.mp4")); // 3.不断地从输入流中读取数据并写入到输出流中 System.out.println("正在玩命地拷贝..."); // 外部也能自己提供一个额外的缓冲区,效率也就更高了 byte[] bArr = new byte[1024]; int res = 0; while ((res = bis.read(bArr)) != -1) { bos.write(bArr, 0, res); } System.out.println("拷贝文件成功!"); } catch (IOException e) { e.printStackTrace(); } finally { // 4.关闭流对象并释放有关的资源 // 把外围的流对象关了之后,内部的流对象也会顺带着自动关闭了。而且目前的代码想关也关不了,因为没使用变量接一下 if (null != bos) { try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != bis) { try { bis.close(); } catch (IOException e) { e.printStackTrace(); } } } long g2 = System.currentTimeMillis(); System.out.println("使用缓冲区拷贝视频文件消耗的时间为:" + (g2-g1)); // 44 } }
-
缓冲字节流和文件字节流效率比较:使用普通文件字节流拷贝,消耗 165毫秒;使用缓冲字节流,消耗 44毫秒(以自己提供的文件为例)。由此可见,缓冲流比文件流效率更快,文件越大,效率越快,时间差更明显。以后只要涉及到文件的拷贝,我们首选缓冲流。
-
-
BufferedWriter类(重点)
-
基本概念
- java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中。
-
常用的方法
方法声明 功能介绍 BufferedWriter(Writer out) 根据参数指定的引用来构造对象 BufferedWriter(Writer out, int sz) 根据参数指定的引用和缓冲区大小来构造对象 void write(int c) 写入单个字符到输出流中 void write(char[] cbuf, int off, int len) 将字符数组cbuf中从下标off开始的len个字符写入输出流 中 void write(String s, int off, int len) 将参数s中下标从off开始的len个字符写入输出流中 void write(char[] cbuf) 将字符串数组cbuf中所有内容写入输出流中 void write(String str) 将参数指定的字符串内容写入输出流中 void newLine() 用于写入行分隔符到输出流中(就是换到下一行) void flush() 刷新流 void close() 关闭流对象并释放有关的资源
-
-
BufferedReader类(重点)
-
基本概念
- java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串。
-
常用的方法
方法声明 功能介绍 BufferedReader(Reader in) 根据参数指定的引用来构造对象 BufferedReader(Reader in, int sz) 根据参数指定的引用和缓冲区大小来构造对象 int read() 从输入流读取单个字符,读取到末尾则返回-1,否则返回实际读取到 的字符内容 int read(char[] cbuf, int off, int len) 从输入流中读取len个字符放入数组cbuf中下标从off开始的位置上, 若读取到末尾则返回-1,否则返回实际读取到的字符个数 int read(char[] cbuf) 从输入流中读满整个数组cbuf String readLine() 读取一行字符串并返回,返回null表示读取到末尾(可以和newLine搭配使用)一个换行加2个字节,10是\n(换行符),13是\r(回车符),Windows系统里面,每行结尾是“<换行><回车>”,即“\n\r”; void close() 关闭流对象并释放有关的资源 -
性能依旧高于上方的文件字符流
-
案例题目:使用缓存字符流拷贝文件,此处不测性能
package com.lagou.module05.task02; import java.io.*; public class BufferedCharCopyTest { public static void main(String[] args) { BufferedReader br = null; BufferedWriter bw = null; try { // 1.创建BufferedReader类型的对象与d:/a.txt文件关联 br = new BufferedReader(new FileReader("d:/a.txt")); // 2.创建BufferedWriter类型的对象与d:/b.txt文件关联 bw = new BufferedWriter(new FileWriter("d:/b.txt")); // 3.不断地从输入流中读取一行字符串并写入到输出流中 System.out.println("正在玩命地拷贝..."); String str = null; while ((str = br.readLine()) != null) { bw.write(str); bw.newLine(); // 当前系统中的行分隔符是:\r\n } System.out.println("拷贝文件成功!"); } catch (IOException e) { e.printStackTrace(); } finally { // 4.关闭流对象并释放有关的资源 if (null != bw) { try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
-
PrintStream类(打印流)
-
基本概念
- java.io.PrintStream类主要用于更加方便地打印各种数据内容。就是我们的sout,out变量就是PrintStream类型的。PrintStream就是像控制台打印,除了向控制台打印之外,根据构造方法,我们还可以向文件中打印。也就是写入文件。
- 重载的意义:对调用者来说,只需要记住一个方法名就可以调用各种各样的版本。
-
常用的方法
方法声明 功能介绍 PrintStream(OutputStream out) 根据参数指定的引用来构造对象 void print(String s) 用于将参数指定的字符串内容打印出来 void println(String x) 用于打印字符串后并终止该行(换行) void flush() 刷新流 void close() 用于关闭输出流并释放有关的资源
-
-
PrintWriter类
-
基本概念
- java.io.PrintWriter类主要用于将对象的格式化形式打印到文本输出流。
-
常用的方法
方法声明 功能介绍 PrintWriter(Writer out) 根据参数指定的引用来构造对象 void print(String s) 将参数指定的字符串内容打印出来 void println(String x) 打印字符串后并终止该行 void flush() 刷新流 void close() 关闭流对象并释放有关的资源 -
案例题目(也就是转换流和打印流的使用)
不断地提示用户输入要发送的内容,若发送的内容是"bye"则聊天结束,否则将用户输入的内容写 入到文件d:/a.txt中。
要求使用BufferedReader类来读取键盘的输入 System.in代表键盘输入
要求使用PrintStream类负责将数据写入文件
new对象的代码要放到循环的外边。
package com.lagou.module05.task02; import java.io.*; import java.text.SimpleDateFormat; import java.util.Date; public class PrintStreamChatTest { public static void main(String[] args) { // 由手册可知:构造方法需要的是Reader类型的引用,但Reader类是个抽象类,实参只能传递子类的对象 字符流 // 由手册可知: System.in代表键盘输入, 而且是InputStream类型的 字节流 BufferedReader br = null; PrintStream ps = null; try { br = new BufferedReader(new InputStreamReader(System.in)); ps = new PrintStream(new FileOutputStream("d:/a.txt", true)); // 声明一个boolean类型的变量作为发送方的代表 boolean flag = true; while(true) { // 1.提示用户输入要发送的聊天内容并使用变量记录 System.out.println("请" + (flag? "张三": "李四") + "输入要发送的聊天内容:"); String str = br.readLine(); // 2.判断用户输入的内容是否为"bye",若是则聊天结束 if ("bye".equals(str)) { System.out.println("聊天结束!"); break; } // 3.若不是则将用户输入的内容写入到文件d:/a.txt中 //else { // 获取当前系统时间并调整格式 Date d1 = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ps.println(sdf.format(d1) + (flag?" 张三说:":" 李四说:") + str); //} flag = !flag; } ps.println(); // 写入空行 与之前的聊天记录隔开 ps.println(); ps.println(); } catch (IOException e) { e.printStackTrace(); } finally { // 4.关闭流对象并释放有关的资源 if (null != ps) { ps.close(); } if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
-
-
OutputStreamWriter类(转换流)
-
基本概念
- java.io.OutputStreamWriter类主要用于实现从字符流到字节流的转换。
-
常用的方法
方法声明 功能介绍 OutputStreamWriter(OutputStream out) 根据参数指定的引用来构造对象 OutputStreamWriter(OutputStream out, String charsetName) 根据参数指定的引用和编码构造 对象 void write(String str) 将参数指定的字符串写入 void flush() 刷新流 void close() 用于关闭输出流并释放有关的资 源
-
-
InputStreamReader类(转换流)
-
基本概念
- java.io.InputStreamReader类主要用于实现从字节流到字符流的转换。
-
常用的方法
方法声明 功能介绍 InputStreamReader(InputStream in) 根据参数指定的引用来构造对象 InputStreamReader(InputStream in, String charsetName) 根据参数指定的引用和编码来构造对 象 int read(char[] cbuf) 读取字符数据到参数指定的数组 void close() 用于关闭输出流并释放有关的资源
-
-
字符编码(给字符指定编号)
- 编码表的由来
- 计算机底层只能识别1和0组成的二进制数据,早期就是电信号。为了方便计算机可以识别各个国家的文字,就需要 将各个国家的文字采用数字编号的方式进行描述并建立对应的关系表(比如:字符’a’的ASCII码是97),该表就叫做编码表。
- 常见的编码表
- ASCII:美国标准信息交换码, 使用一个字节的低7位二位进制进行表示(高位放个0空着即可)。
- ISO8859-1:拉丁码表,欧洲码表,使用一个字节的8位二进制进行表示。
- GB2312:中国的中文编码表,最多使用两个字节16位二进制为进行表示。
- GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多使用两个字节16位二进制位表 示。 最多也就是说如果出现英文使用一个二进制位来表示,出现了汉字就使用两个字节来表示,这样就不容易引起资源的浪费。规定:如果二进制的最高位是0,我们就认为当前一个字节代表一个字符;如果二进制的最高位是1,我们就认为当前两个字节代表一个字符。也就是看最高位的二进制位即可。
- Unicode:国际标准码,融合了目前人类使用的所有字符,为每个字符分配唯一的字符码。所有的 文字都用两个字节16位二进制位来表示。和上方的GBK以及GB2312一样出现了问题:如何知道是一个字节表示,还是两个字节表示?从而导致不知道怎么去解析的问题。
- 编码的发展(解决了Unicode只是指了一个编号,却不知道如何去解析的问题)
- 面向传输的众多 UTF(UCS Transfer Format)标准出现了,UTF-8就是每次8个位传输数据,而 UTF-16就是每次16个位。这是为传输而设计的编码并使编码无国界,这样就可以显示全世界上所 有文化的字符了。
- Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体 存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。
- UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。如果当前二进制最高位是0,代表是一个字节表示一个编号;如果最高位是110,就认为两个字节代表一个编号;如果最高位是1110,就认为三个字节代表一个编号,这样依赖就把所有文字以及解析方式都指定好了。这样依赖就实现了对全球各个国家的文字进行解析了。
- 编码表的由来
-
DataOutputStream类(了解)
-
基本概念
- java.io.DataOutputStream类主要用于以适当的方式将基本数据类型写入输出流中。专门针对之前所说的8种基本数据类型进行写入操作的。
-
常用的方法
方法声明 功能介绍 DataOutputStream(OutputStream out) 根据参数指定的引用构造对象 OutputStream类是个抽象 类,实参需要传递子类对象 void writeInt(int v) 用于将参数指定的整数一次性写入输出流,优先写入高字 节(int在内存种占4个字节,这个也就意味着从高到低依次写入) void close() 用于关闭文件输出流并释放有关的资源
-
-
DataInputStream类(了解)
-
基本概念
- java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据。
-
常用方法
方法声明 功能介绍 DataInputStream(InputStream in) 根据参数指定的引用来构造对象 InputStream类是抽象类, 实参需要传递子类对象 int readInt() 用于从输入流中一次性读取一个整数数据并返回 void close() 用于关闭文件输出流并释放有关的资源 package com.lagou.module05.task02; import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class DataOutputStreamTest { public static void main(String[] args) { DataOutputStream dos = null; try { // 1.创建DataOutputStream类型的对象与d:/a.txt文件关联 dos = new DataOutputStream(new FileOutputStream("d:/a.txt")); // 2.准备一个整数数据66并写入输出流 // 66: 0000 0000 ... 0100 0010 => B int num = 66; //dos.writeInt(num); // 写入4个字节 输出的是 B,因为B的ASCII码值就是66,前面还有三个空白位置是因为写入的是4个字节,且优先从高字节写入 dos.write(num); // 写入1个字节 System.out.println("写入数据成功!"); } catch (IOException e) { e.printStackTrace(); } finally { // 3.关闭流对象并释放有关的资源 if (null != dos) { try { dos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
package com.lagou.module05.task02; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; public class DataInputStreamTest { public static void main(String[] args) { DataInputStream dis = null; try { // 1.创建DataInputStream类型的对象与d:/a.txt文件关联 dis = new DataInputStream(new FileInputStream("d:/a.txt")); // 2.从输入流中读取一个整数并打印 //int res = dis.readInt(); // 读取4个字节 那边是writeInt,这边就使用readInt int res = dis.read(); // 读取1个字节 那边是write,这边就使用read,千万不要那边用write,这边用readInt,否则会报EOFException文件末尾异常 System.out.println("读取到的整数数据是:" + res); // 66 那边写66,这边就应该读出来66 } catch (IOException e) { e.printStackTrace(); } finally { // 3.关闭流对象并释放有关的资源 if (null != dis) { try { dis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
-
ObjectOutputStream类(重点)(将对象转换成字符序列)序列化
-
基本概念
- java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。 写入之后需要靠程序去读取而不是提供肉眼去读取,因为肉眼读取会产生乱码。
- 只能将支持 java.io.Serializable 接口的对象写入流中。必须实现。
- 类通过实现 java.io.Serializable 接口以启用其序列化功能。 实现接口的目的。先前就有这个功能,我们只是把它给打开了。
- 所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。
- 假设我们有一张图,图片上有文字、空白、线条,比如线条有自己的粗细,文字有自己的颜色,有很多的内容。这张图片可以看作是一个对象,万物皆对象,我们按下ctr + S 也就是将这张图片保存到磁盘上,实际上也就是将该对象保存到磁盘上,但是这个对象保存的信息量很大,有字体的颜色、大小、所在位置,线条的粗细、长短、所在的位置、颜色等等。把这些信息都保存到硬盘上的时候需要有效的组织一下,要不按照特点的规则去组织这些信息的话,下次打开就不一定是这个样子了,就可能乱套甚至是打不开了。我们将一个对象需要存储的相关信息有效组织成字节序列的转化过程称为序列化。
-
常用的方法
方法声明 功能介绍 ObjectOutputStream(InputStream in) 根据参数指定的引用来构造对象 Object writeObject(Object obj) 用于将参数指定的对象整体写入到输出流中 void close() 用于关闭输出流并释放有关的资源
-
-
ObjectInputStream类(重点)(将字符序列转换回对象)反序列化
-
基本概念
- java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。
- 所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。
-
常用的方法
方法声明 功能介绍 ObjectInputStream(InputStream in) 根据参数指定的引用来构造对象 Object readObject() 主要用于从输入流中读取一个对象并返回 无法通过返回值 来判断是否读取到文件的末尾 void close() 用于关闭输入流并释放有关的资源 -
序列化版本号的作用
- 序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的(用来进行版本验证的)。在进行反序列化时, JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如 果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(incaildCastException)。也就是我们提供对象输出流输出对象的时候会将版本号也输出出去,如果文件中读取出来的版本号和我们本地的实体类的序列化版本号一致,就反序列化,否则抛出异常。也可以理解为是序列化和反序列化之间建立起来的暗号、对应关系。
-
transient关键字
- transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行 化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进 去的。就是不让该成员变量参与序列化,也就是不会写入到文件中,自然也就读取不到了。
-
经验的分享
- 当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一 个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来, 从而避免了通过返回值进行是否达到文件末尾的判断。整体写进去,整体读出来,只写一次,也就只读一次。就相当于把多个对象同时读取。这是因为readObject方法无法通过返回值 来判断是否读取到文件的末尾导致的问题。
-
案例
package com.lagou.module05.task02; public class User implements java.io.Serializable { /** * IDEA提示没写序列化版本号的功能:Settings => Editor => Inspections => Java => Serialization issues => 勾选上Serialization class without 'serialVersionUID' * 勾选上之后类名上面就会提示没有添加序列化版本号,此时把鼠标移至类名上,点击Add 'serialVersionUID' field 就会自动添加一个序列化版本号,然后就完事了 * * Java官方规定不仅要实现Serializable接口,还要给一个serialVersionUID序列化版本号,获取序列化版本号的方法见上方 */ private static final long serialVersionUID = -5814716593800822421L; private String userName; // 用户名 private String password; // 密码 private transient String phoneNum; // 手机号 表示该成员变量不参与序列化操作 public User() { } public User(String userName, String password, String phoneNum) { this.userName = userName; this.password = password; this.phoneNum = phoneNum; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPhoneNum() { return phoneNum; } public void setPhoneNum(String phoneNum) { this.phoneNum = phoneNum; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", password='" + password + '\'' + ", phoneNum='" + phoneNum + '\'' + '}'; } }
package com.lagou.module05.task02; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class ObjectOutputStreamTest { public static void main(String[] args) { ObjectOutputStream oos = null; try { // 1.创建ObjectOutputStream类型的对象与d:/a.txt文件关联 oos = new ObjectOutputStream(new FileOutputStream("d:/a.txt")); // 2.准备一个User类型的对象并初始化 User user = new User("qidian", "123456", "13511258688"); // 3.将整个User类型的对象写入输出流 oos.writeObject(user); // 对象的所有数据确实写入到文件种去了,但是存在乱码现象,因为有些字符解析不完全。 System.out.println("写入对象成功!"); } catch (IOException e) { e.printStackTrace(); } finally { // 4.关闭流对象并释放有关的资源 if (null != oos) { try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
package com.lagou.module05.task02; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class ObjectInputStreamTest { public static void main(String[] args) { ObjectInputStream ois = null; try { // 1.创建ObjectInputStream类型的对象与d:/a.txt文件关联 ois = new ObjectInputStream(new FileInputStream("d:/a.txt")); // 2.从输入流中读取一个对象并打印 Object obj = ois.readObject(); System.out.println("读取到的对象是:" + obj); // qidian 123456 13511258688 null } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { // 3.关闭流对象并释放有关的资源 if (null != ois) { try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
-
RandomAccessFile类(实现对文件内容的随机访问)
-
基本概念
- java.io.RandomAccessFile类主要支持对随机访问文件的读写操作。
- 前面的流虽然也能对文件内容进行读写,但是无论是写文件还是读文件,默认都是从文件的开始位置开始访问的。而且读一个字节的内容以后,光标就要往后移。随着读写操作的进行,读写位置也就是光标都会往后移一位。默认从文件开头位置开始。
- 我们以后开发中可能涉及到这样一种场景:文件一打开我不想从文件的开头位置开始读。比如说想跳过几个字符之后再去读,还有就是我们读着读着又想跳过几个字符之后再去写,等等。也就是我们要想跳跃式的对文件进行读写操作的时候我们就可以使用RandomAccessFile类。
-
- 常用场景:1、更新内容;2、跳到某个地方读取数据。跟IO流相比的优点在于:可以指定读写位置。所以称之为随机。
- 常用的方法
方法声明 | 功能介绍 |
---|---|
RandomAccessFile(String name, String mode) | 根据参数指定的名称(文件名)和模式(关联这个文件的方式)构造对象 r: 以只读方式打开 rw:打开以便读取和写入 rwd:打开以便读取和写入,同步文件内容的更新 rws:打开以便读取和写入,同步文件内容和元数据 的更新 |
int read() | 读取单个字节的数据 |
void seek(long pos) | 用于设置从此文件的开头开始测量的文件指针偏移量,相当于文件开头位置到底要跳几个字节。 |
void write(int b) | 将参数指定的单个字节写入 |
void close() | 用于关闭流并释放有关的资源 |
- 练习代码
package com.lagou.module05.task02;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
public static void main(String[] args) {
RandomAccessFile raf = null;
try {
// 1.创建RandomAccessFile类型的对象与d:/a.txt文件关联
raf = new RandomAccessFile("d:/a.txt", "rw");
// 2.对文件内容进行随机读写操作
// 随机流是可以不从文件开头位置读写操作的
// 设置距离文件开头位置的偏移量,从文件开头位置向后偏移3个字节 aellhello
raf.seek(3);
int res = raf.read();
// 如果此处不作类型转换的话,得到的实际上是字符的ASCII码值
System.out.println("读取到的单个字符是:" + (char)res); // a l
res = raf.read();
System.out.println("读取到的单个字符是:" + (char)res); // h 之后光标就指向了h之后的e
raf.write('2'); // 执行该行代码后覆盖了字符'e'
System.out.println("写入数据成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3.关闭流对象并释放有关的资源
if (null != raf) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
转换流除了可以处理字符流和字节流之间的相互转换,还可以处理编码问题。