Java编程思想 第十八章 Java I/O系统

1. File类

1. 作用:
    1. 代表一个特定文件的名称
    2. 代表一个目录下的一组文件的名称。此时可以使用list()方法,返回一个字符数组。
2. 目录列表器
    1. 查看一个目录列表,两种方法来使用File对象:
        1. 调用不带参数的list()方法,获得此File对象包含的全部列表。
        2. 使用“目录过滤器”获得一个受限列表,如:所有扩展名为.java的文件。
            1. 使用方式是实现FilenameFilter接口。目的在于把accept()方法提供给list()使用,及实现其中的唯一方法accept()。该方法必须接受一个代表某个特定文件所在目录的File对象,以及包含了那个文件名的一个String。
            2. list()方法会为此目录对象下的每个文件名调用accept(),来判断该文件的名字。通过使用accept(),list()方法最后会返回一个数组。
            3. accept()会使用一个正则表达式的matcher对象,来查看此正则表达式regex是否匹配这个文件的名字。
            4. 如:
                `class DirFilter implements FilenameFilter{
                    private Pattern pattern;
                    public DirFilter(String regex){
                        pattern = regex;
                    }
                    public boolean accepte(File dir, String name){
                        return pattern.matcher(name).matches();
                    }
                }`''
    2. 通过使用匿名内部类来实现FilenameFilter接口。优点:将解决特定问题的代码隔离,聚拢于一点,而另一方面,这种方法却不易阅读,因此要谨慎使用。
3. 目录实用工具——P528
4. 目录的检查及创建
    1. File对象不仅可以代表存在的文件或目录,也可以用File对象来创建新的目录或上不存在的整个目录。
    2. 还可以查看文件的特性(如:大小、最后修改时间、读/写),检查某个File对象代表的是一个文件还是一个目录,并可以删除文件
    3. 方法:——P532
        1. renameTo():用来把一个文件重命名(或移动)到由参数所指示的另一个完全不同的新路径(也就是另一个File对象),这适用于任意长度的文件目录。
        2. mkdirs():创建此抽象路径指定的目录,包括所有但不存在的父目录。
        3. mkdir():与mkdirs()的不同在于,只能创建一级且需要存在父目录 

2. 输入和输出

1. 流:代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。它屏蔽了实际的I/O设备中处理数据的细节。
2. I/O类分为输入和输出两部分。
    1. 任何自Inputstream或Reader派生出来的类:都包含read()的基本方法,用于读取单个字节或字节数组
    2. 任何自Outputstream或Writer派生出来的类:都包含write()的基本方法,用于写单个字节或字节数组
    上述的这些方法存在是因为别的类可以使用它们,以便提供更有用的接口。实际上,我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能——装饰器模式
3. Java1.0中,限定所有的输入和输出相关的类都应该从InputStream和OutputStream继承。
4. InputStream类型
    1. 作用:用来表示那些从不同数据源产生输入的类
    2. 数据源包括:字节数组、String对象、文件、“管道”(工作方式:从一端输入,从另一端输出);一个由其他种类的流组成的序列,以便我们可以将他们收集合并到一个流内;其他数据源(如Internet连接)
    3. 每一种数据源都有相应的InputStream子类。
    4. 其中FilterInputStream也属于InputStream,为“装饰器”类提供基类,其中,“装饰器“类可以把属性或有用的接口与输入流连接在一起。
