




已阅读5页,还剩94页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第14章 输入/输出输入/输出(Input/Output)泛指对某个设备或环境进行数据的输入或输出。例如对硬盘进行输入/输出、对视频设备进行输入/输出、对网络主机进行输入/输出等,可以想象,因设备或环境的不同,会有各式各样的输入/输出问题与解决方案。输入/输出问题在程序设计中实际上是一个很复杂的问题。对于输入/输出问题,Java将之抽象化为流(Stream)对象来解决。对不同的输入/输出问题,会有相应的流对象提供解决的方案。本章就是要学习Java中各式各样解决输入/输出问题的对象。然而输入/输出问题所涉及的领域相当广,基于学习的角度来看,必须选择一个主题来专门讨论,所以本章主题会围绕在文件输入/输出。有了本章的基础,在了解其他领域的输入/输出问题时就不难入手。14.1文件在正式了解Java中如何处理文件输入/输出之前,要先了解一下在Java中如何表示一个文件。本小节也将简单地介绍随机文件存取,让您初步了解文件输入/输出时一些必须注意的事项。14.1.1File类不同的操作系统对于文件系统路径的设置各有差别。例如在Windows中,一个路径的表示法可能是:C:WorkspaceCH14而在Linux下的路径设置可能会像是:/home/justin/workspace/ch14Windows的路径指定是使用UNC(Universal Naming Convention)路径名,以开始表示硬盘根目录。如果没有以开始表示相对于当前工作目录的路径,C是可选的硬盘指定,后面跟随着:字符。而UNIX-Like系统没有Windows系统的C、D、E这样的硬盘驱动器概念,UNIX-Like系统的路径指定以/开始表示从根目录开始的绝对路径,不以/开始表示相对于当前工作目录的路径。在程序中设置路径时会有系统相依性的问题,java.io.File类提供一个抽象的、与系统独立的路径表示。给它一个路径字符串,它会将其转换为与系统无关的抽象路径表示,这个路径可以指向一个文件、目录或是URI(Uniform Resource Identifier)。一个File的实例被建立时,它就不能再被改变内容。File实例除了用作一个文件或目录的抽象表示之外,它还提供了不少相关操作方法:可以用它来对文件系统作一些查询与设置的动作。要注意的是,不管是文件还是目录,在Java中都是以File的实例来表示。范例14.1是一个设置与操作File实例的简单示范,可以指定查询某个目录下的所有文件与目录名称。 范例14.1 FileDemo.java package onlyfun.caterpillar;import java.io.*;import java.util.*;public class FileDemo public static void main(String args) try File file = new File(args0); if(file.isFile() / 是否为文件 System.out.println(args0 + 文件); System.out.print( file.canRead() ? 可读 : 不可读 ); System.out.print( file.canWrite() ? 可写 : 不可写 ); System.out.println( file.length() + 字节); else / 列出所有的文件及目录 File files = file.listFiles(); ArrayList fileList = new ArrayList(); for(int i = 0; i files.length; i+) / 先列出目录 if(filesi.isDirectory() /是否为目录 / 取得路径名 System.out.println( + filesi.getPath() + ); else / 文件先存入fileList,待会再列出 fileList.add(filesi); / 列出文件 for(File f: fileList) System.out.println(f.toString(); System.out.println(); catch(ArrayIndexOutOfBoundsException e) System.out.println( using: java FileDemo pathname); 执行结果:java onlyfun.caterpillar.FileDemo C:C:WINDOWSC:workspaceC:Documents and SettingsC:Program FilesC:System Volume InformationC:pagefile.sysC:A3N_A3L.10C:bootfont.binC:ntldr.略这里先简单地介绍一下File类。File类主要是文件的抽象代表,若要作文件输出/输入,必须配合其他相关类来使用。接下来会配合各小节的内容并适时地使用File类。14.1.2RandomAccessFile类在正式介绍如何使用Java的输入/输出相关类来进行文件存取前,先简单地通过使用java.io.RandomAccessFile来存取文件,以认识一些文件存取时所必须注意的概念与事项。文件存取通常是循序的,每在文件中存取一次,文件的读取位置就会相对于目前的位置前进一次。然而有时必须指定文件的某个区段进行读取或写入的动作,也就是进行随机存取(Random Access),即要能在文件中随意地移动读取位置。这时可以使用RandomAccessFile,使用它的seek()方法来指定文件存取的位置,指定的单位是字节。为了移动存取位置时的方便,通常在随机存取文件中会固定每一个数据的长度。例如长度固定为每一个学生个人数据,Java中并没有直接的方法可以写入一个固定长度数据(像C/C+中的structure),所以在固定每一个长度方面必须自行设计。范例14.2先设计一个学生数据的类。 范例14.2 Student.java package onlyfun.caterpillar;public class Student private String name; private int score; public Student() setName(noname); public Student(String name, int score) setName(name); this.score = score; public void setName(String name) StringBuilder builder = null; if(name != null) builder = new StringBuilder(name); else builder = new StringBuilder(15); builder.setLength(15); / 最长 15 字符 = builder.toString(); public void setScore(int score) this.score = score; public String getName() return name; public int getScore() return score; / 每个数据固定写入34字节 public static int size() return 34; 对于每一个学生数据的实例在写入文件时,会固定以34字节的长度写入,也就是15个字符(30字节)加上一个int整数的长度(4字节)。范例14.2中是使用StringBuilder来固定字符长度,可以使用size()方法来取得长度信息。范例14.3则示范了如何使用RandomAccessFile来写入文件,并可随机指定一个所想读出的数据。 范例14.3 RandomAccessFileDemo.java package onlyfun.caterpillar;import java.io.*;import java.util.*;public class RandomAccessFileDemo public static void main(String args) Student students = new Student(Justin, 90), new Student(momor, 95), new Student(Bush, 88), new Student(caterpillar, 84); try File file = new File(args0); / 建立RandomAccessFile实例并以读写模式打开文件 RandomAccessFile randomAccessFile = new RandomAccessFile(file, rw); for(int i = 0; i students.length; i+) / 使用对应的write方法写入数据 randomAccessFile.writeChars(studentsi.getName(); randomAccessFile.writeInt(studentsi.getScore(); Scanner scanner = new Scanner(System.in); System.out.print(读取第几个数据?); int num = scanner.nextInt(); / 使用seek()方法操作存取位置 randomAccessFile.seek(num-1) * Student.size(); Student student = new Student(); / 使用对应的read方法读出数据 student.setName(readName(randomAccessFile); student.setScore(randomAccessFile.readInt(); System.out.println(姓名: + student.getName(); System.out.println(分数: + student.getScore(); / 设置关闭文件 randomAccessFile.close(); catch(ArrayIndexOutOfBoundsException e) System.out.println(请指定文件名称); catch(IOException e) e.printStackTrace(); private static String readName(RandomAccessFile randomAccessfile) throws IOException char name = new char15; for(int i = 0; i name.length; i+) namei = randomAccessfile.readChar(); / 将空字符取代为空格符并返回 return new String(name).replace(0, ); 执行结果:java onlyfun.caterpillar.RandomAccessFileDemo student.dat读取第几个数据?2姓名:momor分数:95RandomAccessFile上的相关方法实现都在批注中说明了,可以看到读写文件时几个必要的流程: 打开文件并指定读写方式在Java中,当实例化一个与文件相关的输入/输出类时,就会进行打开文件的动作。在实例化的同时要指定文件是要以读出(r)、写入(w)或可读可写(rw)的方式打开,可以将文件看作是一个容器,要读出或写入数据都必须打开容器的瓶盖。 使用对应的写入方法对文件进行写入,要使用对应的写入方法。在Java中通常是write的名称作为开头,在低级的文件写入中,要写入某种类型的数据,就要使用对应该类型的方法,如writeInt()、writeChar()等。 使用对应的读出方法对文件进行读出,要使用对应的读出方法。在Java中通常是read的名称作为开头,在低级的文件读出中,要读出某种类型的数据,就要使用对应该类型的方法,如readInt()、readChar()等。 关闭文件可以将文件看作是一个容器,要读出或写入数据都必须打开容器的瓶盖,而不进行读出或写入时,就要将瓶盖关闭。对于某些文件存取对象来说,关闭文件的动作意味着将缓冲区(Buffer)的数据全部写入文件,如果不作关闭文件的动作,某些数据可能没有写入文件而遗失。14.2位流计算机中的数据都是以0与1的方式来存储,如果要在两个装置之间进行数据的存取,当然也是以0与1位的方式来进行,Java将数据于目的地及来源之间的流动抽象化为一个流(Stream),而流当中流动的则是位数据。14.2.1InputStream和OutputStream计算机中实际上数据的流动是通过电路,而上面流动的则是电流,电流的电位有低位与高位,即数字的0与1位。从程序的观点来说,通常会将数据目的地(例如内存)与来源(例如文件)之间的数据流动抽象化为一个流(Stream),而其中流动的则是位数据,如图14-1所示。图14-1 数据的流动抽象化为流的概念在Java SE中有两个类用来作流的抽象表示: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 package onlyfun.caterpillar;import java.io.*;public class StreamDemo public static void main(String args) try System.out.print(输入字符: ); System.out.println(输入字符十进制表示: + System.in.read(); catch(IOException e) e.printStackTrace(); 执行结果:输入字符: A输入字符十进制表示: 65字符A输入后由标准输入流in读取,A的位表示以十进制来看就是65,这是A字符的编码(查查ASCII编码表就知道了)。一般来说,很少直接实现InputStream或OutputStream上的方法,因为这些方法比较低级,通常会实现它们的子类。这些子类上所定义的方法在进行输入/输出时更为方便。14.2.2FileInputStream和FileOutputStreamjava.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 package onlyfun.caterpillar;import java.io.*; public class FileStreamDemo public static void main(String args) try byte buffer = new byte1024; / 来源文件 FileInputStream fileInputStream = new FileInputStream(new File(args0); / 目的文件 FileOutputStream fileOutputStream = new FileOutputStream(new File(args1); / available()可取得未读取的数据长度 System.out.println(复制文件: + fileInputStream.available() + 字节); while(true) if(fileInputStream.available() 1024) / 剩余的数据比1024字节少 / 一位一位读出再写入目的文件 int remain = -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(ArrayIndexOutOfBoundsException e) System.out.println( using: java FileStreamDemo src des); e.printStackTrace(); catch(IOException e) e.printStackTrace(); 程序中示范了两个read()方法,一个方法可以读入指定长度的数据至数组,另一个方法一次可以读入一个字节。每次读取之后,读取的光标都会往前进,如果读不到数据则返回1,使用available()方法获得还有多少字节可以读取。除了使用File来建立FileInputStream、FileOutputStream的实例之外,也可以直接使用字符串指定路径来建立。/ 来源文件FileInputStream fileInputStream = new FileInputStream(args0); / 目的文件FileOutputStream fileOutputStream = new FileOutputStream(args1);在不使用文件流时,记得使用close()方法自行关闭流,以释放与流相依的系统资源。一个执行的结果范例如下,它将FileDemo.java复制为FileDemo.txt:java onlyfun.caterpillar.FileStreamDemo FileDemo.java FileDemo.txt复制文件:1723字节复制完成FileOutputStream默认会以新建文件的方式来开启流。如果指定的文件名称已经存在,则原文件会被覆盖;如果想以附加的模式来写入文件,则可以在构建FileOutputStream实例时指定为附加模式。例如:FileOutputStream fileOutputStream = new FileOutputStream(args1, true);构建方法的第二个append参数如果设置为true,在开启流时如果文件不存在则会新建一个文件,如果文件存在就直接开启流,并将写入的数据附加至文件末端。虽然我一向不喜欢使用过长的范例来作程序示范(也不喜欢看很长的范例),不过本章的范例与其他各章的比起来相对长了一些,我会在程序中多用注释解释程序的逻辑。因为解释输入/输出操作最好的方式,是呈现一个具实用性的范例,本章的范例除了练习的作用之外,日后需要某些输入/输出功能时,也可以来参考看看如何实现。14.2.3BufferedInputStream和BufferedOutputStream在介绍FileInputStream和FileOutputStream的例子中,使用了一个byte数组来作为数据读入的缓冲区,以文件存取为例,硬盘存取的速度远低于内存中的数据存取速度。为了减少对硬盘的存取,通常从文件中一次读入一定长度的数据,而写入时也是一次写入一定长度的数据,这可以增加文件存取的效率。java.io.BufferedInputStream与java.io.BufferedOutputStream可以为InputStream、OutputStream类的对象增加缓冲区功能。构建BufferedInputStream实例时,需要给定一个InputStream类型的实例,实现BufferedInputStream时,实际上最后是实现InputStream实例。同样地,在构建BufferedOutputStream时,也需要给定一个OutputStream实例,实现BufferedOutputStream时,实际上最后是实现OutputStream实例。BufferedInputStream的数据成员buf是一个位数组,默认为2048字节。当读取数据来源时,例如文件,BufferedInputStream会尽量将buf填满。当使用read()方法时,实际上是先读取buf中的数据,而不是直接对数据来源作读取。当buf中的数据不足时,BufferedInputStream才会再实现给定的InputStream对象的read()方法,从指定的装置中提取数据,如图14-2所示。图14-2 BufferedInputStream在内部有buf成员作为缓冲区BufferedOutputStream的数据成员buf是一个位数组,默认为512字节。当使用write()方法写入数据时,实际上会先将数据写至buf中,当buf已满时才会实现给定的OutputStream对象的write()方法,将buf数据写至目的地,而不是每次都对目的地作写入的动作。下面将范例14.5做个改写,这次不用自行设置缓冲区,而使用BufferedInputStream和BufferedOutputStream让程序看来简单一些,也比较有效率。 范例14.6 BufferedStreamDemo.java package onlyfun.caterpillar;import java.io.*;public class BufferedStreamDemo public static void main(String args) try byte data = new byte1; File srcFile = new File(args0); File desFile = new File(args1); BufferedInputStream bufferedInputStream = new BufferedInputStream( new FileInputStream(srcFile); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( new FileOutputStream(desFile); System.out.println(复制文件: + srcFile.length() + 字节); while(bufferedInputStream.read(data) != -1) bufferedOutputStream.write(data); / 将缓冲区中的数据全部写出 bufferedOutputStream.flush(); / 关闭流 bufferedInputStream.close(); bufferedOutputStream.close(); System.out.println(复制完成); catch(ArrayIndexOutOfBoundsException e) System.out.println( using: java UseFileStream src des); e.printStackTrace(); catch(IOException e) e.printStackTrace(); 为了确保缓冲区中的数据一定被写出至目的地,建议最后执行flush()将缓冲区中的数据全部写出目的流中。这个范例的执行结果与范例14.5是相同的。BufferedInputStream和BufferedOutputStream并没有改变InputStream或 OutputStream的行为,读入或写出时的动作还是InputStream和OutputStream负责。BufferedInputStream和BufferedOutputStream只是在操作对应的方法之前,动态地为它们加上一些额外功能(像缓冲区功能),在这里是以文件存取流为例,实际上可以在其他流对象上也使用BufferedInputStream和BufferedOutputStream功能。14.2.4DataInputStream和DataOutputStreamjava.io.DataInputStream和java.io.DataOutputStream可提供一些对Java基本数据类型写入的方法,像读写int、double和boolean等的方法。由于Java的数据类型大小是规定好的,在写入或读出这些基本数据类型时,就不用担心不同平台间数据大小不同的问题。这里还是以文件存取来进行说明。有时只是要存储一个对象的成员数据,而不是整个对象的信息,成员数据的类型假设都是Java的基本数据类型,这样的需求不必要使用到与Object输入、输出相关的流对象,可以使用DataInputStream、DataOutputStream来写入或读出数据。下面使用范例来介绍如何使用DataInputStream与DataOutputStream。先设计一个Member类。 范例14.7 Member.java package onlyfun.caterpillar;public class Member private String name; private int age; public Member() public Member(String name, int age) = name; this.age = age; public void setName(String name) = name; public void setAge(int age) this.age = age; public String getName() return name; public int getAge() return age; 打算将Member类实例的成员数据写入文件中,并打算在读入文件数据后,将这些数据还原为Member对象。范例14.8简单示范了如何实现这个需求。 范例14.8 DataStreamDemo.java package onlyfun.caterpillar;import java.io.*;public class DataStreamDemo public static void main(String args) Member members = new Member(Justin, 90), new Member(momor, 95), new Member(Bush, 88); try DataOutputStream dataOutputStream = new DataOutputStream( new FileOutputStream(args0); for(Member member : members) / 写入UTF字符串 dataOutputStream.writeUTF(member.getName(); / 写入int数据 dataOutputStream.writeInt(member.getAge(); / 读出所有数据至目的地 dataOutputStream.flush(); / 关闭流 dataOutputStream.close(); DataInputStream dataInputStream = new DataInputStream( new FileInputStream(args0); / 读出数据并还原为对象 for(int i = 0; i members.length; i+) / 读出UTF字符串 String name = dataInputStream.readUTF(); / 读出int数据 int score = dataInputStream.readInt(); membersi = new Member(name, score); / 关闭流 dataInputStream.close(); / 显示还原后的数据 for(Member member : members) System.out.printf(%st%d%n, member.getName(), member.getAge(); catch(IOException e) e.printStackTrace(); 在从文件中读出数据时,不用费心地自行判断读入字符串时或读入int类型时何时该停止,使用对应的readUTF()或readInt()方法就可以正确地读入完整类型数据。同样地,DataInputStream、DataOutputStream并没有改变InputStream或OutputStream的行为,读入或写出时的动作还是InputStream、OutputStream负责。DataInputStream、DataOutputStream只是在实现对应的方法时,动态地为它们加上类型判断功能,在这里虽然是以文件存取流为例,实际上可以在其他流对象上也使用DataInputStream、DataOutputStream功能。14.2.5ObjectInputStream和ObjectOutputStream在Java程序执行的过程中,很多数据都是以对象的方式存在于内存中。有时会希望直接将内存中整个对象存储至文件,而不是只存储对象中的某些基本类型成员信息,而在下一次程序运行时,希望可以从文件中读出数据并还原为对象。这时可以使用java.io.ObjectInputStream和java.io.ObjectOutputStream来进行这项工作。如果要直接存储对象,定义
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论