Java学习笔记(7)——输入输出

1、File

不同的操作系统对于档案系统路径的设定各有差别,例如在Windows中,一个路径的表示法可能是:

"c:\\Windows\\Fonts\\"

而在Linux下的路径设定可能是:

"/home/justin/"

Windows的路径指定是使用UNC(Universal Naming Convention)路径名,以\\开始表示磁盘根目录,如果没有以\\开始表示相对路径,c是可选的磁盘指定,后面跟随着 : 字符。而UNIX-Like系统的路径指定以 / 开始表示绝对路径,不以 / 开始表示相对路径。

因而在程序中设定路径时会有系统相依性的问题,File类别提供一个抽象的、与系统独立的路径表示,您给它一个路径字符串,它会将它转换为与系统无关的抽象路径表示,这个路径可以指向一个档案、目录或是URI,您可以用以下四种方式来建构File的实例:

File(File parent, String child)
File(String pathname)
File(String parent, String child)
File(URI uri)

一个File的实例被建立时,它就不能再被改变内容;File类别除了用来表示一个档案或目录的抽象表示之外,它还提供了不少相关操作方法,您可以用它来对档案系统作一些查询与设定的动作。

来看个简单的程序:

FileDemo.java
package onlyfun.caterpillar;
import java.io.*;
import java.util.*;
public class FileDemo {
	public static void main(String[] args) {
		try {
			File file = new File(args[0]);
			if(file.isFile()) { // 是否为档案
				System.out.println(args[0] + " 档案");
				System.out.print(
				file.canRead() ? "可读 " : "不可读 ");
				System.out.print(
				file.canWrite() ? "可写 " : "不可写 ");
				System.out.println(
				file.length() + "字节");
			}
			else {
				// 列出所有的档案及目录
				File[] files = file.listFiles();
				ArrayList fileList =
				new ArrayList();
				for(int i = 0; i < files.length; i++) {
					// 先列出目录
					if(files[i].isDirectory()) { //是否为目录
						// 取得路径名
						System.out.println("[" +
						files[i].getPath() + "]");
					}
					else {
						// 档案先存入fileList,待会再列出
						fileList.add(files[i]);
					}
				}
				// 列出档案
				for(File f: fileList) {
					System.out.println(f.toString());
				}
				System.out.println();
			}
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println(
			"using: java FileDemo pathname");
		}
	}
}

执行结果:

java onlyfun.caterpillar.FileDemo C:\
[C:\WINDOWS]
[C:\Documents and Settings]
[C:\Program Files]
[C:\System Volume Information]
[C:\Recycled]
C:\A3N_A3L.10
C:\bootfont.bin
C:\ntldr
C:\NTDETECT.COM
C:\boot.ini
C:\CONFIG.SYS
C:\AUTOEXEC.BAT
C:\IO.SYS
C:\MSDOS.SYS
C:\Finish.log
C:\pagefile.sys
C:\VIRTPART.DAT

9、RandomAccessFile

档案存取通常是「循序的」,每在档案中存取一次,读取档案的位置就会相对于目前的位置前进,然而有时候您必须对档案的某个区段进行读取或写入的动作,也就是进行「随机存取」(Random access),也就是说存取档案的位置要能在档案中随意的移动,这时您可以使用RandomAccessFile,使用seek()方法来指定档案存取的位置,指定的单位是字节,藉由它您就可以对档案进行随机存取的动作。

为了方便,通常在随机存取档案时会固定每组资料的长度,例如一组学生个人数据,Java中并没有像C/C++中可以直接写入一个固定长度结构(Structure)的方法,所以在固定每组长度的方面您必须自行设计。

下面这个程序示范了如何使用RandomAccessFile来写入档案,并随机读出一笔您所想读出的资料:

