第11章 Java IO输入输出流.docx
- 文档编号:11301276
- 上传时间:2023-02-26
- 格式:DOCX
- 页数:64
- 大小:312.14KB
第11章 Java IO输入输出流.docx
《第11章 Java IO输入输出流.docx》由会员分享,可在线阅读,更多相关《第11章 Java IO输入输出流.docx(64页珍藏版)》请在冰豆网上搜索。
第11章JavaIO输入输出流
第十一章IO/输入输出
大多数应用程序都需要与外部设备进行数据交换,最常见的外部设备包含磁盘和网络,IO就是指应用程序对这些设备的数据输入与输出,在程序中,键盘被当作输入文件,显示器被当作输出文件使用。
Java语言定义了许多类专门负责各种方式的输入输出,这些类都被放在java.io包中。
11.1File类
File类是IO包中唯一代表磁盘文件本身的对象,File类定义了一些与平台无关的方法来操纵文件,通过调用File类提供的各种方法,我们能够创建、删除文件,重命名文件,判断文件的读写权限及是否存在,设置和查询文件的最近修改时间。
在Java中,目录也被当作File使用,只是多了一些目录特有的功能——可以用list方法列出目录中的文件名。
在Unix下的路径分隔符为(/),在Dos下的路径分隔符为(\),Java可以正确处理Unix和Dos的路径分隔符,即使我们在Windows环境下使用(/)作为路径分隔符,Java仍然能够正确处理。
我们用下面的一个简单应用来演示一下File类用法,判断某个文件是否存在,存在则删除,不存在则创建,读者可以在Windows的资源管理器下观察到这个变化。
程序清单:
FileTest.java
importjava.io.*;
publicclassFileTest
{
publicstaticvoidmain(String[]args)
{
Filef=newFile("c:
\\1.txt");
if(f.exists())
f.delete();
else
try
{
f.createNewFile();
}
catch(Exceptione)
{
System.out.println(e.getMessage());
}
System.out.println("Filename:
"+f.getName());
System.out.println("Filepath:
"+f.getPath());
System.out.println("Abspath:
"+f.getAbsolutePath());
System.out.println("Parent:
"+f.getParent());
System.out.println(f.exists()?
"exists":
"doesnotexist");
System.out.println(f.canWrite()?
"iswriteable":
"
isnotwriteable");
System.out.println(f.canRead()?
"isreadable":
"isnotreadable");
System.out.println(f.isDirectory()?
"is":
"isnot"+"adirectory");
System.out.println(f.isFile()?
"isnormalfile":
"mightbeanamedpipe");
System.out.println(f.isAbsolute()?
"isabsolute":
"
isnotabsolute");
System.out.println("Filelastmodified:
"+f.lastModified());
System.out.println("Filesize:
"+f.length()+"Bytes");
}
}
当运行这个程序时会因为文件1.txt的存在和不存在而出现两种结果:
结果1:
Filename:
1.txt
Filepath:
c:
\1.txt
Abspath:
c:
\1.txt
Parent:
c:
\
exists
iswriteable
isreadable
isnotadirectory
isnormalfile
isabsolute
Filelastmodified:
1051755103126
Filesize:
0Bytes
结果2:
Filename:
1.txt
Filepath:
c:
\1.txt
Abspath:
c:
\1.txt
Parent:
c:
\
doesnotexist
isnotwriteable
isnotreadable
isnotadirectory
mightbeanamedpipe
isabsolute
Filelastmodified:
0
Filesize:
0Bytes
注:
delete方法删除由File对象的路径所表示的磁盘文件。
它只能删除普通文件,而不能删除目录,即使是空目录也不行。
关于File类的其它方法,是没法死记硬背的,读者在需要时自己查看JDK文档,应该能够明白怎么使用。
初步接触了File类,我们发现File类不能访问文件的内容,即不能够从文件中读取数据或往文件里写数据,它只能对文件本身的属性进行操作。
11.2RandomAccessFile类
RandomAccessFile类可以说是Java语言中功能最为丰富的文件访问类,它提供了众多的文件访问方法。
RandomAccessFile类支持“随机访问”方式,我们可以跳转到文件的任意位置处读写数据。
在你访问一个文件的时候,不想把文件从头读到尾,并希望像访问一个数据库一样的访问一个文本文件,使用RandomAccessFile类就是你的最佳选择。
RandomAccessFile对象类有个位置指示器,指向当前读写处的位置,当读写n个字节后,文件指示器将指向这n个字节后的下一个字节处。
刚打开文件时,文件指示器指向文件的开头处,我们可以移动文件指示器到新的位置,随后的读写操作将从新的位置开始。
RandomAccessFile在等长记录格式文件的随机(相对顺序而言)读取时有很大的优势,但该类仅限于操作文件,不能访问其他的IO设备,如网络,内存映象等。
有关RandomAccessFile类中的成员方法及使用说明,请参阅JDK文档。
下面是一个使用RandomAccessFile的例子,往文件中写入三名员工的信息,然后按照第二名员工,第一名员工,第三名员工的先后顺序读出。
RandomAccessFile可以以只读或读写方式打开文件,具体使用哪种方式取决于我们创建RandomAccessFile类对象的构造方式:
newRandomAccessFile(f,"rw");//读写方式
newRandomAccessFile(f,"r");//只读方式
注:
当我们的程序需要以读写的方式打开一个文件时,如果这个文件不存在,程序会为你创建它。
我们还需要设计一个类来封装员工信息。
一个员工信息就是文件中的一条记录,我们必须保证每条记录在文件中的大小相同,也就是每个员工的姓名字段在文件中的长度是一样的,我们才能够准确定位每条记录在文件中的具体位置。
假设name中有八个字符,少于八个则补空格(这里我们用"\u0000"),多于八个则去掉后面多余的部分。
由于年龄是整型数,不管这个数有多大,只要它不超过整型数的范围,在内存中都是占4个字节大小。
程序清单:
RandomFileTest.java
importjava.io.*;
publicclassRandomFileTest
{
publicstaticvoidmain(String[]args)throwsException
{
Employeee1=newEmployee("zhangsan",23);
Employeee2=newEmployee("Lisi",24);
Employeee3=newEmployee("Wangwu",25);
RandomAccessFilera=newRandomAccessFile("c:
\\1.txt","rw");
ra.write(e1.name.getBytes());
ra.writeInt(e1.age);
ra.write(e2.name.getBytes());
ra.writeInt(e2.age);
ra.write(e3.name.getBytes());
ra.writeInt(e3.age);
ra.close();
RandomAccessFileraf=newRandomAccessFile("c:
\\1.txt","r");
intlen=8;
raf.skipBytes(12);//跳过第一个员工的信息,其中姓名8字节,年龄4字节
System.out.println("第二个员工信息:
");
Stringstr="";
for(inti=0;i str=str+(char)raf.readByte(); System.out.println("name: "+str); System.out.println("age: "+raf.readInt()); System.out.println("第一个员工的信息: "); raf.seek(0);//将文件指针移动到文件开始位置 str=""; for(inti=0;i str=str+(char)raf.readByte(); System.out.println("name: "+str); System.out.println("age: "+raf.readInt()); System.out.println("第三个员工的信息: "); raf.skipBytes(12);//跳过第二个员工信息 str=""; for(inti=0;i str=str+(char)raf.readByte(); System.out.println("name: "+str.trim()); System.out.println("age: "+raf.readInt()); raf.close(); } } classEmployee { Stringname; intage; finalstaticintLEN=8; publicEmployee(Stringname,intage) { if(name.length()>LEN) { name=name.substring(0,8); } else { while(name.length() name=name+"\u0000"; } this.name=name; this.age=age; } } 运行结果: 第二个员工信息: name: Lisi age: 24 第一个员工的信息: name: zhangsan age: 23 第三个员工的信息: name: Wangwu age: 25 c盘还多了个文件1.txt: 图11.1 上面的这个程序完成了我们想要的功能,演示了RandomAccessFile类的作用。 String.substring(int beginIndex,int endIndex)方法可以用于取出一个字符串中的部分子字符串,要注意的一个细节是: 子字符串中的第一个字符对应的是原字符串中的脚标为beginIndex处的字符,但最后的字符对应的是原字符串中的脚标为endIndex-1处的字符,而不是endIndex处的字符。 在实际生活中,我们常用的数据库和数据库管理工具实际上就是这种原理。 我们的1.txt就相当于数据库的数据文件,而我们这个程序提供了往这个数据文件写入和读取数据的功能。 11.3节点流 11.3.1理解流的概念 数据流是一串连续不断的数据的集合,就象水管里的水流,在水管的一端一点一点地供水,而在水管的另一端看到的是一股连续不断的水流。 数据写入程序可以是一段、一段地向数据流管道中写入数据,这些数据段会按先后顺序形成一个长的数据流。 对数据读取程序来说,看不到数据流在写入时的分段情况,每次可以读取其中的任意长度的数据,但只能先读取前面的数据后,再读取后面的数据。 不管写入时是将数据分多次写入,还是作为一个整体一次写入,读取时的效果都是完全一样的。 我们将IO流类分为两个大类,节点流类和过滤流类(也叫处理流类)。 程序用于直接操作目标设备所对应的类叫节点流类,程序也可以通过一个间接流类去调用节点流类,以达到更加灵活方便地读写各种类型的数据,这个间接流类就是过滤流类(也叫处理流类),我更喜欢称之为包装类。 不管叫什么,都只是一个代名词而已,读者不要太在意,你可以根据自己的习惯和喜好来定。 11.3.2InputStream与OutputStream 程序可以从中连续读取字节的对象叫输入流,用InputStream类完成,程序能向其中连续写入字节的对象叫输出流,用OutputStream类完成。 InputStream与OutputStream对象是两个抽象类,还不能表明具体对应哪种IO设备。 它们下面有许多子类,包括网络,管道,内存,文件等具体的IO设备,如FileInputStream类对应的就是文件输入流,是一个节点流类,我们将这些节点流类所对应的IO源和目标称为流节点(Node)。 很多人搞不清程序要将A文件的内容写入B文件中,程序对A文件的操作所用的是输出类还是输入类这个问题。 读者也先自己想想,再记住下面的话,输入输出类是相对程序而言的,而不是代表文件的,所以我们应该创建一个输入类来完成对A文件的操作,创建一个输出类来完成对B文件的操作。 InputStream定义了Java的输入流模型。 该类中的所有方法在遇到错误的时候都会引发IOException异常,下面是InputStream类中方法的一个简要说明: ✓intread()返回下一个输入字节的整型表示,,如果返回-1表示遇到流的末尾,结束。 ✓intread(byte[]b)读入b.length个字节放到b中并返回实际读入的字节数。 ✓intread(byte[]b,intoff,intlen)这个方法表示把流中的数据读到,数组b中,第off个开始的len个数组元素中. ✓longskip(longn)跳过输入流上的n个字节并返回实际跳过的字节数。 ✓intavailabale()返回当前输入流中可读的字节数。 ✓voidmark(intreadlimit)在输入流的当前位置处放上一个标志,允许最多再读入readlimit个字节。 ✓voidreset()把输入指针返回到以前所做的标志处。 ✓booleanmarkSupported()如果当前流支持mark/reset操作就返回true。 ✓voidclose()在操作完一个流后要使用此方法将其关闭,系统就会释放与这个流相关的资源。 InputStream是一个抽象类,程序中实际使用的是它的各种子类对象。 不是所有的子类都会支持InputStream中定义的某些方法的,如skip,mark,reset等,这些方法只对某些子类有用。 一个对象在没有引用变量指向它时会变成垃圾,最终会被垃圾回收器从内存中清除。 对于我们创建的流对象,干嘛还要“调用close方法将它关闭,以释放与其相关的资源”呢? 这相关的资源到底是些什么呢? 我们在程序中创建的对象都是对应现实世界中有形或无形的事物,计算机操作系统所产生的东西当然也是现实世界中的事物,也就是说,程序中的对象也可以对应计算机操作系统所产生的一个其他东西,专业地说,这些东西叫资源,流就是操作系统产生的一种资源。 当我们在程序中创建了一个IO流对象,同时系统内也会创建了一个叫流的东西,在这种情况下,计算机内存中实际上产生了两个事物,一个是Java程序中的类的实例对象,一个是系统本身产生的某种资源,我们以后讲到的窗口,Socket等都是这样的情况。 Java垃圾回收器只能管理程序中的类的实例对象,没法去管理系统产生的资源,所以程序需要调用close方法,去通知系统释放其自身产生的资源。 OutputStream是一个定义了输出流的抽象类,这个类中的所有方法均返回void,并在遇到错误时引发IOException异常。 下面是OutputStream的方法: ✓voidwrite(intb)将一个字节写到输出流。 注意,这里的参数是int型,它允许write使用表达式而不用强制转换成byte型。 ✓voidwrite(byte[]b)将整个字节数组写到输出流中。 ✓voidwrite(byte[]b,intoff,intlen)将字节数组b中的从off开始的len个字节写到输出流。 ✓voidflush彻底完成输出并清空缓冲区。 ✓voidclose关闭输出流。 计算机访问外部设备,要比直接访问内存慢得多,如果我们每一次write方法的调用都直接写到外部设备(如直接写入硬盘文件),CPU就要花费更多的时间等待外部设备;如果我们开辟一个内存缓冲区,程序的每一次write方法都是写到这个内存缓冲区中,只有这个缓冲区被装满后,系统才将这个缓冲区的内容一次集中写到外部设备。 使用内存缓冲区有两个方面的好处,一是有效地提高了CPU的使用率,二是write并没有马上真正写入到外设,我们还有机会回滚部分写入的数据。 使用缓冲区,能提高整个计算机系统的效率,但也会降低单个程序自身的效率,由于有这么一个中间缓冲区,数据并没有马上写入到目标中去,例如在网络流中,就会造成一些滞后。 对于输入流,我们也可以使用缓冲区技术。 在程序与外部设备之间到底用不用缓冲区,是由编程语言本身决定的,我们通常用的C语言默认情况下就会使用缓冲区,而在Java语言中,有的类使用了缓冲区,有的类没有使用缓冲区,我们还可以在程序中使用专门的包装类来实现自己的缓冲区。 flush方法就是用于即使在缓冲区没有满的情况下,也将缓冲区的内容强制写入到外设,习惯上称这个过程为刷新。 可见,flush方法不是对所有的OutputStream子类都起作用的,它只对那些使用缓冲区的OutputStream子类有效。 如果我们调用了close方法,系统在关闭这个流之前,也会将缓冲区的内容刷新到硬盘文件的。 作者开发过一个邮件服务器程序,需要7*24小时不间断工作,这个服务器程序要面对internet上各种可能的非法格式的数据输入和攻击,而我的程序正好又没考虑到某种非法格式的数据,一旦碰到这样的情况,程序就会崩溃。 有经验的人都知道,为了找出服务器程序崩溃的原因,我们可以将程序每次接收到的数据都记录到一个文件中,当服务器程序崩溃后,我们便打开这个记录文件,查看最后记录的那条数据,这个数据就是让我的程序毙命的罪魁祸首,然后拿着这条数据一步步测试我们的程序,就很容易找出程序中的问题了。 遗憾的是,我每次用最后记录的这条数据测试我的程序,程序均安然无恙。 最后,我发现就是因为有缓冲区的原因,缓冲区的内容还没来得及刷新到硬盘文件,程序就崩溃了,所以,文件中并没有记录最后接收到的那些数据,我在文件中看到的最后以条记录并不是真正最后接收到的那条数据。 发现了这个原因,我修改程序,在每一次调用write语句后,都立即调用flush语句,这样,我就终于找到了肇事元凶,并修复了程序的这个漏洞。 尽管我以前从来没有真正认真思考和编程试验过缓冲区问题,但是正因为还有那么一点点概念和印象,所以,在出现问题时,我才能从多方面去思考并最终解决问题。 我建议读者花更多的时间去开阔自己的知识面和思维,了解更多的原理,而不是去花大量时间去死记硬背某些细节和术语,特别是一个类中的每个函数名的具体拼写、具体的参数形式,Java中有哪些关键字等这些死板的东西,只要有个印象就足够了。 11.3.3FileInputStream与FileOutputStream 这两个流节点用来操作磁盘文件,在创建一个FileInputStream对象时通过构造函数指定文件的路径和名字,当然这个文件应当是存在的和可读的。 在创建一个FileOutputStream对象时指定文件如果存在将要被覆盖。 下面是对同一个磁盘文件创建FileInputStream对象的两种方式。 其中用到的两个构造函数都可以引发FileNotFoundException异常: FileInputStreaminOne=newFileInputStream("hello.test"); Filef=newFile("hello.test"); FileInputStreaminTwo=newFileInputStream(f); 尽管第一个构造函数更简单,但第二个构造函数允许在把文件连接到输入流之前对文件做进一步分析。 FileOutputStream对象也有两个和FileInputStream对象具有相同参数的构造函数,创建一个FileOutputStream对象时,可以为其指定还不存在的文件名,但不能是存在的目录名,也不能是一个已被其他程序打开了的文件。 FileOutputStream先创建输出对象,然后再准备输出。 其实在上一章中讲Properties类的时候,我们已经使用过这两个类。 在下面的例子中,我们用FileOutputStream类向文件中写入一串字符,并用FileInputStream读出。 程序清单: FileStream.java importjava.io.*; publicclassFileStream { publicstaticvoidmain(String[]args) { Filef=newFile("hello.txt"); try { FileOutputStreamout=newFileOutputStream(f); bytebuf[]="www.it315.org".getBytes(); out.write(buf); out.close(); } catch(Exceptione) { System.out.println(e.getMessage()); } try { FileInputStreamin=newFileInputStream(f); byte[]buf=newbyte[1024]; intlen=in.read(buf); System.out.println(newString(buf,0,len)); } catch(Exceptione) { System.out.println(e.getMessage()); } } } 编译运行上面的程序,我们能够看到当前目录下产生了一个hello.txt的文件,用记事本程序打开这个文件,能看到我们写入的内容。 随后,程序开始读取文件中的内容,并将读取到
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第11章 Java IO输入输出流 11 IO 输入输出