JAVA的I/O流

I/O流的介绍

I/O:

  • I:Input:输入
  • O:Output:输出

在这里插入图片描述
使用的是输入流还是输出流,需要一当前程序为判别标准:
凡是向当前程序中读入数据的,都是输入流
凡是从当前程序向外写出数据的,都是输出流

根据输入输出方向分类:

  • 输入流:向当前程序读入数据
  • 输出流:从当前程序写出数据

Java中流对象的命名方式是十分有规律的:功能 + 方向 + 类型
在这里插入图片描述
除此之外,还有很多类型的流:

  • ImageInputStream/ImageOutputStream:图像字节流
  • AudioInputStream/AudioOutputStream:多媒体字节流

***注意:***流是沟通内外存的通道,我们可以将一个文件通过输入流读入内存程序,也可以将内存程序产生的数据通过输出流写入到外存文件当中
***注意:***字节流本身是一种“万能流”,通过字节流可以读写任何一种格式的文件

节点流

节点流是流的最低级的流,也是学习过程中最重要的一个重要部分。
流的使用基本步骤:

  1. 创建IO流对象
  2. 通过IO流对象开始对文件进行读写
  3. 关闭IO流对象,释放占用资源

如何对流进行优化:

  • 使用字节缓存区(byte[]),缓存区大小一般不超过32MB
  • 在每次读取文件的时候,记录读取的字节数量,在写出字节的时候,读多少写多少

字节流

字节流:操作的基本数据单元是字节(byte),凡是以Stream为结尾的流对象,都是字节流
字节流又称万能流,可以读写任何类型的数据。

IO流的使用案例:字节文件拷贝
代码如下:

