15.输入-输出

本章要点

  • 使用File类访问本地文件系统
  • 使用文件过滤器
  • 理解IO流的模型和处理方式
  • 使用IO流执行输入,输出操作
  • 使用转换流将字节流转换字符流
  • 推回流的功能和用法
  • 重定向标准输入,输出
  • 访问其他进程的输入,输出
  • RandomAccessFile的功能和用法
  • 对象序列号机制和作用
  • 通过实现Serializable接口实现序列化
  • 实现定制的序列化
  • 实现Externalizable接口实现序列化
  • java新IO的概念和作用
  • 使用Buffer和Channel完成输入,输出
  • Charset的功能和用法
  • FileLock的功能和用法

IO(输入/输出)是比较乏味的事情,因为看不到明显的运行效果,但输入/输出是所有程序都必需的部分:使用输入机制,允许程序读取外部数据(包括来自磁盘,光盘等存储设备的数据),用户输入数据;使用输出机制,允许程序记录运行状态,将程序数据输出到磁盘,光盘等存储设备中。

java的IO支持通过java.io包下的类和接口来支持,在java。io包下主要包括输入,输出两种IO流,每种输入,输出流又可分为字节流和字符流两大类。其中字节流以字节为单位来处理输入,输出操作,而字符流则以字符来处理输入,输出操作。除此之外,java的IO流使用了一种装饰器设计模式,它将IO流分成底层节点流和上层处理器,其中节点流用于和底层物理存储节点直接关联--不同物流节点获取节点流的方式可能存在一定的差异,但程序可以把不同的物理节点流包装成统一的处理流,从而允许程序使用统一的输入,输出代码来读取不同物理存储节点的资源。

通过使用序列化机制,我们可以把内存中的java对象转换成二进制字节流,这样就可以把java对象存储到磁盘里,或者在网络上传输java对象。这也是java提供分布式编程的重要基础。

15.1 File类

File类是java.io包下代表与平台无关的文件和目录,也就是说如果希望在程序中操作文件和目录都可以通过file类来完成,值得指出的是不管是文件,还是目录都是使用File来操作的,file能新建,删除和重命名文件和目录,File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流、

15.1.1 访问文件和目录

File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径,默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属性“user.dir”指定,通常也就是运行java虚拟机时所在的路径。

一旦创建了Filr对象后就可以调用File对象的方法来访问,File类提供了很多方法来操作文件和目录,下面列出一些比较常用的方法。

访问文件名相关的方法

  • String getName()
  • STrinng getPath()
  • File getAbsoluteFile()
  • String getAbsolutePath()
  • String getParent()
  • boolean renameTo(File newName)

文件检测相关的方法

  • boolean exists()
  • boolean canWrite()
  • boolean canRead()
  • boolean isFile()
  • boolean isAbsolute()
  • long lastModified()
  • long length()
  • boolean createNewFile()
  • boolean delete()
  • static File createTempFile(String prefix,String suffix)
  • static File createTempFile(String prefix,String suffix,File directory)
  • void deleteOnExit()
  • boolean mkdir()
  • String[] list()
  • File[] listFiles()
  • static File[] listRoots()

注意:

Windows的路径分隔符使用反斜线,而java程序中的反斜线表示转义字符,所以如果需要在windows中路径下包括反斜线,应该用两条反斜线,或者直接使用斜线也可以,java程序支持将斜线当成平台无关的路径分隔符。

15.1.2 文件过滤器

FilenameFilter

15.2 理解java的IO流

java的IO流是实现输入/输出的基础,它可以方便地实现数据的输入输出操作,java中把不同的输入输出源(键盘,文件,网络连接等)抽象表述为“流”,通过流的方式允许java程序使用相同的方式来访问不同的输入输出源。stream是从起源到接收的有序数据。

java把所有传统的流类型都放在java.io包中,用以实现输入输出功能。

15.2.1 流的分类

按照不同的分类方式,可以将流分为不同的类型,下面我们从不同的角度来对流进行分类,他们在概念上可能存在重叠的地方。

输入流和输出流

按照流的流向来分,可以分为输入流和输出流。

  • 输入流:只能从中读取数据,而不能向其写出数据。
  • 输出流:只能向其写出数据,而不能从中读取数据。

java的输入流主要由InputStrean和Reader作为基类,而输出流则主要由OutputStrean和Writer作为基类。

字节流和字符流

字节流和字符流区别非常简单,他们的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同:字节流操作的最小数据单元是8位的字节,而字符流操作的最小数据单元是16位的字符。

字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类。

节点流和处理流

按照流到的角色分,可以分为节点流和处理流。

可以从/向一个特定能够的IO设备(如磁盘,网络)读/写数据的流,称为节点流,节点流常常也被称为低级流。

当使用节点流来进行输入/输出时,程序直接连接到实际的数据源,和实际的输入输出节点连接。

处理流则用于对一个已存在的流进行连接或封装,通过封装后流来实现数据读写功能。处理流也被称为高级流。

当使用处理流来进行输入输出时,程序并不会直接连接到实际的数据源,没有和实际的输入输出节点连接。使用处理流的一个明显好处是,只要使用相同的处理流,程序就可以采用完全相同的输入输出代码来访问不同的数据源,随着处理流所包装节点流的改变,程序实际所访问的数据源也相应发生改变。

提示:

实际上java使用处理流来包装节点流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出功能,以此处理流也被称为包装流。

15.2.2 流的概念模型

java把所有设备里的有序数据抽象成流模型简化了输入输出的处理,理解了流的概念模型也就了解了java IO。

