下面将要说到的这些示例,所有的异常处理都被简化给了控制台。在实际项目中,建议加入更复杂的错误处理能力。
如果要打开一个文件,从文件中读取字符,那么可以使用 FileInputReader。但因为 使用 FileInputReader 读取字符时,会造成中文乱码。所以我们这里采用 FileInputStream,然后利用 InputStreamReader 设置编码格式。最后为了提高处理速度,这里把 InputStreamReader 的引用传给了 BufferedReader 构造器。因为 BufferedReader 也有 readLine() 方法,这样当 readLine() 返回 null 时,就说明已经到了这个文件的末尾,那么读取终止:
public class BufferedInputFile {
// Throw exceptions to console:
public static String read(String filename) throws IOException {
// Reading input by lines:
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream
(filename), "UTF-8"));
String s;
StringBuilder sb = new StringBuilder();
while ((s = in.readLine()) != null)
sb.append(s + "\n");
in.close();
return sb.toString();
}
public static void main(String[] args) throws IOException {
System.out.println(read("xxx"));
}
}
使用 BufferedInputFile.read() 的返回字符串,作为 StringReader 构造器的参数。然后调用 read() 每次读取一个字符,最后发送给控制台:
public class MemoryInput {
public static void main(String[] args) throws IOException {
StringReader in = new StringReader(BufferedInputFile.read("xxx"));
int c;
while ((c = in.read()) != -1)
System.out.println((char) c);
}
}
这里使用 DataInputStream,因为它是一个面向字节的类,所以我们这里用的是 InputStream 类,这个类可以以字节的形式读取任何数据:
public class FormattedMemoryInput {
public static void main(String[] args) throws IOException {
try {
DataInputStream in = new DataInputStream(new ByteArrayInputStream
(BufferedInputFile.read("xxx").getBytes()));
while (true)
System.out.print((char) in.readByte());
} catch (EOFException e) {
System.err.println("End of stream");
}
}
}
下面演示了一次一个字节地读取文件,并检测输入是否结束的方法:
public class TestEOF {
public static void main(String[] args) throws IOException {
DataInputStream in = new DataInputStream(new BufferedInputStream(new
FileInputStream("xxx")));
while (in.available() != 0) {
System.out.print((char) in.readByte());
}
}
}
这里使用了 available() 方法查看还有多少可供存取的字符,注意 available() 的工作方法会随着读取的媒介类型的不同而不同。对于文件,指的是整个文件;如果是其他类型的流,可能不是这样,所以要谨慎使用!
FileWriter 可以向文件写入数据,但会造成中文乱码,所以我们改用 OutputStreamWriter+FileOutputStream 的方式写入文件,我们还使用了 BufferedReader 将其包装起来以便提供缓冲输出的能力(缓冲会显著地增加 I/O 操作的性能)。我们使用 PrintWriter 以便提供格式化的能力:
public class BasicFileOutput {
static String file = "D:\\temp\\1\\1.txt";
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read
("D:\\temp\\1\\2.txt")));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new
FileOutputStream(file), "UTF-8")));
int lineCount = 1;
String s;
while ((s = in.readLine()) != null)
out.println(lineCount++ + ":" + s);//记录行号
out.close();
// Show the stored file:
System.out.println(BufferedInputFile.read(file));
}
}
Java SE5 为 PrintWriter 添加了一个辅助构造器,这样我们就可以不必添加装饰器而直接写入文件啦:
public class FileOutputShortcut {
static String file = "D:\\temp\\1\\1.txt";
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read("D:\\temp\\1\\2.txt")));
// Here's the shortcut:
PrintWriter out = new PrintWriter(file);
int lineCount = 1;
String s;
while ((s = in.readLine()) != null)
out.println(lineCount++ + ":" + s);
out.close();
// show the stored file:
System.out.println(BufferedInputFile.read(file));
}
}
可惜的是,其他常见的写入任务都没有这样的快捷方式,所以典型的 I/O 仍然会写的很长(因为加入了多个装饰器类,以便提供相应的能力)。
使用 DataOutputStream 写入数据,然后用 DataInputStream 读取数据,因为 DataOutputStream 和 DataInputStream 都是面向字节的,所以这里使用的是 InputSteam 和 OutputStream:
public class StoringAndRecoveringData {
public static void main(String[] args) throws IOException {
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new
FileOutputStream("D:\\temp\\1\\Data.txt")));
out.writeDouble(3.14159);
out.writeUTF("That was pi");
out.writeDouble(1.41413);
out.writeUTF("Square root of 2");
out.close();
DataInputStream in = new DataInputStream(new BufferedInputStream(new
FileInputStream("D:\\temp\\1\\Data.txt")));
System.out.println(in.readDouble());
// Only readUTF() will recover the Java-UTF String properly:
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
}
}
使用 DataOutputStream 写入的数据,都可以用 DataInputStream 准确地读取数据,这可以在跨平台的系统中使用。但我们必须使用 UTF-8 编码,所以在上述示例中,我们读写字符串用的是 writeUTF() 和 readUTF() 方法!UTF-8 将 ASCII 字符编码成单一字节的格式,非 ASCII 字符被编码为两到三个字节的格式,这样可以节省空间和带宽。字符串的长度存储在 UTF-8 字符串的前两个字节中。因为 writeUTF() 和 readUTF() 使用的是适用于 Java 的 UTF-8 变体,所以如果用一个非 Java 程序读取用 writeUTF() 写入的字符串时,需要编写一些特殊代码才能正确读取。
writeDouble() 会将 double 类型的数字存储到流中,然后再用 readDouble() 读取它。其他基本类型也有类似的方法可以读写。为了保证读取数据准确无误,我们必须知道流中的数据项的确切位置。因此,要么把文件中的数据定义为固定格式,要么将额外的信息保存在文件中,这样才能够正确解析以便确定数据的存放位置。所以,对象序列化和 XML 可能是更容易读写的、复杂的数据结构!
在 RandomAccessFile 类中,使用 seek() 可以在文件中移动,并修改文件某处的值。
必须事先知道文件的排版内容,才能正确地操作它。在 RandomAccessFile 类中,有读取基本类型和 UTF-8 字符串的各种方法:
public class UsingRandomAccessFile {
static String file = "rtest.dat";
static void display() throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "r");
for (int i = 0; i < 7; i++) {
System.out.println("Value " + i + ":" + rf.readDouble());
}
System.out.println(rf.readUTF());
rf.close();
}
public static void main(String[] args) throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "rw");
for (int i = 0; i < 7; i++) {
rf.writeDouble(i * 1.414);
}
rf.writeUTF("The end of the file");
rf.close();
display();
rf = new RandomAccessFile(file, "rw");
rf.seek(5 * 8);//寻找第 5 个 double 类型的值(double 类型为 8 字节长度)
rf.writeDouble(47.0001);
rf.close();
display();
}
}