输入和输出

I/O类库中常使用的流这个抽象概念,代表任何有能力产出数据的数据源对象或有能力接收数据的接收端对象。

InputStream类型

作用是用来表示那些从不同数据源产生输入的类,这些数据源包括字节数组、String对象、文件、管道、一个有其他种类的流组成的序列,以便可以将它们收集合并到一个流内以及其他数据源。
在这里插入图片描述

OutPutStream

该类别的类决定输出所要去的目标,字节数组、文件或管道。FilteOutputStream为“装饰器”类提供一个基类,“装饰器”类把属性或者有用的接口与输出流连接起来。
在这里插入图片描述

添加属性和有用的接口

装饰器必须具有和它所装饰的对象相同的接口,但它可以扩展接口,而这种情况只发生在个别filter类中。
装饰器模式有一个缺点:在编写程序时,有很大的灵活性,但是它同时也增加代码的复杂性。Java I/O类库操作不便的原因:徐创建很多类,核心I/O类型加上所有的装饰器,才能得到我们单个I/O对象。FilterInputStream和FilterOutputStream是用来提供装饰器类接口以控制特定输入流和输出流的两个类。
FilterInputStream;类能够完成两件完全不同的事情。DataInputStream允许我们读取不同的基本类型数据以及String对象。搭配相应的DataOutputStream,通过数据“流”将基本类型的数据从一个地方迁移到另一个地方。
其他FilterInputStream类则在内部修改InputStream的行为方式:是否缓冲,是否保留他所读过的行以及是否把单一字符推回输入流。FilterInputStream的类型及功能如下:
在这里插入图片描述

  • 通过FilterOutPutStream向OutputStream写入
    PrintStream最初的目的是为了可视化格式打印所有的基本数据类型以及String对象。DataOutputStream的目的是将数据元素置入“流”中,使DataInputStream能可移植的重构它们。PrintStream有两个重要的方法:print()和println(),对其进行重载便可以打印各种类型数据。
    BufferedOutputStream是一个修改过的OutputStream,对数据流使用缓冲技术,因此当每次向流写入时不必每次进行实际的物理写动作。在进行输出时更经常使用。
    在这里插入图片描述

Reader和Writer

  • 数据的来源和去处

在这里插入图片描述

  • 更改流的行为:
    对于InputStream和OutputStream来说,使用FIlterInputStream和FilterOutputStream装饰器自乐修改“流”Reader和Writer的类继承层次结构继续沿用相同的思想。
    在这里插入图片描述

自我独立的类:RandomAccessFile

RandomAccessFile适用由大小一致的记录组成的文件,所以可以使用seek()将记录从一处转移到另一处,然后读取或修改记录。文件中记录的大小不一定都相同,只要能确定记录有多大以及他们在文件中的位置即可。RandomAccessfIle不是InputStreeam或OutputStream继承层次结构中的一部分。除了实现DataInput和DataOutput接口,和这两个继承层次结构没有任何关联。RandomAccessFile拥有和别的I/O类型本质不同的行为,因为可以在一个文件内向前和向后移动。
RandomAccessFile的工作方式类似于把DataInputStream和DataOutStream组合起来使用。getFilePointer()用于查找当前所处的文件位置,seek()用于在文件内移至新的位置,length()用于判断文件的最大尺寸。构造器还需要第二个参数用来指示“随机读”还是“既读又写”。并不支持写文件。

