IO


大多数应用程序都要实现与设备之间的数据传输,例如键盘可以输入数据,显示器可以显示程序的运行结果等。在Java中,将这种通过不同输入输出设备之间的数据传输抽象表述为“流”,程序允许通过流的方式与输入输出设备进行数据传输。Java中的“流”都位于java.io包中,称为IO(输入输出)流。
IO流有很多中,按照操作数据的不同,可以分为字节流和字符流,按照数据传输方向的不同又可以分为输入流和输出流,程序从输入流中读取数据,向输出流中写入数据。在IO包中,字节流的输入输出流分别用java.io.InputStream和java.io.OutputStream表示,字符流的输入输出流分别用java.io.Reader和java.io.Writer表示。

1、字节流

1.1、字节流的概念

在计算机中,无论是文本、图片、音频还是视频,所有的文件都是以二进制(字节)形式存在,IO流中针对字节的输入输出提供了一系列的流,统称为字节流。字节流是程序中最常用的流,根据数据的传输方向可将其分为字节输入流和字节输出流。在JDK中,提供了两个抽象类IuputStream和OutputStream,它们是字节流的顶级父类。
在JDK中,InputStream和OutputStream提供了一系列与读写相关的方法,如表1-1所示。

表1-1 InputStream的常用方法

方法声明功能描述
int read()从输入流中读取一个8位的字节,把它转化位0~255之间的整数,并返回这一整数
int read(byte[ ] b)从输入流中读取若干字节,把它保存到参数b指定的字节数组中,返回的整数表示读取字节书
int read(byte[ ] b,int off,int len)从输入流中读取若干字节,把它保存到参数b指定的字节数组中,off表示指定字节数组开始保存数据的起始下标,len表示读取字节的数目
void close()关闭此输入流并释放与该流关联的所有系统资源

表1-2 OutputStream的常用方法

方法名称方法描述
void write(int b)向输出流写入一个字节
void write(byte[ ] b)把参数指定的字节数组的所有字节写道输出流
void write(byte[ ] b,int off,int len)将指定byte数组中从偏移量off开始的len个字节写入输入流
void flush()刷新此输出流并强制写出所有缓冲的输出字节
void close()关闭此输出流并释放与此流相关的所有系统资源

注意:InputStream类和OutputStream类虽然提供了一系列与读写相关的方法,但是这两个类是抽象类,不能被实例化。

1.2、字节流读写文件

由于计算机中的数据基本上都保存在硬盘的文件中,因此操作文件中的数据是一种很常见的操作。在操作文件上,最常见的就是从文件中读取数据并将数据写入文件,即文件的读写。针对文件的读写,JDK专门提供了两个类,FileInputStream和FileOutputStream。
FileInputStream是InputStream类的子类,它是操作文件的字节输入流,专门用于读取文件中的数据。由于读取数据是重复的操作,因此需要通过循环语句来实现数据的持续读取,接下来通过一个案例来实现字节流对文件数据的读取。
首先在目标目录下创建一个文本文件test.txt,在文件中输入内容“test”,具体代码如例一。

例一

package io.example1;

import java.io.File;
import java.io.FileInputStream;

public class Example1 {
	public static void main(String[] args) throws Exception{
		String logsFile = "E:\\read.txt";
		//实例化File对象,读取e盘read.txt文件
		File file = new File(logsFile);
		//实例化FileInputStream对象,将file作为参数传入
		FileInputStream in = new FileInputStream(file);
		int b = 0;
		while(true){
			//变量b记住读取的一个字节
			b = in.read(); 
			//如果读取的字节为-1,跳出while循环
			if(b == -1){
				break;
			}
			System.out.println(b);
		}
		in.close();
	}
}

