IO继承关系、字节流和字符流

在Java中,可从中读出一系列数据的对象称为“输入流(InputStream)”,而能向其中写入一系列数据的对象称为“输出流(OutputStream)”。Java的输出/输入都是通过继承抽象类InputStream和OutputStream(面向字节)、Reader和writer(面向字符)来实现的。

一、IO流对象层次关系

IO继承关系、字节流和字符流_第1张图片IO继承关系、字节流和字符流_第2张图片

IO继承关系、字节流和字符流_第3张图片IO继承关系、字节流和字符流_第4张图片

IO继承关系、字节流和字符流_第5张图片

二、IO 基本操作

在java.io包中流的操作主要有字节流、字符流两大类,两者都有输入和输出的操作。在字节流中输出数据主要使用OutputStream类完成,输入使用InputStream类。在字符流中输出主要是使用Writer类完成,输入主要使用Reader类完成。

在Java中IO操作也有相应的步骤,以文件的操作为例,主要的操作流程如下:

(1) 使用File类打开一个文件。

(2) 通过字节流或字符流的子类指定输出位置。

(3) 进行读/写操作。

(4) 关闭输入/输出。

下面分别介绍如何使用字节流或字符流保存或读取数据。

三、字节流

字节流主要存操作byte类型数据,以byte数组为准,主要操作类是OutputStream类和InputStream类。

1.字节输出流:OutputStream

OutputStream是整个IO包中字节输出流的最大父类,此类的定义如下:

public abstract class OutputStream extends Objectimplements Closeable, Flushable

从以上定义中可以发现,OutputStream是一个抽象类,如果要是使用此类,则首先必须通过子类实例化对象。如果现在操作的是一个文件,则可以使用FileOutputStream类,通过向上转型后,可以为OutputStream实例化,在OutputStream类中的主要操作方法:

// 关闭此输出流并释放与此流有关的所有系统资源
public void close() throws IOException 
// 刷新此输出流并强制写出所有缓冲的输出字节。
public void flush() throws IOException
// 将 b.length 个字节从指定的 byte 数组写入此输出流
public void write(byte[] b) throws IOException
// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流
public void write(byte[] b, int off, int len) throws IOException

 

范例:向文件中写入字符串。

public class OutputStreamDemo {
	/**
	 * 向文件中写入字符串
	 */
	public static void main(String[] args) throws IOException {
		// 1.使用File类,找到一个将字符串写入的文件
		File file = new File("E:" + File.separator + "ops.txt");
		// 2.通过子类FileOutputStream实例化OutputStream
		OutputStream outputStream = null;
		try {
			outputStream = new FileOutputStream(file);
			// 3.进行写操作
			String str = "Hello world!";
			byte[] b = str.getBytes();
			outputStream.write(b);
		} finally {
			try {
				// 4.关闭输出流
				outputStream.close();
			} catch (IOException e) {
				throw new RuntimeException("关闭字符输出流失败!");
			}
		}
	}
}

 

①提问:通过上面的例子,我们发现将字符串写入到文件中,但是我们再次运行时会发现,新写入的字符串将过去的覆盖了,我们该如何解决了?

回答:

此时我们应该使用FileOutPutStream的另外一个构造方法:

// 如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
public FileOutputStream(File file, boolean append) throws FileNotFoundException

②提问:如何增加换行?

回答:如果要换行,则直接在字符串要换行处加一个"\r\n"。在windows操作系统中“\r\n”表示换行,但是在Linux操作系统中“\n”表示换行,解决办法,如下:

private static final String FILE_SEPARATOR = System.getProperty("line.separator");

2.字节输入流

既然程序可以向文件中写入内容,则也可以通过InputStream从文件中将内容读取出来。InputStream类的定义如下:

public abstract class InputStreamextends Objectimplements Closeable

与OutputStream类一样,InputStream本身也是一个抽象类,需要依靠子类来实现,如果从文件中读取数据,子类是FileInputStream。InputStream的主要方法:

// 返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数
public int available() throws IOException
// 关闭此输入流并释放与该流关联的所有系统资源。 
public void close() throws IOException
// 将输入流中最多 len 个数据字节读入 byte 数组
public int read(byte[] b, int off, int len) throws IOException

范例:从文件中读取内容。

public class InputStreamDemo {
	public static void main(String[] args) throws IOException {
		// 1.使用File类,找到一个将字符串写入的文件
		File file = new File("E:" + File.separator + "ops.txt");
		InputStream inputStream = null;
		try {
			// 2.实例化字节输入流 InputStream对象
			inputStream = new FileInputStream(file);
			// 3.进行读操作
			byte[] bs = new byte[1024];
			int len = 0;
			while ((len = inputStream.read(bs)) != -1) {
				System.out.println(new String(bs, 0, len));
			}
		} finally {
			try {
				// 4.关闭资源
				inputStream.close();
			} catch (IOException e) {
				throw new RuntimeException("关闭输入字节流失败!");
			}
		}
	}
}

①问题:上面程序中,开辟的byte数据大小为1024,而实际的内容只有12个字节,显然开辟的空间太大,该怎么解决了?

回答:

byte[] bs = new byte[(int) file.length()];

此程序明确知道了要读取的内容为14字节,所以可以定义(int)file.length()大小的空间,如果不确定大小,不建议使用此种方法。

四、字符流

在程序中一个字符等于两个字节,那么Java提供了Reader和Writer两个专门操作字符流的类。

1.字符输出流Writer

此类的定义如下:

public abstract class Writer extends Objectimplements Appendable, Closeable, Flushable