Student.java
package onlyfun.caterpillar;
public class Student {
	private String name; // 固定 15 字符
	private int score;
	public Student() {
		setName("noname");
	}
	public Student(String name, int score) {
		setName(name);
		this.score = score;
	}
	public void setName(String name) {
		StringBuilder builder = null;
		if(name != null)
		builder = new StringBuilder(name);
		else
		builder = new StringBuilder(15);
		builder.setLength(15);
		this.name = builder.toString();
	}
	public void setScore(int score) {
		this.score = score;
	}
	public String getName() {
		return name;
	}
	public int getScore() {
		return score;
	}
	// 每笔数据固定写入34字节
	public static int size() {
		return 34;
	}
}
RandomAccessFileDemo.java
package onlyfun.caterpillar;
import java.io.*;
import java.util.*;
public class RandomAccessFileDemo {
	public static void main(String[] args) {
		Student[] students = {
			new Student("Justin", 90),
			new Student("momor", 95),
			new Student("Bush", 88),
		new Student("caterpillar", 84)};
		try {
			File file = new File(args[0]);
			// 建立RandomAccessFile实例并以读写模式开启档案
			RandomAccessFile randomAccessFile =
			new RandomAccessFile(file, "rw");
			for(int i = 0; i < students.length; i++) {
				randomAccessFile.writeChars(students[i].getName());
				randomAccessFile.writeInt(students[i].getScore());
			}
			Scanner scanner = new Scanner(System.in);
			System.out.print("读取第几笔数据?");
			int num = scanner.nextInt();
			randomAccessFile.seek((num-1) * Student.size());
			Student student = new Student();
			student.setName(readName(randomAccessFile));
			student.setScore(randomAccessFile.readInt());
			System.out.println("姓名:" + student.getName());
			System.out.println("分数:" + student.getScore());
			randomAccessFile.close();
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("请指定文件名称");
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
	private static String readName(
	RandomAccessFile randomAccessfile)
	throws IOException {
		char[] name = new char[15];
		for(int i = 0; i < name.length; i++)
		name[i] = randomAccessfile.readChar();
		return new String(name).replace('\0', ' ');
	}
}

在实例化一个RandomAccessFile对象时,要设定档案开启的方式,设定"r"表示只供读取,设定"rw"表示可读可写;为了让每组数据长度固 定,在写入name时,我们使用 StringBuilder 并设定其长度固定为15个字符,而读回name时则直接读回15个字符,然后再去掉空格符传回。

10、InputStream与OutputStream

计算机中的数据都是以0与1的方式来储存,如果您要在两个装置之间进行数据的存取,当然也是以0与1位的方式来进行,实际上数据的流动是透过电路,而上面 的数据则是电流,而在程序上来说,将数据目的地与来源之间抽象化为一个串流(Stream),而当中流动的则是位数据。

01010101 Stream -->
来源地 ===================== 目的地

在Java中有两个类别用来作串流的抽象表示:InputStream与OutputStream。

InputStream是所有表示位输入串流的类别之父类别,它是一个抽象类别,子类会重新定义它当中所定义的方法, InputStream用于从装置来源地读取数据的抽象表示,例如System中的标准输入串流 in 对象就是一个 InputStream,在程序开始之后,这个串流对象就会开启,以从标准输入装置中读取数据,这个装置通常是键盘或是其它使用者定义的装置。

OutputStream是所有表示位输出串流的类别之父类别,它是一个抽象类别,子类会重新定义它当中所定义的方法, OutputStream是用于将数据写入目的地的抽象表示,例如System中的标准输出串流对象 out ,out 的类型是PrintStream, 这个类别是OutputStream的子类别(FilterOutputStream继承OutputStream, PrintStream再继承FilterOutputStream),在程序开始之后,这个串流对象就会开启,您可以将数据透过它来写入目的地装置,这 个装置通常是屏幕或其它使用者定义的装置。

下面程序可以读取键盘输入串流,并将资料以10进位方式显示在屏幕上:

StreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class StreamDemo {
	public static void main(String[] args) {
		try {
			System.out.print("输入字符: ");
			System.out.println("输入字符十进制表示: " +
			System.in.read());
			System.out.println("换行字符十进制表示: " +
			System.in.read());
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
}

执行结果: 

输入字符: A?
输入字符十进制表示: 65 
换行字符十进制表示: 10

字符A输入后被标准输入串流读取,A的位表示以十进制来看就是65,这是A字符的编码(查查ASCII编码表就知道了),在这边要注意的是read()只读取一个字节的数据,而当输入A并按Enter键时,实际上在串流中会有A的位数据与换行字符的位数据,换行字符的位数据以十进制来表示的话就是10。

操作系统之间的换行字符各不相同,Windows 为"\r\n",Linux 为'\n',而 Mac 为'\r'。

11、BufferedInputStream、 BufferedOutputStream

在介绍 FileInputStream、 FileOutputStream的 例子中,您使用了一个数组来作为数据读入的缓冲区,以档案存取为例的话,您知道磁盘存取的速度是远低于内存中的数据存取速度,为了减少对磁盘的存 ,您一次读入一定长度的数据,如上一个主题范例中的1024字节,而写入时也是一次写入一定长度的数据,这可以增加数据存取的效率。

BufferedInputStream与BufferedOutputStream可以为InputStream类的对象增加缓冲区功能,使用它们,您无需自行设计缓冲区。

BufferedInputStream的数据成员buf是个位数组,预设为2048字节大小,当读取数据来源时,例如档案, BufferedInputStream会尽量将buf填满,当使用read()方法时,实际上是先读取buf中的数据,而不是直接对数据来源作读取,当buf中的数据不足时,BufferedInputStream才会再从数据来源中提取数据。

BufferedOutputStream的数据成员buf是个位数组,预设为512个字节,当写入数据时,会先将资料存至buf中,当buf已满时才会一次将数据写至目的地,而不是每次写入都对目的地作写入。

将上一个主题的范例作个改写,这次不用自行设定缓冲区并进行判断了,使用BufferedInputStream、 BufferedOutputStream让程序看来简单一些,也比较有效率:

BufferedStreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class BufferedStreamDemo {
	public static void main(String[] args) {
		try {
			byte[] data = new byte[1];
			File srcFile = new File(args[0]);
			File desFile = new File(args[1]);
			BufferedInputStream bufferedInputStream =
			new BufferedInputStream(
			new FileInputStream(srcFile));
			BufferedOutputStream bufferedOutputStream =
			new BufferedOutputStream(
			new FileOutputStream(desFile));
			System.out.println("复制档案:" +
			srcFile.length() + "字节");
			while(bufferedInputStream.read(data) != -1) {
				bufferedOutputStream.write(data);
			}
			// 将缓冲区中的数据全部写出
			bufferedOutputStream.flush();
			// 关闭串流
			bufferedInputStream.close();
			bufferedOutputStream.close();
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println(
			"using: java UseFileStream src des");
			e.printStackTrace();
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
}

为了确保缓冲区中的数据一定被写出,建议最后执行flush()将缓冲区中的数据全部写出目的串流中。

BufferedInputStream、BufferedOutputStream并没有改变来源InputStream或目的 OutputStream的行为,读入或写出时的动作还是InputStream、OutputStream负责, BufferedInputStream、BufferedOutputStream只是在这之前动态的为它们加上一些功能(像是缓冲区功能),在这边是 以档案存取串流为例,实际上您可以在其它串流对象上加上BufferedInputStream、BufferedOutputStream功能。

12、FileInputStream、 FileOutputStream

FileInputStream是InputStream的子类,由名称上就可以知道, FileInputStream主要就是从指定的档案中读取数据至目的地。

FileOutputStream是OutputStream的子类,顾名思义,FileInputStream主要就是从来源地写入数据至指定的档案中。

标准输入输出串流对象在程序一开始就会开启,但只有当您建立一个FileInputStream或FileOutputStream的实例时,实际的串流才会开启,而不使用串流时,也必须自行关闭串流,以释放与串流相依的系统资源。

下面这个程序可以复制档案,程序先从来源档案读取数据至一个位缓冲区中,然后再将位数组的数据写入目的档案:

FileStreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class FileStreamDemo {
	public static void main(String[] args) {
		try {
			byte[] buffer = new byte[1024];
			FileInputStream fileInputStream =
			new FileInputStream(new File(args[0]));
			FileOutputStream fileOutputStream =
			new FileOutputStream(new File(args[1]));
			System.out.println("复制档案:" +
			fileInputStream.available() + "字节");
			while(true) { // 从来源档案读取数据至缓冲区
				if(fileInputStream.available() < 1024) {
					int remain;
					while((remain = fileInputStream.read())
					!= -1) {
						fileOutputStream.write(remain);
					}
					break;
				}
				else {
					fileInputStream.read(buffer);
					// 将数组数据写入目的档案
					fileOutputStream.write(buffer);
				}
			}
			// 关闭串流
			fileInputStream.close();
			fileOutputStream.close();
			System.out.println("复制完成");
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println(
			"using: java FileStreamDemo src des");
			e.printStackTrace();
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
}

这个程序示范了两个 read() 方法,一个可以读入指定长度的数据至数组,一个一次可以读入一个字节,每次读取之后,读取的指标都会往前进,您使用available()方法获得还有多少字节可以读取;除了使用File来建立FileInputStream、FileOutputStream的实例之外,您也可以直接使用字符串指定路径来建立。

不使用串流时,记得使用close()方法自行关闭串流,以释放与串流相依的系统资源。

13、ObjectInputStream、ObjectOutputStream

在Java这样支持对象导向的程序中撰写程序,很多数据都是以对象的方式存在,在程序运行过后,您会希望将这些数据加以储存,以供下次执行程序时使用,这时您可以使用ObjectInputStream、ObjectOutputStream来进行这项工作。

要被储存的对象必须实作Serializable接口,说是实作,其实Serializable中并没有规范任何必须实作的方法,所以这边所谓实作的意义,其实像是对对象贴上一个标志,代表该对象是可以序列化的(Serializable)。

一个实作的例子如下所示:

Student.java
package onlyfun.caterpillar;
import java.io.*;
public class Student implements Serializable {
	private static final long serialVersionUID = 1L;
	private String name;
	private int score;
	public Student() {
		name = "N/A";
	}
	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setScore(int score) {
		this.score = score;
	}
	public String getName() {
		return name;
	}
	public int getScore() {
		return score;
	}
	public void showData() {
		System.out.println("name: " + name);
		System.out.println("score: " + score);
	}
}

您要注意到serialVersionUID,这代表了可序列化对象的版本, 如果您没有提供这个版本讯息,则会自动依类名称、实现的接口、成员等讯息来产生,如果是自动产生的,则下次您更改了Student类,则自动产生的 serialVersionUID也会跟着变更,当反序列化时两个serialVersionUID不相同的话,就会丢出 InvalidClassException,如果您想要维持版本讯息的一致,则要显式宣告serialVersionUID。

ObjectInputStream、ObjectOutputStream为InputStream、OutputStream加上了可以让使用者写入 对象、读出对象的功能,在写入对象时,我们使用writeObject()方法,读出对象时我们使用readObject()方法,被读出的对象都是以 Object的型态传回,您必须将之转换为对象原来的型态,才能正确的操作被读回的对象,下面这个程序示范了如何简单的储存对象至档案中,并将之再度读 回:

ObjectStreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
import java.util.*;
public class ObjectStreamDemo {
	public static void writeObjectsToFile(
	Object[] objs, String filename) {
		File file = new File(filename);
		try {
			ObjectOutputStream objOutputStream =
			new ObjectOutputStream(
			new FileOutputStream(file));
			for(Object obj : objs) {
				objOutputStream.writeObject(obj);
			}
			objOutputStream.close();
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
	public static Object[] readObjectsFromFile(
	String filename)
	throws FileNotFoundException {
		File file = new File(filename);
		if(!file.exists())
		throw new FileNotFoundException();
		List list = new ArrayList();
		try {
			FileInputStream fileInputStream =
			new FileInputStream(file);
			ObjectInputStream objInputStream =
			new ObjectInputStream(fileInputStream);
			while(fileInputStream.available() > 0) {
				list.add(objInputStream.readObject());
			}
			objInputStream.close();
		}
		catch(ClassNotFoundException e) {
			e.printStackTrace();
		}
		catch(IOException e) {
			e.printStackTrace();
		}
		return list.toArray();
	}
	public static void appendObjectsToFile(
	Object[] objs, String filename)
	throws FileNotFoundException {
		File file = new File(filename);
		if(!file.exists())
		throw new FileNotFoundException();
		try {
			ObjectOutputStream objOutputStream =
			new ObjectOutputStream(
			new FileOutputStream(file, true)) {
				protected void writeStreamHeader()
				throws IOException {}
			};?
			for(Object obj : objs) {
				objOutputStream.writeObject(obj);
			}
			objOutputStream.close();
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		Student[] students = {new Student("caterpillar", 90),
		new Student("justin", 85)};
		// 写入新档
		writeObjectsToFile(students, "data.dat");
		try {
			// 读取档案数据
			Object[] objs = readObjectsFromFile("data.dat");
			for(Object obj : objs) {
				((Student) obj).showData();
			}
			System.out.println();
			students = new Student[2];
			students[0] = new Student("momor", 100);
			students[1] = new Student("becky", 100);
			// 附加至档案
			appendObjectsToFile(students, "data.dat");
			// 读取档案数据
			objs = readObjectsFromFile("data.dat");
			for(Object obj : objs) {
				((Student) obj).showData();
			}
		}
		catch(FileNotFoundException e) {
			e.printStackTrace();
		}
	}
}

对象被写出时,会写入对象的类别型态、类别署名(Class signature),static与被标志为transient的成员则不会被写入。

在这边注意到以附加的形式写入数据至档案时,在试图将对象附加至一个先前已写入对象的档案时,由于ObjectOutputStream在 写入数据时,还会加上一个特别的标示头,而读取档案时会检查这个标示头,如果一个档案中被多次附加对象,那么该档案中会有多个标示头,如此读取检查时就会 发现不一致,这会丢出StreamCorrupedException,为此,您重新定义ObjectOutputStream的writeStreamHeader()方法,如果是以附加的方式来写入对象,就不写入标示头:

ObjectOutputStream objOutputStream =
new ObjectOutputStream(
new FileOutputStream(file, true)) {
protected void writeStreamHeader()
throws IOException {}
};

对象写出或读入并不仅限于档案存取,您也可以用于网络的数据传送,例如传送整个对象数据或是影像档案

14、DataInputStream、DataOutputStream

DataInputStream、DataOutputStream可提供一些对Java基本数据型态写入的方法,像是读写int、double、 boolean等的方法,由于Java的数据型态大小是规定好的,在写入或读出这些基本数据型态时,就不用担心不同平台间资料大小不同的问题。

这边还是举档案存取来进行说明,有时候您只是要储存一个对象的成员数据,而不是整个对象的信息,成员数据的型态假设都是Java的基本数据型态,您不必要 使用Object输入、输出相关串流对象,而可以使用DataInputStream、DataOutputStream来写入或读出数据,下面这个程序 是个简单的示范:

Student.java
package onlyfun.caterpillar;
public class Student?{
	private String name;
	private int score;
	public Student() {
		name = "N/A";
	}
	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setScore(int score) {
		this.score = score;
	}
	public String getName() {
		return name;
	}
	public int getScore() {
		return score;
	}
	public void showData() {
		System.out.println("name: " + name);
		System.out.println("score: " + score);
	}
}
DataStreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class DataStreamDemo {
	public static void main(String[] args) {
		Student[] students = {new Student("Justin", 90),
			new Student("momor", 95),
		new Student("Bush", 88)};
		try {
			DataOutputStream dataOutputStream =
			new DataOutputStream(
			new FileOutputStream("data.dat"));
			for(Student student : students) {
				dataOutputStream.writeUTF(student.getName());
				dataOutputStream.writeInt(student.getScore());
			}
			dataOutputStream.flush();
			dataOutputStream.close();
			DataInputStream dataInputStream =
			new DataInputStream(
			new FileInputStream("data.dat"));
			for(int i = 0; i < students.length; i++) {
				String name = dataInputStream.readUTF();
				int score = dataInputStream.readInt();
				students[i] = new Student(name, score);
				students[i].showData();
			}
			dataInputStream.close();
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
}

这个程序在写入档案时,只提取对象的成员数据,而在读出时将这些数据读出,并将读回的数据设定给一个实例,是对象数据还原的一种方式。

15、SequenceInputStream

您将一个档案分割为数个档案,接下来要将之再度组合还原为原来的档案,最基本的作法是使用数个 FileInputStream来开启分割后的档案,然后一个一个档案的读取,并连续写入至同一个FileOutputStream中,在这中间,您必须 要自行判断每一个分割档案的读取是否完毕,如果完毕就换读取下一个档案。

如果您使用SequenceInputStream就不用这么麻烦,SequenceInputStream可以看作是数个 InputStream对象的组合,当一个InputStream对象的内容读取完毕后,它就会取出下一个InputStream对象,直到所有的 InputStream对象都读取完毕为止。

下面这个程序是SequenceInputStream的使用示范,它可以将指定的档案进行分割,也可以将分割后的档案还原为一个档案:

SequenceStreamDemo.java
package onlyfun.caterpillar;
import java.util.*;
import java.io.*;
public class SequenceStreamDemo {
	public static void main(String[] args) {
		try {
			// args[0]: 指定分割(s)或连接(c)
			switch (args[0].charAt(1)) {
				case 's':
				// args[1]: 每个分割档案的大小
				int size = Integer.parseInt(args[1]);
				// args[2]: 指定要被分割的文件名称
				seperate(args[2], size);
				break;
				case 'c':
				// args[1]: 指定要被组合的档案个数
				int number = Integer.parseInt(args[1]);
				// args[2]: 组合后的文件名称
				concatenate(args[2], number);
				break;
			}
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println(
			"Using: java UseSequenceStream [-s/-c]" +
			" (size/number) filename");
			System.out.println("-s: 分割档案\n-c: 组合档案");
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
	// 分割档案
	public static void seperate(String filename, int size)
	throws IOException {
		FileInputStream fileInputStream =
		new FileInputStream(new File(filename));
		BufferedInputStream bufInputStream =
		new BufferedInputStream(fileInputStream);
		byte[] data = new byte[1];
		int count = 0;?
		// 从原档案大小及指定分割的大小
		// 决定要分割为几个档案
		if(fileInputStream.available() % size == 0)
		count = fileInputStream.available() / size;
		else
		count = fileInputStream.available() / size + 1;
		// 开始进行分割
		for(int i = 0; i < count; i++) {
			int num = 0;
			// 分割的档案加上底线与编号
			File file = new File(filename + "_" + (i + 1));
			BufferedOutputStream bufOutputStream =
			new BufferedOutputStream(
			new FileOutputStream(file));
			while(bufInputStream.read(data) != -1) {
				bufOutputStream.write(data);
				num++;
				if(num == size) { // 分割出一个档案
					bufOutputStream.flush();
					bufOutputStream.close();
					break;
				}
			}
			if(num < size) {
				bufOutputStream.flush();
				bufOutputStream.close();
			}
		}
		System.out.println("分割为" + count + "个档案");
	}
	// 连接档案
	public static void concatenate(String filename,
	int number) throws IOException {
		// 收集档案用的List
		List list =
		new ArrayList();
		for(int i = 0; i < number; i++) {
			// 文件名必须为底线加上编号
			File file = new File(filename + "_" + (i+1));
			list.add(i, new FileInputStream(file));
		}
		final Iterator iterator = list.iterator();
		// SequenceInputStream 需要一个Enumeration对象来建构
		Enumeration enumation =
		new Enumeration() {
			public boolean hasMoreElements() {
				return iterator.hasNext();
			}
			public InputStream nextElement() {
				return iterator.next();
			}
		};
		// 建立SequenceInputStream
		// 并使用BufferedInputStream
		BufferedInputStream bufInputStream =
			new BufferedInputStream(
				new SequenceInputStream(enumation),
					8192);
		BufferedOutputStream bufOutputStream =
			new BufferedOutputStream(
				new FileOutputStream(filename), 8192);
					byte[] data = new byte[1];
		// 读取所有档案数据并写入目的地档案
		while(bufInputStream.read(data) != -1)
		bufOutputStream.write(data);
		bufInputStream.close();
		bufOutputStream.flush();
		bufOutputStream.close();
		System.out.println("组合" + number + "个档案 OK!!");
	}
}

分割档案时的范例如下:

java onlyfun.caterpillar.SequenceStreamDemo -s 1048576 test.zip
分割为6个档案

组合档案时的范例如下:

java onlyfun.caterpillar.SequenceStreamDemo -c 6 test.zip
组合6个档案 OK!!

16、PrintStream

之前所介绍过的Stream输出对象,都是直接将内存中的数据写出至目的地(例如一个档案),举个例子来说,如果您将 int 整数 1 使用之前介绍的Stream对象输出至档案,则档案中所储存的是 int 整数 1 在内存中的值,例如:

FileStream.java
package onlyfun.caterpillar;
import java.io.*;
public class FileStreamDemo {
	public static void main(String[] args)
	throws IOException {
		FileOutputStream file =
		new FileOutputStream(
		new File("test.txt"));
		file.write(1);
		file.close();
	}
}

由于您使用write()方法,这会将 1 在内存中的值之低字节0000001写入档案中,所以如果您使用文字编辑软件(像vi或UltraEdit)观看test.txt的16进位表示,其结果会显示 01(16进位表示)。

有时候您所想要储存的结果是转换为字符之后的结果,例如若程序的执行结果是3.14159,您会希望使用字符来储存3.14159,也就是俗称的储存为纯文本文件,如此当您使用简单的纯文字编辑器观看时,就可以直接看到程序执行的结果。

例如您若想使用纯文本文件看到test.txt的显示结果是1,则必须先将内存中的整数1,也就是二进制00000000 00000000 00000000 00000001转换为对应的字符编码,也就是0x31(十进制表示49)并加以储存。

使用PrintStream可以自动为您进行字符转换的动作,它会使用操作系统的预设编码来处理对应的字符转换动作,直接使用下面这个例子来作示范:

PrintStreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class PrintStreamDemo {
	public static void main(String[] args)
	throws FileNotFoundException {
		PrintStream printStream = new PrintStream(
		new FileOutputStream(
		new File("pi.txt")));
		printStream.print("PI = ");
		printStream.println(Math.PI);
		printStream.close();
	}
}

执行程序之后使用纯文字编辑器开启pi.txt,其内容会是PI = 3.141592653589793,print()或println()接受int、char、String、double等等数据型态, println()会在输出之后加上换行字符,而print()则不会。

注意在档案储存上实际并没有二进制档案或是纯文本文件的分别,所有的档案所储存的都是二进制的数据,您俗称的纯文本文件,其实正确的说,是指储存的结果是经过字符转换,例如将 int 整数 1转换为字符 '1' 的编码结果并加以储存。

17、Reader、Writer

Reader、Writer支持Unicode标准字符集(Character set)(字节串流则只支持ISO-Latin-1 8-bit),在处理串流时,会根据系统预设的字符编码来进行字符转换,它们是抽象类别,真正您会使用其子类别,子类别通常会重新定义相关的方法。

在 PushbackInputStream 中,您读入一个含BIG5中文字及ASCII字符的文本文件,这边改写一下这个例子,使用Reader的子类别 InputStreamReader来转换读入的两个字节为汉字字符,并显示在屏幕上:

ReaderWriterDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class ReaderDemo {
	public static void main(String[] args) {
		try {
			PushbackInputStream pushbackInputStream =
			new PushbackInputStream(
			new FileInputStream(args[0]));
			byte[] array = new byte[2];
			ByteArrayInputStream byteArrayStream =
			new ByteArrayInputStream(array);
			// reader会从已读的位数组中取出数据
			InputStreamReader reader =
			new InputStreamReader(byteArrayStream);
			int tmp = 0;
			int count = 0;
			while((count = pushbackInputStream.read(array))
			!= -1) {
				// 两个字节转换为整数
				tmp = (short)((array[0] << 8) |
				(array[1] & 0xff));
				tmp = tmp & 0xFFFF;
				// 判断是否为BIG5,如果是则显示BIG5中文字
				if(tmp >= 0xA440 && tmp < 0xFFFF) {
					System.out.println("BIG5: " +
					(char)reader.read());
					// 重置ArrayInputStream的读取光标
					// 下次reader才会再重头读取数据
					byteArrayStream.reset();
				}
				else {
					// 将第二个字节推回串流
					pushbackInputStream.unread(array, 1, 1);
					// 显示ASCII范围的字符
					System.out.println("ASCII: " +
					(char)array[0]);
				}
			}
			pushbackInputStream.close();
		}
		catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("请指定文件名称");
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
}

假设的文本文件中有以下的文字:"这T是e一s个t测试" ,执行结果会是:

BIG5: 这 
ASCII: T 
BIG5: 是 
ASCII: e 
BIG5: 一 
ASCII: s 
BIG5: 个 
ASCII: t 
BIG5: 测 
BIG5: 试 
ASCII: ! 
EOF?

InputStreamReader可以用字节串流中取出字节数据,并进行字符处理动作,关于Reader、Writer相关子类别,之后会于各相关主题进行介绍。

其余未看:

ByteArrayInputStream、ByteArrayOutputStream;

CharArrayReader、CharArrayWriter;

PushbackReader;

http://www.iteedu.com//plang/java/javadiary/92.php

你可能感兴趣的:(Java学习笔记(7)——输入输出)