输入与输出I/0:
此章总结的主要思想是理解各个类的机制,整理各个接口的用途,以便加深印象,在实际中需要的时候能想到最合适的方法。
一、 File类
File类是IO包中唯一代表磁盘本身信息的对象。通过调用File类提供的各种方法,我们能够创建、删除、重命名文件,判断文件的读写权限是否存在,设置和查询文件的最近修改时间。
File类对象的list方法可以返回目录中所有的子目录和文件名。
File类对象的delete方法删除有File对象的路径表示的磁盘文件或目录,如果要删除的对象是目录,该目录中的内容必须为空。
二、 RandomAccessFile类:
该类提供了众多的文件访问方法,支持随机访问方式,该对象中有个叫指示器的标志,可以跳转到文件的任意位置,都是从该指示器所指示的当前位置开始读写的。
RandomAccessFile类仅限于操作文件,不能访问其他的I/O设备,如网络、内存映像等RandomAccessFile类有两种构造方法:
1、new RandomAccessFile(file,"rw"); // r读方式,w写方式
2、new RandomAccessFile(file,"r"); // r只读方式
利用RandomAccessFile类的读写方法读写文件的时候,共享一个文件指示器,即只有一个文件指示器。
三、 数据流、节点流和过滤流类的理解
数据流是一串连续不断的数据的集合,就像水管里的水流,在水管的一端一点一点的供水,而在水管的另一端看到的是一股连续不断的水流。这个是官方解释,个人理解有点类似黑匣子,不关注黑匣子里面的状况,只看进口和出口。
IO流分为:节点流类和过滤流类(也叫处理流类或包装类),程序用于直接操作目标设备所对应的类就叫做节点流类。程序间接调用节点流类的就是过滤流类。
四、 InputStream和OutputStream ;
都是抽象流类,并没有对应到具体的I/O设备,只描述了所有的流设备的共性。
InputStream的主要方法(skip/mark/reset等方法某些子类不一定能用):
int read(),读取流,并返回读取的字节数,如果读到了流末尾,则返回-1;
void mark(int readlimit): 用于在输入流中建立一个标记,当建立mark标记之后,该方法接受参数readlimit,即从mark标记开始所能读取的最 多的字节数。
void close():在完成流对象读取之后,不再需要该流,此时需要调用该方法来关闭流即与流相关的资源(Java垃圾回收器只能管理程序中的类的实例对象,没法去管理系统产生的资源,所以需要用close方法通知系统释放其自身产生的资源)。
OutputStream类的方法:
void flush():将内存缓冲区中的内容彻底的清空,并输出到目标IO设备中。应用程序与I/O设备之间有内存缓冲区相隔,这是由于设备速度不一致导致的,只有当内存缓冲区全部载满之后,才会一起写到I/O设备之中。使用内存缓冲区有作用。这个方法非常有用,可以用于将即时操作的信息即时更新备份,有利于检查Bug;
五、 FileInputStream、FileOutputStream类:
分别用来创建磁盘文件的输入流和输出流对象,通过它们的构造函数来制定文件路径和文件名。
对于同一个磁盘文件创建FileInputStream类对象时支持两种方式:
1、FileInputStream inFile = new FileInputStream("完整文件名(包括路径)");
2、File file = new File("完整文件名(包括路径)");
FileInputStream infile = new FileInputStream(file);
其中,第二种方法支持对文件对象file的一系列操作,如:文件存在性的检查、文件是否为目录、文件是否可写……
· 创建FileOutputStream类对象的时候,可以指定不存在的文件名,但是不能指定一个已经被其他程序打开的文件,因为其他程序打开了该文件之后,会给文件上锁,从而无法创建FileOutputStream对象。
六、 Reader、Write类:
Reader 和 Writer类主要用于读写文本文件的内容,而OutputStream和InputStream则用于读写二进制文件。
在对比FileOutputStream和FileWriter对象之后,可以知道:
1、FileOutputStream中的write(byte b)方法,写入单个字节的时候,不会调用flush方法来讲数据直接刷新硬盘的文件上。但是,在写入write(byte[] b)字节数组的时候,会自动调用flush方法来讲数据直接刷新到硬盘的文件上。
2、而FileWrite()方法,则不会直接将字符串写入硬盘,除非关闭该流。
七、 PipedInputStream、PipedOutputStream类。
一个PipedInputStream类对象必须和一个PipedOutputStream类对象相对应,产生一个通信管道。PipedOutputStream对象可以向该管道中写入数据,PipedInputStream可以像该管道中读取由PipedOutputStream对象写入的数据。
这两个类主要用于完成线程之间的通信,一个线程的PipedInputStream对象可以从另外一个线程的PipedOutputStream对象读取数据。
PipedWriter和PipedReader类:用来处理字符文本的管道通信
八、 管道流类的优点:
1、使用管道流类,可以实现各个程序模块间的松耦合通信。
这样,在程序中可以灵活的将多个模块的输出流和出入流相连,然后拼装成满足各种应用的程序,而不需要对各种模块的内部进行修改。
2、使用管道流类的程序模块,具有强内聚,弱耦合的特性。
九、 ByteArrayInputStream类 和 ByteArrayOutputStream类:
这两个类可以实现内存虚拟文件的功能;
内存虚拟文件或者内存映像文件,其实就是把内存的一块数据存储缓冲区虚拟成一个文件,原来该写入到硬盘的内容,可以先写入该内存中。原来该从一个硬盘文件上读取的数据,可以从该内存中读取。要在程序中需要定义一个大的数据存储缓冲区,该缓冲区通常是一个字节数组。在Java中定义了两个专门的类,如上,用于以I/O流的方式来完成对字节数组内容的读写,从而实现类似于虚拟文件或内存映像文件的功能。
字符串,其实就相当于一个字符数组,即字符类缓冲区。Java中提供了StringReader类和StringWriter类,来以字符I/O的形式来处理字符串。
十、 重视I/O程序代码的复用:
不论是文件流还是网络流,他们都需要有一个结束标记来表示底层物理设备中的终止点,
不论是各种底层物理设备用什么方式实现数据的终止点,InputStream 类的read方法总是返回-1来表示输入流的结束。
在Windows 下,可以按住 Ctrl + Z 组合键产生输入流的结束标记,在Linux 下,则是按住 Ctrl + D 组合键来产生输入流的标记。
要编程从键盘上读取一大段数据时,应尽量将读取数据的过程放在函数中完成,使用 -1 来作为键盘输入的结束点。
在该函数中编写的程序代码中不应直接使用 System.in 读取数据,而是作为一个InuputStream 类型的形式参数对象来读取数据,然后将 System.in 作为实参传递给InputStream 类型的形参来调用该函数。如:(即并不是直接把System.in作为一个对象来使用,而是将之复制给InputStream对象来使用,这样,以后如果需要从文件读取的时候,不需要修改太多的代码。)
BufferString str = new StringBuffer(new InputStream(System.in))
十一、 过滤类
BufferedInputStream与BufferedOutputStream类: 是Java提供的两个缓冲包装类,不管底层系统是否使用了缓冲区,这两个类在自己的实例对象中 创建缓冲区。这种缓冲区称为间接缓冲区,与底层系统提供的缓冲区的区别:
底层系统创建的缓冲区直接与目标设备交换数据
包装类中创建的缓冲区,需要调用包装类所包装的类的输出流对象,将缓冲区的数据读写到目标设备或者底层缓冲区中。
DataInputStream与DataOutputStream类:
这两个类提供了可以读写各种基本数据类型数据的各种方法。
由于writeUTF()方法向目标设备中写入了字符串的长度,故可以使用readUTF()方法来读取该字符串。
PrintStream类:提供了一系列的print和println方法,可以将基本数据类型的数据格式化成字符串输出。
(1)、对于基本类型数据,这两方法将基本数据类型转换成字符串,而不是输出原始的字节内容。而对于输出内容为非基本类型的对象,将首先调用该对象重载的toString()方法,返回该方法返回的字符串。
(2)、格式化输出:即将一个数据按照字符串的格式输出。
ObjectInputStream与ObjectOutputStream类:这两个类主要用于从底层流中读写对象类型的数据。使用ObjectInputStream与ObjectOutputStream类保存和读取对象的机制叫序列化。
十二、 字节流和字符流的转换
InputStreamReader和OutputStreamWriter类:是用于字节流转换成字符流来读写的两个类,InputStreamReader可将一个字节流中的字节解码成字符读取,OutputStreamWriter将字符编码成字节后写入一个字节流中。
在实际应用中,避免频繁的在字符与字节中进行转换。最好不要直接使用InputStreamReader和OutputStreamWriter。应该使用BufferStreamReader类包装InputStreamReader,使用BufferStreamWriter包装OutputStreamWriter类。
因为InputStreamReader和OutputStreamWriter用于实现字符间的转换,如果读到一个字节,就将之转换,这样当需要读取的字符较多的时候,则需要频繁的转换,这样效率就会比较低。
十三、 字符集的编码问题
不用刻意搞清楚字符编码的具体规律原理什么的,只要知道我的程序用的什么编码编写的即可,我们后面调用程序时也按和被调用程序的编码方式匹配的工具读取就行。
字符编码的操作体验:
1、getBytes()方法:将Unicode字符串转换为实参指定的字符集,如果不指定实参,则为默认的本地字符集,并放回到字节数组中存储。该方法首先找到实参的字符编码器类,然后进行编码转换。Unicode码转换为某种字符集的转换称为编码。
2、如何查看当前系统所使用的字符编码集: 该字符编码集作为一个JVM的环境而返回。
System.getProperties().list(System.out); //该语句将JVM环境在屏幕中输出。
3、在Eclipse中,如何修改编译所用到的运行环境中的字符集?
4、在实际中,toHexString()方法显示的字符中,如果高字节为0,则不显示高字节。
十四、 Decorator(装饰、包装)设计模式:在程序中用一个对象(The Decorators)包装另外一个对象,这是一种被称为Decorator的设计模式。
1、包装类提供了一个更高层次的类的应用。
若要设计自己的I/O包装类,这个类需要继承以FilterXXX命名,如:设计一个对输入输出包装的类:RecordInputStream和RecordOutputStream来完成从数据库文件中读取记录和往数据库文件中写入记录。 一般所设计的输入输出包装类,都是成对出现的,因为有输出,必须要有对应的输入才能读取该输出类数据。
2、包装类的设计与应用很巧妙:如
Exception类从Throwable类继承的三个printStackTrace方法的定义如下:
public void printStackTrace()
public void printStackTrace(PrintStream s)
public void printStackTrace(PrintWriter s)
那么,如何将printStackTrace方法打出的详细异常信息存储在一个字符串中?
字符串与输出流的桥梁:StringWriter,用PrintWriter去包装一个StringWriter对象即可。