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;
本文转自ZH奶酪博客园博客,原文链接:http://www.cnblogs.com/CheeseZH/archive/2012/12/10/2811913.html,如需转载请自行联系原作者