name[i]=randomAccessfile.readChar();
//将空字符取代为空格符并返回
returnnewString(name).replace('\0','');
}
}
执行结果:
javaonlyfun.caterpillar.RandomAccessFileDemostudent.dat
读取第几个数据?
2
姓名:
momor
分数:
95
RandomAccessFile上的相关方法实现都在批注中说明了,可以看到读写文件时几个必要的流程:
打开文件并指定读写方式
在Java中,当实例化一个与文件相关的输入/输出类时,就会进行打开文件的动作。
在实例化的同时要指定文件是要以读出(r)、写入(w)或可读可写(rw)的方式打开,可以将文件看作是一个容器,要读出或写入数据都必须打开容器的瓶盖。
使用对应的写入方法
对文件进行写入,要使用对应的写入方法。
在Java中通常是write的名称作为开头,在低级的文件写入中,要写入某种类型的数据,就要使用对应该类型的方法,如writeInt()、writeChar()等。
使用对应的读出方法
对文件进行读出,要使用对应的读出方法。
在Java中通常是read的名称作为开头,在低级的文件读出中,要读出某种类型的数据,就要使用对应该类型的方法,如readInt()、readChar()等。
关闭文件
可以将文件看作是一个容器,要读出或写入数据都必须打开容器的瓶盖,而不进行读出或写入时,就要将瓶盖关闭。
对于某些文件存取对象来说,关闭文件的动作意味着将缓冲区(Buffer)的数据全部写入文件,如果不作关闭文件的动作,某些数据可能没有写入文件而遗失。
14.2 位流
计算机中的数据都是以0与1的方式来存储,如果要在两个装置之间进行数据的存取,当然也是以0与1位的方式来进行,Java将数据于目的地及来源之间的流动抽象化为一个流(Stream),而流当中流动的则是位数据。
14.2.1 InputStream和OutputStream
计算机中实际上数据的流动是通过电路,而上面流动的则是电流,电流的电位有低位与高位,即数字的0与1位。
从程序的观点来说,通常会将数据目的地(例如内存)与来源(例如文件)之间的数据流动抽象化为一个流(Stream),而其中流动的则是位数据,如图14-1所示。
图14-1 数据的流动抽象化为流的概念
在JavaSE中有两个类用来作流的抽象表示:
java.io.InputStream与java.io.OutputStream。
InputStream是所有表示位输入流的类之父类,它是一个抽象类,继承它的子类要重新定义其中所定义的抽象方法。
InputStream是从装置来源地读取数据的抽象表示,例如System中的标准输入流in对象就是一个InputStream类型的实例。
在Java程序开始之后,in流对象就会开启,目的是从标准输入装置中读取数据,这个装置通常是键盘或是用户定义的输入装置。
OutputStream是所有表示位输出流的类之父类,它是一个抽象类。
子类要重新定义其中所定义的抽象方法,OutputStream是用于将数据写入目的地的抽象表示。
例如System中的标准输出流对象out其类型是java.io.PrintStream,这个类是OutputStream的子类(java.io.FilterOutputStream继承OutputStream,PrintStream再继承FilterOutputStream)。
在程序开始之后,out流对象就会开启,可以通过out来将数据写至目的地装置,这个装置通常是屏幕显示或用户定义的输出装置。
范例14.4可以读取键盘输入流,in对象的read()方法一次读取一个字节的数据,读入的数据以int类型返回。
所以在使用out对象将数据显示出来时,就是10进制方式。
范例14.4 StreamDemo.java
packageonlyfun.caterpillar;
importjava.io.*;
publicclassStreamDemo{
publicstaticvoidmain(String[]args){
try{
System.out.print("输入字符:
");
System.out.println("输入字符十进制表示:
"+
System.in.read());
}
catch(IOExceptione){
e.printStackTrace();
}
}
}
执行结果:
输入字符:
A
输入字符十进制表示:
65
字符A输入后由标准输入流in读取,A的位表示以十进制来看就是65,这是A字符的编码(查查ASCII编码表就知道了)。
一般来说,很少直接实现InputStream或OutputStream上的方法,因为这些方法比较低级,通常会实现它们的子类。
这些子类上所定义的方法在进行输入/输出时更为方便。
14.2.2 FileInputStream和FileOutputStream
java.io.FileInputStream是InputStream的子类。
从开头File名称上就可以知道,FileInputStream与从指定的文件中读取数据至目的地有关。
而java.io.FileOutputStream是OutputStream的子类,顾名思义,FileOutputStream主要与从来源地写入数据至指定的文件中有关。
当建立一个FileInputStream或FileOutputStream的实例时,必须指定文件位置及文件名称,实例被建立时文件的流就会开启;而不使用流时,必须关闭文件流,以释放与流相依的系统资源,完成文件读/写的动作。
FileInputStream可以使用read()方法一次读入一个字节,并以int类型返回,或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节。
在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承接数据的中间角色。
范例14.5是使用FileInputStream与FileOutputStream的一个例子。
程序可以复制文件,它会先从来源文件读取数据至一个byte数组中,然后再将byte数组的数据写入目的文件。
范例14.5 FileStreamDemo.java
packageonlyfun.caterpillar;
importjava.io.*;
publicclassFileStreamDemo{
publicstaticvoidmain(String[]args){
try{
byte[]buffer=newbyte[1024];
//来源文件
FileInputStreamfileInputStream=
newFileInputStream(newFile(args[0]));
//目的文件
FileOutputStreamfileOutputStream=
newFileOutputStream(newFile(args[1]));
//available()可取得未读取的数据长度
System.out.println("复制文件:
"+
fileInputStream.available()+"字节");
while(true){
if(fileInputStream.available()<1024){
//剩余的数据比1024字节少
//一位一位读出再写入目的文件
intremain=-1;
while((remain=fileInputStream.read())
!
=-1){
fileOutputStream.write(remain);
}
break;
}
else{
//从来源文件读取数据至缓冲区
fileInputStream.read(buffer);
//将数组数据写入目的文件
fileOutputStream.write(buffer);
}
}
//关闭流
fileInputStream.close();
fileOutputStream.close();
System.out.println("复制完成");
}
catch(ArrayIndexOutOfBoundsExceptione){
System.out.println(
"using:
javaFileStreamDemosrcdes");
e.printStackTrace();
}
catch(IOExceptione){
e.printStackTrace();
}
}
}
程序中示范了两个read()方法,一个方法可以读入指定长度的数据至数组,另一个方法一次可以读入一个字节。
每次读取之后,读取的光标都会往前进,如果读不到数据则返回-1,使用available()方法获得还有多少字节可以读取。
除了使用File来建立FileInputStream、FileOutputStream的实例之外,也可以直接使用字符串指定路径来建立。
//来源文件
FileInputStreamfileInputStream=
newFileInputStream(args[0]);
//目的文件
FileOutputStreamfileOutputStream=
newFileOutputStream(args[1]);
在不使用文件流时,记得使用close()方法自行关闭流,以释放与流相依的系统资源。
一个执行的结果范例如下,它将FileDemo.java复制为FileDemo.txt:
javaonlyfun.caterpillar.FileStreamDemoFileDemo.javaFileDemo.txt
复制文件:
1723字节
复制完成
FileOutputStream默认会以新建文件的方式来开启流。
如果指定的文件名称已经存在,则原文件会被覆盖;如果想以附加的模式来写入文件,则可以在构建FileOutputStream实例时指定为附加模式。
例如:
FileOutputStreamfileOutputStream=
newFileOutputStream(args[1],true);
构建方法的第二个append参数如果设置为true,在开启流时如果文件不存在则会新建一个文件,如果文件存在就直接开启流,并将写入的数据附加至文件末端。
虽然我一向不喜欢使用过长的范例来作程序示范(也不喜欢看很长的范例),不过本章的范例与其他各章的比起来相对长了一些,我会在程序中多用注释解释程序的逻辑。
因为解释输入/输出操作最好的方式,是呈现一个具实用性的范例,本章的范例除了练习的作用之外,日后需要某些输入/输出功能时,也可以来参考看看如何实现。
14.2.3 BufferedInputStream和BufferedOutputStream
在介绍FileInputStream和FileOutputStream的例子中,使用了一个byte数组来作为数据读入的缓冲区,以文件存取为例,硬盘存取的速度远低于内存中的数据存取速度。
为了减少对硬盘的存取,通常从文件中一次读入一定长度的数据,而写入时也是一次写入一定长度的数据,这可以增加文件存取的效率。
java.io.B