I/O流的典型使用方式

  • 缓冲输入文件
    如果想要打开一个文件用于字符输入,可以使用以String或File对象作为文件名的FileInputReader。为提高速度,对文件进行缓冲,那么我们将所产生的引用给一个BufferedReader构造器,由于BufferedReader提供readLine()方法,所以是最终对象和进行读取的接口。当ReadLine()将返回null时,就到达文件末尾。
    在这里插入图片描述
    字符串sb用来累计文件的全部内容。

  • 从内存输入
    从BufferedInputFile.read()读入的String结果被用来创建一个StringReader,然后调用read()每次读取一个字符,并把它发送到控制台。
    在这里插入图片描述

  • 格式化内存输入
    要读取格式化数据,可以使用DataInputStream,是一个面向字节的I/O类,因此必须使用InputSream类而不是Reader类。
    在这里插入图片描述
    为了产生该数组String包含一个可以实现此工作的getBytes()方法,产生的ByteArrayInputStream是一个适合传递给DataInputStream的InputStream。

  • 基本的文件输出
    FileWriter对象可以向文件写入数据。
    在这里插入图片描述
    当问本行被写入文件时,增加行号,但是不使用LineNumberInputStream。当读完输入数据流,readLine()会返回null。可以看到要为out显示调用close()。如果不为所有的输出文件调用close(),则缓冲区内容不会被刷新清空。

  • 文本文件输出的快捷方式
    在PrintWriter中添加一个辅助构造器,使得不必每次创建文本文件向其中写入时,都去执行所有的装饰工作。
    在这里插入图片描述
    在这里插入图片描述

  • 存储和恢复数据
    PrintWriter可以对数据进行格式化,为了输出可供另一个流恢复的数据,需要用DataOutPutStream写入数据,并用DataInputStream恢复数据。DataOutputStream和DataInputStream是面向对象的
    在这里插入图片描述
    当使用DataOutputStream写入数据,则可以使用DataInputStream读取数据。
    当使用DataOutputStream时,写字符串并令DataInputStream能够恢复它的唯一方法是使用UTF-8编码。UTF-8是一种多字节格式,编码长度根据实际使用的字符集有所变化。使用writerUTF()和readUTF(),就可以用DataInputStream把字符串和其它数据类型相混合,字符串完全可以作为Unicode来存储,并且可以很容易使用DataInputStream来恢复它。
    writeDouble()将double类型的数字存储到流中并且相应的readDouble()恢复。
    为了保证所有的读方法都能够正常工作,必须知道六中数据项所在的确切位置,因为既可能将保存的double数据作为一个简单的字节序列、char或者其他类型读入。因此要么为文件中的数据采用固定的格式,要么将额外的信息保存在文件中,以便能够对其进行解析已确定数据的存放位置。

  • 读写随机访问文件
    使用RandomAccessFile,类似于组合使用DataInputStream和DataOutputStream。seek()可以在文件中到处移动修改文件中的某个值。RandomAccessFile拥有读取基本类型和UTF-8字符串的各种具体方法。
    在这里插入图片描述
    在这里插入图片描述display()方法打开一个文件,并以double值的形式显示了其中的7个元素。
    RandomAccessFile除了实现DataInput和DataOutput接口外,有效的与I/O继承层次结构的其他部分实现了分离。

  • 管道流
    PipedInputStream、PipedOutputStream、PipedReader以及PipedWriter

文件读写的实用工具

一个常见的程序化任务是读取文件到内存,修改,然后再写出。TextFile类包含static犯法可以像简单字符串那样读取文本文件,并且创建一个TextFile对象,用一个ArrayList来保存文件的若干行。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述构造器利用read()方法将文件转换成字符串,接着使用String.split()以换行符为界将结果划分为行。

  • 读取二进制文件
    在这里插入图片描述
    其中一个重载方法接收File参数,第二个重载方法接受表示文件名的String参数。两个方法都返回产生的byte数组。available()方法被用来产生恰当的数组尺寸,并且read()方法的特定的重载版本填充了这个数组。

标准I/O

程序的所有输入都可以来自于标准输入,所有的输出都可以发送到标准输出,所有的错误信息都可以发送到标准错误。标准I/O的意义在于:我们可以很容易的把程序串联起来,一个程序的标准输出可以成为另一个程序的标准输入。

  • 从标准输入中读取
    通常用readLine()一次一行的读取输入,我们将System.in包装成BufferedReader来使用这要求我们必须用InputStreamReader将System.in转换成Reader。
    在这里插入图片描述
    使用异常规范是因为readLine()会抛出IOException。

  • 将System.out转换为Writer
    在这里插入图片描述
    要使用有两个参数的PrintWriter的构造器,并将第二个参数设为true,开启自动清空功能。

  • 标准I/O重定向
    Java的System类提供一些简单的静态方法调用,允许对标准输入、输出和错误.I/O流的重定向:setIn(InputStream)、setOut(PrintStream)、setErr(PrintStream)。
    当突然创建大量输出,这些输出滚动的太快以至于无法阅读时,重定向输出就显得极为重要。对于想重复测试某个特定用户的输入序列的命令行程序来说,重定向输入有价值。
    在这里插入图片描述将标准输入附接到文件上,并将标准输出和标准错误重定向到另一个文件。
    I/O重定向操作的是字节流,而不是字符流,因此使用的是InputStream和OutputStream,而不是Reader和Writer。

进程控制

普通的导致异常的错误:对这些错误只能重新抛出一个运行时异常。
从进程自身的执行过程中产生的错误:希望用单独的异常来报告这些错误。

新I/O