![InputStream类型](https://img-blog.csdn.net/20161008202318706)
5.  OutputStream类型
    1. 该类别的类决定了输出所有去往的目标:字节数组、文件或管道
    2. 其中FilterOutputStream也属于OutputStream,为“装饰器”类提供基类,其中,“装饰器“类可以把属性或有用的接口与输入流连接在一起。
![OutputStream类型](https://img-blog.csdn.net/20161008202720571)

3. 添加属性和有用的接口

1.  Java I/O类库里存在filter(过滤器)类的原因就是因为抽象类filter是所有装饰器类的基类。装饰器必须具有和他所装饰的对象相同的接口,也可以扩展接口,但是这只是发生在少数情况下。
    装饰器模式的缺点:增加了代码的复杂性。
2. Java I/O类库操作不便的原因就在于:我们必须创建许多类——核心I/O类加上所有的装饰器,才能得到我们所希望的单个I/O对象
3. FilterOutputStream和FilterInputStream是用来提供装饰器类接口以控制特定输入流和输出流的两个类。它们是装饰器的必要条件
4. 通过FilterInputStream从InputStream读取数据
    1. FilterInputStream类能够完成两件事情:
        1.  DataInputStream允许我们读取不同的基本类型以及String对象(所有方法都以“read”开头,readByte())。搭配相应的DataOutputStream,我们就可以通过数据“流”将基本类型的数据从一个地方迁移到另一个地方。
        2. 其他的FilterInputStream类则在内部修改InputStream的行为方式,是否缓冲、是否保留他所读过的行,以及是否把单一字符推回输入流等。最后两个类一般编程不使用。
    ![FilterInputStream类型](https://img-blog.csdn.net/20161008203417392)
5. 通过FilterOutputStream向OutputStream写入
    1. DataOutputStream将各种基本数据类型以及String对象格式化输出到“流”中;这样一来,任何机器上的任何DataInputStream都能够读取他们。所有的方法都以“write”开头。
    2. PrintStream最初的目的是以可视化格式打印所有的基本数据类型以及String对象。而DataOutputStream不同,他的目的是将数据元素置入“流”中,使DataInputStream能够可移植的重构他们
        1. PrintStream的两个重要方法:print()和println()。通过对他们进行重载,可以打印各种数据类型,差异在于,后者操作完毕后会添加一个换行符
        2. PrintStream的问题:
            1. 它捕捉了所有的IOException,故我们必须使用checkError()自行测试错误状态。
            2. 它未完全国际化,不能以平台无关的方式处理换行动作
    3. BufferedOutputStream是一个修改后的OutputStream,他对数据流使用缓存技术,因此当每次向流中写入时,不必每次进行实际的物理写动作
![FilterOutputStream类型](https://img-blog.csdn.net/20161008203510019)

4. Reader和Writer

1. Java1.1中出现了Reader和Writer类。但是它们并不是为了替代InputStream和OutputStream,**InputStream和OutputStream在以面向字节形式的I/O中仍可以提供极有价值的功能,Reader和Writer类则提供兼容Unicode与面向字符的I/O功能。**另外:
    1.  InputStream和OutputStream不会被取代。
    2. 有时必须把来自于“字节”层次结构中的类和“字符”层次结构中的类结合起来使用。此时需要用到“适配器”类:InputStreamReader可以把InputStream转换为Reader,而OutputStreamWriter可以把OutputStream转换为Writer
2.  设计Reader和Writer基础层次结构主要是为了国际化。为了在所有的I/O操作中都支持Unicode(老的I/O继承层次结构仅支持8位字节流,不能很好的处理16位的Unicode字符,而Unicode用于字符国际化)
3. 数据的来源和去处
    1. 几乎所有原始的Java I/O流类都有相应的Reader和Writer类来提供天然的Unicode操作。但是在某些场合,面向字节的InputStream和OutputStream才是正确的解决方案。例如:java.util.zip类库就是面向字节而不是面向字符的。
    2.  **最明智的做法:尽量尝试使用Reader和Writer,一旦程序代码无法编译成功,我们就会发现自己不得不使用面向字节的类库。**
    ![](https://img-blog.csdn.net/20161008204454716)
 4. 更改流的行为
    1. 对于InputStream和OutputStream来说,我们会使用FilterInputStream和FilterOutputStream的装饰器子类来修改“流”以满足特殊需要。Reader和Writer的类继承层次结构继续沿用相同的思想——但是并不完全相同。
        ![](https://img-blog.csdn.net/20161008204545017)
    2. 无论我们何时使用readLine(),都不应该使用DataInputStream(这会遭到编译器的强烈反对),而应该使用BufferedReader。除了这一点,DataInputStream仍是I/O类库的首选成员。
    3. 为了更容易的过渡到PrintWriter,他提供了一个既能接受Writer对象,又能接受任何OutputStream对象的构造器。PrintWriter的格式化接口实际上与PrintStream相同
    4. 有一种PrintWriter构造器还有一个选项,就是“自动执行清空”选项,如果构造器设置此选项,则在每个Println()执行之后,便会自动清空。
    5. 未发生变化的类
        ![](https://img-blog.csdn.net/20161008204630045)
        1. 特别是DataOutputStream,在使用时候没有任何变化;因此如果想以“可传输的”格式存储和检索数据,可以使用InputStream和OutputStream继承层次结构。

5. RandomAccessFile

1. **RandomAccessFile适用于由大小一致的记录组成的文件,因此我们可以使用seek()将记录从一处转移到另一处,然后读取或者修改记录。文件中记录的大小不一定都相同,只要我们能够确定那些记录有多大以及它们在文件中的位置即可。**
2. 除了实现DataInput和DataOutput接口之外,他和这两个继承层次没有任何关联,他是一个完全独立的类。他拥有和别的I/O类型本质不同的行为,因为我们可以在一个文件内向前和向后移动,它直接从Object继承而来。
3. 方法
    1. getFilePointer():用于查找当前所处的文件位置
    2. seek():用于在文件内移至新的位置
    3. length():用于判断文件的最大尺寸
    4. 其构造器还需要第二个参数用来指示我们只是“随机读”(r)还是“既读又写”(rw)。他并不支持只写文件。
4. RandomAccessFile支持搜寻方法,并且只适用于文件。BufferedInputStream却能允许标注(mark())位置(其值存储与内部某个简单变量内)和重新设定位置(reset())。

6. I/O流的典型使用方式

1. 缓冲输入文件
    1. 想要打开一个文件用于字符输入,可以使用以String或File对象作为文件名的FileInputReader,为了提高速度,希望对那个文件进行缓冲,因此我们将所产生的引用传给一个BufferedReader构造器。它提供readLine()方法,是我们的最终对象和进行读取的接口。
    2. BufferedReader in = new BufferedReader(new FileReader());
        in.close();
    3. readLine()将会把换行符删除。
    4. 最后必须调用close()关闭文件。
2. 从内存输入
    1. 从BufferedReader in = new BufferedReader(new FileReader());中读入的String结果用来创建一个StringReader,然后调用read()每次读取一个字符
3. 格式化的内存输入
    1. DataInputStream是一个面向字节的I/O类。因此我们必须使用InputStream类而不是Reader类。我们可以用InputStream以字节的形式读取任何数据(例如一个文件),不过,在这里使用的是字符串。如:
        DataInputStream in = new DataInputStream(new ByteArrayInputStream(new BufferedReafer(new FileReader("Forarfa.java")).getBytes()));
        必须为ByteArrayInputStream提供字节数组,为了产生该数组String包含了一个可以实现此项工作的getBytes()方法。所产生的ByteArrayInputStream是一个适合传递给DataInputStream的InputStream。
    2. 通过readByte()从DataInputStream一次一个字节的读取字符,任何字节的值都是合法的结果,因此返回值不能用来检测输入是否结束。但是可以使用available()方法查看还有多少可供存取的字符。
        available()的工作方式会随着所读取的媒介类型的不同而有所不同,字面意思就是“在没有阻塞的情况下所能读取的字节数”。
        可以通过捕获异常来检测输入的末尾。
    3. 基本的文件输出
        1. FileWriter对象可以向文件写入数据。首先,创建一个与指定文件链接的FileWriter;其次,通常用BufferedWriter将其包装起来用以缓冲输出(缓冲往往能够显著的增加I/O操作的性能)。如:
            PrinterWriter out = new PrintWriter(new BufferedWriter(new FileWriter("Basic.java")));
        2. 当文本行被写入文件时,行号会增加。因此不使用LineNumberInputStream,这个类没有多大帮助。
            注:一旦读完数据流,readLine()会返回null
        3. 要为PrinterWriter显式调用close()(当使用完毕后),如果我们不为所有的输出文件调用close(),就会发现缓冲区内容不会被刷新清空,那么输出文件就是不完整的。
    4. 文本文件输出的快捷方式
        1. Java SE5为PrintWriter添加了一个辅助构造器。
            PrintWriter out = new PrintWriter("File.out");
            注:你任然是在使用缓存,只是类库已经帮你做好了,不必自己去实现。
    5. 存储和恢复数据——P543
        1. PrintWriter可以对数据进行格式化,以便人民阅读,但是为了输出可供另一个“流”恢复的数据,我们需要使用DataOutputStream写入数据,并用DataInputStream恢复数据。
        2. DataOutputStream和DataInputStream是面向字节的,因此要使用InputStream和OutputStream
        3. 如果我们使用DataOutputStream写入数据,Java可以保证我们可以使用DataInputStream准确的读取数据——无论读和写数据的平台多么不同。
        4. 当我们使用DataOutputStream时,写字符串并且让DataInputStream能够恢复它的唯一可靠的做法就是使用UTF-8编码,例如使用writeUTF()和readUTF()来实现。
        5. UTF-8是一种多字节格式,其编码长度根据实际使用的字符集会有所变化。UTF-8将ASC||编码成单一字节的形式,而非ASC||字符则编码成两到三个字节的形式。字符串的长度存储在utf-8字符串的前两个字节中。
        6. 但是writeUTF()和readUTF()使用的是适合于Java的utf-8变体,因此如果我们用一个非Java程序读取用writeUTF()所写的字符串时,必须编写一些特殊代码才能正确的读取字符串。
        7. writeDouble()将double类型的数字存储到流中,并用相应的readDouble()恢复它。但是为了保证所有的读方法都能够正常的工作,我们必须知道流中数据项所在的确切位置。故:必须要么文件中的格式采用固定的格式,要么将额外的信息存储到文件中,以便能够对其进行解析已确定数据的存放位置。
        8. 注:对象序列化和XML可能是更容易的存储和读取复杂数据结构的方式
    6. 读写随机访问文件
        1. 使用RandomAccessFile,类似于组合使用了DataOutputStream和DataInputStream。
            利用seek()可以在文件中到处移动,并修改文件中的某个值。它拥有读取基本类型和utf-8字符串的各种具体方法。可以自行选择的是第二个构造器参数,我们可以指定为“只读”(r)方式或“读写”(rw)方式打开一个RandomAccessFile文件。
        2. 使用要求:必须知道文件排版,这样才能正确的操作它;不能将其与InputStream和OutputStream子类的任何部分组合起来;不提供添加缓冲的功能(我们必须假定他已经正确缓存)。

7. 文件读写的实用工具

1. 见工具类——P546
2. 另一种解决读取文件的方法是使用在JavaSE5中引入的java.util.Scanner类。但是,这只能用于读取文件,而不能用于写入文件,并且这个工具主要是设计用来创建编程语言的扫描器或“小语言”的。
3. 读取二进制文件
    1. 见工具类——P548
    2. available()方法被用来产生恰当的数组尺寸

8. 标准I/O

1. 程序的所有输入都可以来自于标准输入,他的所有输出也都可以发送到标准输出,以及所有的错误信息都可以发送到标准错误。标准I/O的意义在于:我们可以很容易的把程序串联起来,一个程序的标准输出可以成为另一个程序的标准输入。
2. 从标准输入中读取
    1. System.out已经事先被包装成了PrintStream对象。System.err同样也是PrintStream,但System.in却是一个没有被包装过的未经加工的InputStream
    2. 示例:
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        stdin.readLine();
3. 将System.out转换成PrintWriter
    1. PrintWriter有一个可以接受OutputStream作为参数的构造器。因此只要需要,就可以使用那个构造器把System.out转换成PrintWriter
    2. 示例:PrintWriter out = new PrintWriter(System.out, true);
        注:第二个参数设为true,表示开启自动清空功能。
4. 标准I/O重定向
    1. Java的System类允许我们对标准输入、输出和错误I/O流进行重定向
        1. setIn(InputStream)
        2. setOut(PrintStream)
        3. setErr(PrintStream)
        注:I/O重定向操作的是字节流,而不是字符流

9. 进程控制

10. 新I/O

1. JDK1.4的java.nio.*包中引入了新的javaI/O类库,其目的在于提高速度,旧的I/O包已经使用nio重新实现过,以便充分利用这种速度提高。因此,即便我们不显示的使用nio编写代码。也能从中受益
2. 速度的提高在于所使用的结构更接近与操作系统执行的I/O方式:通道和缓冲器
3. ByteBuffer是唯一直接与通道交互的缓冲器,可以存储未加工字节的缓存器;java.nio.ByteBuffer是相当基础的类,通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用于以原始的字节形式或基本数据类型输出和读取数据。没办法输出或读取对象;通过告知分配多少存储空间来创建一个ByteBuffer对象。
4. Reader和Writer这种字符模式类不能用于产生通道。
5. java.nio.channerls.Chanels类提供了实用方法,用以在通道中产生Reader和Writer
6. 通道是一个相当基础的东西,可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。
7. 将字节存放于ByteBuffered的方法:
    1. 使用一种“put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值。
    2. 也可以使用warp()方法将已存在的字节数组“包装”到ByteBuffer中。一旦如此,就不再复制底层的数组,而是把它作为所产生的ByteBuffer的存储器。我们称之为数组支持的ByteBuffer
8. 对于只读访问,必须显示的使用静态的allocate()方法分配ByteBuffer。allocateDirect()是更高的速度,但是由于它依赖于平台,随着平台不同而不同,故需要谨慎使用
9. 一旦调用FileChaal.read(ByteBuffer)来告知FileChannal向ByteBuffer存储字节,就必须使用缓冲器上的flip();如果我们打算使用缓冲器执行进一步的read()操作,必须调用clear()来为每个read()做好准备
    1. flip():反转此缓冲区,将限制设置为当前位置,然后将位置设置为0
    2. rewind():返回到数据开始部分,与flip()不同,不会修改限制位置。
    3. FileChannal.read():返回-1表示我们已经到达了输入的末尾。
    4. flip()是准备缓存器,以便他的信息可以由write()提取。write()操作之后,信息仍在缓冲器中,接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接收数据的准备
10. 特殊方法transferTo()和transferFrom()允许我们将一个通道和另一个通道直接相连
11. 转换数据:缓冲期容纳的是普通字节,为了把它们转换成字符,a:我们在输入它们的时候对其进行编码;b:在将其从缓存器输出时对他们进行解码。可以使用java.nio.charset.Charset类实现这些功能,该类提供了把数据编码成多种不同类型的字符集的工具。示例:
    String encoding = System.getPropertuy("file.encoding");//发现默认字符集,他会产生代表字符集名称的字符串
    Charset charset = Charset.forName(encoding);//产生Charset对象,可以使用该对象对字符串进行解码
    charset.decode(buff);//解码为char
12. 获取基本类型
    1. 尽管ByteBuffer只能保存字节类型数据,但是它具有可以从其所容纳的字节中产生出各种不同基本类型值得方法
    2. 插入基本类型数据:利用asCharBuffer()等获得该缓冲器上的视图,然后使用视图的put()方法。适用于所有的基本数据类型,除了ShoreBuffer的put()方法,需要进行类型转换——put((short)476767);
13. 视图缓冲器(view buffer):可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。
    1. 对视图的任何操作都会映射成为对ByteBuffer中数据的修改
    2. 方法
        1. hasRemaining()
        2. position()
    3. 字节存放次序
        1. 两只方式:
            1. 大端:将最重要的字节存放在地址最低的存储器单元——正常顺序
            2. 小端:将最重要的字节存放在地址最高的存储器单元
        2. 可以使用带有参数ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN方法改变ByteBuffer的字节排序方式。
        3. 使用array()方法显示视图底层的字节,此方法是“可选的”,只能对有数组支持的缓冲器调用此方法。
14. 用缓冲器操纵数据
    ![](https://img-blog.csdn.net/20161008224032188)
    1. ByteBuffer是将数据移进移除通道的唯一方式,并且我们只能创建一个独立的基本类型缓冲器,或者使用“as”方法从ByteBuffer中获得。即,我们不能把基本类型的缓冲器转换成ByteBuffer,但是我们可以经由视图缓冲器将基本类型数据移进移出ByteBuffer
    2. 例如:如果想把一个字节数组放到文件中,先将字节数组包装起来(使用ByteBuffer.wrap()),在用getChannal()方法在FileOutputStream上开一个通道,接着将来自于ByteBuffer的数据写到FileChannal中。
15. 缓冲器的细节
    1. Buffer由数据和可以高效的访问及操纵这些数据的四个索引组成,这四个索引是:mark(标记), position(位置), limit(界限), capacatiy(容量).在缓冲器中插入或者提取数据会更新这些索引,用于反映发生的变化。
    2. 下面是用于设置和复位索引以及查询它们的值的方法。
        ![](https://img-blog.csdn.net/20161008224134158)
        一旦调用缓冲器上相对的get()和put()方法,position指针会随之发生相应的改变。
        当调用包含一个索引参数的get()和put()方法(参数指明方法发生的位置)。但是,这些方法不会改变position的位置。
        reset():把position的值设为mark的值。
        rewind():调用之后缓冲器的状态——返回到数据开始部分(position),与flip()不同,不会修改限制位置(limit)。
16. 内存映射文件
    1. 内存映射文件允许我们创建和修改哪些因为太大而不能放入内存的文件。
    2. 有了内存映射文件,我们可以假定整个文件都放在了内存中,而且可以完全把他当作非常大的数组来访问。
    3. 例子
        MappedByteBuffer out = new RandomAccessFile("test.dat", "rw").getChannel().map(FileChannal.MapMode.READ_WRITE, 0, length);
    4. 调用map()产生MappedByteBuffer(继承ByteBuffer而来),这是一种特殊类型的直接缓冲器。
        注:我们必须指定映射文件的初始位置和映射区域的长度——我们可以映射某个大文件的较小部分
    5. 性能:“映射文件访问”往往可以更加显著的加快速度。
    6. System.namoTime()——得到系统时间
    7. 映射文件中的所有输出必须使用RandomAccessFile.
17. 文件加锁
    1. 允许我们同步访问某个作为共享资源的文件。竞争同一个文件的两个线程可能在不同的java虚拟机上,或者一个是java线程,一个是操作系统中其他的某个本地线程。文件锁对其他的操作系统进程是可见的,因为java的文件加锁直接映射到了本地操作系统的加锁工具
    2. 通过对FileChannel调用tryLock()或lock(),就可以获得整个文件的FileLock。(SocketChannal,DatagramChannal,ServerSocketChannal不需要加锁,因为他们是从单进程实体继承而来,我们通常不在两个进程之间共享网络)
        1. tryLock(long position, long size, boolean shared):是非阻塞式的,它设法获取锁,但是如果不能获得(当其他一些进程已经持有相同的锁,并且不共享锁),它将直接从方法调用返回
        2. lock(long position, long size, boolean shared):是阻塞式的,他要阻塞进程直至锁可以获得,或调用lock()的线程中断,或调用lock()的通道关闭。
            注:枷锁区域为:size-position,第三个参数指定是否共享锁。
        3. 使用FileLock.release()可以释放锁。
        4. 无参数的枷锁方法将根据文件尺寸的变化而变化,它会对整个文件进行加锁,甚至文件变大也是如此。
            有参数的你将获得某一个区域上的锁,当文件增大时,区域之外的部分不会被锁定。
        5. 锁的类型(共享或者独占)可以通过FileLock.isShared()进行查询。
        6. 对共享或者独占锁的支持必须有底层的操作系统支持。
        7. 对映射文件的部分加锁:对巨大的文件进行部分加锁,以便其他进程可以修改文件中未加锁的部分。例如:数据库。
        8. ByteBuffer.slice():用于创建一个共享了原始缓冲区子序列的新缓冲区。新缓冲区的position值是0,而其limit和capacity的值都等于原始缓冲区的limit 和position的差值。slice()方法将新缓冲区数组的offset值设置为原始缓冲区的position值,然而,在新缓冲区上调用 array()方法还是会返回整个数组。
        9. 如果有java虚拟机,它会自动释放锁,或者关闭加锁的通道,不过也可以显示的为FileLock对象调用release()释放锁。

11.压缩

1. 压缩类库是按字节方式而不是字符方式处理的
2. 使用:非常直观——直接将输出流封装成GZIPOutputStream或ZipOutputStream,并将输入流封装成GZIPInputStream或ZipInputStream。其他操作全是通常的I/O读写。
3. 常用的有:Zip和GZIP
4. GZIP:**接口非常简单,如果我们只想对单个数据流(而不是一系列互异数据)进行压缩,那么他是比较合适的选择。**
    1. GZIPOutputStream的构造器只能接受OutputStream对象,不能接受Writer对象。在打开文件时,GZIPInputStream就会被转换为Reader。
    2. 例子:
        BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("test.out")));
        BufferedReader in = new BufferedReader(new InputStreamReader(new GZIPIputStream(new FileInputStream("test.gz"))));
5. 用Zip进行多文件保存——详情见P570
    1. 使用标准Zip格式
    2. 用Checksum类来计算和校验文件的校验和方法,有两种Checksum类型:
        1. Adler32:快一些
        2. CRC32:慢一些,但更准确
6. java档案文件
    1. Zip格式也被用于JAR文件格式中。JAR文件也是跨平台的。声音和图像文件可以向类文件一样被包含在其中。
    2. 安全的考虑:jar文件中的每个条目都可以加上数字化签名。
    3. 一个jar文件由一组压缩文件构成,同时还有一张描述了所有这些文件的“文件清单”(可自行创建,也可以有jar程序自动生成)。
    4. sun的jdk自带的jar程序可根据我们的选择自动环境文件。可以用命令行的形式调用它。
    5. 子目录也会被自动的添加到jar文件中,且包含该子目录的所有子目录,路径信息也会被保留。
    6. jar工具的功能没有zip工具那么强大

12. 对象序列化

1. java的对象序列化将那些实现了Serializable接口的对象转化成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一个过程可以通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。
2. 利用对象序列化可以实现“轻量级持久性”。
    1.  “持久性”:一个对象的生存周期不取决于程序是否正在执行。通过将一个序列化对象写入磁盘,然后再重新调用程序时回复该对象,就能够实现持久性的效果。
    2. “轻量级”:不能使用某种关键字来简单的定义一个对象,并让系统自动维护其他细节问题。相反,对象必须在程序中现实的序列化和反序列化。
3. 对象序列化的概念加入到语言中是为了支持两种主要特性:
    1. java的远程方法调用(RMI),它使存活于其他计算机上的对象使用起来就像存活于本机上一样。当远程对象发送消息时,需要通过对象序列化来传输参数和返回值。
    2. 对于Java Bean来说,对象序列化是必须的。使用一个Bean时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动的时候进行后期恢复,这种具体工作就是由对象序列化完成的。
4. 使用——对象实现Serializable接口(仅仅是一个标记接口,没有任何方法)。步骤:
    1. 序列化一个对象:
        1. 创建某些OutputStream对象,
        2. 将其封装在一个ObjectOutputStream对象内。
        3. 只需调用writeObject()即可将对象序列化。
            注:也可以为一个String调用writeObject();也可以用与DataOutputStream相同的方法写入所有基本数据类型(它们具有同样的接口)
    2. 反序列化
        1. 将一个InputStream封装在ObjectInputStream内,然后调用readObject()。最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们
5. 对象序列化不仅保存了对象的“全景图”,而且能够追踪对象内所包含的所有引用,并保存这些对象;接着又能对对象内包含的每个这样的引用进行追踪;以此类推。这种情况有时被称为“对象网”。
6. 例子:
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out");
    out.writeObject(w);
    out.close();
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out");
    String s = (String)in.readObject();
7. 在对一个Serializable对象进行还原的过程汇总,没有调用任何构造器,包括默认的构造器。整个对象都是通过InputStream中取得数据恢复而来的。
8. 寻找类:必须保证java虚拟机能够找到相关的.class文件。找不到就会得到一个ClassNotFOundExcption的异常。
9. 序列化的控制——通过实现Externalizable接口——代替实现Serializable接口——来对序列化过程进行控制。
    1. Externalizable接口继承了Serializable接口,增加了两个方法,writeExternal()和readExternal(),这两个方法会在序列化和反序列化还原的过程中被自动调用。
    2. Externalizable对象,在还原的时候所有普通的默认构造器都会被调用(包括在字段定义时的初始化)(只有这样才能使Externalizable对象产生正确的行为),然后调用readExternal().
    3. 如果我们从一个Externalizable对象继承,通常需要调用基类版本的writeExternal()和readExternal()来为基类组件提供恰当的存储和恢复功能。
    4. 为了正常运行,我们不仅需要在writeExternal()方法中将来自对象的重要信息写入,还必须在readExternal()中恢复数据
    5. 防止对象的敏感部分被序列化,两种方式:
        1. 将类实现Externalizable,在writeExternal()内部只对所需部分进行显示的序列化
        2. 实现Serializable,用transient(瞬时)关键字(只能和Serializable一起使用)逐个字段的关闭序列化,他的意思:不用麻烦你保存或恢复数据——我自己会处理。
    6. Externalizable的替代方法
        1. 实现Serializable接口,并添加名为writeObject()和readObject()的方法,这样一旦对象被序列化或者被反序列化还原,就会自动的分别调用writeObject()和readObject()的方法(它们不是接口的一部分,接口的所有东西都是public的)。只要提供这两个方法,就会使用它们而不是默认的序列化机制。
        2. 这两个方法必须具有准确的方法特征签名,但是这两个方法并不在这个类中的其他方法中调用,而是在ObjectOutputStream和ObjectInputStream对象的writeObject()和readObject()方法
            ![](https://img-blog.csdn.net/20161008231635096) 
        3. 技巧:在你的writeObject()和readObject()内部调用defaultWriteObject()和defaultReadObject来选择执行默认的writeObject()和readObject();如果打算使用默认机制写入对象的非transient部分,那么必须调用defaultwriteObject()和defaultReadObject(),且作为writeObject()和readObject()的第一个操作。
    7. 版本控制
10. 使用“持久性”
    1. 只要将任何对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。当然,我们可以在写出第一个对象和写出最后一个对象期间改变这些对象的状态,但是这是我们自己的事;无论对象在被序列化时处于什么状态(无论它们和其他对象有什么样的连接关系),我们都可以被写出。
    2. Class是Serializable的,因此只需要直接对Class对象序列化,就可以很容易的保存static字段,任何情况下,这都是一种明智的做法。但是必须自己动手去实现序列化static的值。
        使用serializeStaticState()和deserializeStaticState()两个static方法,它们是作为存储和读取过程的一部分被显示的调用的
    3. 安全问题:序列化会将private数据保存下来,对于你关心的安全问题,应将其标记为transient。但是这之后,你还必须设计一种安全的保存信息的方法,以便在执行恢复时可以复位那些private变量。

13. XML

1. 对象序列化只是java的解决方案,一种更具互操作性的解决方案是将数据转换为XML格式,这可以使其被各种各样的平台和语言使用
2. 类库
    1. javax.xml.*类库
    2. Elliotte Rusty Harold的开源XOM类库(可以从www.xom.nu下载):最直观的用java产生和修改XML的方式——详情见P586
        1. 例子——序列化
            Element person = new Element("person");
            Element firstName = new Element("first");
            firstName.appendChild("first")
            person.appendChild(firstName);
        2. XOM还包含一个Serializer类,被用来将XML转换为更具可读性的格式
        3. 例子——反序列化
            ![](https://img-blog.csdn.net/20161008231950569)

14. Preferences

1. Preferences API相对于对象序列化,前者与对象的持久性更密切,因为它可以自动存储和读取信息。
2. 但是它只能用于小的、受限的数据集合——我们只能存储基本类型和字符串,并且每个字符串的存储长度不能超过8K
3. Preferences用于存储和读取用户的偏好以及程序配置项的设置
4. Preferences是一个键-值集合,存储在一个节点层次结构中。通常是创建以你的类名命名的单一节点,然后将信息存储与其中
5. 两种创建方式:
    1. Preferences.userNodeForPackage()
    2. Preferences.systemNodeForPackage()
    两者都可以选择,但是通常最好将“user”用于用户的偏好,“system”用于通常的安装配置。
    get()的第二个参数:如果某个关键字下没有任何条目,那么这个参数就是所产生的默认值

14.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值