第1节 JAVA的I/O
一、什么是流(Stream)?
能产生数据的数据源或能接收数据的接收端,叫流。
常见的数据源有:
A、字节数组:byte array。
B、字串对象:String对象。
C、文件:File对象
D、管道:pipe
E、流序列:即多股流,可把它们汇合成一股新流。
F、其他数据源:如网络通信等。
二、什么要用流?
使用流后,可以把实际的I/O数据处理细节隐藏起来,为编程提供更方便、更好用的编程接口。
例如:希望从键盘读到是直接就是“双精度数”,而不是字符串时,就不能直接访问键盘,而是对原始的键盘操作进行多次封装后得到一个新的类,用这类中的函数实现新的读写操作。用流更方便功能扩充。
三、JAVA标准I/O
标准I/O的概念来自UNIX。它是指能被一个程序使用的单一的数据流。
标准I/O主要有:
A、标准输入:Standard Input
程序可从标准输入中读取数据,而不必关心这个输出是:键盘、文件、网络等。
B、标准输出:Standard Output
程序的结果可以直接输出到标准输出,而不必关心输出对象是:命令窗口、文件、网络。
C、标准错误:Standard Error
数据流读写过程中发生的错误,会集中送到标准错误中进行处理。
有了流的概念后,程序A(可以是文件)的输出(print)信息可以直接作为程序B(可能是网络)的输入,B的输出可以直接作为程序C的输出等,很将多个不同的程序串接起来,各自独立工作,又能协同。
第2节 流的分类
一、字节流:
比较适合7位ASCII码的操作。
二、字符流:
以字符为单位进行处理,用2字节的Unicode作编码。
三、文件流
四、对象流:
序列化后的对象。内存中的对象是以二进制的“字节”为单位进行读写,把它们“双字节”化(Unicode编码)的过程,叫序列化,以方便网络传输。
第3节 字节流
一、标准I/O
1、System.in
称之为标准输入流。
默认时,它是指键盘。通过System.setIn(另一个流对象)可将它定义成另一个流(如文件、程序的缓冲区等)。
2、System.out
称之为标准输出流。
默认时,它是命令行窗口。可以
通过System.setOut(另一个输出流)将其定义成另一个输出流(如文件等)。
3、System.err
把错误的信息输出到命令行上。
System.err和System.out都能输出信息,两者的区别是:前者一般用来输出错误信息、日志、跟踪信息等。
通过System.setErr()可以把输出定向到另一个输出流中(如文件)。
4、注意:
(1)
System.in不能直接使用,要使用其它类对它进行包装后间接使用。因为它是逐个读取字符,会造系统资源严重浪费(在Unix/Linux多用户终端环境下特别明显)。
System.out/err可以直接使用。
(2)以上重定向流的输入/输出方向后,新的方向只对相应的程序有效,对其它运行着的无关JAVA程序无效。
二、输入/输出的方法
1、InputStream类
它负责从合法的数据源中取得输入。
每一种数据源都有相应的InputStream子类,这些子类是对InputStream进行包装后得到,功能、应用场合不同。详见以下各种的过滤类FilterInputStream。
这个也不直接使用,而是用各种具体的过滤类。
2、OutputStream类
将信息送至输出目标中。
这个类也不直接使用,而是对它进行包装后,使用各种过滤类。
三、过滤流
1、设计模式:Design Pattern
它是指:针对某个普遍问题的一种通用的解决方案。一种思路,与编程语言无关。
可参考“四人帮”写的《设计模式(可复用面向对象软件的基础)》一书。
2、Decorator设计模式
也叫“装饰模式”、“外覆(Wrapper)模式”。
基本思想:要给某对象增加一些功能时,如果使用继承的方法会造成子类大量的增加,程序设计时可改用“装饰模式”。它是把一个对象A“包装”在另一个对象B中,使得新对象B是“多个对象叠加组合”的结果,B有A的功能,并可在B中直接使用这个功能、或扩充这个功能。
例如:有一个文本框,没有滚动条,不能滚动文本:
给这个文本框添加一个滚动条对象即可。这种方法叫“装饰”。
“装饰模式”规定,被修饰过的类热拥有内置对象的接口,所以能侦听、接收发给内置对象的消息、数据,最后将这些消息转发给内置的对象。
过滤器实际上就是一个外覆类。
四、常用的字节输入流类
程序中要首先导入:java.io.*;
详见P179页图8.1和P182页表8.1。
1、DataInputStream
它是按基本数据类型进行读写,调用相应的readInt()、readDouble()、readLine()等函数。主的用法是:
DataInputStream a=new DataInputStream();
int b=a.readInt();//试输入整数再打印
String c=a.readLine();//返回一个串
这个类的一次读操作会引发一次真实的直接设备读操作,在Unix/Linux多用户终端环境会造成操作系统的停机等待的现象,现在已经不建议使用。
2、BufferedInputStream
它是从缓冲区中读取数据,是间接操作数据流。比较建议使用。常用的函数有:
read() 返回一个字符的ASCII码。
五、字节输出流
1、DataOutputStream
可以直接把基本数据类型、这串直接送至输出流中。有相关的write()。
2、BufferedOutputStream
每次的write()不会直接输出,而是写入缓冲区中,缓冲区满后才一次性将数据输出到流中。
3、PrintStream
可以对基本数据类型进行格式化后打印,这个类容易出错。
注意:使用流操作时,可能会产生异常,所以要用异常处理机制处理。
第5节 字符流
一、字符流与字节流的区别
前者是从缓冲区读取数据,后者是直接操作。所以,一般使用字符流。只有要对字节对象操作时,才会用到字节流。
二、字符流类
特点:类名尾部有“Reader”、”Writer”字样。参见P187页的图8.2。
三、常用的字符流类
1、InputStreamReader类
它实际上是对字节流对象包装成一个字符流对象。起到一个“适配器”的作用。
适配器:Adapter ,一些起到数据转换作用的类。如本例中:把字节流转换成字符流。
一般不直接使用本类。
2、BufferedReader类
字符流缓冲区。关键函数:
readLine():读取一行,返回字串。
第4节 文件I/O
一、File类
这个类的主要作用是:判断文件是否存在、文件的读写权限、创建文件夹等。这个文件夹有同样的作用。常用的函数有:
1、构造函数
用来表示一个(一组)文件/文件夹。
如:File a=new File(“c:\\aa\\bb”);
File b=new File(“c://my1.txt”);
注意:Windows环境下,根文件夹、分隔符要用“//” 或 “\\” 表示;不能用“/”、“\”表示。
2、makdirs()
一般对文件夹对象有效,即创建文件夹的作用。如上例:
a.makedirs();返回真假值。
3、createNewFile();
创建一个新文件。如果文件已经存在,则不创建。返回真假值。如:
a.createNewFile();
4、a.getAbsoluteFile()
返回目录/文件的绝对路径。
如:在指定的文件夹下创建文件
File b=new File(a.getAbsoluteFile()+"//my1.txt");
二、文件流类
把文件当作一个输入流,可从流中读到文件中的内容。流创建好后,就可对它进行读、写操作。
1、FileInputStream类
把文件当作输入流对象。
如:
File a=new File(“c:\\r1.txt”);
FileInputStream b=
new FileInputStream(a);//文件流对象
[例]把一个文件中的行读出显示在屏幕上。
File a=new File("c://r1.txt");
FileInputStream b=new
FileInputStream(a);
InputStreamReader c=new
InputStreamReader(b);
BufferedReader d=new
BufferedReader(c);
//读出文件显示
String temp=d.readLine();
while( temp != null)
{
System.out.println(temp);
temp=d.readLine();
}
b.close();//最后关闭文件
2、文件重定向
File a=new File("c://r1.txt");
FileInputStream b=new
FileInputStream(a);
System.setIn(b);//重定向
InputStreamReader c=
new InputStreamReader(System.in);
BufferedReader d=new
BufferedReader(c);
3、FileOutputStream类
把一个文件作输出流,实现写文件的操作。它会将文件先清空复位,从文件头开始。
[例] 把1、2、3写到一个文本文件中。
File a=new File(“c:\\r1.txt”);//文件对象
FileOutputStream b=//文件流
new FileOutputStream(a);
OutputStreamWriter c=//输出流
new OutputStreamWriter(b);
//写缓冲区
BufferedWriter d=new BufferedWriter(c); for(int i=0;i<100;i++)
{
d.write(i+"\n");//写文件
}
d.close(); //关闭文件
注意:
(1)关闭文件时,将缓冲区关闭即可。
(2)文件不一定包装成缓冲区。文件流直接就可以读写。包装成缓冲区是为了使用缓冲的一些函数,如:readLine()等。
三、RandomAccessFile
随机读写文件。注意:这个类不是流,它是普通的类,比较类似于C语言中的文件操作。主要函数:
1、构造函数
生成一个随机文件对象。
RandomAccessFile b=new
RandomAccessFile("c://r1.txt","rw");
第1个参数是文件名。
第2个参数是文件的读写属性。
2、读
read():读
readLine():读一行
readInt():读一个整数,整数在文件中以二进制形式存在。
readFloat():
3、写
Write()
writeInt()
4、移动指针
seek(int n):从文件头开始算,移动指针N个字节。
5、关闭:
close();
[例]
(1)产生10个随机小数,写入到文件中保存。
(2)修改程序,读出这个数据显示在屏幕上。
(3)修改程序,读出最后5个数显示。
(1)
RandomAccessFile b=new
RandomAccessFile("c://r1.txt","rw");
double temp;
for(int i=0;i<100;i++)
{
temp=Math.random();
b.writeDouble(temp);//写文件
}
b.close();//关闭文件
(2)
RandomAccessFile b=new
RandomAccessFile("c://r1.txt","rw");
double temp;
for(int i=0;i<100;i++)
{
temp=b.readDouble();//读一个数
System.out.println(temp);//显示
}
b.close();//关闭文件
(3)每个double数64位,8字节。
b.seek(5*8);//移动5个数
RandomAccessFile b=new
RandomAccessFile("c://r1.txt","rw");
double temp;
for(int i=0;i<100;i++)
{
temp=b.readDouble();//读一个数
System.out.println(temp);//显示
}
b.close();//关闭文件
第6节 对象序列化
一、JAVA的I/O操作不足
在网络传输、文件读写中,能被传输、读写的一般是:基本数据类型、String字串;大部份的系统类、用户自定义的类所产生的对象不能被直接传输、读写。
解决方法:先把内存中的对象转换为字节流对象,对这个流对象进行传输、读写即可。
1、对象序列化:Serializing
把内存中的对象字节化形成一个对象流的过程,叫对象序列化。
2、反序列化:Deserializing
把一个对象流解码,得到一个原始的对象并放入JVM的堆中,形成一个内存对象,并返回它的引用的过程,叫反序列化。
它们是一对逆运算。
二、一个类,如何实现可序列化?
实现接口 Serializable即可。
这是个“标记接口”,其中没有任何的函数定义。
如:
class My1 implements Serializable
{
…………..
}
三、如何将一个可序列化的对象写盘、读盘?
1、持久性:persistent
内存中的对象会因关机、程序结束运行等原因而从内存消失。
持久性是指:对象的生命期不取决于程序是否执行,即便程序结束了,对象仍然存在。
实现持久性的方法是:将对象写盘、写数据库。这个过程叫持久化对象。
2、把一个对象写盘的方法
A、把一个磁盘文件包装成“文件输出流”对象。FileOutputStream类。
B、把文件输出流对象包装成“对象输出流”对象。ObjectOutputStream。
C、使用对象流中的writeObject(obj)即可将一个可序列化的对象写盘。
[例] 定义一个可序列化的类。生成一个对象并写盘。
import java.io.*;
class Test23 implements Serializable
{
public double i=0;
Test23()
{
i=Math.random()*10;
System.out.println(“对象中有一个随机数:"+i);
}
}
写盘:
FileOutputStream a=
new FileOutputStream("c:\\w1.txt");
ObjectOutputStream b=
new ObjectOutputStream(a);
//生成一个可序列化的对象
Test23 c=new Test23();
//把这个可序列化的对象写盘
b.writeObject(c);
b.close();//关闭文件
3、对象读盘
A、把一个存有序列化对象的文件包装成“文件输入流”。FileInputStream类。
B、把文件输入流对象包装成“对象输入流”。ObjectInputStream类。
C、使用 readObject()即可读到一个反序列化的通用对象Object。必要时作类型转换。
[例] 将上例中的对象从文件中读取显示。
//从文件中读对象
FileInputStream d=
new FileInputStream("c:\\w1.txt");
ObjectInputStream e=
new ObjectInputStream(d);
Test23 f=(Test23)e.readObject();
System.out.println("读到的对象的值为:"+f.i);
e.close();//关闭文件
注意:反序列化一个对象时,要保证JVM能通过classpath或Internet定位到与对象相应的*.class文件,否则反序列会失败。
如:上例的读盘反序列化操作的*.class文件,将它复制到 c:\kk1下,在命令下运行它:
c:\kk1> java xxx.class
这时会出现找不到对象对应的类的错误。
4、防止某些属性被序列化
如果某些属性因保密等原因,希望它不能被序列化存盘、传输,可用transient定义它即可。如:
transient int j=100;
另一种做法:使用private代替transient,即使属性序列化了,也无法访问。如:
private int j=100;
四、序列化对象的版本号
一个类被修改后,读取先前的它生成的序列化对象后,还原时可能问题。所以最好在对象中加入一个符号常量,标识这个对象是哪个版本的类产生。
如:
static final long version=1234566L;
通过version这个符号常量识别类的版本号。修改类时,要修改这个版本号。