引入新的I/O类库目的是在于提高速度。速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。唯一直接与通道交互的缓冲器是ByteBuffer,可以存储未加工字节的缓冲器。
旧的I/O类库有三个类被修改,用来产生FileChannel,三个被修改的类是FileInputStream、FileOutputStream以及RandomAccessFile。Reader和Writer这种字符模式类不能用于产生通道,但是java.nio.channels.Channels类提供了使用的方法,可以使得在通道中产生Reader和Writer。
在这里插入图片描述上面例子中,getChannel()将会产生一个FileChannel,通道是一个相当基础的,可以向他传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。
将字节放入ByteBuffer的方法之一是使用一种“put”方法直接对他们进行填充,填入一个或多个字节,或者基本数据类型的值。也可以使用warp()方法将已存在的字节数组“包装”到ByteBuffer中。一旦如此,不再复制底层数组,而是把它作为所产生的ByteBuffer的存储器。对于只读访问,必须显示得使用静态的allecate()方法来分配ByteBuffer。一旦调用read()告知FileChannel向ByteBuffer存储字节,则必须调用缓冲器上的fiip()。如果我们打算使用缓冲器执行进一步的read()操作,则我们也必须调用clear()为每个read()做准备。
在这里插入图片描述上面的例子中,每次read()操作后,会将数据输入到缓冲器中,flip()是准备缓冲器来使得它的信息可以由write()提取,write()操作后,信息仍然在缓冲器中,然后使用clear()操作对所有内部指针重新安排,使得缓冲器在另一个read()操作期间能够做好接受数据的准备。
使用transferTo()和transferFrom()则允许将一个通道和另一个通道直接相连。
在这里插入图片描述

视图缓冲器

可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer,ByteBuffer依然是世纪存储数据的地方,“支持”前面的视图,对视图的任何修改都会映射成为对ByteBuffer中的数据的修改。使得我们可以向ByteBuffer中插入数据。视图允许从ByteBuffer一次一个的或者成批(在数组中)的读取基本类型值。
在这里插入图片描述在这里插入图片描述
先使用重载的put()方法存储一个整数数组,接着get()和put()方法调用直接访问底层ByteBuffer中的某个整数位置。
一旦底层的ByteBuffer通过视图缓冲器填满整数或其他基本类型,则可以直接被写入到通道中,然后使用视图缓冲器将任何数据都转化成某个特定的基本类型。
通过在同一个ByteBuffer中建立不同的视图缓冲器,将一个字节转换为不同的类型数据。
在这里插入图片描述
在这里插入图片描述ByteBuffer通过被“包装”过的8字节数组产生,通过各种不同的基本类型的视图缓冲器显示。
在这里插入图片描述

  • 字节存放次序
    不同的机器使用不同的字节排序方法存储数据,高位优先将最重要的字节存放在地址最低的存储器单元,而低位优先则将最重要的字节放在地址最高的存储器单元。ByteBuffer是以高位优先的形式存储数据的并且数据在网上传送时常使用高位优先的形式。
    下面这个例子展示如何通过字节存放模式设置来改变字符中的字节次序。
    在这里插入图片描述
    ByteBuffer有足够的空间,以存储作为外部缓冲器的charArray中的所有字节,因此可以调用array()方法显示视图底层的字节。通过CharBuffer视图可以将charAarray插入到ByteBuffer中。

  • 用缓冲器操纵数据
    ByteBuffer将数据移进移除通道的唯一方法并且我们只能创建一个独立的基本类型缓冲器或者使用“as”方法从ByteBuffer中获得。

  • 缓冲器的细节
    Buffer由数据和可以高效访问及操作这些数据的四个索引组成,这四个索引是:mark(标记)、position(位置)、limit(界限)、capactiy(容量)。
    在这里插入图片描述
    在这里插入图片描述
    下面是进入symmetricScramble犯法时缓冲器的样子:
    在这里插入图片描述Position指针指向缓冲器中的第一个元素,capacity和limit则指向最后一个元素。在该方法中,迭代执行while循环直到position等于limit。一旦调用缓冲器上相对的get()或put()函数,position指针会随之变化。也可以调用绝对的、包含一个索引参数的get()和put()方法。但是这些方法不会改变缓冲器的position指针。当操纵到while循环时,使用mark()调用来设置mark的值。此时,缓冲器状态:
    在这里插入图片描述
    两个相对的get()调用把两个字符保存到变量c1和c2中,调用完这两个方法后,缓冲器如下:
    在这里插入图片描述
    为实现交换,在position=0时写入c2,position=1时写入c1或者使reset()将position的值设为mark的值:
    在这里插入图片描述
    这两个put()方法先写c2,接着写c1:
    在这里插入图片描述
    在下次循环迭代期间,将mark设置为position的当前值:
    在这里插入图片描述
    这个过程将会持续到遍历完整个缓冲器。在while循环的最后,position指向缓冲器的末尾。如果打印缓冲器,只能打印出position和limit之间的字符。因此如果想要显示缓冲器的全部内容,必须使用rewind()将position设置到缓冲器的开始位置。下面是使用rewind()之后缓冲器的状态:
    在这里插入图片描述
    当再次调用symmetricScramble()功能,会对CharBuffer进行同样的处理,并将其恢复到初始状态。

  • 内存映射文件
    允许创建和修改那些因为太大而不能放入内存的文件。内存映射文件可以假定整个文件都放入内存中,而且可以完全把它当做非常大的数组来访问。
    在这里插入图片描述
    为了既能写又能读,先由RandomAccessFile开始,获得该文件上的通道,然后调用map()产生MappedByteBuffer,必须指定映射文件的初始位置和映射区域的长度,这意味着可以映射某个大文件的较小部分。MappedByteBuffer由ByteBuffer继承,因此具有ByteBuffer的所有方法。

  • 文件加锁
    允许我们同步访问某个作为共享资源的文件。不过竞争同一个文件的两个线程可能在不同的java虚拟机上或者一个是java线程,另一个是操作系统中其他的某个本地线程。文件锁对其他的操作系统进程是可见的,因为java的文件加锁直接映射到本地操作系统的加索工具。下面一个例子显示文件加锁:
    在这里插入图片描述
    通过对FileChannel调用tryLock()或lock()就可以获得整个文件的FileLock()不需要加锁,因为他们是从单进程实体继承而来的。tryLock()是非阻塞式的,设法获取锁,但是如果不能获得(当其他一些进程已经持有相同的锁,并且不共享时),将直接从方法调用返回。lock()则是阻塞式的,它要阻塞进程直到锁可以获得,或调用lock()的线程中断或调用lock()的通道关闭。使用FileLock.release()可以释放锁。
    可以使用如下方法对文件的一部分上锁:

tryLock(long position,long size,boolean shared)
或者lock(long position,long size,boolean shared)

加锁的区域由size-position决定,第三个参数指定是否是共享锁。
尽管无参数的加锁方式将根据文件尺寸的变化而变化,但是具有固定尺寸的锁不随文件尺寸的变化而变化。如果获得一个区域上的锁,当文件增大超出position+size时,则在position+size之外的部分不会被锁定。午餐时的加锁方法会对整个文件进行加锁,甚至文件变大后亦是如此。
如果操作系统不支持共享锁并为每一个请求都创建一个锁,则会使用独占锁。锁的类型可以通过FileLock.isShraed()进行查询。
对映射文件的部分加锁:
文件映射通常用于巨大的文件,需要对这种巨大的文件进行部分加锁,以便其他进程可以修改文件中未被加锁的部分。

压缩

在这里插入图片描述

  • 用GZIP进行简单压缩
    如果只需要对单个数据流进行压缩,则可能比较适合,如下面的例子:
    在这里插入图片描述
    在这里插入图片描述
    压缩类直接将输出流封装成GZIPOutputStream或ZIPOutoutStream,并将输入流封装成GZIPInputStream或ZIPInputStream。该例子将面向字符的流和面向字节的流混合起来;输入用Reader类,而GZIPOutputStream的构造器只能接受OutputStream对象,不能接受Writer对象。在打开文件时GZIPInputStream会被转换为Reader。
  • 用ZIP进行多文件保存
    下面这个例子就拥有与前例相同的形式,但是根据需要来处理任意多个命令行参数。另外显示了用Checksum类来计算和校验文件的校验和的方法。一共有两种Checksum类型:Adler32和CRC32。
    在这里插入图片描述
    在这里插入图片描述
    对于每个要加入压缩档案的文件都必须调用putNextEntry(),并将其传递给一个ZIPEntry对象。ZIPEntry对象包含一个功能广泛的接口,允许获取和设置ZIP文件内该特定项上所有可能利用的数据:压缩的和未压缩的文件大小、名字、日期等。虽然CheckedInputStream和CheckedOutputStream都支持Adler32和CRC2两种类型的校验和,但是ZipEntry类只有一个支持CRC的接口。
    为了能够解压缩文件,ZipInputStream提供一个getNextEntry()方法返回下一个ZipEntry。解压缩文件有一个更简单的方法:利用ZipFile对象读取文件。该对象由一个entries()方法用来向ZipEntries返回一个Enumeration。
    为了读取校验和,必须拥有对与之相关联的Checksum对象的访问权限。
  • java档案文件
    Zip格式被应用于JAR文件格式中。JAR文件非常有用,特别是涉及因特网应用,如果采用JAR文件,web浏览器在下载构成一个应用的所有文件时必须重复多次请求Web服务器,而且所有这些文件都是未经压缩的。如果将所有这些文件合并到一个JAR文件中,只需向远程服务器发出一次请求即可。由于采用了压缩技术,可以使传输的时间更短。处于安全原因,JAR文件中的每一个条目都可以加上数字化签名。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值