javaSE 基础笔记之输入/输出流
第十二章 输入/输出流
学习目标:
² I/O 包简介
² 掌握文件和过滤器
² 掌握 Reader 和 Writer
² 掌握二进制流的读写
² 掌握文本流的读写
一:Java 的 io 包
1:流是什么
简单的说:流是字节从源到目的地运行的轨迹。
次序是有意义的,字节会按照次序进行传递。
2:流有什么
两种基本的流是:输入流和输出流。你可以从输入流读,但你不能对它写。要从输入流 读取字节,必须有一个与这个流相关联的字符源。
在java.io包中,有一些流是结点流,即它们可以从一个特定的地方读写,例如磁盘或者一块内存。 其他流称作过滤器。 一个过滤器输入流是用一个到已存在的输入流的连接创建的。此后,当你试图从过滤输入流对象读时,它向你提供来自另一个输入流对象的字符。
2.1: IO包中基本流类
在 java.io 包中定义了一些流类。下图表明了包中的类层次。一些更公共的类将在后面介绍。
2.1.1: FileInputStream和FileOutputStream
这些类是结点流,而且正如这个名字所暗示的那样,它们使用磁盘文件。这些类的构造 函数允许你指定它们所连接的文件。要构造一个 FileInputStream,所关联的文件必须存在而 且是可读的。如果你要构造一个 FileOutputStream而输出文件已经存在,则它将被覆盖。
FileInputStream infile =new FileInputStream("myfile.dat");
FileOutputStream outfile =new FileOutputStream("results.dat");
2.1.2 BufferInputStream和BufferOutputStream
这些是过滤器流,它们可以提高 I/O 操作的效率。
2.1.3 DataInputStream和DataOutputStream
DataInputStream方法
- l-byte readByte()
- l-long readLong()
- l-double readDouble()
DataOutputStream方法
- l-void writeByte(byte)
- l-void writeLong(long)
- l-void writeDouble(double)
注意 DataInputStream和DataOutputStream的方法是成对的。 这些流都有读写字符串的方法,但不应当使用这些方法。它们已经被后面所讨论的reader和writer所取代。
2.1.4 PipedInputStream和PipedOutputStream
管道流用来在线程间进行通信。一个线程的 PipedInputStream 对象从另一个线程的 PipedOutputStream对象读取输入。要使管道流有用,必须有一个输入方和一个输出方。
3:InputStream与OutputStream流能干什么
3.1:InputStream
InputStream的作用是标志那些从不同起源地产生输入的类。这些起源地包括(每个都 有一个相关的InputStream子类):
(1) 字节数组
(2) String对象
(3) 文件
(4) “管道”,它的工作原理与现实生活中的管道类似:将一些东西置入一端,它们在 另一端出来。
(5) 一系列其他流,以便我们将其统一收集到单独一个流内。
(6) 其他起源地,如Internet连接等(将在本书后面的部分讲述)。
除此以外,FilterInputStream 也属于 InputStream的一种类型,用它可为“破坏器” 类提供一个基础类,以便将属性或者有用的接口同输入流连接到一起。这将在以后讨论。
ByteArrayInputStream 允许内存中的一个缓冲区作为 InputStream 使用 从中提取字 节的缓冲区作为一个数据源使用。通过将其同一个FilterInputStream对象连接,可提供一 个有用的接口。
StringBufferInputStream 将一个 String 转换成 InputStream 一个 String(字串)。 基础的实施方案实际采用一个 StringBuffer(字串缓冲)作为一个数据源使用。通过将其 同一个FilterInputStream对象连接可提供一个有用的接口。
FileInputStream 用于从文件读取信息 代表文件名的一个 String,或者一个 File 或 FileDescriptor 对象作为一个数据源使用。通过将其同一个 FilterInputStream对象连接, 可提供一个有用的接口。
PipedInputString 产生为相关的 PipedOutputStream 写的数据。实现了“管道化”的 概念。 PipedOutputStream 作为一个数据源使用。通过将其同一个 FilterInputStream 对 象连接,可提供一个有用的接口。
SequenceInputStream 将两个或更多的InputStream对象转换成单个InputStream使用 两个InputStream对象或者一个 Enumeration,用于 InputStream对象的一个容器/作为一 个数据源使用。通过将其同一个 FilterInputStream对象连接,可提供一个有用的接口
FilterInputStream 对作为破坏器接口使用的类进行抽象;那个破坏器为其他 InputStream类提供了有用的功能。
3.1.1:InputStream方法
三个 read方法
- l-int read()
- l-int read(byte [])
- l-int read(byte[], int ,int )
这三个方法提供对输入管道数据的存取。简单读方法返回一个 int值,它包含从流 里读出的一个字节或者-1,其中后者表明文件结束。其它两种方法将数据读入到字节数 组中,并返回所读的字节数。第三个方法中的两个 int参数指定了所要填入的数组的子 范围。
注-考虑到效率,总是在实际最大的块中读取数据。
- l-void close()
你完成流操作之后,就关闭这个流。如果你有一个流所组成的栈,使用过滤器流, 就关闭栈顶部的流。这个关闭操作会关闭其余的流。
- l-int available()
这个方法报告立刻可以从流中读取的字节数。在这个调用之后的实际读操作可能返 回更多的字节数。
- l-skip(long)
这个方法丢弃了流中指定数目的字符。
- l-boolean markSupported()
- l-void mark(int)
- l-void reset()
如果流支持 “回放” 操作, 则这些方法可以用来完成这个操作。如果 mark()和 reset() 方法可以在特定的流上操作,则 markSupported()方法将返回 ture。mark(int)方法用 来指明应当标记流的当前点和分配一个足够大的缓冲区, 它最少可以容纳参数所指定数 量的字符。在随后的read()操作完成之后,调用 reset()方法来返回你标记的输入点。
3.2 OutputStream
这一类别包括的类决定了我们的输入往何处去:一个字节数组(但没有 String;假定 我们可用字节数组创建一个) ;一个文件;或者一个“管道”。
除此以外,FilterOutputStream 为“破坏器”类提供了一个基础类,它将属性或者有 用的接口同输出流连接起来。这将在以后讨论。
ByteArrayOutputStream 在内存中创建一个缓冲区。 我们发送给流的所有数据都会置入 这个缓冲区。 可选缓冲区 的初 始 大小用 于 指 出 数 据 的 目 的 地 。 若 将其同 FilterOutputStream对象连接到一起,可提供一个有用的接口。
FileOutputStream 将信息发给一个文件 用一个String 代表文件名,或选用一个File 或FileDescriptor对象用于指出数据的目的地。 若将其同 FilterOutputStream对象连接到 一起,可提供一个有用的接口。
PipedOutputStream 我们写给它的任何信息都会自动成为相关的 PipedInputStream 的 输出。实现了“管道化”的概念 PipedInputStream为多线程处理指出自己数据的目的地将 其同FilterOutputStream对象连接到一起,便可提供一个有用的接口。
FilterOutputStream 对作为破坏器接口使用的类进行抽象处理;那个破坏器为其他 OutputStream 类提供了有用的功能。
3.2.1:OutputStream方法
三个基本的 write()方法
- l- void write(int)
- l- void write(byte [])
- l- void write(byte [], int, int)
这些方法写输出流。和输入一样,总是尝试以实际最大的块进行写操作。
- l-void close()
当你完成写操作后,就关闭输出流。如果你有一个流所组成的栈,就关闭栈顶部的流。这个关闭操作会关闭其余的流。
- l-void flush()
有时一个输出流在积累了若干次之后才进行真正的写操作。flush()方法允许你 强制执行写操作。
4: URL输入流
除了基本的文件访问之外,Java技术提供了使用统一资源定位器(URL)来访问网络上的 文件。当你使用 Applet 的 getDocumentBase()方法来访问声音和图象时,你已经隐含地使 用了URL 对象。
String imageFile = new String ("images/Duke/T1.gif");
images[0] = getImage(getDocumentBase(), imageFile);
然而,你必须象下面的程序那样提供一个直接的URL
1 java.net.URL imageSource; 2 3 try { 4 5 imageSource = new URL("http://mysite.com/~info"); 6 7 } catch ( MalformedURLException e) {} 8 9 images[0] = getImage(imageSource, "Duke/T1.gif");
4.1 打开一个输入流
你可以通过存储文档基目录下的一个数据文件来打开一个合适的 URL输入流。
1 InputStream is = null; 2 3 String datafile = new String("Data/data.1-96"); 4 5 byte buffer[] = new byte[24]; 6 7 try { 8 9 // new URL throws a MalformedURLException 10 11 // URL.openStream() throws an IOException 12 13 is = (new URL(getDocumentBase(), 14 15 datafile)).openStream(); 16 17 } catch (Exception e) {}
现在,你可以就象使用 FileInputStream对象那样来用 it 来读取信息:
1 try { 2 3 is.read(buffer, 0, buffer.length); 4 5 } catch (IOException e1) {}
警告-记住大多数用户进行了浏览器的安全设置,以防止 Applet存取文件。
5: Reader和Writer
Java中的Reader 和Writer 的结构如下图
5.1 Unicode
Java技术使用 Unicode来表示字符串和字符,而且它提供了 16 位版本的流,以便用类 似的方法来处理字符。这些 16 位版本的流称为读者和作者。和流一样,它们都在 java.io 包中。
读者和作者中最重要的版本是 InputStreamReader 和 OutputStreamWriter。这些类用 来作为字节流与读者和作者之间的接口。
当你构造一个 InputStreamReader 或 OutputStreamWriter 时,转换规则定义了 16 位 Unicode 和其它平台的特定表示之间的转换。
缺省情况下,如果你构造了一个连接到流的读者和作者,那么转换规则会在使用缺省平 台所定义的字节编码和Unicode之间切换。 在英语国家中, 所使用的字节编码是: ISO 8859-1。
你可以使用所支持的另一种编码形式来指定其它的字节编码。在native2ascii工具中, 你可以找到一个关于所支持的编码形式的列表。 使用转换模式,Java 技术能够获得本地平台字符集的全部灵活性,同时由于内部使用 Unicode,所以还能保持平台独立性。
5.2 缓冲读者和写者
因为在各种格式之间进行转换和其它 I/O 操作很类似,所以在处理大块数据时效率最 高。在 InputStreamReader 和 OutputStreamWriter 的结尾链接一个 BufferedReader 和 BufferedWriter 是一个好主意。记住对BufferedWriter 使用 flush()方法。
5.3 读入字符串输入
下面这个例子说明了从控制台标准输入读取字符串所应当使用的一个技术。
1 import java.io.*; 2 3 public class CharInput { 4 5 public static void main (String args[]) throws 6 7 java.io.IOException { 8 9 String s; 10 11 InputStreamReader ir; 12 13 BufferedReader in; 14 15 ir = new InputStreamReader(System.in); 16 17 in = new BufferedReader(ir); 18 19 while ((s = in.readLine()) != null) { 20 System.out.println("Read: " + s); 21 } 22 23 } 24 25 }
5.4 使用其它字符转换
如果你需要从一个非本地(例如, 从连接到一个不同类型的机器的网络连接读取)的字符 编码读取输入,你可以象下面这个程序那样,使用显式的字符编码构造
ir=new InputStreamReader(System.in, “iso8859-1”);
对于已有的字符串,还可以这样进行编码转换:
String s = new String(str.getBytes(“ISO8859-1”),“GB2312”);
注意:
(1) :如果你通过网络连接读取字符,就应该使用这种形式。否则,你的程序会总是试图将 所读取的字符当作本地表示来进行转换,而这并不总是正确的。ISO 8859-1是映射到 ASCII 的Latin-1编码模式。
(2) :用于表示中文的编码方式,常用的是“GB2312”或者“GBK”
6: 串行化(序列化)
将一个对象存放到某种类型的永久存储器上称为保持。如果一个对象可以被存放到磁盘 或磁带上,或者可以发送到另外一台机器并存放到存储器或磁盘上,那么这个对象就被称为 可保持的。
java.io.Serializable 接口没有任何方法,它只作为一个“标记者” ,用来表明实现了这个 接口的类可以考虑串行化。类中没有实现 Serializable的对象不能保存或恢复它们的状态。
6.1 对象图
当一个对象被串行化时,只有对象的数据被保存;方法和构造函数不属于串行化流。如 果一个数据变量是一个对象,那么这个对象的数据成员也会被串行化。树或者对象数据的结 构,包括这些子对象,构成了对象图。
因为有些对象类所表示的数据在不断地改变,所以它们不会被串行化;例如, java.io.FileInputStream 、java.io.FileOutputStream和 java.lang.Thread等流。如果一个可串行 化对象包含对某个不可串行化元素的引用,那么整个串行化操作就会失败,而且会抛出一个NotSerializableException。
如果对象图包含一个不可串行化的引用, 只要这个引用已经用 transient关键字进行了标 记,那么对象仍然可以被串行化。
public class MyClass implements Serializable {
public transient Thread myThread;
private String customerID;
private int total;
域存取修饰符对于被串行化的对象没有任何作用。写入到流的数据是字节格式,而且字
符串被表示为 UTF(文件系统安全的通用字符集转换格式)。transient 关键字防止对象被串行
化。
public class MyClass implements Serializable {
public transient Thread myThread;
private transient String customerID;
private int total;
7: 对象流的基本读写示例
7.1 写
对一个文件流读写对象是一个简单的过程。考虑如下代码段,它将一个 java.util.Data 对象的实例发送到一个文件:
1 public class SerializeDate { 2 3 SerializeDate() { 4 5 Date d = new Date (); 6 7 try { 8 9 FileOutputStream f = new 10 11 FileOutputStream("date.ser"); 12 13 ObjectOutputStream s = new 14 15 ObjectOutputStream(f); 16 17 s.writeObject (d); 18 19 f.close (); 20 21 } catch (IOException e) { 22 23 e.printStackTrace (); 24 } 25 26 } 27 28 public static void main (String args[]) { 29 30 new SerializeDate(); 31 32 } 33 34 }
7.2 读
读对象和写对象一样简单,只需要说明一点-readObject()方法将流作为一个 Object 类 型返回,而且在使用那个类的方法之前,必须把它转换成合适的类名。
1 public class UnSerializeDate { 2 3 UnSerializeDate () { 4 5 Date d = null; 6 7 try { 8 9 FileInputStream f = new 10 11 FileInputStream("date.ser"); 12 13 ObjectInputStream s = new 14 15 ObjectInputStream(f); 16 17 d = (Date) s.readObject (); 18 19 f.close (); 20 21 } catch (Exception e) { 22 23 .e.printStackTrace (); 24 } 25 26 System.out.println("Unserialized Date object from date.ser"); 27 System.out.println("Date: "+d); 28 29 } 30 31 32 33 public static void main (String args[]) { 34 new UnSerializeDate(); 35 36 } 37 }
二:文件和过滤器
1: File操作
File类是IO 中文件操作最常用的类。
File类有一个欺骗性的名字——通常会认为它对付的是一个文件,但实情并非如此。它 既代表一个特定文件的名字,也代表目录内一系列文件的名字。若代表一个文件集,便可用 list()方法查询这个集,返回的是一个字串数组。之所以要返回一个数组,而非某个灵活的集 合类,是因为元素的数量是固定的。而且若想得到一个不同的目录列表,只需创建一个不同 的 File 对象即可。事实上,“FilePath” (文件路径)似乎是一个更好的名字。本节将向大家完整地例示如何使用这个类,其中包括相关的 FilenameFilter(文件名过滤器)接口。
1.1:创建一个新的File对象
File类提供了若干处理文件和获取它们基本信息的方法。
File myFile;
myFile = new File("mymotd");
myFile = new File("/", "mymotd");
// more useful if the directory or filename is
// a variable
File myDir = new File("/");
myFile = new File(myDir, "mymotd");
你所使用的构造函数经常取决于你所使用的其他文件对象。例如,如果你在你的应用程 序中只使用一个文件,那么就会使用第一个构造函数。如果你使用一个公共目录中的若干文 件,那么使用第二个或者第三个构造函数可能更容易。
File 类提供了独立于平台的方法来操作由本地文件系统维护的文件。然而它不允许你 存取文件的内容。
注-你可以 使用一个 File 对象来代 替一个 String 作 为 FileInputStream 和 FileOutputStream 对象的构造函数参数。这是一种推荐方法,因为它独立于本地文件系统 的约定。
1.2:目录列表器
File类并不仅仅是对现有目录路径、文件或者文件组的一个表示。亦可用一个File对象 新建一个目录,甚至创建一个完整的目录路径——假如它尚不存在的话。亦可用它了解文件 的属性(长度、上一次修改日期、读/写属性等),检查一个 File对象到底代表一个文件还 是一个目录,以及删除一个文件等等。下列程序完整展示了如何运用 File 类剩下的这些方法:
// MakeDirectories.java
1 import java.io.*; 2 3 public class MakeDirectories { 4 5 private final static String usage = 6 7 "Usage:MakeDirectories path1 ...\n" + 8 9 "Creates each path\n" + 10 11 "Usage:MakeDirectories -d path1 ...\n" + 12 13 "Deletes each path\n" + 14 15 "Usage:MakeDirectories -r path1 path2\n" + 16 17 "Renames from path1 to path2\n"; 18 19 private static void usage() { 20 21 System.err.println(usage); 22 23 System.exit(1); 24 25 } 26 27 private static void fileData(File f) { 28 29 System.out.println( 30 31 "Absolute path: " + f.getAbsolutePath() + 32 33 "\n Can read: " + f.canRead() + 34 35 "\n Can write: " + f.canWrite() + 36 37 "\n getName: " + f.getName() + 38 39 "\n getParent: " + f.getParent() + 40 41 "\n getPath: " + f.getPath() + 42 43 "\n length: " + f.length() + 44 45 "\n lastModified: " + f.lastModified()); 46 47 if(f.isFile()) 48 49 System.out.println("it's a file"); 50 51 else if(f.isDirectory()) 52 53 System.out.println("it's a directory"); 54 55 } 56 57 public static void main(String[] args) { 58 59 if(args.length < 1) usage(); 60 61 if(args[0].equals("-r")) { 62 63 if(args.length != 3) usage(); 64 65 File 66 67 old = new File(args[1]), 68 69 rname = new File(args[2]); 70 71 old.renameTo(rname); 72 73 fileData(old); 74 75 fileData(rname); 76 77 return; // Exit main 78 79 } 80 81 int count = 0; 82 83 boolean del = false; 84 85 if(args[0].equals("-d")) { 86 87 count++; 88 89 del = true; 90 91 } 92 93 for( ; count < args.length; count++) { 94 95 File f = new File(args[count]); 96 97 if(f.exists()) { 98 99 System.out.println(f + " exists"); 100 101 if(del) { 102 103 System.out.println("deleting..." + f); 104 105 f.delete(); 106 107 } 108 109 } 110 111 else { // Doesn't exist 112 113 if(!del) { 114 115 f.mkdirs(); 116 117 System.out.println("created " + f); 118 119 } 120 121 } 122 123 fileData(f); 124 125 } 126 127 } 128 129 }
在fileData()中,可看到应用了各种文件调查方法来显示与文件或目录路径有关的信息。 main()应用的第一个方法是 renameTo(),利用它可以重命名(或移动)一个文件至一个全新的路径(该路径由参数决定),它属于另一个 File对象。这也适用于任何长度的目录。
若试验上述程序,就可发现自己能制作任意复杂程度的一个目录路径,因为mkdirs()会 帮我们完成所有工作。
1.3:文件测试和工具
当你创建一个 File对象时,你可以使用下面任何一种方法来获取有关文件的信息:
文件名
- l- String getName()
- l- String getPath()
- l- String getAbsolutePath()
- l- String getParent()
- l- boolean renameTo(File newName)
文件测试
- l- boolean exists()
- l- boolean canWrite()
- l- boolean canRead()
- l- boolean isFile()
- l- boolean isDirectory()
- l- boolean isAbsolute()
通用文件信息和工具
- l- long lastModified()
- l- long length()
- l- boolean delete()
目录工具
- l- boolean mkdir()
- l- String[] list()
1.4:随机存取文件
你经常会发现你只想读取文件的一部分数据,而不需要从头至尾读取整个文件。你可能 想访问一个作为数据库的文本文件,此时你会移动到某一条记录并读取它的数据,接着移动 到另一个记录,然后再到其他记录――每一条记录都位于文件的不同部分。Java 编程语言提供了一个 RandomAccessFile类来处理这种类型的输入输出。
你可以用如下两种方法来打开一个随机存取文件:
- l 用文件名
myRAFile = new RandomAccessFile(String name, String mode);
- l 用文件对象
myRAFile = new RandomAccessFile(File file, String mode);
mode参数决定了你对这个文件的存取是只读(r)还是读/写(rw)。 例如,你可以打开一个打开一个数据库文件并准备更新:
RandomAccessFile myRAFile;
myRAFile = new RandomAccessFile("db/stock.dbf","rw");
存取信息
RandomAccessFile对象按照与数据输入输出对象相同的方式来读写信息。 你可以访问在 DataInputStrem和DataOutputStream中所有的 read()和 write()操作。 Java 编程语言提供了若干种方法,用来帮助你在文件中移动。
- l- long getFilePointer();
返回文件指针的当前位置。
- l- void seek(long pos);
设置文件指针到给定的绝对位置。这个位置是按照从文件开始的字节偏移量
给出的。位置 0标志文件的开始。
- l- long length()
返回文件的长度。位置 length()标志文件的结束。
添加信息
你可以使用随机存取文件来得到文件输出的添加模式。
myRAFile = new RandomAccessFile("java.log","rw");
myRAFile.seek(myRAFile.length());
// Any subsequent write()s will be appended to the file
2: 过滤器
FilterInputStream和FilterOutputStream(这两个名字不十分直观)提供了相应的装 饰器接口,用于控制一个特定的输入流(InputStream)或者输出流(OutputStream)。它们分别是从 InputStream和 OutputStream 衍生出来的。此外,它们都属于抽象类,在理论上为我们与一个流的不同通信手段都提供了一个通用的接口。事实上,FilterInputStream 和 FilterOutputStream只是简单地模仿了自己的基础类,它们是一个装饰器的基本要求。
2.1 通过 FilterInputStream从InputStream里读入数据
FilterInputStream 类要完成两件全然不同的事情。其中,DataInputStream 允许我们 读取不同的基本类型数据以及String对象(所有方法都以“read”开头,比如readByte(),readFloat()等等)。伴随对应的 DataOutputStream,我们可通过数据“流”将基本类型的数据从一个地方搬到另一个地方。若读取块内的数据,并自己进行解析,就不需要用到DataInputStream。但在其他许多情况下,我们一般都想用它对自己读入的数据进行自动格式化。
剩下的类用于修改 InputStream的内部行为方式:是否进行缓冲,是否跟踪自己读入的 数据行,以及是否能够推回一个字符等等。后两种类看起来特别象提供对构建一个编译器的支持(换言之,添加它们为了支持Java编译器的构建),所以在常规编程中一般都用不着它们。
也许几乎每次都要缓冲自己的输入,无论连接的是哪个 IO 设备。所以IO 库最明智的做法就是将未缓冲输入作为一种特殊情况处理,同时将缓冲输入接纳为标准做法。
2.2 通过 FilterOutputStream向OutputStream里写入数据
与 DataInputStream 对应的是 DataOutputStream,后者对各个基本数据类型以及 String对象进行格式化,并将其置入一个数据“流”中,以便任何机器上的 DataInputStream 都能正常地读取它们。所有方法都以“wirte”开头,例如writeByte(),writeFloat()等等。
若想进行一些真正的格式化输出,比如输出到控制台,请使用 PrintStream。利用它可以 打印出所有基本数据类型以及 String 对象,并可采用一种易于查看的格式。这与DataOutputStream 正好相反,后者的目标是将那些数据置入一个数据流中,以便DataInputStream能够方便地重新构造它们。System.out静态对象是一个PrintStream。
PrintStream内两个重要的方法是 print()和 println()。它们已进行了覆盖处理,可打印出所有数据类型。print()和println()之间的差异是后者在操作完毕后会自动添加一个新行。
BufferedOutputStream属于一种“修改器” ,用于指示数据流使用缓冲技术,使自己不必每次都向流内物理性地写入数据。通常都应将它应用于文件处理和控制器 IO。
三:I/O 流的基本应用
1: 输入流
1.1:缓冲的输入文件
程序示例
1 import java.io.*; 2 3 public class Test 4 5 { 6 7 public static void main(String args[]) 8 9 { 10 11 String currentLine; 12 13 try 14 15 { 16 17 DataInputStream in = 18 19 new DataInputStream( 20 21 new BufferedInputStream( 22 23 new FileInputStream("Test.java") 24 25 ) 26 27 ); 28 29 while ((currentLine = in.readLine()) != null) 30 31 System.out.println(currentLine); 32 33 } 34 35 catch (IOException e) 36 37 { 38 39 System.err.println("Error: " + e); 40 41 } // End of try/catch structure. 42 43 } // End of method: main 44 45 } // End of class
为打开一个文件以便输入, 需要使用一个FileInputStream, 同时将一个 String 或 File 对象作为文件名使用。为提高速度,最好先对文件进行缓冲处理,从而获得用于一个 BufferedInputStream 的构建器的结果句柄。为了以格式化的形式读取输入数据,我们将那个结果句柄赋给用于一个 DataInputStream 的构建器。DataInputStream 是我们的最终(final)对象,并是我们进行读取操作的接口。
在这个例子中,只用到了 readLine()方法,但理所当然任何 DataInputStream 方法都可以采用。一旦抵达文件末尾,readLine()就会返回一个 null(空),以便中止并退出 while循环。
1.2:格式化内存输入
StringBufferInputStream 的接口是有限的,所以通常需要将其封装到一个 DataInputStream 内,从而增强它的能力。然而,若选择用 readByte()每次读出一个字符, 那么所有值都是有效的, 所以不可再用返回值来侦测何时结束输入。相反, 可用 available()
方法判断有多少字符可用。下面这个例子展示了如何从文件中一次读出一个字符:
// TestEOF.java
1 import java.io.*; 2 3 public class TestEOF { 4 5 public static void main(String[] args) { 6 7 try { 8 9 DataInputStream in = 10 11 new DataInputStream( 12 13 new BufferedInputStream( 14 15 new FileInputStream("TestEof.java"))); 16 17 while(in.available() != 0) 18 19 System.out.print((char)in.readByte()); 20 21 } catch (IOException e) { 22 23 System.err.println("IOException"); 24 25 } 26 27 } 28 29 }
注意取决于当前从什么媒体读入,avaiable()的工作方式也是有所区别的。它在字面上 意味着“可以不受阻塞读取的字节数量”。对一个文件来说,它意味着整个文件。但对一个不同种类的数据流来说,它却可能有不同的含义。因此在使用时应考虑周全。
为了在这样的情况下侦测输入的结束,也可以通过捕获一个违例来实现。然而,若真的用违例来控制数据流,却显得有些大材小用。
2 输出流
两类主要的输出流是按它们写入数据的方式划分的:一种按人的习惯写入,另一种为了以后由一个 DataInputStream 而写入。RandomAccessFile 是独立的,尽管它的数据格式兼容于DataInputStream和DataOutputStream。
2.1:保存与恢复数据
PrintStream能格式化数据,使其能按我们的习惯阅读。但为了输出数据,以便由另一个数据流恢复,则需用一个 DataOutputStream 写入数据,并用一个 DataInputStream 恢复(获取)数据。当然,这些数据流可以是任何东西,但这里采用的是一个文件,并进行了缓冲处理,以加快读写速度。
注意字串是用 writeBytes()写入的,而非 writeChars()。若使用后者,写入的就是 16 位 Unicode 字符。由于 DataInputStream 中没有补充的“readChars”方法,所以不得不用readChar()每次取出一个字符。所以对ASCII 来说,更方便的做法是将字符作为字节写入,在后面跟随一个新行;然后再用readLine()将字符当作普通的 ASCII行读回。
writeDouble()将 double 数字保存到数据流中,并用补充的 readDouble()恢复它。但为了保证任何读方法能够正常工作,必须知道数据项在流中的准确位置,因为既有可能将保存的double数据作为一个简单的字节序列读入,也有可能作为 char或其他格式读入。所以必须要么为文件中的数据采用固定的格式,要么将额外的信息保存到文件中,以便正确判断数据的存放位置。
2.2:读写随机访问文件
示例代码
1 import java.io.*; 2 3 public class Test 4 5 { 6 7 public static void main(String args[]) 8 9 { 10 11 try 12 13 { 14 15 RandomAccessFile f =new RandomAccessFile(“test.txt","rw"); 16 17 int i; 18 19 double d; 20 21 for (i= 0; i< 10; i++){ 22 23 f.writeDouble(3.14f*i); 24 25 } 26 27 f.seek(16); 28 29 f.writeDouble(0); 30 31 f.seek(0); 32 33 for (i= 0; i< 10; i++) 34 35 { 36 37 d=f.readDouble(); 38 39 System.out.println("["+i+"]: "+d); 40 41 } 42 43 f.close(); 44 45 } 46 47 catch (IOException io) 48 49 { 50 51 System.out.println(io); 52 53 System.exit(-1); 54 55 }// End of try/catch structure. 56 57 } // End of method: main 58 59 } // End of class:
正如早先指出的那样, RandomAccessFile与 IO 层次结构的剩余部分几乎是完全隔离的,尽管它也实现了DataInput和DataOutput接口。 所以不可将其与 InputStream及 OutputStream子类的任何部分关联起来。尽管也许能将一个 ByteArrayInputStream当作一个随机访问元素对待,但只能用RandomAccessFile打开一个文件。必须假定 RandomAccessFile已得到了正确的缓冲,因为我们不能自行选择。
可以自行选择的是第二个构建器参数:可决定以“只读”(r)方式或“读写”(rw)方式打开一个RandomAccessFile文件。
使用 RandomAccessFile 的时候,类似于组合使用 DataInputStream 和 DataOutputStream(因为它实现了等同的接口)。除此以外,还可看到程序中使用了 seek(),以便在文件中到处移动,对某个值作出修改。
3: 快捷文件处理
由于以前采用的一些典型形式都涉及到文件处理, 所以大家也许会怀疑为什么要进行那么多的代码输入——这正是装饰器方案一个缺点。 本部分将向大家展示如何创建和使用典型文件读取和写入配置的快捷版本。为了将每个类都添加到库内,只需将其置入适当的目录,并添加对应的package语句即可。
3.1:快速文件输入
若想创建一个对象,用它从一个缓冲的 DataInputStream中读取一个文件,可将这个过程封装到一个名为InFile 的类内。如下所示:
// InFile.java
1 import java.io.*; 2 3 public class InFile extends DataInputStream { 4 5 public InFile(String filename) 6 7 throws FileNotFoundException { 8 9 super( 10 11 new BufferedInputStream( 12 13 new FileInputStream(filename))); 14 15 } 16 17 public InFile(File file) 18 19 throws FileNotFoundException { 20 21 this(file.getPath()); 22 23 } 24 25 }
无论构建器的 String 版本还是 File 版本都包括在内,用于共同创建一个FileInputStream。
就象这个例子展示的那样,现在可以有效减少创建文件时由于重复强调造成的问题。
3.2:快速输出格式化文件
亦可用同类型的方法创建一个PrintStream,令其写入一个缓冲文件。
// PrintFile.java
1 import java.io.*; 2 3 public class PrintFile extends PrintStream { 4 5 public PrintFile(String filename) 6 7 throws IOException { 8 9 super( 10 11 new BufferedOutputStream( 12 13 new FileOutputStream(filename))); 14 15 } 16 17 public PrintFile(File file) 18 19 throws IOException { 20 21 this(file.getPath()); 22 23 } 24 25 }
注意构建器不可能捕获一个由基础类构建器“掷”出的违例。
3.3:快速输出数据文件
最后,利用类似的快捷方式可创建一个缓冲输出文件,用它保存数据(与由人观看的数据格式相反):
// OutFile.java
1 package com.bruceeckel.tools; 2 3 import java.io.*; 4 5 public class OutFile extends DataOutputStream { 6 7 public OutFile(String filename) 8 9 throws IOException { 10 11 super( 12 13 new BufferedOutputStream( 14 15 new FileOutputStream(filename))); 16 17 } 18 19 public OutFile(File file) 20 21 throws IOException { 22 23 this(file.getPath()); 24 25 } 26 27 }
4:从标准输入中读取数据
以Unix首先倡导的“标准输入” 、 “标准输出”以及“标准错误输出”概念为基础,Java 提供了相应的 System.in,System.out 以及 System.err。贯这一整本书,大家都会接触到如何用System.out进行标准输出,它已预封装成一个PrintStream 对象。System.err 同样是一个 PrintStream,但 System.in是一个原始的 InputStream,未进行任何封装处理。这意味着尽管能直接使用 System.out 和 System.err,但必须事先封装 System.in,否则不能从中读取数据。
典型情况下,我们希望用 readLine()每次读取一行输入信息,所以需要将 System.in 封装到一个 DataInputStream 中。这是 Java 1.0 进行行输入时采取的“老”办法。在本章 稍后,大家还会看到 Java 1.1 的解决方案。下面是个简单的例子,作用是回应我们键入的每一行内容:
// Echo.java
1 import java.io.*; 2 3 public class Echo { 4 5 public static void main(String[] args) { 6 7 DataInputStream in = 8 9 new DataInputStream( 10 11 new BufferedInputStream(System.in)); 12 13 String s; 14 15 try { 16 17 while((s = in.readLine()).length() != 0) 18 19 System.out.println(s); 20 21 // An empty line terminates the program 22 23 } catch(IOException e) { 24 25 e.printStackTrace(); 26 27 } 28 29 } 30 31 }
之所以要使用 try块,是由于readLine()可能“掷”出一个 IOException。注意同其他大多数流一样,也应对System.in进行缓冲。由于在每个程序中都要将System.in 封装到一个DataInputStream内, 所以显得有点不方便。但采用这种设计方案,可以获得最大的灵活性。
练习实践课:
本章内容为IO操作, 实践重点:
-
- l- IO 基础类
- l- 文件操作,包括文件的建立、写入、读出、删除等
程序 1:
文件操作
需求:文件的建立、写入、读出、删除。
目标:
1、 目录的建立;
2、 文件的建立;
3、 文件内容写入;
4、 文件内容读取。
程序:
//: FileProcess.java
1 package com.useful.java.part6; 2 3 import java.io.*; 4 5 public class FileProcess{ 6 7 public FileProcess(){ 8 9 } 10 11 public static void main(String[] args){ 12 13 FileProcess process=new FileProcess(); 14 15 String dirname="c:/testdir"; 16 17 String filename="a.txt"; 18 19 process.createDir(dirname); 20 21 process.createFile(dirname+"/"+filename); 22 23 process.writeFile(dirname+"/"+filename); 24 25 process.readFile(dirname+"/"+filename); 26 27 process.writeTxt(dirname+"/testtext.txt"); 28 29 } 30 31 public void createDir(String dirname){ 32 33 File file=new File(dirname); 34 35 if(file.exists()==false){ 36 37 file.mkdirs(); 38 39 } 40 41 } 42 43 public void createFile(String filename){ 44 45 try{ 46 47 File file=new File(filename); 48 49 file.createNewFile(); 50 51 } 52 53 catch(Exception e){ 54 55 e.printStackTrace(); 56 57 } 58 59 } 60 61 public void writeFile(String filename){ 62 63 try{ 64 65 File file=new File(filename); 66 67 FileOutputStream output=new FileOutputStream(file); 68 69 output.write("i am test.".getBytes()); 70 71 output.close(); 72 73 } 74 75 catch(Exception e){ 76 77 e.printStackTrace(); 78 79 } 80 81 } 82 83 public void readFile(String filename){ 84 85 try{ 86 87 File file=new File(filename); 88 89 FileInputStream input=new FileInputStream(file); 90 91 byte byteValues[]=new byte[(int)file.length()]; 92 93 input.read(byteValues); 94 95 input.close(); 96 97 System.out.println("file content is "+(new String(byteValues))); 98 99 } 100 101 catch(Exception e){ 102 103 e.printStackTrace(); 104 105 } 106 107 } 108 109 public void writeTxt(String filename){ 110 111 try{ 112 113 File file=new File(filename); 114 115 Writer writer=new FileWriter(file); 116 117 writer.write("Holen ,Holen"); 118 119 writer.close(); 120 121 } 122 123 catch(Exception e){ 124 125 e.printStackTrace(); 126 127 } 128 129 } 130 131 }
说明:
1、 本程序将在C盘根目录下,建立 testdir 目录, 并在此目录下建立两个文本文件;
注意上例中,往文件中写入内容的方式,一种是用流的方式,一种是文本方式。
作业
1:利用输入输出流编写一个程序,可以实现文件复制的功能,程序的命令行参数的形
式及操作功能均类似于DOS中的 copy命令
2:为前面完成的地址本程序添加文件的操作,把存放在内存集合中的数据存放到文件
中,形成用Swing完成表现层,集合完成逻辑层,I/O 完成数据存储的结构。

浙公网安备 33010602011771号