第四十七讲 I/O流——常用IO流(打印流、合并流、序列流、随机访问流以及管道流)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yerenyuan_pku/article/details/84642108

打印流

打印流即输出流,分为字节打印流PrintStream和字符打印流PrintWriter。下面分别对它们进行介绍。

字节打印流

概述

PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream永远不会抛出IOException;而是,异常情况仅设置可通过checkError方法测试的内部标志。另外,为了自动刷新,可以创建一个PrintStream;这意味着可在写入byte数组之后自动调用flush方法,可调用其中一个println方法,或写入一个换行符或字节 (’\n’)。
PrintStream打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用PrintWriter类。

打印的目的地

通过查阅API帮助文档可知,PrintStream类的构造方法如下:
在这里插入图片描述
所以,字节打印流PrintStream的打印目的地有File对象,字符串路径,字节输出流。

解决的问题

字节打印流PrintStream可以方便地打印各种数据值表示形式。它的打印方法可以保证数值的表现形式不变,即写的是什么样子,目的地就是什么样子。

package cn.liayun.otherio.print;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;

public class PrintStreamDemo {

	public static void main(String[] args) throws IOException {
		File dir = new File("tempfile");
		if (!dir.exists()) {
			dir.mkdir();
		}
		//演示PrintStream的特有方法
		//1,创建PrintStream对象,目的就定为文件
		PrintStream out = new PrintStream("tempfile\\print2.txt");
		
		//将数据打印到文件中
//		out.write(353);//字节流的write方法一次只写出一个字节,也就是将一个整数的最低8位写出
//		out.write("353".getBytes());//麻烦
		
		out.print(97);//保证数值的表现形式不变,其实原理就是将数值都转成了字符串。
		
		out.close();
	}

}

注意,字节流一次写出一个字节的write方法,就是将一个整数的最低8位写出。

字符打印流

概述

向文本输出流打印对象的格式化表示形式。此类实现在PrintStream中的所有print方法。它不包含用于写入原始字节的方法,对于这些字节,程序应该使用未编码的字节流进行写入。
与PrintStream类不同,如果启用了自动刷新,则只有在调用println、printf或format的其中一个方法时才可能完成此操作,而不是每当正好输出换行符时才完成。这些方法使用平台自有的行分隔符概念,而不是换行符。
此类中的方法不会抛出I/O异常,尽管其某些构造方法可能抛出异常。客户端可能会查询调用checkError()是否出现错误。

打印的目的地

通过查阅API帮助文档可知,PrintWriter类的构造方法如下:
在这里插入图片描述
所以,字符打印流PrintWriter的打印目的地有File对象,字符串路径,字节输出流、字符输出流。

案例

读取键盘录入,将数据转成大写,显示在屏幕上。

package cn.liayun.otherio.print;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class PrintWriterDemo {

	public static void main(String[] args) throws IOException {
		/**
		 * 演示一个小例子
		 * 读取键盘入录入。将数据转成大写,显示在屏幕上。
		 */
		
		//1,键盘录入
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		
		//2,
		//BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
		PrintWriter pw = new PrintWriter(System.out, true);//对println方法可以实现自动刷新
		
		//改变目的为文件,我还想自动刷新。
//		pw = new PrintWriter(new BufferedWriter(new FileWriter("1.txt")), true);
		
		//3,读一行,写一行,键盘录入一定要定义结束标记
		String line = null;
		while ((line = bufr.readLine()) != null) {
			if ("over".equals(line)) {
				break;
			}
			pw.println(line.toUpperCase());
//			pw.flush();
		}
		pw.close();
//		bufr.close();//不需要关闭键盘录入这种标准输入流,一旦关闭,后面获取不到。
	}

}

合并流

概述

SequenceInputStream表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。

解决的问题

将多个输入流合并成了一个输入流,将多个源合并成了一个源,对于多个源的操作会变的简单。

功能(构造方法)

通过查阅API帮助文档可知,SequenceInputStream类的构造方法如下:
在这里插入图片描述
特殊之处在构造函数上,一初始化就合并了多个流进来。

应用场景

对多个文件进行数据的合并,多个源对应一个目的。

package cn.liayun.otherio.sequence;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;

public class SequenceInputStreamDemo {

