Java中的I/O流复习

1 流概述

流是一组有序的数据序列,根据操作的类型,可分为输入流和输出流两种。I/O(Input/Output)流提供了一条通道程序,可以使用这条通道把源中的字节序列送到目的地。Java中的I/O类都被放在java.io包中,所有输入流类都是抽象类InputStream(字节输入流)或抽象类Reader(字符输入流)的子类,输入流类都提供了read()方法和close()方法;而所有输出流类都是抽象类OutputStream(字节输出流)或抽象类Writer(字符输出流)的子类,输出流类都提供了write()方法、flush()方法和close()方法。

2 File类

File类是java.io包中唯一代表磁盘文件本身的对象。File类定义了一些与平台无关的方法来操作文件,可以通过调用File类中的方法,实现创建、删除、重命名文件、获取文件所在的目录、文件的长度、文件读写权限等。

文件的创建与删除示例:
package captain;

import java.io.File;

//文件的创建和删除演示
public class FileDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File file = new File("word.txt");//创建文件对象,并不是直接在磁盘上直接创建了一个文件
		if(file.exists()){//判断上面指定路径下是否已存在同名的文件
			file.delete();//若存在,则删除该文件
			System.out.println("文件已删除");
		}
		else{
			try{//捕捉异常,文件可能创建不成功,引发IOException
				file.createNewFile();//在磁盘上创建文件
				System.out.println("文件已创建");
			}
			catch(Exception e){
				e.printStackTrace();
			}
		}

	}

}
获取文件信息示例:
package captain;

import java.io.File;

//获取文件信息演示
public class FileDemo2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File file = new File("word.txt");//创建文件对象,并不是直接在磁盘上直接创建了一个文件
		if(file.exists()){//判断上面指定路径下是否已存在同名的文件
			String name = file.getName();//获取文件名
			long length = file.length();//获取文件的字节长度
			boolean hidden = file.isHidden();//判断文件是否为隐藏文件
			System.out.println("文件名称:" + name);
			System.out.println("文件长度:" + length);
			System.out.println("该文件是否为隐藏文件:" + hidden);
		}
		else{
			System.out.println("该文件不存在");
		}
	}	

}

3 文件输入输出流

程序运行期间,大部分数据都是在内存中进行操作,当程序结束或关闭时,这些数据将消失。如果需要将数据永久保存,可使用文件输入输出流与指定的文件建立连接,将需要的数据永久保存到文件中。

(1)FileInputStream类和FileOutputStream类

FileInputStream类与FileOutputStream类都是用来操作磁盘文件。如果用户的文件读取需求简单,只需基本的文件写入能力,则可以使用这两个类。这两个类分别是InputStream类和OutputStream类的子类。这两个类的使用示例如下。
package captain;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

