二进制 IO(全套)

二进制I/O

文件分为文本文件和二进制文件

Java源程序:文本文件,用文本编译器读取

Java类文件:二进制文件,由Java虚拟机读取

文本文件由字符序列构成,二进制文件由位(bit)序列构成,文本文件中的字符使用某种字符编码模式(例如ASCII编码或者Unicode编码)来进行编码。二进制文件的优势在于它的处理效率比文本文件高

在Java中如何处理文本I/O

Java中许多用于各种目的的I/O类,通常分为输入类和输出类。输入类包含读数据的方法,而输出类包含写数据的方法,如PrintWriter是一个输出类的例子,而Scanner是一个输入类的例子

计算机并不区分二进制文件和文本文件。所有的文件都是以二进制文件存储的,所以从本质来讲,所有的文件都是二进制文件。文本I/O建立在二进制I/O的基础之上,它能提供一层抽象,用于字符的编码和解码(编码和解码是自动进行),在写入字符时,Java虚拟机会将Unicode码转化为文件特定的编码,而读取字符时,将文件特定的编码转化为Unicode码。

二进制I/O不需要转化,如果使用二进制I/O向文件写入一个数值,就是将内存中的值复制到文件中。

一般来说,对于文本编译器或者文本输出程序创建的文件,应该使用文本输入来读取,对于Java二进制输出程序创建的文件,应该使用二进制输入来读取

由于二进制I/O不需要编码和解码,所以它比文本I/O效率高,二进制文件与主机的编码方案无关,因此它是可移植的。任何机器上的Java程序都可以读取Java程序所创建的二进制文件。这就是为什么Java的类文件存储为二进制文件的原因。Java类文件可以在任何具有Java虚拟机的机器上运行

二进制I/O类

抽象类InputStream是读取二进制数据的根类,抽象类OutputStream是写入二进制数据的根类

注意:二进制I/O类中的所有方法都声明为抛出java.io.IOException.

FileInputStream和FileOutputStream

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6kZR8zbF-1597894499677)(D:%5C%E5%88%98%E9%9B%A8%E5%85%B5%5C%E5%9B%BE%E7%89%87%5CFileOutputStream.PNG)]

FileInputStream和FileOutputStream类用于从/向文件读取/写入字节。它们的所有方法都是从InputStream类和OutputStream类继承的。

如果试图为一个不存在的文件创建FileInputStream对象,将会发生java.io.FileNotFoundException异常

要构造一个FileOutputStream对象,要使用下面的方法。

如果该文件不存在,就创建一个新文件。如果该文件已经存在,前面两个构造方法会删除文件的当前内容,为了既保留文件现有内容又可以为文件追加新数据,将最后两个构造方法中的append参数设置为true。

import java.io.*;
public class IO01 {
public static void main(String[] args) throws FileNotFoundException, IOException {
	// TODO Auto-generated method stub
    try(
    		FileOutputStream output = new FileOutputStream("temp.dat");
    ){
            for(int i = 0;i<=10;i++) {
            	output.write(i);
            }
    }
    try(
    		FileInputStream input = new FileInputStream("temp.dat");
    ){
            int value;
            while((value = input.read()) != -1) {
            	System.out.print(value + " ");
            }
    }
}

程序使用try-with-resources来声明和创建输入输出流,可以在使用后自动关闭。

文件用write()方法依靠循环将11个数字写入文件。调用write(i)方法与调用write((byte)i)具有相同的功能。通过input.read()读取一个字节,然后把它赋值给value,并且检验他是否为-1,输入值为-1意味着文件的结束。

注意:当流不再使用时,记得使用close()方法将其关闭,或者使用try-with-resource语句自动关闭,不关闭流可能会使输出文件中的数据受损,或导致其他的程序设计错误。上述例子中创建的文件temp.dat是一个二进制文件,可以从Java程序中读取它,但是不能用文本编译器阅读。

FileInputStream类的实例可以作为参数来构造一个Scanner对象,FileOutputStream类的实例可以作为参数来构造一个PrintWriter对象。可以使用new PrintWriter(new FileOutputStream(“temp.txt”,true));创建一个PrinterWriter对象来向文件中追加文本,如果temp.txt不存在,就会创建这个文件,如果temp.txt文件已经存在,就将新数据追加到该文件中。

FilterInputStream和FileterOutputStream

过滤器数据流(filter stream)是为某种目的过滤字节的数据流。基本字节输入流提供的读取方法read只能用来读取字节。如果要读取整数值,双精度值或字符串,那就需要一个过滤器类来包装字节输入流。使用过滤器就可以读取整数值、双精度值和字符串,而不是字节或字符。FilterInputStream类和FileterOutputStream类是用来过滤数据的基类。需要处理基本数据类型时,就使用DataInputStream类和DataOutputStream类来过滤字节。

DataInputStream和DataOutputStream

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fa8Z3FlZ-1597894499680)(D:%5C%E5%88%98%E9%9B%A8%E5%85%B5%5C%E5%9B%BE%E7%89%87%5CDataOutputStream%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B%E5%9B%BE.PNG)]

