input output stream:输入输出流
计算数据是在cpu,有cpu两大功能:运算器和控制器。但是cpu不能存储数据,数据存在存储设备,如磁盘、硬盘、U盘、光盘,才能永久存储数据,需要将数据计算之后由cpu运算到磁盘上,会用到IO流;同样的,要计算某个数据,需要将数据先传到cpu中,也要用到IO流
1)、从狭义上来说:数据在内存中输入和输出
将数据提前缓存,加快程序的运行
代码包括操作系统,普通应用层软件,jvm,都是在在内存中运行
2)、从广义来说,不同电脑之间的数据流动,(借助网络设备)也是一种IO流
狭义上的IO流:本地进程间的数据流动 (同一个台电脑,不同元器件之间的流动)
广义上的IO流:远程进程间的数据流动 (不同电脑)
1)、流的流动方法(数据的传输方向)站在内存的角度:
|-- 输入流 如将数据从磁盘读到内存
|-- 输出流 如将计算好的数据存到磁盘上(内存读到磁盘上)
2)、流的数据格式
|-- 字节流 (二进制形式) 所有都能用
|-- 字符流 (二进制效率差,比如一句话,按照字节去翻译就很长)注意:不是所有的 流都能用字符流,如一张图片,字符串适用于肉眼能看到的符号
|-- 节点流
|-- 装饰流(过滤流)装饰节点流
转换字节和字符流的特殊流
字节流:
|-- InputStream 输入流
|-- OutputStream 输出流
字符流:
|-- Reader 输入流
|-- Writer 输出流
字节流
装饰流
数据流
字符流
对象流
对象序列化
转换流
字节流:
输入流:
输出流:
示例:
存在bug,读整个字符数组不合适,应该给多少位读多少位。不能保证刚好就是1024,会出现多读的情况
对比原代码,程序本来到红色的部分已经结束了,但是控制台却多输出了很多其他的代码(绿色部分及之后未展示出来的结果)显然不对.读满了会重复去读
- @Test
- void testIStream01() {
- InputStream is = null;
- try {
- is = new FileInputStream(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java"));
- //read()方法是一次读尽数据,若文件很大,内存是有限的,所以不建议
- //所以做一个字节数组
- //也可以是1024*4,文件存在磁盘上,是以页存储,一个页是1024*4
- //返回int值,-1表示读到末尾
- byte[] buf = new byte[1024];
- while(is.read(buf) != -1) {
- System.out.write(buf);
- //println是输出字符串,不能用,操作不了,这里是字节数组
- }
-
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // 关闭流
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }

我们将读取的字节数记录下来,只要没读到末尾,一直循环,从0读到记录的位置,这样就可以避免上述情况的发生
- @Test
- void testIStream02() {
- InputStream is = null;
- try {
- is = new FileInputStream(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java"));
- byte[] buf = new byte[1024];
- int len = 0;//记录读的字节数
- while((len = is.read(buf)) != -1) {
- System.out.write(buf, 0, len);
- }
-
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // 关闭流
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }

1)、FileInputStream 字节输入流
- @Test
- void test01() {
- InputStream is = null;
- try {
- // 1、创建一个输入流对象(XXX数据流)
- is = new FileInputStream("a.txt");
-
- // 创建字节数组,通过字节数组读取数据
- byte[] buf = new byte[1024];
- int len = 0;
- while ((len = is.read(buf)) != -1) {
- System.out.write(buf, 0, len);//从buf中读,从第0个读len长度
- }
-
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // 一定要注意:流必须关闭
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }


若是带一次的代码,不用len记录,直接读。会导致字节数组读不满,打出空格(byte[] 默认初始化为0)
- @Test
- void test01() {
- InputStream is = null;
- try {
-
- // 1、创建一个输入流对象(XXX数据流)
- is = new FileInputStream("a.txt");
-
- // 创建字节数组,通过字节数组读取数据
- byte[] buf = new byte[1024];
-
- while ((len = is.read(buf)) != -1) {
- System.out.write(buf);//从buf中读,从第0个读len长度
- }
-
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // 一定要注意:流必须关闭
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }

读字符串时,可以用System.out.println()。(借助字符串对象,将字节数组构造为字符串对象)
- @Test
- void test02() {
- InputStream is = null;
- try {
- // 1、创建一个输入流对象(XXX数据流)
- is = new FileInputStream("a.txt");
-
- // 创建字节数组,通过字节数组读取数据
- byte[] buf = new byte[1024];
- int len = 0;
- while ((len = is.read(buf)) != -1) {
- // 借助字符串对象,将字节数组构造为字符串对象
- String str = new String(buf, 0, len);
- System.out.println(str);
- }
-
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // 一定要注意:流必须关闭
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
2)、FileOutputStream 字节输出流
- @Test
- void test03() {
- String msg = "你好啊";
- FileOutputStream fos = null;
- try {
- fos = new FileOutputStream(new File("b.txt"));
- // 直接将数据一次写入进去(只适用于比较少,下面的例子时拷贝文件,则必须定义字节数组了)
- fos.write(msg.getBytes());
- System.out.println("写入数据成功!!");
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }

3)综合
字节输入流,字节输出流(以文件输入流,文件输出流为例)来拷贝文件
- @Test
- void test04() {
- FileInputStream fis = null;
- FileOutputStream fos = null;
- try {
- // 使用输入流读取数据
- fis = new FileInputStream("D:\\javaSE\\note\\第23天笔记.txt");
- fos = new FileOutputStream(new File("c:\\a.txt"));
-
- // 一定要通过字节数组进行读取和写入
- // 防止内存过大,导致出现OOM
- byte[] buf = new byte[1024];
- int len = 0;
- while((len = fis.read(buf)) != -1) {
- fos.write(buf, 0, len);
- }
- System.out.println("写入数据成功!!");
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }


又被称为过滤流,这种流不能直接使用,主要的作用就是用来装饰节点流(各种可以直接使用流)。如果节点流读取数据比较慢,使用装饰流做缓冲

FilterInputStream是个普通类,但是一般使用它的子类,BufferedInputStream

装饰流,本质就是一种装饰者设计模式的体现。对原有对象的功能进一步的装饰增强。
GOF 23中设计模式:
|-- 单例设计模式(对象不需要多个)节约内存,避免频繁创建对象。加快效率
|-- 装饰者设计
1)、BufferedInputStream
案列:
- @Test
- void test05() {
- BufferedInputStream bis = null;
- BufferedOutputStream bos = null;
- try {
- //装饰流装饰节点流,所以需要传一个节点流
- bis = new BufferedInputStream(new FileInputStream("G:\\windows 系统\\CentOS-7-x86_64-DVD-1810.iso"));
- bos = new BufferedOutputStream(new FileOutputStream(new File("c:\\a.iso")));
-
- byte[] buf = new byte[1024*8];
- int len = 0;
-
- while ((len = bis.read(buf)) != -1) {
- bos.write(buf, 0, len);
- }
- System.out.println("文件拷贝成功!!");
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (bis != null) {
- try {
- bis.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (bos != null) {
- try {
- bos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
如果不关闭流:
文件少了,且拷贝的图片下面部分是灰色的
满了才会将缓冲区的数据刷进去,但是最后一次不一定满,可能把之前的数据拿过来,数据出问题
不满数字是0,0在颜色里面表示为黑色
- @Test
- void test06() {
- BufferedInputStream bis = null;
- BufferedOutputStream bos = null;
- try {
- bis = new BufferedInputStream(new FileInputStream("c:\\a.jpg"));
- bos = new BufferedOutputStream(new FileOutputStream(new File("c:\\b.jpg")));
-
- byte[] buf = new byte[1024];
- int len = 0;
-
- while ((len = bis.read(buf)) != -1) {
- bos.write(buf, 0, len);
- }
- System.out.println("文件拷贝成功!!");
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- }
- }


如果不关闭,我们可以利用flush()方法,强制刷新,将缓冲池的东西清到目的地
但是不建议直接手动刷新,建议关闭流。
缓存流,必须要关闭,因为JVM在缓存流关闭时,将最后数据完成自动刷新
节点流也需要关闭,因为操作的是一个具体的文件,你一旦占用不关闭,别人就操作不了。比如打开一个文件,此时删除这个文件是删除不掉的


- @Test
- void test06() {
- BufferedInputStream bis = null;
- BufferedOutputStream bos = null;
- try {
- bis = new BufferedInputStream(new FileInputStream("c:\\a.jpg"));
- bos = new BufferedOutputStream(new FileOutputStream(new File("c:\\b.jpg")));
-
- byte[] buf = new byte[1024];
- int len = 0;
-
- while ((len = bis.read(buf)) != -1) {
- bos.write(buf, 0, len);
- }
-
- // 当代码执行到这儿的时候,读取完成
- // 如果最后一次数据没有读取完成,则可以手动刷新缓冲区
- // bos.flush();
-
- System.out.println("文件拷贝成功!!");
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // 缓存流,必须要关闭,因为JVM在缓存流关闭时,将最后数据完成自动刷新
- if (bis != null) {
- try {
- bis.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (bos != null) {
- try {
- bos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
2)、DataOutputStream DataInputStream
数据流
特殊的装饰流
引入:存入数字
- @Test
- void test07() throws Exception {
- FileOutputStream fis = new FileOutputStream("a.dat");
- int msg = 100;
- //fis.write(int b),这个方法是记录读了多少次,并不能存int数据
-
- // String str = Integer.valueOf(msg).toString();先转成包装类,再转成字符串
- // fis.write(str.getBytes());利用之前示例过的方法,转成字节数组
- // 也可以这样完成转换
- fis.write((msg + "").getBytes());
- fis.close();
- }

但是这样存储也有问题,比如将msg的值放大
我们知道int只占4个字节,但是转成字符串就是一个符号占一个字节,所以就是7个字节了
我们可以用DataOutputStream来解决这个问题
数据流的使用:
我们之前讲解的流,如果要保存数字,只要将数字转换为字符串,也就是说
以字符流的形式保存数据,这样有时候并不是我们需要的,我们有时候就是
需要以字节保存数据,因此就可以使用数据流来包装文件流完成。
int msg = 1000000;

- @Test
- void test08() throws Exception {
- int msg = 1000000;
-
- DataOutputStream dos = new DataOutputStream(new FileOutputStream("a.dat"));
- dos.writeInt(msg);
- dos.writeInt(100);
- dos.writeInt(10000);
- dos.writeInt(123456);
- dos.close();
- }


DataInputStream :
读int值:
- @Test
- void test09() throws Exception {
- DataInputStream dis = new DataInputStream(new FileInputStream("a.dat"));
- System.out.println(dis.readInt());
- System.out.println(dis.readInt());
- System.out.println(dis.readInt());
- System.out.println(dis.readInt());
- dis.close();
- }

如果再读会抛错

读long值:
- @Test
- void test10() throws Exception {
- long msg = 1000000000L;
-
- DataOutputStream dos = new DataOutputStream(new FileOutputStream("b.dat"));
- dos.writeLong(msg);
- dos.close();
- }
用int读long 数据会返回0
- @Test
- void test11() throws Exception {
- DataInputStream dis = new DataInputStream(new FileInputStream("b.dat"));
- System.out.println(dis.readInt());
- dis.close();
- }

用long读long 结果正常
- @Test
- void test11() throws Exception {
- DataInputStream dis = new DataInputStream(new FileInputStream("b.dat"));
- System.out.println(dis.readLong());
- dis.close();
- }

字符流:
计算机底层使用的二进制数据(字节数据)。所以计算机中都可以用字节去操作,字节流是计 算机最核心的流,但是速度慢。在开发过程中,若是字符串,就非常慢
字符流是为了加快流的操作,而设计的专门用来操作字符串的一种流。
注意:字符流 字符串存在编码问题,需要保证在读取和写入时,保持一致!!!(在Ascll码中 的不影响)
字节流无编码问题(二进制)
字符输入流:
Reader(抽象类),使用的时候应该使用它的子类
字符串输出流:
Writer(抽象类)
字符装饰流:
BufferedReader 是Reader的子类
BufferedWriter 是Writer的子类
PrintWriter 更推荐使用这个打印输出流
字符输入流例:
- @Test
- void test01() {
- Reader reader = null;
-
- try {
- reader = new FileReader(new File("D:\javaSE\eclipse\code\classDemo\src\com\openlab\demo02\TestFile.java"));
-
- char[] buf = new char[1024];
- int len = 0;
- while((len = reader.read(buf)) != -1) {
- System.out.println(String.valueOf(buf, 0, len));
- //valueOf支持字符数组,不支持字节数组
- }
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (reader != null) {
- try {
- reader.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
字符输出流例:
- @Test
- void test02() {
- Reader reader = null;
- Writer writer = null;
-
- try {
- reader = new FileReader(new File("D:\javaSE\eclipse\code\classDemo\src\com\openlab\demo02\TestFile.java"));
- writer = new FileWriter("c:\\a.java");
-
- char[] buf = new char[1024];
- int len = 0;
- while((len = reader.read(buf)) != -1) {
- writer.write(buf, 0, len);
- }
- System.out.println("拷贝文件成功");
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (reader != null) {
- try {
- reader.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (writer != null) {
- try {
- writer.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
BufferedReader :
- @Test
- void test03() {
- BufferedReader br = null;
- try {
- br = new BufferedReader(new FileReader(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java")));
- String msg = null;
- // 字符输入流,可以使用装饰流,按行读取
- while ((msg = br.readLine()) != null) {
- System.out.println(msg);
- }
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (br != null) {
- try {
- br.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
BufferedWriter:
- @Test
- void test04() {
- BufferedReader br = null;
- BufferedWriter bw = null;
- try {
- br = new BufferedReader(new FileReader(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java")));
- bw = new BufferedWriter(new FileWriter("d:\\a.java"));
- String msg = null;
- // 字符输入流,可以使用装饰流,按行读取
- while ((msg = br.readLine()) != null) {
- // bw.write(msg);
- // 我们可以手动添加换行符
- bw.write(msg + "\n");
- }
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (br != null) {
- try {
- br.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (bw != null) {
- try {
- bw.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
PrintWriter:
- @Test
- void test05() {
- BufferedReader br = null;
- PrintWriter out = null;
- try {
- br = new BufferedReader(new FileReader(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java")));
- out = new PrintWriter("d:\\a.java");
- String msg = null;
- // 字符输入流,可以使用装饰流,按行读取
- while ((msg = br.readLine()) != null) {
- out.println(msg);
- }
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (br != null) {
- try {
- br.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (out != null) {
- out.close();
- }
- }
- }
总结:
|-- 字符缓存流完成字符的操作
通过行来读取和写入字符串,所以字符流建议带上装饰流(能按行读取)
|-- 字符串输入缓存流
BufferedReader
|-- 字符串输出缓存流
BufferedWriter
不太推荐大家使用BufferedWriter,因为需要手动添加换行符(按行读取,readLine找到\n 就解析了,能把数据写进去,但是最终结果是不会换行)
推荐使用打印输出流(节点流)
PrintWriter
|-- print()
|-- println()
将字节流转换为字符流操作
读取的是字节数据,一般是文本数据(字符串数据)为了加快效率,可以转成字符流
如控制台的标准输入流是字节流,直接转流类型不匹配。BufferedReader只能接受Reader
InputStreamReader Reader的子类
OuputStreamWriter Writer的子类
InputStreamReader 可以做一个简单人工智能
- @Test
- void test01() throws IOException {
- BufferedReader br = null;
-
- // InputStreamReader 转换流
- // 可以将字节输入流转换为字符输入流
- br = new BufferedReader(new InputStreamReader(System.in));
-
- String str = null;
- while ((str = br.readLine()) != null) {
- if (str.equalsIgnoreCase("exit"))
- break;
- System.out.println(str.replace("吗?", ""));
- }
- br.close();
- }

- @Test
- void test02() throws IOException {
- BufferedReader br = null;
- PrintWriter out = null;
-
- br = new BufferedReader(new InputStreamReader(System.in));
- out = new PrintWriter(new OutputStreamWriter(new FileOutputStream("d:\\a.html")));
-
- String str = null;
- while ((str = br.readLine()) != null) {
- if (str.equalsIgnoreCase("exit"))
- break;
- out.println(str);
- }
- br.close();
- out.close();
- }
对象流:
对象本质:它是一个抽象概念,是JVM中的一种虚拟出来的抽象概念
在jvm中,通过new 关键字,配合class类,就可以构造一个java对象
对象流就是java提供一种,可以将java对象这种虚拟的概念转换为
一种物理可以存储或者传输的真实数据。
将虚拟的JVM中的对象转换为字节数据
String实现了Serializable接口,可以直接用
- @Test
- void test01() {
- String msg = "你好";
- ObjectOutputStream oos = null;
- try {
- oos = new ObjectOutputStream(new FileOutputStream("c:\\info.dat"));
-
- oos.writeObject(msg);
- System.out.println("对象保存成功");
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (oos != null) {
- try {
- oos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- @Test
- void test02() {
- ObjectInputStream ois = null;
- try {
- ois = new ObjectInputStream(new FileInputStream("c:\\info.dat"));
-
- // Object msg = ois.readObject();不强转
- String msg = (String) ois.readObject();//刚刚存的字符串,知道数据类型,所以直接强转了
- System.out.println(msg);
-
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } finally {
- if (ois != null) {
- try {
- ois.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
对象序列化:
- @Test
- void test03() {
- Person p1 = new Person(1, "张三", "法外狂徒", "男", 30);
-
- ObjectOutputStream oos = null;
- try {
- oos = new ObjectOutputStream(new FileOutputStream("c:\\info.dat"));
- oos.writeObject(p1);
- System.out.println("对象保存成功");
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (oos != null) {
- try {
- oos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }

对象反序列化:
- @Test
- void test04() {
-
- ObjectInputStream ois = null;
- try {
- ois = new ObjectInputStream(new FileInputStream("c:\\info.dat"));
-
- Person p1 = (Person) ois.readObject();
- System.out.println(p1);
-
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } finally {
- if (ois != null) {
- try {
- ois.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
Serialize:将虚拟对象转换为一种可以直接传输或者保存到数据(字节、字符)过程
将序列化后的字节或者字符数据重新转换为对象,对象反序列化
将数据永久保存(可以通过IO、数据库)
java官方提供的序列化,是将java对象转换为字节数据。
注意:java的对象要实现序列化和反序列化,必须实现Serializable(这个标记接口)。
transient:被这个关键字修饰的属性,无法被持久化。
运算过程中临时使用的,不需要记录值
例:
将Person的gender用transient关键字修饰,之后重新将数据序列号、持久化(Test03)。
之后反持久化,反序列化(Test04)
private transient String gender;

在持久化的时候没有将gender持久化,所以读不到,赋了默认值(String是对象,默认值为null)