	public static void main(String[] args) throws IOException {
		/*
		 * 演示序列流,SequenceInputStream。
		 */
		//如何获取一个Enumeration呢?Vector有,但是效率低,使用ArrayList。
		ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
		//添加三个输入流对象,和指定的具体文件关联
		for (int x = 1; x <= 3; x++) {
			al.add(new FileInputStream("tempfile\\" + x + ".txt"));
		}
		
		//怎么通过ArrayList获取枚举接口,可以使用Collections工具类中的方法。
		Enumeration<FileInputStream> en = Collections.enumeration(al);
		
		//创建一个序列流对象。需要传递参数Enumeration。
		SequenceInputStream sis = new SequenceInputStream(en);
		
		//创建目的(文件)
		FileOutputStream fos = new FileOutputStream("tempfile\\4.txt");
		
		//频繁的读写操作
		//1,创建缓冲区
		byte[] buf = new byte[1024];
		int len = 0;
		while ((len = sis.read(buf)) != -1) {
			fos.write(buf, 0, len);
		}
		
		//关闭流
		fos.close();
		sis.close();
	}

}

序列流

序列流分为序列化流ObjectOutputStream和反序列化流ObjectInputStream。下面分别对它们进行介绍。

序列化流ObjectOutputStream

概述

ObjectOutputStream将Java对象的基本数据类型和图形写入OutputStream。可以使用 ObjectInputStream读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。

解决的问题

可以对对象进行序列化(又叫对象的持久化或对象的串行化)。注意,对象序列化一定要实现Serializable接口,为了给类定义一个serialVersionUID。

功能(特殊方法)

方法 描述
public final void writeObject(Object obj) 将指定的对象写入ObjectOutputStream。对象的类、类的签名,以及类及其所有超类型的非瞬态和非静态字段的值都将被写入

Serializable接口

序列化流ObjectOutputStream只能将支持java.io.Serializable接口的对象写入流中,也即对象要想序列化一定要实现Serializable接口。否则会报未序列化异常NotSerializableException,诸如下图所示。
在这里插入图片描述
序列化数据后,再次修改类文件,读取数据会出问题,如何解决呢?每次修改java文件的内容的时候,class文件的id值都会发生改变。而读取文件的时候,会和class文件中的id值进行匹配,所以,就会出问题。可以让这个id值在java文件中是一个固定的值,这样,当你修改文件的时候,这个id值就不会发生改变。看到类实现了序列化接口的时候,要想解决黄色警告线问题,就可以自动产生一个序列化id值。
在这里插入图片描述
而且产生这个值以后,我们对类进行任何改动,它读取以前的数据是没有问题的。

transient关键字

当使用Serializable接口实现序列化操作时,如果一个对象中的某个属性不希望被序列化,则可以使用transient关键字进行声明。当然了,static修饰的静态属性也不能被序列化,序列化的只是堆内存中对象的属性。

案例

将一个对象存储到持久化(硬盘)的设备上。要序列化的Person类:

package cn.liayun.domain;

import java.io.Serializable;

/*
 * Person类的对象如果需要序列化,就需要实现Serializable标记接口。
 * 该接口给需要序列化的类,提供了一个序列版本号---serialVersionUID
 * 该版本号的目的在于用于验证序列化的对象和对应的类是否版本匹配。
 */
public class Person implements Serializable {
	/**
	 * 给类显示声明一个序列版本号
	 */
	private static final long serialVersionUID = 1L;

	private /*static*/ String name;
	private transient/*瞬态*/ int age;

	public Person() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Person(String name, int age) {
		super();
		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;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

}

序列化一个Person对象,即将对象内容保存到文件中:

package cn.liayun.otherio.objectstream;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

import cn.liayun.domain.Person;

public class ObjectStreamDemo {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		/*
		 * 将一个对象存储到持久化(硬盘)的设备上。
		 */
		writeObj();//对象的序列化
	}

	public static void writeObj() throws IOException {
		//1,明确存储对象的文件
		FileOutputStream fos = new FileOutputStream("tempfile\\obj.object");
		//2,给操作文件对象加入写入对象的功能
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		//3,调用写入对象的方法
		oos.writeObject(new Person("wangcai", 20));
		//关闭资源
		oos.close();
	}

}

反序列化流ObjectInputStream

概述

ObjectInputStream对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化。

功能(特殊方法)

方法 描述
public final Object readObject() 从ObjectInputStream读取对象。对象的类、类的签名和类及所有其超类型的非瞬态和非静态字段的值都将被读取

readObject方法用于从流读取对象。应该使用Java的安全强制转换来获取所需的类型。在Java中,字符串和数组都是对象,所以在序列化期间将其视为对象。读取时,需要将其强制转换为期望的类型。

案例

反序列化一个Person对象,即从文件中读取到一个对象:

package cn.liayun.otherio.objectstream;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

import cn.liayun.domain.Person;