public static void copyFile1(File from, File to) {
	//常见流对象
    FileInputStream fis = null;
    FileOutputStream fos = null;

    try {
    	//建立连接,连接IO流对象
        fis = new FileInputStream(from);
        fos = new FileOutputStream(to);
        //模拟一个缓存,这里用数组来存储读出来的数据
        byte[] buffer = new byte[1024 * 1024 * 8];
        //定义一个变量,用来存储读出来的数据有效位数
        int len = 0;
        //fis.read()这是读的方法,返回值是int类型的,是有效位数
        while((len = fis.read(buffer)) != -1) {
        	//这是写的方法,()里的是从哪个数组中取值,从数组的那个位置写,写多少位。
            fos.write(buffer, 0, len);
        }
		//写完后,不管系统会不会自动刷新,都要手写一个刷新
        fos.flush();
    }catch(Exception e) {
        e.printStackTrace();
    }finally {
        //关闭流管道,首先判断管道是否被创建,没有创建就无需关闭
        if(fos != null) {
            try {
            	//如果有被创建,就关闭并释放资源
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //关闭流管道,首先判断管道是否被创建,没有创建就无需关闭
        if(fis != null) {
            try {
            	//如果有被创建,就关闭并释放资源
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字符流

字符流:操作的基本数据单元是字符(char),凡是以Reader/Writer为结尾的流对象,都是字符流
字符流只能读写纯文本的内容,什么叫做纯文本,就是打开一个文档,能看懂的就是纯文本。
FileReader/FileWriter代码如下:

/**
* 文件拷贝方法
* 这个文件拷贝的方法是字符文件的拷贝
* 直接的文件字符流,操作的个体是文件中的字符
* 读写的单位是字符缓存区的大小
* 字符和字符之间,行的关系被削弱了
* 也就是说,FileReader和FileWriter不能按行读写
* @param from 源文件
* @param to 目标文件
*/
public static void copyFile2(File from, File to) {
   //[1]创建流对象
   FileReader fr = null;
   FileWriter fw = null;
   
   try {
  		 //建立连接,连接IO流对象
       fr = new FileReader(from);
       fw = new FileWriter(to);
     
       //[2]进行读写操作
       //模拟一个缓存,这里用数组来存储读出来的数据
       char[] buffer = new char[1024];
       //定义一个变量,用来存储读出来的数据有效位数
       int len = 0;
       //fis.read()这是读的方法,返回值是int类型的,是有效位数
       while((len = fr.read(buffer)) != -1) {
      		 //这是写的方法,()里的是从哪个数组中取值,从数组的那个位置写,写多少位。
           fw.write(buffer, 0, len);
       }
     	//写完后,不管系统会不会自动刷新,都要手写一个刷新
       fw.flush();
   }catch(Exception e) {
       e.printStackTrace();
   }finally {
       //关闭流管道,首先判断管道是否被创建,没有创建就无需关闭
       if(fw != null) {
           try {
           	//关闭流对象,释放资源
               fw.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
       //关闭流管道,首先判断管道是否被创建,没有创建就无需关闭
       if(fr != null) {
           try {
           	//关闭流对象,释放资源
               fr.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }
}

功能流

当我们学会了节点流后,指导过程是什么样子的,那么我们就可以把功能流里面的流程很快的写出来并了解。
BufferedReader和BufferedWriter是自带缓存空间的字符流对象,所以在进行文件读写的时候,我们不需要手动指定其缓存空间的大小。

缓冲流

为什么有了节点流还需要用到缓冲流呢,就好一个小区里的住户每家都有一个水管,打开就用能,那么小区里面会有一个大的水箱,会先把用户用的水放到这个水箱,那么用户用的时候就是使用这个水箱的水,这就能保证每家每户的水流都是差不多的。
如果小区里没有这个水箱,用户用的水都是从水管道里来的,那么多的用户使用一个小小的管道,那个水流能大么,应该是很小的。
所以小区里的水箱就好比我们的缓冲流,能够提高性能,提高读写效率,并且还有自己的功能。

字节缓冲流

BufferedInputStream/BuffereOutputStream代码如下:

public class BufferedDemo01 {
	public static void main(String[] args) throws IOException {
		//创建流对象,进行连接
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/haha.txt"));
		BufferedOutputStream bos=new BufferedOutputStream( new FileOutputStream("D:/dd.txt"));
		//模拟一个缓存,这里用数组来存储读出来的数据
		byte[] car = new byte[1024];
		//定义一个变量用来存储读的值的有效位
		int len=-1;
		//fis.read()这是读的方法,返回值是int类型的,是有效位数
		//如果不等于-1,说明有读到数据
		while(-1!=(len=is.read(car))){
			//这是写的方法,()里的是从哪个数组中取值,从数组的那个位置写,写多少位。
			bos.write(car,0,len);
		}
		//刷出流管道
		bos.flush();
		//关闭流管道
		bos.close();
		bis.close();
	}
}

字符缓冲流

BufferedReader/BufferedWriter代码如下:

/**
 * BufferedReader和BufferedWriter是自带缓存空间的字符流对象
 * 所以在进行文件读写的时候,我们不需要手动指定其缓存空间的大小
 * @param from
 * @param to
 */
public static void copyFile3(File from, File to) {
    //创建流对象
    BufferedReader br = null;
    BufferedWriter bw = null;
    
    try {
        /*
         * 目的:
         * 1.传递一个Reader对象给BufferedReader,创建BUfferedReader对象出来
         * 2.这个Reader对象,一边沟通File文件对象
         * 3.这个Reader对象,一边沟通Reader类型
         * 结论:
         * 这个Reader对象的类型应该是一个FileReader
         */
        br = new BufferedReader(new FileReader(from));
        bw = new BufferedWriter(new FileWriter(to));
        
        //执行读写
        String line = "";  //就是BufferedReader对象读一行返回的一个字符串对象
        while((line = br.readLine()) != null) {  //按行读
            
            /*
             * 注意:
             * BufferedReader中提供的readLine()方法是不会读取一行中的行结束符的
             * 行结束符包括:\r和\n
             * 也就是说通过br.readLine()方法得到的字符串中,没有换行符
             * 那么写出的时候,将会把所有的行都拼接到一起去
             * 
             * 解决方案:
             * 每次写出一行完毕之后
             * 都通过bw声明在字符文件中另起一行
             */
            
            bw.write(line);  //按行写出
            System.out.println(line);
            bw.newLine();  //通过这个方法可以直接在字符文件中声明另起一行
        }
        bw.flush();
    }catch(Exception e) {
        e.printStackTrace();
    }finally {
        //关闭流对象
        if(bw != null) {
            try 
                bw.close();
            } catch (IOException e) {
                e.printStackTrace(){
            }
        }
        if(br != null) {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

转换流

转换流:字节转为字符流
只有字节转为字符的流,没有字符转换为字节的流。我们看一下转换流是怎么用的。
InputStreamReader/OutputStreamWriter代码如下:

public class ChangeDemo02 {
	public static void main(String[] args) throws IOException {
		//选择流
		//写法一拆分写法:
		//创建字节流
		FileInputStream fis = new FileInputStream("D:/haha.txt");
		//将字节流包装成字节缓冲流,为的是读写和效率
		BufferedInputStream bis = new BufferedInputStream(fis);
		//在将字节缓冲流转换成字符流
		InputStreamReader isr = new InputStreamReader(bis);
		//再将字符流包装秤字符缓冲流
		BufferedReader br = new BufferedReader(isr);
		
		//方法二合并写法:
		//BufferedReader br=new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream("D:/haha.txt"))));
		BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream("D:/bbb.txt"))));
		String msg=null;
		//读入写出
		while((msg=bw.readLine())!=null){
			br.write(msg);
			br.newLine();
		}
		//刷出
		br.flush();
		//关闭路管道
		bw.close();
	}
}

Data流

基本数据类型流:字节流的功能流
当我们看API帮助文档的时候,看到这个流里面的方法基本上都是带有基本数据类型的
所以读写带有基本数据类型|字符串类型的数据
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
DataInputStream/DataOutputStream代码如下:

public class DataDemo03 {
	public static void main(String[] args) throws IOException {
		write("D:/eee.txt");
		read("D:/eee.txt");
	}
	//写入
	public static void read(String src) throws IOException{
		//输入流
		DataInputStream dis=new DataInputStream(new FileInputStream(src));
		//读入和写出的顺序要保持一致
		boolean b=in.readBoolean();
		int i=in.readInt();
		String s=in.readUTF(); 
		System.out.println(b+"-->"+i+"-->"+s);
		//关闭流管道
		dis.close();
	}
	
	//写出
	public static void write(String dest) throws IOException{
		//1.输出流
		DataOutputStream dos=new DataOutputStream(new FileOutputStream(dest));
		//2.准备数据
		boolean flag=false;
		int i=100;
		String s="哈哈";
		//3.写出
		out.writeBoolean(flag);
		out.writeInt(i);
		out.writeUTF(s);
		//4.刷出
		dos.flush();
		//5.关闭
		dos.close();
	}
}

我们有几点需要注意的是:

  1. 我们在使用Data流的时候一定是用代码往文件里面进行写入,这样文档才能存储带有数据类型的字符串,如果你是在文档里手动写入的文字,再用读的方法去读,那么代码会不识别这个文字是什么类型的,会报出异常
  2. 我们在读取值的时候,一定要按照我们写入的顺序去读取我们的数据。

对象流

使用对象流的时候我们需要了解一个知识,就是序列化和反序列化。

对象的序列化和反解析
Java对象在内存中的存储结构:字节码
对象序列化:将内存中一个对象的字节码通过输出流保存到本地磁盘的文件中
对象反解析:将本地磁盘中一个保存对象字节码的文件通过输入流再次读取到内存中,生成一个指定类型的对象

对象序列化:
前提:被序列化的对象所对应的类必须实现java.io.Serializable接口

java.io.Serializable接口:
这个接口中,本身没有任何未实现方法,我们将这种没有方法的接口称之为“标签接口”
一个类实现一个接口,除了为了得到其中的未实现方法之外,还可以通过实现接口,为这个类添加一个新的“身份”
标签接口的作用仅仅是为了给他的实现类添加一重身份
java.io.Serializable接口的实现类的统一身份就是:可以被序列化的类型。

代码如下:

/**
 * 将内存中一个对象的字节码保存到本地磁盘(外存)的一个字节码文件当中
 * 对象的序列化过程
 * @param p 被序列化的Person类型对象
 * @param to 保存对象字节码的字节码文件的位置
 * 
 * java.io.NotSerializableException: com.oracle.test1.Person
 * 不可序列化异常:Person类不是一个可以被序列化的类型
 * 
 * @param obj 之所以这个被序列化对象的参数类型是Serializable类型
 * 是为了说明:所有可以被序列化的对象,都可以用这个方法保存到外部磁盘的文件中
 */
public static void objectToFile(Serializable obj, File to) {
    //创建 对象输出流 对象
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(new FileOutputStream(to));
        //执行对象的序列化操作(字节码输出)
        oos.writeObject(obj);  //将一个对象的字节码直接写出到外存的文件当中去
        oos.flush();
    }catch(Exception e) {
        e.printStackTrace();
    }finally {
        //关闭流对象
        if(oos != null) {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

对象反解析:
serialVersionUID表示的含义:
在一个程序当中,可能有多个类型都是Serializable接口的实现类
那么一个对象在反解析得到之后,如何区分其具体类型呢?
系统会自动根据解析得到的对象的serialVersionUID属性的值来判断这个“可以被序列化”的对象的具体类型
前提是要保证所有能够被序列化的类型的serialVersionUID取值不能重复

代码如下:

 /**
  * 将一个保存对象的文件中的字节码读取出来
  * 加入到JVM内存当中,重新编织为一个Java对象
  * @param from 保存字节码的磁盘文件
  * @return 一个可以被序列化的Java对象
  */
 public static Serializable fileToObject(File from) {
     Serializable result = null;
     //创建 对象输入流 对象
     ObjectInputStream ois = null;
     try {
         ois = new ObjectInputStream(new FileInputStream(from));
         //执行字节码文件的读取和内存对象的创建
         result = (Serializable) ois.readObject();
     }catch(Exception e) {
         e.printStackTrace();
     }finally {
         //关闭流对象
         if(ois != null) {
             try {
                 ois.close();
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
     return result;
 }

对象序列化的一些细节:

  1. 如果一个对象中含有一个集合(集合对象本身是可以序列化的),但是集合中保存的对象不实现Serializable接口
    那么这个含有集合的对象还能够序列化吗?
    答案是不能。
    如果希望一个对象能够被序列化,那么这个对象中的集合中的元素,也必须同时是可以被序列化的
  2. 如何让一个对象中的某些属性不能被序列化?
    在这些不想被序列化的属性中添加transient关键字
    一个可以被序列化的对象中,凡是使用transient关键字修饰的属性,都不会被保存在序列化的文件中

总结:

  • 先序列化后反序列化
  • 不是所有的类都能序列化 实现java.io.Serializable接口
  • 不是所有的属性都需要序列化 transient
  • 静态的内容不能序列化
  • 如果父类有实现序列化,子类没有,子类中所有的内容都能序列化
  • 如果父类中没有实现序列化,子类有实现序列化,子类只能序列化自己的内容
版权声明:本文为weixin_42036256原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42036256/article/details/103357376

智能推荐

20145107 《Java程序设计》第五次实验报告

实验简述: 在本周,我们进行了Java的第五次试验,本次实验的主要内容是结对编程。本次实验的大体过程是: 1.先进行Java的客户端与服务端的代码编写。结对是两个人,一人负责客户端,一人负责服务端。 2.利用加解密代码包,编译运行代码,客户端加密,服务器解密。 3.客户端加密明文后将密文通过TCP发送。 4.在本次的代码编写上,要求代码可以实现两者之间的数据传输,在代码传输的基础上加上一定的加密过...

更改springboot启动拼成的字母

1.更改springboot启动拼成的字母 其实很好改,只需要在resources下新建一个txt文件就可以,命名为banner.txt,那这种字符该怎么拼出来呢,下面推荐一个网址,有这种工具 传送门 2.集成...

Node.js安装配置

好久都没更新博客了,今天心血来潮,决定是时候更新一篇了,首先我们来认识一下node.js。 什么是node.js? 简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。 Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的...

RocketMQ之双Master集群搭建笔记记录

一:RocketMQ双master集群部署 服务器环境(我采用的虚拟机,centos6 .5【特别注意:安装的虚拟机centos系统一定得是64位的,32位的会启动不起来。即便起来了也会有很多问题,深坑勿踩】)  ip       用户名    密码        角色     模式 192.168.197.101   root        nameServer1,brokerServer1  ...

蓝桥杯试题集-基础练习题-数列特征(Java)

//做题笔记,仅自己看得懂 题目: 正确姿势:...

猜你喜欢

多线程爬取4k超高清美图壁纸

多线程爬取4k美图壁纸 前言:看完此篇文章你可以更加深入的了解多线程的使用,并且最重要的你能够下载你自己想要的超高清4k壁纸 爬取结果: 1. 分析网站 要爬取的url :http://pic.netbian.com/ a) 判断网页是动态加载还是静态加载页面。右击查看网页源代码,按Ctrl + f在源代码中搜索网站的详情页地址,从而判断整个网页是静态加载的 b) 明确爬取的目标。我们要爬取的目标...

elementUI-添加自定义图标

elementui的小图标有限,跟UI给的不一样,这个时候咋办呢?百度走起。。。。参考了两篇博主分享的 自定义elementui中的图标 和 建立图标库,这里主要用到第一种 实际中: elementUI导航栏 具体代码: 汉字转换Unicode编码: 直接打开控制台: 汉字.chatCodeAt().toString(16); 然后回车; 至于三角形的图标,我直接把箭头的 unicode 值改成了...

[Linux]——文件缓冲区

文件缓冲区 提到文件缓冲区这个概念我们好像并不陌生,但是我们对于这个概念好像又是模糊的存在脑海中,之间我们在介绍c语言文件操作已经简单的提过这个概念,今天我们不妨深入理解什么是文件缓冲区。 为什么需要文件缓冲区 当我们在程序中写下一条printf语句时,我们希望将这条语句的内容打印到屏幕上。但是如果你将语句放在循环中,难道你执行一次循环那么操作系统就要打印一次这条数据么?答案当然不是 我们对于程序...

基于FPGA的IIC协议详解——EEPROM控制器(1)

IIC协议举例 常用IIC协议使用地方 常见IIC协议的注意点 24LC64芯片读写命令的时序图 eeprom控制器的系统框图 时序图设计 代码设计 EEPROM控制器测试模块的代码 结束语 常用IIC协议使用地方 熟悉一个协议一定要知道这个协议应该用到什么地方,IIC协议作为飞利浦公司定义的一个慢速传输协议,常用于: 1、芯片寄存器的配置; 2、eeprom的读写; 本次实验我们将使用eepro...

ssm 工程简易搭建(idea 工具下)

现在都流行springboot了,大部分都给你配合了了,可以快速开发。但是今天我们回顾一下ssm的搭建,其实也挺简单的,比刚入门那会觉得简单多了。 这个先讲几点: 1.开始搭建前,idea 自己要配置maven 和tomcat和jdk ,这里就不讲了。 2.为了简化配置,我们按照“约定优于配置”的原则(这种原则在sprigboot项目里面特别明显),启动项目时,项目有个文件...