DataInputStream从数据流读取字节,并且将它们转换为合适的基本类型值或字符串。DataOutputStream将基本类型的值或字符串转换为字节,并且将字节输出到流。

DataInputStream类继承自FileterInputStream类,并实现DataInput接口,DataOutputStream类继承自FileteroutputStream类,并实现DataOutput接口

DataInputStream实现了定义在DataInput接口中的方法来读取基本数据类型值和字符串,DataOutputStream实现了定义在DataOutput接口中的方法来读取基本数据类型值和字符串。基本数据类型的值不需要做任何转化就可以从内存复制到输出数据流。

二进制I/O中的字符与字符串

一个Unicode码由两个字节构成,writerChar(char c)方法将字符c的Unicode码写到输出流中,writerChars(char s)方法将字符串s中的所有字符的Unicode码写到输出流中。writeBytes(String s)方法将字符串s中的每个字符Unicode码的低位字节写到输出流,Unicode码的高位字节被丢弃。writeBytes方法适用于由ASCII码字符构成的字符串,因为ASCII码仅存储Unicode码的低位字节。如果一个字符串包含非ASCII码的字符,必须使用writeChars方法实现写入这个字符串。

writeUTF(String s)方法使用UTF编码模式写一个字符串。UTF对于压缩使用Unicode字符的字符串是高效的。readUTF()方法读取一个使用writeUFO方法写入的字符串。

创建DataInputStream类和DataoutputStream类

DataInputStream input = new DataInputStream(new FileInputStream(“in.dat”));

为文件in.dat创建一个输入流

DataOutputStream output = new DataoutputStream(new FileOutputStream(“out.dat”));

为文件out.dat创建一个输出流

public class IO02 {
public static void main(String[] args) throws IOException {
	// TODO Auto-generated method stub
	try(
			 DataOutputStream output = new DataOutputStream(new FileOutputStream("temp.dat"));
			){
		output.writeUTF("John");
		output.writeDouble(85.5);
		output.writeUTF("Jim");
		output.writeDouble(185.5);
		output.writeUTF("George");
		output.writeDouble(105.25);
	}
   try(
		   DataInputStream input = new DataInputStream(new FileInputStream("temp.dat"));
		   ){
	   System.out.println(input.readUTF()+" "+input.readDouble());
	   System.out.println(input.readUTF()+" "+input.readDouble());
	   System.out.println(input.readUTF()+" "+input.readDouble());
   }
}

DataIntputStream 将一个字节输入流过滤成数据,DataOutputStream 将数据转化为字节流。

注意:应该按与存储时相同的顺序和格式读取文件中的数据。例如,由于学生的姓名是用writeUTF方法以UTF格式写入的,因此读取时必须使用readUTF方法。

如果到达InputStream的末尾之后还继续从中读取数据,就会产生EOFException异常。这个异常可以用来检查是否已经到达文件末尾。

public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			try(
					 DataOutputStream output = new DataOutputStream(new FileOutputStream("test.dat"));
					){				
				output.writeDouble(85.5);			
				output.writeDouble(185.5);			
				output.writeDouble(105.25);
			}
	       try(
	    		   DataInputStream input = new DataInputStream(new FileInputStream("test.dat"));
	    		   ){
	    	  while(true) {
	    		  System.out.println(input.readDouble());
	    	  }
	       }
		}
	       catch(EOFException ex) {
	    	   System.out.println("All data were read");
	       }catch(IOException ex) {
	    	   ex.printStackTrace();
	       }   
	}
       

当读取文件超过了文件末尾,就会抛出一个EOFException异常。该异常被捕获,在末尾输出All data were read。

BufferedInputStream和BufferedOutputStream

BufferedInputStream类和BufferedOutputStream类可以通过减少磁盘读写次数来提高输入和输出的速度

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eMNbSsJ5-1597894499684)(C:%5CUsers%5Cdelll%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20200817162733403.png)]

BufferedInputStream类和BufferedOutputStream类在后台管理一个缓冲区,根据需求自动从磁盘中读取读取数据和写入数据。使用BufferedInputStream时,磁盘上的整块整块数据一次性的读入内存的缓冲区中。然后从缓冲区中将单个数据传递到程序中,当缓冲区已满时,缓冲区当中的所有数据一次性写入磁盘中,BuffererOutputStream同理。BufferedInputStream类和BufferedOutputStream类中的方法都是从InputStream类和OutputStream类继承而来,没有包含新的方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gvlXDkjy-1597894499688)(C:%5CUsers%5Cdelll%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20200817165310928.png)]