public class ObjectStreamDemo {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		readObj();//对象的反序列化
	}

	public static void readObj() throws IOException, ClassNotFoundException {
		//1,定义一个流对象关联了存储了对象的文件
		FileInputStream fis = new FileInputStream("tempfile\\obj.object");
		//2,建立用于读取对象的功能对象
		ObjectInputStream ois = new ObjectInputStream(fis);
		Person obj = (Person) ois.readObject();
		System.out.println(obj.toString());
	}

}

随机访问流

概述

RandomAccessFile类不属于流,是Object类的子类,但它融合了InputStream和OutputStream的功能。
此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过getFilePointer方法读取,并通过seek方法设置。

特点

随机访问流有如下几个特点:

  1. 只能操作文件;
  2. 既能读,又能写;
  3. 维护了一个大型byte数组,内部定义了字节流的读取和写入;
  4. 通过对指针的操作,可以实现对文件的任意位置的读取和写入。

功能(构造方法)

通过查阅API帮助文档可知,RandomAccessFile类的构造方法如下:
在这里插入图片描述
通过构造函数可以看出,该类只能操作文件,而且操作文件还有模式,而第二个参数就是操作文件的模式,模式有四种,我们最常用的两种叫只读r,读写rw。

  • 如果模式为只读r,不会创建文件,会去读取一个已存在的文件,如果该文件不存在,则会出现异常;
  • 如果模式为rw,操作的文件不存在,会自动创建,如果存在则不创建也不会覆盖(会修改文件)。

案例

对随机访问文件的读取和写入。

package cn.liayun.otherio.randomaccess;

import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileDemo {

	public static void main(String[] args) throws IOException {
		/*
		 * RandomAccessFile:支持对随机访问文件的读取和写入。
		 * 特点:
		 * 1,只能操作文件
		 * 2,既能读,有能写
		 * 3,维护了一个大型byte数组,内部定义了字节流的读取和写入
		 * 4,通过对指针的操作,可以实现对文件的任意位置的读取和写入
		 */
		
//		writeFile();
		readFile();
	}		

	public static void readFile() throws IOException {
		RandomAccessFile raf = new RandomAccessFile("tempfile\\random.txt", "r");
		
		//随机读取,只要通过设置指针的位置即可
		raf.seek(8 * 1);
		
		byte[] buf = new byte[4];
		raf.read(buf);
		String name = new String(buf);
		
		int age = raf.readInt();
		System.out.println(name + ":" + age);
		raf.close();
	}

	public static void writeFile() throws IOException {
		//1,创建一个随机访问文件的对象。文件不存在,则创建;存在,则不创建也不覆盖。
		RandomAccessFile raf = new RandomAccessFile("tempfile\\random.txt", "rw");
		
		//2,写入人的姓名和年龄
//		raf.write("张三".getBytes());
//		raf.writeInt(97);//它可以保证整数的字节原样性。
//		raf.write("李四".getBytes());
//		raf.writeInt(99);//它可以保证整数的字节原样性。
		
		//3,随机写入
		raf.seek(8);//设置指针的位置
		System.out.println(raf.getFilePointer());
		raf.write("王五".getBytes());
		raf.writeInt(100);
		System.out.println(raf.getFilePointer());
		
		raf.close();
	}

}

管道流

概述

管道流分为管道输入流PipedInputStream和管道输出流PipedOutputStream,由于实际开发中用到的并不多,在这里我就不打算细讲了。但要知道管道流的主要作用是可以进行两个线程间的通信。反正管道流结合的是多线程技术,可以查看API帮助文档。

特点

管道流具有如下两个特点:

  1. 读取管道和写入管道可以连接;
  2. 需要使用多线程技术,单线程容易死锁。

案例

package cn.liayun.otherio.piped;

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

public class PipedStreamDemo {

	public static void main(String[] args) throws IOException {
		//创建管道对象
		PipedInputStream pis = new PipedInputStream();
		PipedOutputStream pos = new PipedOutputStream();
		//将两个流连接上
		pis.connect(pos);
		
		//开启两个线程
		new Thread(new Input(pis)).start();
		new Thread(new Output(pos)).start();
	}

}

// 定义输入任务
class Input implements Runnable {
	private PipedInputStream pis;

	public Input(PipedInputStream pis) {
		super();
		this.pis = pis;
	}

	@Override
	public void run() {
		byte[] buf = new byte[1024];
		int len;
		try {
			len = pis.read(buf);
			String str = new String(buf, 0, len);
			System.out.println(str);
			pis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

// 定义输出任务
class Output implements Runnable {
	private PipedOutputStream pos;

	public Output(PipedOutputStream pos) {
		super();
		this.pos = pos;
	}

	@Override
	public void run() {
		//通过写方法完成
		try {
			pos.write("hi,管道来了!".getBytes());
			
			pos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

运行结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/yerenyuan_pku/article/details/84642108