例一运行结如下
例1-1运行结果
例一中,创建的字节流FileInputStream 通过read()方法将当前目录文件“test.txt”中的数据读取并打印。通常情况下,读取文件应该输出字符,而运行结果输出数字是因为硬盘上的文件是以字节的形式存在的,在test.txt中,t、e、s、t四个字符各占一个字节,因此,最终结果显示的就是该文件中四个字节所对应的十进制数。
注意:在读取文件数据时,必须保证文件是存在并且可读的,否则会抛出异常FileNotFoundException。
与FileInputStream对应的是FileOutputStream,它是OutputStream的子类,是操作文件的字节输出流,专门用于将数据写入文件,接下来通过例二来演示如何将数据写入文件。
例二

package io.example2;

import java.io.FileOutputStream;

public class Example2 {
	public static void main(String[] args) throws Exception{
		//创建一个文件字节输出流
		FileOutputStream out = new FileOutputStream("E:\\read.txt");
		String str = "测试案例";
		byte [] b = str.getBytes();
		for(int i = 0; i < b.length; i++){
			out.write(b[i]);
		}
		out.close();
	}
}

例二运行结果
例二运行结果
通过FileOutputStream写入数据时,如果没有read.txt文件,会自动在指定目录创建该文件,并将数据写入。需要注意的是:如果向一个已经存在的文件中写入数据,那么该文件的的数据首先会被清空,再写入新的数据,若希望在已经存在的文件中追加新内容,可以使用FileOutputStream的构造函数FileOutputStream(String fileName,boolean append)来创建文件的输出流对象,并把append的参数值设置为true,接下来我们通过例三来演示该效果。

例三

package io.example3;

import java.io.FileOutputStream;

public class Example3 {
	public static void main(String[] args) throws Exception{
		//创建一个文件字节输出流
		FileOutputStream out = new FileOutputStream("E:\\read.txt",true);
		String str = ":测试成功";
		byte [] b = str.getBytes();
		for(int i = 0; i < b.length; i++){
			out.write(b[i]);
		}
		out.close();
	}
}

例三运行结果
例三运行结果
从例三的运行结果来看,程序把新写入的数据追加到文件的末尾,达到的预期的效果。
从上面的案例中可以看出,IO流在进行数据读写的操作时会出现异常,为了代码的简洁,在程序中使用throws关键字抛出异常。然而一旦遇到IO异常,IO流的close()方法无法得到执行,流所占用的资源将得不到释放,因此为了保证IO流的close()方法必须执行,通常将关闭流的操作写在finally代码块中,具体代码如下所示。