关于对Appendable接口的说明,此接口的定义如下:


public interface Appendable {
	Appendable append(char c) throws IOException;
	Appendable append(CharSequence csq, int start, int end) throws IOException
	Appendable append(CharSequence csq) throws IOException
}

此接口表示的是内容可以被追加,接受的参数是CharSequence,实际上String类也实现了此接口,所以可以直接通过此接口的方法向输出流中追加内容。

Writer的主要方法:

// 关闭此流,但要先刷新它
public abstract void close()throws IOException
// 刷新该流的缓冲
public abstract void flush() throws IOException
// 写入字符串
public void write(String str) throws IOException
// 写入字符数组
public void write(char[] cbuf) throws IOException
范例:向文件中写入数据
File file = new File("E:" + File.separator + "w.txt");
Writer out = new FileWriter(file);
String str = "hello world!";
out.write(str);
out.close();

2.字符输入流Reader

Reader是使用字符的方式从文件中读取数据,Reader类的定义如下:

public abstract class Reader extends Objectimplements Readable, Closeable

Reader类的常用方法:

// 关闭该流并释放与之关联的所有资源
public abstract void close()throws IOException
// 读取单个字符
public int read() throws IOException
// 将字符读入数组
public int read(char[] cbuf) throws IOException

范例:从文件中读取内容

File file = new File("E:" + File.separator + "w.txt");
Reader reader = new FileReader(file);
char[] c = new char[1024];
int len = 0;
while ((len = reader.read(c)) != -1) {
	System.out.println(new String(c, 0, len));
}
reader.close();

五、字节流与字符流的区别

字节流与字符流使用非常相似,两者除了操作代码上的不同之外,是否还有其他的不同了?

实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身操作的,二字符流在操作时使用了缓冲区,通过缓冲区在操作文件。

下面一两个写文件的操作为主进行比较,但是在操作时字节流和字符流的操作完成之后都不关闭输出流。

范例:使用字节流不关闭执行

File file = new File("E:" + File.separator + "ops.txt");
OutputStream outputStream = new FileOutputStream(file);
String str = "Hello world!";
byte[] b = str.getBytes();
outputStream.write(b);
// 关闭输出流
// outputStream.close();

没有关闭字节流操作,但是文件中也依然存在输出的内容,证明字节流是直接操作文件本身的。下面使用字符流,观察效果

范例:使用字符流不关闭执行

File file = new File("E:" + File.separator + "w.txt");
Writer out = new FileWriter(file);
String str = "hello world!";
out.write(str);
// 关闭输出流
// out.close();

程序运行之后发现文件中没有任何内容,这是因为字符流操作使用了缓冲区,而在关闭字符流时会强制性的将缓冲区中的内容进行输出,但是如果程序没有关闭,则缓冲区中的内容是无法输出的,所以得出结论:字符流使用缓冲区,而字节流没有使用缓冲区。
①问题:什么是缓冲区?

回答:可以简单地把缓冲区理解为一段特殊的内存。

某些情况下,一个程序频繁地操作一个资源(如文件),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域中,以后直接从此区域中读取数据即可,因为读内存速度比较快,这样提高性能。

强制性清空缓冲区

OutputStream的flush()方法。

②问题:使用字节流好还是字符流好?

回答:字节流更好。所有的文件在硬盘或者在传输时都是以字节的方式进行的,包括图片等,而字符是只有在内存中才使用。

范例:文件复制

1.实现要求,在dos命令中存在一个文件的复制命令(copy),copy命令的语法格式如下:

copy 源文件 目标文件

下面使用Java完成以上功能,程序运行时可以按照如下格式进行:

java Copy 源文件 目标文件 

2.思路,从运行格式中发现,要输入源文件和目标文件的路径,可以使用命令行参数完成,必须对输入的参数进行验证,如果输入的参数不是两个,或者源文件不存在,则程序给出错误信息并退出。

是选择字节流还是字符流,因为要复制的文件不一定都是文本文件,也可能包含图片或者声音,所以使用字节流。

两种操作方式:

将源文件中的内容全部读取到内存,并一次性写入到目标文件中。

不要将源文件中的内容全部都取进来,而是使用边读边写的方式。

很显然,使用边读边写更好,文件内容过多,则内存是无法装的。

public class Copy {
	public static void main(String[] args) {
		if (args.length != 2) {
			System.out.println("输入的参数不正确!");
			System.out.println("例:java Copy 源文件路径 目标文件路径");
			System.exit(1);    // 系统退出
		}
		File source = new File(args[0]);  // 源文件的File对象
		File target = new File(args[1]);  // 目标文件的File对象
		if (!source.exists()) {
			System.out.println("源文件不存在!");
			System.exit(1);
		}
		InputStream inputStream = null;
		OutputStream outputStream = null;
		try {
			inputStream = new FileInputStream(source);
			outputStream = new FileOutputStream(target);
			if (inputStream != null && outputStream != null) {
				int len = 0;
				byte[] bs = new byte[1024];
				while ((len = inputStream.read(bs)) != -1) {
					outputStream.write(bs, 0, len);
				}
				System.out.println("复制完成!");
			}
		} catch (IOException e) {
			System.out.println("复制失败!");
			throw new RuntimeException(e.getMessage());
		}
		finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (IOException e) {
					throw new RuntimeException("输出关闭流失败!");
				}
			}
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					throw new RuntimeException("输入关闭流失败!");
				}
			}
		}
	}
}

 

你可能感兴趣的:(Java)