第一节 文件读写器基本问题
一、文件操作
文件分为两种,一种是文本文件,文本文件的内容以16 bit表示(2 byte),另一种为非文本文件(流文件),以8 bit表示(1 byte),比如图片、声音文件等等。这两种文件读写的一个很大的区别在于,文本文件读写单位是字符,它是两个字节一组的组合,是可以解析成字符的,而流文件每个读写单位为一个子节,单独的一个字节是没有办法解析成字符的,所以处理上有很大的不同。
二、读写器的结构总图
文件读写器所有的类都在类包 java.io 中,文件读入器一般使用字符input或者reader,文件写出器一般使用out或者writer。
流:它是通过缓冲机制将数据从生产者(如键盘、磁盘文件、内存或其他设备)传送到接受该数据的消费者(如屏幕、文件或者内存等)的这一过程的抽象。
文本文件读入器:Reader。
文本文件写出器:Write。
流文件读入器:InputStream。
流文件写出器:OutputStream。
而文件类File主要是用于创建文件对象,这是一个有关文件名及目录名的类:File 类独立于系统平台,利用构造函数
File( String path)、
File(String path, String FileName)、
File(File dir, String name) 等可以创建出File 对象,再利用canRead() 、canWrite()、 getParent()、 getPath()等成员函数实现对文件的各个属性的操作,以实现文件与目录的管理功能。
要点:
File 类提供了一种与机器无关的方式来描述一个文件对象的属性,通过类File所提供的方法,可以得到文件或目录的描述信息,这主要包括名称、所在路经、可读性、可写性、文件的长度等,还可以生成新的目录、改变文件名、删除文件、列出一个目录中所有的文件等文件与目录的管理功能。
第二节 文件读写各种类的研究
一、有关文件名及目录名的类
File 类独立于系统平台,利用构造函数
File( String path)、
File(String path, String FileName)、
File(File dir, String name) 等创建出File 对象;再利用canRead() 、canWrite()、 getParent()、 getPath()等成员函数实现对文件的各个属性的操作,以实现文件与目录的管理功能。
要点:
File 类提供了一种与机器无关的方式来描述一个文件对象的属性,通过类File所提供的方法,可以得到文件或目录的描述信息,这主要包括名称、所在路经、可读性、可写性、文件的长度等,还可以生成新的目录、改变文件名、删除文件、列出一个目录中所有的文件等文件与目录的管理功能。
实例:文件对象的属性操作方法
import java.io.*;
public class FileTest
{ public static void main(String []args)
{
String FileName="C://temp//myfile.dat"
File myFile=new File(FileName);
If( ! myFile. exists() )
{ System.err.println(“Can’t Find " + FileName);
return;
}
System.out.println(“File " + FileName + “is " +myFile.length() + “bytes Long !");
If( myFile. isDirectory() )
{ System.err.println(“File" + FileName +"Is a Directory !");
return;
}
}
}
实例:演示File类中各种方法的使用
import java.io.*;
public class FileTest
{ //在执行该文件之前应该先拷贝一个文件名为File.txt的文件到当前程序所在的路径下
public static void main(String args[])
{
// “;"各个文件路径之间的分隔符
System.out.println("Path separator" +File.pathSeparator);
System.out.println("Path separator char" +File.pathSeparatorChar);
System.out.println("separator " +File.separator); // “/"文件与路径之间的分隔符
System.out.println("separator char" +File.separatorChar);
File f=new File("File.txt");
System.out.println(f);
System.out.println("Exist ?" + f.exists());
System.out.println("name " + f.getName());
System.out.println("path " + f.getPath());
System.out.println("absolute path " + f.getAbsolutePath());
System.out.println("Parent " + f.getParent());
System.out.println("is a file ? " + f.isFile());
System.out.println("is a Directory ? " + f.isDirectory());
System.out.println("length" + f.length());
System.out.println("can read " + f.canRead());
System.out.println("can write " + f.canWrite());
System.out.println("last modified" + f.lastModified());
File newF=new File("newFile");
System.out.println("...Rename " + f+"....");
f.renameTo(newF);
System.out.println("name " + newF.getName());
System.out.println(f+"exist ? " + f.exists());
System.out.println("... delete "+newF+"...");
newF.delete();
System.out.println(newF+ "exist ?" + newF.exists());
}
}
实例:通过File类来实现列出一个目录下所有的*.class文件
import java.io.*;
public class FileFilterTest
{ public static void main(String args[])
{
File dir =new File("E:/jdk1.2/bin");
Filter filter =new Filter("class");
System.out.println("list html files in directory" + dir);
String files[]=dir.list(filter); //获得该目录下的各个文件名
for(int i=0; i<files.length; i++) //列出目录下所有匹配文件并存储到String 数组中。
{ File f=new File(files[i]);
if(f.isFile())
System.out.println("file " +f);
else
System.out.println("sub directory " + f);
}
}
}
class Filter implements FilenameFilter
{ String extent;
Filter(String extent)
{ this.extent=extent;
}
public boolean accept(File dir, String name)
{ return name.endsWith("." + extent);
}
}
二、文件操作的一般方法
1)输入输出抽象基类InputStream/OutputStream
实现文件内容操作的基本功能函数read()、 write()、close()等;一般都是创建出其派生类对象(完成指定的特殊功能)来实现文件读写。在文件读写的编程过程中主要应该注意异常处理的技术。
2)文件操作的一般方法:
(1)生成一个输入输出文件类的对象(根据所要操作的类型);
(2)调用此类的成员函数实现文件数据内容的读写;
(3)关闭此文件。
3)文件操作的一般要点:
在Java I/O编程中主要应该注意:
(1)异常的捕获---由于包java.io中几乎所有的类都声明有I/O异常,因此程序应该对这些异常加以处理。
(2)流结束的判断---方法read()的返回值为-1时;readLine()的返回值为null时。
三、字符流Reader/Writer:
Reader/Writer称之为字符流,提供的对字符流处理的类。由于文本文件属于字符信息,使用这种流处理问题比较不容易出现乱码。
注意,它们为抽象类,它的一个子类:
1)InputStreamReader(InputStream in) / OutputStreamWriter(OutputStream out):
它们可以使用指定的编码规范并基于字节流生成对应的字符流。
FileInputStream is=new FileInputStream("test.txt");
InputStreamReader isr=new InputStreamReader(is," iso-8859-1");
注意:为能正确地读出异种机上的字符,可以采用 ISO 8859_1的编码规范,它是一种映射到ASCII码的编码方式,可以在不同的平台之间正确地转换字符。
实例:
import java.io.*;
public class FileStreamsDemo {
public static void main(String[] args) throws IOException {
//获得两个文件类
FileInputStream infile=new FileInputStream("c://text2.txt");
InputStreamReader in=new InputStreamReader(infile,"GBK");
FileOutputStream outfile=new FileOutputStream("c://text3.txt");
OutputStreamWriter out=new OutputStreamWriter(outfile);
int c;
//如果到了文件尾,read()方法返回的数字是-1
while ((c = in.read()) != -1) out.write(c); //使用write()方法向文件写入信息
in.close(); //关闭文件读入类
out.close(); //关闭文件写出类
}
}
2)BufferedReader(InputStreamReader isr, int size) / BufferedWrite(OutputStreamWriter osr, int size):
为提高字符流的处理效率,可以采用缓冲机制流。JDK引入了BufferedReader和BufferedWriter类,用来对字符流作成批的处理。其中的方法readLine()读起一行字符,而newLine()则是写入一行字符。由于提供缓冲机制,把任意的输入流或输出流“捆绑"到缓冲流上将获得性能的提高。可以在创建缓冲流对象时设置缓冲区的大小。
实例:
读文本文件的内容
import java.io.*;
public class CharInput
{ public static void main(String args[]) throws FileNotFoundException, IOException
{ String s;
FileInputStream is=new FileInputStream("CharInput.java");
InputStreamReader ir=new InputStreamReader(is);
BufferedReader in =new BufferedReader(ir);
while((s=in.readLine()) !=null)
System.out.println("Read:" +s);
}
}
实例:
读键盘输入的数据值
import java.io.*;
public class NumberInput
{ public static void main(String args[]) throws IOException
{ String s;
InputStreamReader ir=new InputStreamReader(System.in);
BufferedReader in=new BufferedReader(ir);
String Str=in.readLine();
System.out.println("Input Value is:" + Str);
int X=Integer.parseInt(Str);
X=X*2;
System.out.println("Input Value Changed after doubled:" +X );
}
}
注意:
在Java中处理命令行方式的键盘输入时,都把输入的内容当作字符串看待。但由于Java没有提供自动将输入串转换为不同的类型数据的方法,所以要从键盘接收输入数据,必须由程序自己来完成类型转换。
实例:按行处理文件
package demopack;
import java.io.*;
public class streamDemo
{
public static void main (String[] args) throws IOException
{
BufferedReader bin=null;
BufferedWriter bout=null;
File inputFile = new File("c://text2.txt");
FileReader in = new FileReader(inputFile);
File outputFile = new File("c://text3.txt");
FileWriter out = new FileWriter(outputFile);
bin=new BufferedReader(in);
bout=new BufferedWriter(out);
String inputLine;
while((inputLine=bin.readLine())!=null)
{
bout.write(inputLine+"/r/n");
bout.flush();
}
in.close(); //关闭文件读入类
out.close(); //关闭文件写出类
}
}
(3)FileReader和FileWriter类:
由于InputStreamReader和OutputStreamWriter字符流类在创建流类的对象时必须以一个字节流作为原始的数据流来打开文件。为了能够直接将一个具体的文件名的文件直接转换为字符流类的对象,在java.io包中还为程序员提供了InputStreamReader和OutputStreamWriter字符流类的两个子类FileReader和FileWriter。
FileReader fr=new FileReader(“c://doc//Java.doc"); 或
FileWriter fw=new FileWriter (“c://doc//Java.doc");
实例:
向文本文件写入文本字符内容
import java.io.;
public class TextFileWrite
{
public static void main(String args[])
{
try
{ //向文件追加数据,因为第二个参数为true表示为追加数据
FileWriter fr=new FileWriter(“UserDataFile.txt",true);
fr.write(“这是我这次新追加的数据 !/n"); //写入一行字符串
fr.close();
}
catch(IOException e)
{
}
}
}
也可以用File对象和文件名建立联系。
实例:文本文件的读写
样例:读入文件C://text1.txt,再创建一个C://text2.txt,把内容写入。
import java.io.*;
public class FileStreamsDemo {
public static void main(String[] args) throws IOException {
//获得两个文件类
File inputFile = new File("c://text1.txt");
File outputFile = new File("c://text2.txt");
FileReader in = new FileReader(inputFile); //创建文件读入类
FileWriter out = new FileWriter(outputFile); //创建文件写出类
int c;
//如果到了文件尾,read()方法返回的数字是-1
while ((c = in.read()) != -1) out.write(c); //使用write()方法向文件写入信息
in.close(); //关闭文件读入类
out.close(); //关闭文件写出类
}
}
四、标准输入输出流:
1)标准输入流:
System.in是InputStream类的对象,当程序中需要从键盘读入数据时候,只需要调用System.in中的read()方法。
try
{ char ch=System.in.read(); //返回二进制数据(低8位为键盘的ASCII码)
}
catch(IOException e)
{
}
注意:
1:必须捕获System.in.read()所抛出的异常;
2:执行System.in.read()方法将从键盘缓冲区读入一个字节的数据,然而返回的16位的数据,其低位才是真正的输入数据,高位为0;
3:使用System.in.read()来实现键盘输入时,如果键盘缓冲区中没有数据时,系统将进入阻塞状态。所以可以利用它来实现程序的暂停,用户输入后再继续执行。
2)标准输出流:
System.out它是打印输出流PrintStream类的对象,它定义了向屏幕输出不同类型数据的方法println()与 print()。
System.out.println()它可以输出多种不同类型的数据(如:boolean,double,float,int,long类型的变量以及Object类的对象);当输出类型为对象时,它将自动调用对象的toString()方法,因此在对象所在的类中应该重写toString()方法以输出特定的文字。
public class MyClass
{ public String toString()
{ return "MyClass String";
}
public staic void main(String args[])
{ MyClass obj=new MyClass();
System.out.println(obj); //将输出“MyClass String"文字串
}
}
3)标准输入输出的重定向:
改变标准输入输出的方向为其它形式如从文件中输入
方法:利用System类中的3个static 方法可以实现。setIn(InputStream in)、setOut(PrintStream out)、setErr(PrintStream out)
五、FileInputStream/FileOutputStream:
这是一对继承于InputStream和OutputStream的类,用于本地文件读写(二进制格式读写并且是顺序读写,读和写要分别创建出不同的文件流对象);
本地文件读写编程的基本过程为:
① 生成文件流对象(对文件读操作时应该为FileInputStream类,而文件写应该为FileOutputStream类);
② 调用FileInputStream或FileOutputStream类中的功能函数如read()、write(int b)等)读写文件内容;
③ 关闭文件(close())。
实例:流文件读写
流文件的单元是字节,所以它不但可以读写文本文件,也可以读写图片、声音、影像文件,这种特点非常有用,因为我们可以把这种文件变成流,然后在网络上传输。
问题是有了通用的流文件以后,为什么还要专门的字符流呢?这是因为文本可以用不同的方式存储,可以是普通的文本(UTF-8编码方式),ASCII文本和Unicode文本,字符流对象可以进行必要的转换,从而读出正确的文本。
有人认为流文件不能读写文本文件,这其实是个误会,因为文本文件本质上也是由字节组成的,当然是流文件的一种。作为读写文件的全体,这是没问题的,但是,如果要处理每次读入的内容,就最好使用字符流。
所以在文本文件处理时,使用字符流是个最常用的方法。
样例:
import java.io.*;
public class FileStreamDemo {
public static void main(String[] args) throws IOException {
//创建两个文件,face.gif是已经存在文件,newFace.gif是新创建的文件
File inFile = new File("face.gif");
File outFile = new File("newFace.gif");
//创建流文件读入与写出类
FileInputStream inStream = new FileInputStream(inFile);
FileOutputStream outStream = new FileOutputStream(outFile);
//通过available方法取得流的最大字符数
byte[] inOutb = new byte[inStream.available()];
inStream.read(inOutb); //读入流,保存在byte数组
outStream.write(inOutb); //写出流,保存在文件newFace.gif中
inStream.close();
outStream.close();
}
}
实例:读写任意大文件应用
因为byte数组最大存储值不超过64M,所以当一个文件大于60M 的时候,需要分开几个流操作。我们把上面的程序作一个修改,就可以写入任意大小的文件。这个程序应用了FileInputStream类的方法如下:
read(byte[] b,int off,int len)
把特定位置的流内容读入数组,已经读入byte[]数组的内容,会在流文件中删除。
程序运行的结果会产生一个新文件。
样例:
import java.io.*;
public class FileStreamDemo2 {
public static void main(String[] args) throws IOException {
//创建两个文件
File inFile = new File("tcty36.rm");
File outFile = new File("newtcty36.rm");
//最大的流为60Mb,当文件的容量大于60Mb的时候便分开流
final int MAX_BYTE = 60000000;
long streamTotal = 0; //接受流的容量
int streamNum = 0; //流需要分开的数量
int leave = 0; //文件剩下的字符数
byte[] inOutb; //byte数组接受文件的数据
//创建流文件读入与写出类
FileInputStream inStream = new FileInputStream(inFile);
FileOutputStream outStream = new FileOutputStream(outFile);
//通过available方法取得流的最大字符数
streamTotal = inStream.available();
//取得流文件需要分开的数量
streamNum = (int)Math.floor(streamTotal/MAX_BYTE);
//分开文件之后,剩余的数量
leave = (int)streamTotal % MAX_BYTE;
//文件的容量大于60Mb时进入循环
if (streamNum > 0) {
for(int i = 0; i < streamNum; ++i){
inOutb = new byte[MAX_BYTE];
//读入流,保存在byte数组
inStream.read(inOutb, 0, MAX_BYTE);
outStream.write(inOutb); //写出流
outStream.flush(); //更新写出的结果
}
}
//写出剩下的流数据
inOutb = new byte[leave];
inStream.read(inOutb, 0, leave);
outStream.write(inOutb);
outStream.flush();
inStream.close();
outStream.close();
}
}
六、 管道PipedInputStream/PipedOutputStream类:
当需要在两个线程中读写数据的时候,由于线程的并发执行,读写的同步问题可能会发生困难,这时候可以使用管道,管道事实上是一个队列。
管道是由系统维护的一个缓冲区,当然程序员也可以自己直接指定该缓冲区的大小(只需要设置管道流类中的PIPE_SIZE属性的值)。当生产者生产出数据后,只需要将数据写入管道中,消费者只需要从管道中读取所需要的数据。利用管道的这种机制,可以将一个线程的输出结果直接连接到另一个线程的输入端口,实现两者之间的数据直接传送。
1.管道的连接:
方法之一是通过构造函数直接将某一个程序的输出作为另一个程序的输入,在定义对象时指明目标管道对象
PipedInputStream pInput=new PipedInputStream();
PipedOutputStream pOutput= new PipedOutputStream(pInput);
方法之二是利用双方类中的任一个成员函数 connect()相连接
PipedInputStream pInput=new PipedInputStream();
PipedOutputStream pOutput= new PipedOutputStream();
pinput.connect(pOutput);
2.管道的输入与输出:
输出管道对象调用write()成员函数输出数据(即向管道的输入端发送数据);而输入管道对象调用read()成员函数可以读起数据(即从输出管道中获得数据)。这主要是借助系统所提供的缓冲机制来实现的。
实例:Java的管道的输入与输出
import java.io.*;
public class PipedIO //程序运行后将sendFile文件的内容拷贝到receiverFile文件中
{
public static void main(String args[])
{
try
{
//构造读写的管道流对象
PipedInputStream pis=new PipedInputStream();
PipedOutputStream pos=new PipedOutputStream();
//实现关联
pos.connect(pis);
//构造两个线程,并且启动。
new Sender(pos,"c://text2.txt").start();
new Receiver(pis,"c://text3.txt").start();
}
catch(IOException e)
{
System.out.println("Pipe Error"+ e);
}
}
}
//线程发送
class Sender extends Thread
{
PipedOutputStream pos;
File file;
//构造方法
Sender(PipedOutputStream pos, String fileName)
{
this.pos=pos;
file=new File(fileName);
}
//线程运行方法
public void run()
{
try
{
//读文件内容
FileInputStream fs=new FileInputStream(file);
int data;
while((data=fs.read())!=-1)
{
//写入管道始端
pos.write(data);
}
pos.close();
}
catch(IOException e)
{
System.out.println("Sender Error" +e);
}
}
}
//线程读
class Receiver extends Thread
{
PipedInputStream pis;
File file;
//构造方法
Receiver(PipedInputStream pis, String fileName)
{
this.pis=pis;
file=new File(fileName);
}
//线程运行
public void run()
{
try
{
//写文件流对象
FileOutputStream fs=new FileOutputStream(file);
int data;
//从管道末端读
while((data=pis.read())!=-1)
{
//写入本地文件
fs.write(data);
}
pis.close();
}
catch(IOException e)
{
System.out.println("Receiver Error" +e);
}
}
}
七、随机文件读写:RandomAccessFile类
它直接继承于Object类而非InputStream/OutputStream类,从而可以实现读写文件中任何位置中的数据(只需要改变文件的读写位置的指针)。
编程步骤:
① 生成流对象并且指明读写类型;
② 移动读写位置;
③ 读写文件内容;
④ 关闭文件。
另外由于RandomAccessFile类实现了DataOutput与DataInput接口,因而利用它可以读写Java中的不同类型的基本类型数据(比如采用readLong()方法读取长整数,而利用readInt()方法可以读出整数值等)。
程序实例:
利用随机数据流RandomAccessFile类来实现记录用户在键盘的输入,每执行一次,将用户的键盘输入存储在指定的UserInput.txt文件中。
import java.io.*;
public class RandomFileRW
{
public static void main(String args[])
{
StringBuffer buf=new StringBuffer();
char ch;
try
{
while( (ch=(char)System.in.read()) !='/n')
{
buf.append(ch);
}
//读写方式可以为"r" or "rw"
RandomAccessFile myFileStream=new RandomAccessFile("c://UserInput.txt","rw");
myFileStream.seek(myFileStream.length()) ;
myFileStream.writeBytes(buf.toString());
//将用户从键盘输入的内容添加到文件的尾部
myFileStream.close();
}
catch(IOException e)
{
}
}
}