finally{
	try {
			if(in != null){	//如果in不为空,关闭输入流
				in.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			if(out != null){  //如果out不为空,关闭输出流
				out.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
}

1.3、文件的拷贝

在应用程序中,IO流通常都是成对出现的,例如文件的拷贝就需要通过输入流读取文件中的数据,通过输出流将数据写入文件。接下来通过案例四来演示文件的拷贝,使用E盘下的test.txt文件为拷贝模板。

例四

package io.example4;

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Example4 {
	public static void main(String[] args) throws Exception{
		//创建一个字节输入流,读取木标文件夹中的文件
		FileInputStream in = new FileInputStream("E:\\read.txt");
		//创建一个字节输出流,用于将读取的数据写入目标文件夹下的文件
		FileOutputStream out = new FileOutputStream("E:\\read1.txt");
		//定义变量len,记住每次读取的一个字节
		int len;
		//获取拷贝前的系统时间
		long begintime = System.currentTimeMillis();
		while((len = in.read()) != -1){
			out.write(len);
		}
		long endtime = System.currentTimeMillis();
		System.out.println("拷贝文件所耗的时间是:"+(endtime-begintime)+"毫秒");
		in.close();
		out.close();
	}
}

例四运行结果

例四运行结果
例四运行结果
例四实现了read.txt文件的拷贝,在拷贝过程中,通过while循环将字节进行拷贝,每循环一次,就通过FileInputStream的read()方法读取一个字节,并通过FileOutputStream的write()方法将该字节写入指定文件,循环往复,直到len的值为-1,表示读取到文件的末尾,结束循环,完成文件的拷贝。程序结束后,会打印出拷贝文件消耗的时间。

1.4、字节流的缓冲区

例四虽然实现了文件的拷贝,但是一个字节一个字节的读写,需要频繁的操作文件,效率非常低。为了提高效率,可以定义一个字节数组作为缓冲区,这样在拷贝文件时,可以一次性地读取多个字节地数据,并保存在字节数组中,然后将字节数组中的数据一次性地写入文件,接下来通过例五来演示如何通过自己缓冲区拷贝文件。

例五

package io.example5;

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Example5 {
	public static void main(String[] args) throws Exception{
		//创建一个字节输入流,读取木标文件夹中的文件
		FileInputStream in = new FileInputStream("E:\\read.txt");
		//创建一个字节输出流,用于将读取的数据写入目标文件夹下的文件
		FileOutputStream out = new FileOutputStream("E:\\read3.txt");
		//以下时用字节缓冲区读写文件
		byte [] buff = new byte [1024]  ;
		//定义变量len,记住每次读取的一个字节
		int len;
		//获取拷贝前的系统时间
		long begintime = System.currentTimeMillis();
		//从第一个字节开始,向文件中写入len个字节
		while((len = in.read(buff)) != -1){
			out.write(buff,0,len);
		}
		long endtime = System.currentTimeMillis();
		System.out.println("拷贝文件所耗的时间是:"+(endtime-begintime)+"毫秒");
		in.close();
		out.close();	
	}
}

例五运行结果
例五运行结果
例五实现了文件的拷贝,在拷贝过程中,使用while循环语句实现文件的拷贝,每循环一次,就从文件中读取若干字节填充字节数组,并通过变量len记住读入数组的字节数,然后从数组的第一个字节开始,将len个字节依次写入文件,循环往复,当len值为-1时,说明已经读到了文件的末尾,循环会结束,整个拷贝过程也就结束了。通过例四与例五的对比,可以看出例五拷贝文件消耗的时间明显减少了,从而说明缓冲区读写文件有效地提高了效率。这是因为程序中地缓冲区就是一块内存,使用缓冲区减少了对文件的操作次数,所以提高了读写数据的效率。

1.5、装饰设计模式

在程序设计中,可以通过装饰一个类,来增强它的功能。装饰设计模式就是通过包装一个类,动态地为它增加功能地一种设计模式。接下来通过例六来演示这种模式。

例六

package io.example6;

public class Car {
	private String carName;
	public Car(String carName){
		this.carName = carName;
	}
	public void show(){
		System.out.println("我是"+carName+"。具有基本功能");
	}
}

public class RedCar {
	public Car myCar;
	public RedCar(Car myCar){
		this.myCar = myCar;
	}
	public void show(){
		myCar.show();
		System.out.println("具有倒车雷达功能");
	}
}
public class Example6 {
	public static void main(String[] args) {
		Car benz = new Car("Benz");
		System.out.println("----包装前-----");
		benz.show();
		RedCar dbenz = new RedCar(benz);
		System.out.println("---包装后----");
		dbenz.show();
	}
}

例六运行结果
例六运行结果
例六实现了RedCar类对Car类的包装,从运行结果看,被RedCar包装后的对象benz不仅具有车的基本功能,还具备了倒车雷达的功能。

1.6、字节缓冲流

在IO包中,提供了两个带缓冲的字节流,分别是BufferedInputStream和BufferedOutputStream,这两个流都使用了装饰设计模式。它们的构造方法中分别接受InputStream和OutputStream类型的参数作为被包装对象,在读写数据时提供缓冲功能。接下来通过例七来学习BufferedInputStream和BufferedOutputStream这两个流的用法。

例七

package io.example7;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Example7 {
	public static void main(String[] args) throws Exception{
		//创建一个带缓冲区的输入流
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\read.txt"));
		//创建一个带缓冲区的输出流
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\read4.txt"));
		int len;
		while((len = bis.read()) != -1){
			bos.write(len);
		}
		bis.close();
		bos.close();
	}
}

例七中,创建了BufferedInputStream和BufferedOutputStream两个缓冲流对象,这两个流内部定义了有一个大小为8192的字节数组,当调用read()或者write()方法读写数据时,首先想到将读写的数据存入定义好的字节数组,然后将字节数组一次性读写到文件中,这种方式与例五将的字节流的缓冲区类似,都对数据进行了缓冲,从而有效地提高了读写效率。

2、字符流

2.1、字符流地定义及基本用法

上面讲的InputStream和OutputStream类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用这两个类就不太方便,为此JDK提供了字符流。同字节流一样,字符流也有两个抽象的顶级父类,分别时Reader和Writer。其中Reader是字符输入流,用于从某个源设备读取字符,Writer是字符输出流,用于向某个目标设备写入字符。Reader和Writer作为字符流的顶级父类,也有许多子类。其中FileReader和FileWriter用于读写文件,BufferedReader和BufferedWriter是具有缓冲功能的流,它们可以提高读写效率。

2.2、字符流操作文件

在程序开发中,如果想从文件中直接读取字符便可以使用字符输入流FileReader,通过此流可以从关联的文件中读取一个或者一组字符。接下来通过例八来学习使用FileReader读取文件中的字符。

例八

package io.example8;

import java.io.FileReader;

public class Example8 {
	public static void main(String[] args) throws Exception{
		//创建一个FileReader对象用来读取文件中的字符
		FileReader reader = new FileReader("E:\\read.txt");
		int ch;
		//循环判断是否读到文件末尾
		while((ch = reader.read()) != -1){
			System.out.println((char) ch);
		}
		//关闭文件读取流,释放资源
		reader.close();
	}
}

例八运行结果
例八运行结果
例八实现了读取文件字符的功能,首先创建一个FileReader对象与文件关联,然后通过while循环每次从文件中读取一个字符并打印,这样就实现了FileReader读文件字符的操作。需要注意的是:字符输入流的read()方法返回的是int类型的值,如果想获取到字符就必须进行强制类型转换,(char)ch就是将变量ch转为char类型再打印。
例八讲解了使用FileReader读取文件中的字符,如果要向文件中写入字符就需要使用FileWriter类。该类是Writer的一个子类,接下来通过例九来学习使用FileWriter将字符写入文件。

例九

package io.example9;
import java.io.FileWriter;
public class Example9 {
	public static void main(String[] args) throws Exception{
		//创建一个FileWriter对象用于向文件中写入数据
		FileWriter fw = new FileWriter("E:\\read001.txt");
		String str = "向文件中写入测试数据";
		//将字符数据写入到文件中
		fw.write(str);
		fw.close();
	}
}

例九运行结果
例九运行结果
程序运行结束后,会在目标文件夹下生成一个“read001.txt”文件,内容如上图所示。FileWtiter和FileOutputStream一样,如果指定的文件不存在,就会先创建文件,再写入数据;如果文件存在,就会先清空文件里的内容,再进行写入,如果想在文件末尾追加数据,同样需要调用重载的构造方法FileWtiter(String fileName,boolean append),并将append的参数值设为true。
字符流和字节流一样提供了带缓冲区的包装流,分别是BufferReader和BufferWriter,其中BufferReader用于对字符输入流进行包装,BufferWriter用于对字符输出流进行包装,需要注意的是,在BufferReader中有一个重要的方法readLine(),该方法用于一次读取一行文本,接下来通过例十来演示使用这两个包装流实现文件的拷贝。

例十

package io.example10;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;

public class Example10 {
	public static void main(String[] args) throws Exception{
		FileReader fd = new FileReader("E:\\read001.txt");
		//创建一个BufferedReader缓冲对象
		BufferedReader br = new BufferedReader(fd);
		FileWriter fw = new FileWriter("E:\\read002.txt");
		//创建一个BufferedWriter缓冲对象
		BufferedWriter bw = new BufferedWriter(fw);
		String str;
		while((str = br.readLine()) != null){//每次读取一行文本,判断是否到达文件末尾
			bw .write(str);
			bw.newLine();//写入一个换行符,该方法会根据不同的操作系统生成相应的换行符
		}
		br.close();
		bw.close();
	}
}

例十运行结果
例十运行结果
在例十中,首先对输入输出流进行包装,并通过一个while循环实现了文本文件的拷贝。在拷贝的过程中,每次循环都是用readLine()方法读取文件的一行,然后通过writer()方法写入目标文件,其中readLine()方法会逐个读取字符,当读到回车‘\r’或者换行‘\n’时会将读到的字符作为一行的内容返回。
需要注意的是:由于包装流内部使用了缓冲区,在循环中调用了BufferedWriter的write()方法写字符时,这些字符首先会被写入缓冲区,当缓冲区写满或者调用close()方法时,缓冲区中的字符才会被写入目标文件。因此在循环结束后一定要调用close()方法,否则极有可能会导致部分存在缓冲区中的数据没有写入目标文件

2.3、LineNumberReader

java程序在编译或者运行期间会经常出现一些错误,在报告中通常会报告出错误的行数,为了方便查找错误,需要在代码中加入行号。JDK提供了一个可以跟踪行号的输入流——LineNumberReader,它是BufferedReader的直接子类,接下来通过例十一来掩饰拷贝文件时是如何为文件加上行号的。

例十一

package io.example11;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.LineNumberReader;

public class Example11 {
	public static void main(String[] args) throws Exception{
		FileReader fr = new FileReader("E:\\read001.txt");//创建字符输入流
		FileWriter fw = new FileWriter("E:\\read003.txt");//创建字符输出流
		LineNumberReader lr = new LineNumberReader(fr);	  //包装
		lr.setLineNumber(0);							  //设置读取文件的起始行数
		String line = null;
		while((line = lr.readLine()) != null){
			fw.write(lr.getLineNumber()+":"+line);		  //将行号写入文件中
			fw.write("\r\n");						      //写入换行
		}
		lr.close();
		fw.close();
	}
}

例十一运行结果
例十一运行结果
程序运行结束后,会看到拷贝前后不带行号和带行号的效果如图例十一所示。在拷贝过程中,使用LineNumberReader类跟踪行号,首先调用setLineNumber()方法设置行号的初始值,本例中将行号初始值设为0,从运行结果来看,调用getLineNumber()方法读取行号时,行号时从1开始的,这是因为LineNumberReader类在读取到换行符’\n’、回车符‘\r’或者回车后紧跟的换行符时,会将行号自动加1。

转换流

前面提到的IO流可以分为字节流和字符流,有时字节流和字符流之间也需要进行转换,在JDK中提供了两个类将可以将字节流转换为字符流InputStreamReader和OutoutStreamWriter。
转换流也是一种包装流,其中OutputStreamWriter是Writer的子类,它可以将一个字节输出流包装成字符输出流,方便直接写入字符,而InputStreamReader是Reader的子类,它可以将一个字节输入流包装成字符输入流,方便直接读取字符。接下来通过例十二来学习将字节流转换为字符流。

例十二

package io.example12;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class Example12 {
	public static void main(String[] args) throws Exception{
		FileInputStream in = new FileInputStream("E:\\read001.txt");//创建字节输入流
		InputStreamReader isr = new InputStreamReader(in);    //将字节流输入转换成字符输入流
		BufferedReader br = new BufferedReader(isr);               //对字符流进行包装
		FileOutputStream out = new FileOutputStream("E:\\read004.txt");//将字节输出流转换成字符输出流
		OutputStreamWriter osw = new OutputStreamWriter(out);
		BufferedWriter bw = new BufferedWriter(osw);    //对字符输出流对象进行包装
		String line;
		while((line = br.readLine()) != null){
			bw.write(line);
		}
		br.close();
		bw.close();
	}
}

例十二运行结果
例十二运行结果
例十二实现了字节流和字符流之间的转换,将字节流转换为字符流,从而实现直接对字符的读写。需要注意的是,在使用转换流时,只能针对操作文本文件的字节流进行转换,如果字节流操作的是一张图片,此时转换成字符流就会造成数据丢失。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值