java的IO流共涉及40多个类,这些类看上去芜杂而凌乱,但实际上非常规则,而且彼此之间存在非常紧密的联系。java的IO流的40多个类都是从4个抽象基类派生出来的:

  • InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。

对于InputStream和Reader而言,它们把输入设备抽象成一个“水管”,这个水管里的每个“水滴”依次排列。

从图中可以看出,字节流和字符流的处理方式其实非常相似,只是它们处理的最小输入输出单位不同而已。输入流使用隐式的记录指针来表示当前正准备从哪个“水滴”开始读取,每当程序从InputStream或Reader里取出一个或多个水滴后,记录指针自动向后移动:除此之外,InputStram和Reader都提供一些方法来控制记录指针的移动。

对于OutputStram和Write而言,它们同样把输出设备抽象成一个水管,只是这个水管里没有任何水滴。

当我们执行输出时,程序相当于依次把水滴放入到输出流的水管中,输出流同样采用隐式的记录指针来标识当前水滴即将放入的位置,每当程序向OutputStream或Writer里输出一个或多个水滴后,记录指针自动向后移动。

java的处理流模型则体现了java输入输出流设计的灵活性。处理流的功能主要体现在两个方面:

  • 性能的提高:主要以增加缓冲的方式来提高输出输出的效率。
  • 操作的便捷:处理流可能提供了系列便捷的方法来一次输入输出大批量的内容,而不是输入输出一个或多个水滴。

而处理流可以“嫁接”在任何已存在的流的基础之上,这就允许java应用程序采用相同代码,透明的方式来访问不同输入输出设备的数据流。

通过使用处理流,java程序无须理会我们的输入输出节点是磁盘,是网络还是其他的输入输出设备,程序只要将这些节点流包装成高级处理流,就可以使用相同的输入输出代码来读写不同的输入输出设备的数据。

15.3 字节流和字符流

笔者喜欢把字节流和字符流放在一起讲解,因此他们的操作方式几乎完全一样,区别只是操作的数据单元有区别而已:字节流操作的数据单元是字节,字符流操作的数据单元是字符。

15.3.1 InputStream和Reader

InputStream和Reader是所有输入流的基类,它们都是两个抽象类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以它们的方法时所有输入流都可使用的方法。

在InputStream里包含如下三个方法:

  • int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型)。
  • int read(byte[] b):从输入流中读取最多b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
  • int read(byte[],int off,int len):从输入流中读取最多len字节的数据,并将其存储在数组b中,放入b数组中时并不是从数组起点开始,而是从off位置开始返回实际读取的字节数。

在Reader里包含如下三个方法:

  • int read():从事输入流中读取单个字符,返回所读取的字符数据(字符数据可直接转换为int类型)。
  • int read(char[] cbuf):从输入流中读取最多cbuf.length个字符的数据,并将其存储在字符数组cbuf中,返回实际读取的字符数。
  • int read(char[] cbuf,int off,int len):从输入流中读取最多len个字符的数据,并将其存储在字符数组cbuf中,放入cbuf数组中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数。

直到read(char[] cbuf)或read(byte[] b)方法返回-1时即表明到了输入流的结束点。

InputStream和Reader都是抽象类,本身不能创建实例,但他们分别有一个用于读取文件的输入流:FileInputStream和FileReader,它们都是节点流--会直接和指定文件关联。

15.3.2 OutputStream和Writer

同上

使用java的IO流执行输出时,不要忘记了关闭输出流,关闭输出流除了可以保证流的物理资源被回收之外,可能还可以将输出流缓冲中的数据flush到物理节点里(相当于在执行close方法之前,自动执行输出流的flush()方法)。java的很多输出流默认都提供了缓冲功能,其实我们没有必要可以去记忆哪些流有缓冲功能,哪些没有,程序只要正常关闭所有输出流即可保证程序正常。

15.4 输入/输出流体系

15.4.1 处理流的用法

处理流可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入输出方法,让程序员只需关心高级流的操作。

因此我们使用处理流时的典型思路是:使用处理流来包装节点流,程序通过处理流来执行输入输出功能,让节点流与底层的IO设备,文件交互。

实际上我们要识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流就一定是处理流:而所有节点流都是直接以物理IO节点作为构造器参数的。

注意:

当我们使用处理流来包装底层节点流之后,关闭输入/输出流资源时,只要关闭最上层的处理流即可。关闭最上层的处理流时,系统会自动关闭该处理流包装的节点流。

15.4.2 输入输出流体系

通常来说,我们认为字节流的功能比字符流功能强大,因为计算机里所有的数据都是二进制的,而字节流可以处理所有的二进制文件--但问题是如果我们使用字节流来处理文本文件时,我们需要使用合适的方式把这些字节转换成字符,这就增加了编程的复杂度。所以通常有一个规则:如果需要进行输入/输出的内容是文本内容,则应该考虑使用字符流,如果需要进行输入输出的二进制内容,则应该考虑使用字节流。

提示:

我们常常会把计算机的文件分为文本文件和二进制文件两大类:所有能用记事本打开并看到其中字符内容的文件我们成为文本文件,反之则称为二进制文件,但实质是:计算机里的所有文件都是二进制文件,文本文件只是二进制文件的一种特例,当二进制文件里的内容恰好能被正常解析成字符时,则该二进制文件就变成了文本文件。更甚至于,即使是正常的文本文件,如果我们打开该文件时强制使用了“错误”的编码集,文件内容会变成乱码。

15.4.3 转换流

15.4.4 推回输入流

15.5 重定向标准输入/输出

15.6 java虚拟机读写其他进程的数据

15.7 RandomAccessFile

15.8 对象序列化

15.9 java新IO

猜你喜欢

转载自blog.csdn.net/qq_21874145/article/details/81408138