//FileInputStream类和FileOutputStream类演示。
public class FileIODemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File file = new File("word.txt");// 创建文件对象,并不是直接在磁盘上直接创建了一个文件。

		// 使用FileOutputStream类创建文件输出流。
		FileOutputStream fos = null;// fos流对象需要在try块外面声明里面创建。
		try {// 捕捉异常,可能发生IOException。
			fos = new FileOutputStream(file);
			byte[] b1 = "I am a String !".getBytes();// 将字符串转化为字节数组。
			fos.write(b1);// 将字节数组中的数据作为输出流的内容。
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (fos != null) {
				try {// 捕捉异常,关闭文件流的时候也可能发生IOException。
					fos.close();// 关闭文件输出流,实际上执行这个方法时先调用了fos.flush()方法刷新缓冲(将缓冲中的数据写到文件中,再清空缓冲)。该语句需放在finally块中。
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

		// 使用FileInputStream类创建文件输入流。
		FileInputStream fis = null;// fis流对象需要在try块外面声明里面创建。
		try {// 捕捉异常,可能发生IOException
			fis = new FileInputStream(file);
			byte[] b2 = new byte[1024];// 创建一个字节数组
			int len = fis.read(b2);// 将输入流中的内容读到字节数组中,返回读到的字节长度。
			System.out.println("文件中的信息是:" + new String(b2, 0, len));// 以字符串的形式输出读到的信息。
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (fis != null) {
				try {// 捕捉异常,关闭文件流的时候也可能发生IOException。
					fis.close();// 关闭文件输入流,输入流没有flush()方法。该语句需放在finally块中。
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

}

(2)FileReader类和FileWriter类

使用FileOutputStream类向文件中写入数据与使用FileInputStream类从文件中将内容读出来,存在一点不足,即这两个类都只提供了对字节或字节数组的操作方法。由于汉字在文件中占用两个字节,如果使用字节流可能会出现乱码现象,此时采用字符流Reader或Writer类即可避免这种现象。FileReader类和FileWriter类分别继承于Reader类和Writer类。这两个类的使用示例如下。
package captain;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

//FileReader类和FileWriter类演示。
public class FileRWDemo {

	public static void main(String[] args) throws IOException {//抛出异常。也可以直接捕捉。
		// TODO Auto-generated method stub
		/*这里直接使用文件名作为文件流构造函数的参数,若指定路径下没有与该文件同名的文件,
		 *则会自动创建一个新的该文件,若存在与该文件同名的文件,则会覆盖原有的文件。
		 *也可以另外使用File类来创建文件对象,再通过其方法创建文件,这样麻烦一些。 
		 */
		FileWriter fw = new FileWriter("test.txt");
		fw.write("abcdef");//直接将字符串作为输出流的内容。也可以将字符数组作为输出流的内容。
		fw.close();//关闭文件输出流,实际上执行这个方法时先调用了fw.flush()方法刷新缓冲(将缓冲中的数据写到文件中,再清空缓冲)。
		
		FileReader fr = new FileReader("test.txt");
		int ch = 0;
		//无参的read()方法,一次读取一个字符,返回值为读到字符对应的Unicode码值。也可以将字符数组作为read方法的参数,一次读取多个字符,返回值为读到的字符的个数。
		while((ch = fr.read())!=-1){//当读到文件末尾(返回值为-1)时,停止循环读取。
			System.out.println((char)ch);//将Unicode码值强制转化为字符输出。
		}
		fr.close();//关闭文件输入流,输入流没有flush()方法。
		
	}

}

4 带缓存的输入输出流

缓存可以说是I/O的一种性能优化。缓存流为I/O流增加了内存缓存区,使得在流上执行skip()、mark()和reset()方法都成为可能。

(1)BufferedInputStream类和BufferedOutputStream类

BufferedInputStream类可以对任何的InputStream类进行带缓存区的包装以达到性能的优化。使用BufferedOutputStream输出信息和用OutputStream输出信息完全一样,只不过BufferedOutputStream有一个flush()方法用来将缓存区的数据强制地输出完。在使用这两个类之前,需要先创建其对应的FileInputStream或FileOutputStream的流对象,并将该流对象作为参数传递给带缓存的输入输出流类的构造函数。这两个类其实分别是FileInputStream类和FileOutputStream类的装饰类。

(2)BufferedReader类和BufferedWriter类

BufferedReader类与BufferedWriter类分别继承Reader类与Writer类。这两个类同样具有内部缓存机制,并可以以行为单位进行输入输出。在使用BufferedWriter类的write()方法时,数据并没有立刻被写入至输出流中,而是先进入缓存区中,如果想立刻将缓存区中的数据写入输出流中,一定要调用flush()方法。在使用这两个类之前,需要先创建其对应的FileReader或FileWriter的流对象,并将该流对象作为参数传递给带缓存的输入输出流类的构造函数。这两个类其实分别是FileReader类和FileWriter类的装饰类。这两个类的使用示例如下。
package captain;

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

//BufferedReader类和BufferedWriter类演示。
public class BufferedRWDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String[] str = { "好久不见", "最新好吗", "常联系" };// 定义字符串数组
		File file = new File("bufText.txt");// 创建文件对象

		FileWriter fw = null;// 声明FileWriter类流对象
		BufferedWriter bw = null;// 声明BufferedWriter类对象
		try {
			fw = new FileWriter(file);// 创建FileWriter类流对象
			bw = new BufferedWriter(fw);// 创建BufferedWriter类对象,该类其实是FileWriter类的装饰类
			for (int k = 0; k < str.length; k++) {// 循环遍历数组
				bw.write(str[k]);// 将字符串数组中的元素写入到磁盘文件中
				bw.newLine();// 将数组中的单个元素以单行的形式写入文件
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (bw != null) {
				try {
					/* 将BufferedWriter流关闭,实际上执行这个方法时先调用了bw.flush()方法
					 * 刷新缓冲(将缓冲中的数据写到文件中,再清空缓冲)。另外,装饰类BufferedWriter
					 * 在关闭流的时候,其内部其实调用了被装饰类FileWriter的close()方法。
					 * 如果习惯依次关闭各层流的话,必须按照后开的流先关闭这一顺序来依次关闭。
					 * */
					bw.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}

		FileReader fr = null;// 声明FileReader类流对象
		BufferedReader br = null;// 声明BufferedReader类对象
		try {
			fr = new FileReader(file);// 创建FileReader类流对象
			br = new BufferedReader(fr);// 创建BufferedReader类对象,该类其实是FileReader类的装饰类
			String s = null;
			int i = 0;
			while ((s = br.readLine()) != null) {// 当readLine()方法返回null时停止循环
				i++;
				System.out.println("第" + i + "行:" + s);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (br != null) {
				try {
					/* 将BufferedReader流关闭,输入流没有flush()方法。另外,装饰类BufferedReader
					 * 在关闭流的时候,其内部其实调用了被装饰类FileReader的close()方法。
					 * 如果习惯依次关闭各层流的话,必须按照后开的流先关闭这一顺序来依次关闭。
					 * */
					br.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

}

5 转换流

InputStreamReader类和OutputStreamWriter类都是字节流与字符流之间的转换流。InputStreamReader类继承于Reader类,FileReader类又继承于InputStreamReader类。OutputStreamWriter类继承于Writer类,FileWriter类又继承于OutputStreamWriter类。这两个转换流的作用:(1)如果源或目的对应的设备是字节流,但是操作的却是文本文件,可以使用转换流作为桥梁,提高对文本操作的便捷;(2)一旦操作的文本涉及到具体的指定编码表时,必须使用转换流才能实现。转换流的第一个作用的代码示例如下。
package captain;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

//InputStreamReader类和OutputStreamWriter类这两个转换流作为字节流和字符流之间的桥梁演示。
public class IROWDemo {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		InputStream is = System.in;//键盘输入的流为字节流
		InputStreamReader isr = new InputStreamReader(is);//将字节流转换(解码)为字符流
		BufferedReader br = new BufferedReader(isr);//将字符流装饰成带缓冲区的字符流
		
		OutputStream os = System.out;//控制台输出的流为字节流
		OutputStreamWriter osw = new OutputStreamWriter(os);//将字符流转换(编码)为字节流
		BufferedWriter bw = new BufferedWriter(osw);//将字符流装饰成带缓冲区的字符流
		
		String str = null;
		while((str=br.readLine())!=null){
			if("over".equals(str))//键盘输入结束标志
				break;
			//System.out.println(str);//可直接输出
			bw.write(str);//写一行
			bw.newLine();//换行
			bw.flush();//写一行后就刷新
		}
		br.close();//关闭流
		bw.close();//关闭流
	}

}

6 对象的序列化和反序列化

ObjectOutputStream将Java对象的基本数据类型和图形写入OutputStream,该过程为对象的序列化。可以使用ObjectInputStream读取(重构)对象,该过程为对象的反序列化。通过在流中使用文件可以实现对象的持久存储。只能将支持java.io.Serializable接口的对象写入流中。对象的序列化和反序列化代码示例如下。
package captain;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

//对象的序列化和反序列化演示,Serializable接口、ObjectOutputStream类、ObjectInputStream类及transient关键字。
public class ObjectIODemo {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		// TODO Auto-generated method stub
		//对象的序列化
		FileOutputStream fos = new FileOutputStream("obj.object");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(new Student("zhangsan", 23));//将对象写到文件中
		oos.close();
		
		//对象的反序列化
		FileInputStream fis = new FileInputStream("obj.object");
		ObjectInputStream ois = new ObjectInputStream(fis);
		Student s = (Student)ois.readObject();//从文件中读对象,会引发ClassNotFoundException
		System.out.println(s.getName()+":"+s.getAge());
		ois.close();
	}

}

//被序列化和被反序列化的类,必须实现java.io.Serializable接口
class Student implements Serializable{

	private static final long serialVersionUID = 1L;//使用默认序列化版本号,给类加ID
	private transient String name;//使用transient关键字,瞬态的成员变量不会被序列化
	private int age;
	Student(String name, int age){
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}

7 随机访问文件

RandomAccessFile类可以用来随机访问文件中指定位置的内容。该对象既能读也能写。该对象内部维护了一个byte数组,并通过指针可以操作数组中的元素,可以通过getFilePointer方法获取指针的位置,通过seek方法获取指针的位置。其实该对象内部将字节输入流和输出流进行了封装。通过其构造函数可知该对象的源或目的只能是文件。

8 管道输入输出流

管道输出流PipedOutputStream与管道输入流PipedInputStream可以直接连接。管道流通常结合多线程使用,否则容易发生死锁。管道输入输出流的代码示例如下。
package captain;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

//管道输入输出流演示,PipedInputStream类、PipedOutputStream类及多线程。
public class PipedIODemo {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		PipedInputStream pis = new PipedInputStream();//管道输入流
		PipedOutputStream pos = new PipedOutputStream();//管道输出流
		pis.connect(pos);//连接管道输入输出流,抛出IOException
		new Thread(new Input(pis)).start();//启动管道输入流线程
		new Thread(new Output(pos)).start();//启动管道输出流线程
	}

}

//输出,实现Runnable接口
class Output implements Runnable{

	private PipedOutputStream pos;//管道输出流
	Output(PipedOutputStream pos){
		this.pos = pos;
	}
	public void run() {
		try {//注意run()方法内的异常不能抛出只能捕捉。
			pos.write("hi,Pipe is coming !".getBytes());
			pos.close();//关闭管道流
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}

//输入,实现Runnable接口
class Input implements Runnable{

	private PipedInputStream pis;//管道输入流
	Input(PipedInputStream pis){
		this.pis = pis;
	}
	public void run() {
		try {//注意run()方法内的异常不能抛出只能捕捉。
			byte[] b = new byte[1024];
			int len = pis.read(b);
			String str = new String(b,0,len);
			System.out.println(str);
			pis.close();//关闭管道流
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}

9 数据输入输出流

数据输出流DataOutputStream允许应用程序以适当方式将基本Java数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。 数据输入流DataInputStream允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型。数据输入输出流的代码示例如下。

package captain;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

//数据输入输出流演示,DataOutputStream类和DataInputStream类。
public class DataIODemo {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		FileOutputStream fos = new FileOutputStream("data.txt");
		DataOutputStream dos = new DataOutputStream(fos);//数据输出流
		dos.writeUTF("你好");//以UTF修改版的编码方式写
		dos.close();//关闭数据输出流
		
		FileInputStream fis = new FileInputStream("data.txt");
		DataInputStream dis = new DataInputStream(fis);//数据输入流
		String str = dis.readUTF();//以UTF修改版的编码方式读
		System.out.println(str);
		dis.close();//关闭数据输入流
	}

}

10 字节数组、字符数组及字符串输入输出流

ByteArrayInputStream包含一个内部缓冲区(字节数组),该缓冲区包含从流中读取的字节,内部计数器跟踪read方法要提供的下一个字节。ByteArrayOutputStream实现了一个输出流,其中的数据被写入一个byte数组,缓冲区会随着数据的不断写入而自动增长,可使用 toByteArray() 和 toString() 获取数据。ByteArrayInputStream流和ByteArrayOutputStream流不需要被关闭,其中也不会引发IOException,因为这两个对象都不会操作类似于硬盘这样的系统底层资源,只涉及到内存。字符数组输入输出流CharArrayReader和CharArrayWriter、字符串输入输出流StringReader和StringWriter类似于字节数组输入输出流。字节数组输入输出流的代码示例如下。

package captain;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

//字节数组输入输出流演示,ByteArrayInputStream类和ByteArrayOutputStream类。
public class ByteArrayIODemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ByteArrayInputStream bais = new ByteArrayInputStream("字节数组流".getBytes());
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		int ch = 0;
		while((ch=bais.read())!=-1){//读字节
			baos.write(ch);//写字节
		}
		System.out.println(baos.toString());//通过字节输出流的toString()方法获取数据
	}

}

11 字符编码

除了Java I/O流中的InputStreamReader和OutputStreamWriter这两个转换流可以涉及到字符编码外,字符编码在很多地方也很常见。英文字符的编码方式通常为ASCII(1个字节的7位),欧洲国家文字字符的编码方式通常为ISO8859-1(1个字节的8位),中文简体字符的编码方式通常为GBK(2个字节)。为了统一各国文字字符的编码方式就有了Unicode(所有国家文字都以2个字节编码)编码方式,而常用的UTF-8(1-3个字节编码,其中的中文以3个字节编码)编码方式是一种针对Unicode编码方式的可变长度编码。中文简体字符编码解码的简单演示如下。

package captain;

import java.io.UnsupportedEncodingException;

//字符的编码解码简单演示。字符串转化为字节数组是一种编码,反之是一种解码。
public class EncodeDemo {

	public static void main(String[] args) throws UnsupportedEncodingException {
		// TODO Auto-generated method stub
		String str = "字符编码解码";
		//编码,Windows系统中文简体的默认编码表为gbk,也可以使用其他编码表如utf-8
		//byte[] b = str.getBytes();
		byte[] b = str.getBytes("utf-8");//抛出UnsupportedEncodingException
		
		//解码,Windows系统中文简体的默认解码方式为gbk,若编码时使用的是其他编码表,需要使用该编码表来解码
		//String s = new String(b);
		String s = new String(b,"utf-8");//抛出UnsupportedEncodingException
		System.out.println(s);
	}

}














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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值