(注意:BufferedInputStream类和BufferedOutputStream类同理,如果没有指定缓冲区大小,默认的大小为512个字节)

与TestDataStream同用,可以提高TestDataStream的效率

DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(“test.dat”)));

DataInputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream(“test.dat”)));

提示:使用BufferedInputStream类和BufferedOutputStream类运行小文件性能提升不明显,对于超过100MB的大文件,会带来实质性的性能提升。

复制文件

开发一个复制文件的代码

把源文件的内容复制到目标文件,使用输入流从源文件读出字节,并且使用输出流将字节写入目标文件比较合适。源文件和目标文件都是要在命令行中指定的。为源文件创建一个FileInputStream对象,为目标创建一个FileOutputStream对象,使用read()方法从输入流读取一个字节,使用write(b)方法将一个字节写入输出流。使用BufferedInputStream类和BufferedOutputStream类来提高执行效率。

public static void main(String[] args) throws IOException{
       File sourceFile = new File("源文件路径");
        if(!sourceFile.exists()) {
        	System.out.println("Source file "+"源文件路径" + " does not exits");
        	System.exit(2);
        }
        File targeFile = new File("目标文件路径");
        if(!targeFile.exists()) {
        	System.out.println("Target file "+"目标文件路径"+" alreadn exists");
        	System.exit(3);
        }
        try (
        		BufferedInputStream input = new BufferedInputStream(new FileInputStream(sourceFile));
        		BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(targeFile));
        		)
        {
        	int r ,number = 0;
        	while((r = input.read()) != -1) {//read()方法返回结束时返回-1
        		output.write((byte)r);
        		number++;//计算循环次数,循环次数就是字节数
        	}
        	System.out.println(number+"bytes copied");
        }

对象I/O

ObjectInputStream类和ObjectOutputStream类可以用来用于读/写可序列化的对象。DataInputStream类和DataOutputStream类可以实现基本数据类型与字符串的输出与输入,而ObjectInputStream类和ObjectOutputStream类除了可以实现基本数据类型与字符串的输入和输出外,还可以实现对象的输入和输出,ObjectInputStream类和ObjectOutputStream类包含了DataInputStream类和DataOutputStream类的所有功能,所以ObjectInputStream类和ObjectOutputStream类完全可以代替DataInputStream类和DataOutputStream类。

ObjectInputStream类继承自InputStream类,并实现了ObjectInput和ObjectStreamConstants。ObjectInput是DataInput的子接口,ObjectStreamConstants包含了支持ObjectInputStream类和ObjectOutputStream类的常量。

ObjectOutputStream继承自OutputStream类,并实现了接口ObjectOutput与ObjectStreamconstant。ObjectOutput是DataOutput的子接口。

Serializable接口

并不是每个对象都可以写道输出流,可以写到输出流的对象称为可序列化的。因为可序列化的对象是java.io.Serializable接口的实例,所以,可序列化对象的类必须实现Serializable接口。

Serializable接口是一种标记接口。因为它没有方法,所以不需要在类中为实现Serializable接口增加额外的代码。实现这个接口可以启动Java的序列化机制,自动完成存储对象和数组的过程。

Java提供一个内在机制自动完成写对象的过程,这个过程称为对象序列化,它是在ObjectOutputStream中实现的,与此相反,读取对象的过程称为对象反序列化,它是在ObjectInputStream类中实现的。

许多JavaAPI中的类都实现了Serializable接口。所有针对基本数据类型的包装类,java.math.BigInteger、java.util.Data、java.util.ArrayList等都实现了Serializable接口,试图存储一个不支持Serializable接口的对象会引起一个NotSerializableException异常。

当存储一个可序列化对象时,会对该对象的类进行编码。编码包括类名、类的签名、对象实例变量的值以及该对象引用的任何其他对象的闭包,但是不存储对象静态变量的值。

注意:如果一个对象是Serializable的实例,但它包括了不能序列化的实例数据域,那么就不能序列化这个对象。为了使对象是可序列化的,需要给这些数据域加上关键字trensient,告诉Java虚拟机将对象写入对象流时忽略这些数据域。

起一个NotSerializableException异常。

当存储一个可序列化对象时,会对该对象的类进行编码。编码包括类名、类的签名、对象实例变量的值以及该对象引用的任何其他对象的闭包,但是不存储对象静态变量的值。

注意:如果一个对象是Serializable的实例,但它包括了不能序列化的实例数据域,那么就不能序列化这个对象。为了使对象是可序列化的,需要给这些数据域加上关键字trensient,告诉Java虚拟机将对象写入对象流时忽略这些数据域。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值