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;