《Java编程思想》笔记 第十八章 Java I/O 系统

java

  • File是一个  文件和目录路径名  的抽象表示,通过File可以查看文件的各种信息,也可以增加删除文件。

  • File构造器接受一个路径字符串并把它与实际文件目录映射起来,也能接受父子目录,无论是相对路径还是绝对路径

    • File(File parent, String child)
    • File(String pathname)
    • File(URI uri)

  1.  File 对文件和目录操作的功能几乎都有如 查看读写权限,查看父子目录,创建 删除 重命名文件等等。

2 输入和输出

  • 输入流 InputStream / Reader 就是把数据从某处(一般就是构造器中的对象)读到本身,再read( )到别处。
  • 输出流 OutputStream / Writer 就是从本身writer( )数据写入到其他对象(构造器中的对象)
  • InputStream、OutputStream 是字节流
  • Reader、Writer 是字符流,一般使用字符流,再只能使用字节流解决问题的地方使用字节流。

2.1 输入流

  1. InputStream :所有有关输入的流都从这继承 (抽象类),提供了输入流所具备的基本方法。因为读取的数据源(构造器中的数据源)不相同,所以产生了很多子类。

    int available() 

    返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。

    void close()

    关闭此输入流并释放与流相关联的任何系统资源。

    void mark(int readlimit)

    标记此输入流中的当前位置。

    boolean markSupported()

    测试这个输入流是否支持 mark和 reset方法。

    abstract int read()

    从输入流读取数据的下一个字节。

    int read(byte[] b)

    从输入流读取一些字节数,并将它们存储到缓冲区 b 。

    int read(byte[] b, int off, int len)

    从输入流读取最多 len字节的数据到一个字节数组,从数组off开始写入。

    void reset()

    将此流重新定位到上次在此输入流上调用 mark方法时的位置。

    long skip(long n)

    跳过并丢弃来自此输入流的 n字节数据。

    1. ByteArrayInputSteram( byte[ ]  b)  :从缓冲区(byte[ ])读取数据,可以把b中的字节read出去。
    2. FileInputStream(File / String ):可提供一个File,或者 文件路径String 或者(FileDescriptor)给构造器 ,从文件中读数据,找不到文件报异常。

    3. PipedInputStream() :管输入道流 用来和管道输出流对接,构造器接受一个PipedOutputStream或者同时指定int值为缓冲区的大小。

      • 典型地使用方法,在一个线程中数据从PipedInputStream读入,此数据是由另一个线程写入到对应的PipedOutputStream中。 单线程下使用可能还会造成死锁。

    4. SequenceInputStream:将多个输入流连接起来。它从一个有序的输入流集合开始,从第一个读取到文件的结尾,然后从第二个文件读取,依此类推,直到最后一个输入流达到文件的结尾。
    5. FilterInputStream :装饰器,其他输入流提供有用功能。装饰器的构造器接收的都是流

      1. DataInputStream(InputStream in):可以从流中按基本数据类型和String读取数据,上面的只能按字节读取,输出也只能输出int 或者 byte。(字节流为了解决字符的问题产生)

        int read(byte[] b) 

        boolean readBoolean()

        byte readByte()

        char readChar()

        double readDouble()

        float readFloat()

        int readInt()

        long readLong()

        short readShort()

        String readUTF(string)

        View Code

        DataInputStream必须 DataOutputStream 搭配使用,才能正确得到结果。DataOutputStream 写入非字节数据时会附带数据长度信息,其他输入流 read 出来数据会出现乱码,正是因为这些长度信息使得他与平台无关,不管你在什么环境中DataOutputStream 写入,在什么环境中DataInputStream读出都不会错。

      2. BufferedInputStream 缓冲输入 接收一个输入流,也可以指定缓冲区大小。它比InputStream的优点就是读满缓冲区才写入指定位置,而不是读一个写一个,减少IO次数。

      3. PushbackInputStream 编译器使用,程序员可能永远不会使用。将读到的最后一个字符回退到流。

2.2 输出流

  1. OutputStream:所有有关输出的流都从这继承 (抽象类),提供了输出流所具备的基本方法,flush()方法 是输出流的标配。

void close() 

关闭此输出流并释放与此流相关联的任何系统资源。

void flush()

刷新此输出流并强制任何缓冲的输出。

void write(byte[] b)

将 b.length字节从指定的字节数组写入此输出流。

void write(byte[] b, int off, int len)

从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。

abstract void write(int b)

将指定的字节写入此输出流。

  •  flush( )方法是为了在缓冲区不满时强制刷出数据,因为缓冲区不满时不会向文件写数据。

  • 输入流和输出流的方法相对但都大同小异  read 对应 write

    1. ByteArrayOutputSteram 

      1.   toByteArray()可以返回已经写的字节数组

    2. FileOutputStream  往文件中写数据,找不到文件则新建一个文件。

    3. PipedOutputStream
    4. FilterOutputStream 

      1. DataOutputStream
      2. PrintStream : 格式化输出到流 (字节流为了解决字符的问题产生),System.out 就对应它。 它有 print(),println(),format(  ),append(), write(byte[] buf, int off, int len) 从一个字节数组中写入到流。write(int b)一次只写一个字节,也就是说只取b的低八位。

      3. BufferedOutputStream 

3 Reader 和 Writer

  • 面向字符的流。最好使用 Reader和Writer因为它们操作比较快。
  • 因为它们自带Unicode操作所有某些情况下并不适用,而必须要使用字节流,如java.util.zip类库就是面向字节的而不是面向字符的。

3.1Reader

  • 所有有关输入的流都从这继承 (抽象类),提供了字符输入流所具备的基本方法。

            abstract void close()

    关闭流并释放与之相关联的任何系统资源。

    void mark(int readAheadLimit)

    标记流中的当前位置。

    boolean markSupported()

    告诉这个流是否支持mark()操作。

    int read()

    读一个字符

    int read(char[] cbuf)

    将字符读入数组。

    abstract int read(char[] cbuf, int off, int len)

    将字符读入数组中的一部分。

    int read(CharBuffer target)

    尝试将字符读入指定的字符缓冲区。

    boolean ready()

    告诉这个流是否准备好被读取。

    void reset()

    重置流。

    long skip(long n)

    跳过字符

  1. CharArrayReader(char[] buf / char[] buf, int offset, int length) 从字符数组中读取字符
  2. StringReader(String s) 从字符串中读取字符。
  3. PipedReader (PipedWriter) 管道流,和PipedWriter搭配使用,最好在多线程下分别使用,单一线程同时使用一对可能造成死锁
  4. BufferedReader(Reader in) 缓冲流,此流有readLine()方法可以读一行字符串。
    1.  lineNumberReader 它的2个方法 setLineNumber(int)getLineNumber()用于分别设置和获得当前行号。没多大用处,建议不使用。

  5. InputStreamReader(InputStream in, String charsetName)字节流到字符流的桥梁,in 读取字节,根据charseName编码转为字符。

    1.  FileReader 按字符读取文件.它没有任何方法,作用就是按字符读取文件作为InputStreamReader让别的流控制。

  6. FilterReader 

    1.  PushbackReader (Reader in, int size)  推回输入流  使用pushback缓冲区大小是size,它有unread()方法可以将字符回推到Reader。

3.2 Writer 

  • 所有有关输出的流都从这继承 (抽象类)

            Writer append(char c)

    将指定的字符追加到后面。

    Writer append(CharSequence csq)

    将指定的字符序列追加到后面。

    Writer append(CharSequence csq, int start, int end)

    将指定字符序列的子序列追加到后面。

    abstract void close()

    关闭流,

    abstract void flush()

    刷新流。

    void write(char[] cbuf)

    写入一个字符数组。

    abstract void write(char[] cbuf, int off, int len)

    写入字符数组的一部分。

    void write(int c)

    写一个字符

    void write(String str)

    写一个字符串

    void write(String str, int off, int len)

    写一个字符串的一部分。

  1. CharArrayWriter   自身就是一个缓冲区,构造方法只能设置大小。

    1.   增加writeTo(Writer out) 方法 可以将 write() 写入缓冲区的内容写入另一个字符流。 

    2.   toCharArray()返回写入的内容的字符数组

  2. StringWriter   字符串缓冲区,用于构造字符串,构造方法只能设置大小,toString方法输出字符串

    1.   toString() 返回写入的字符串。

  3. PipedWriter(PipedReader snk)    可在构造器中关联也可使用  connect(PipedReader snk) 与PipedReader关联起来

  4. BufferWrite(Writer out, int sz)   newLine() 添加一行分隔符。
  5. OutputStreamWriter(InputStream in, String charsetName)

    1. FileWriter 按字符写入文件.它没有任何方法,作用就是按字符写入文件作为OutputStreamReader让别的流控制。

  6. FilterWriter 抽象类却没有任何子类
  7. PrintWritre 格式化输出到流,流可以是文件,OutPutStream,

4 输入输出流总结

  1. 所有输入流从构造器中的对象中read数据,输出流除缓存流外都把数据输出到构造器中的对象。构造器就是输入流读取数据的地方,输出流写入数据的地方。

  2. 字节流的read() 读取的是字节,字符流的read()读取的是字符,write方法也一样。
  3. 如果read返回-1或者readLine( )返回null 说明读到了末尾。
  4. 读一行数据不能使用DateInputStream,读一行要用BufferReader 的 readLine().

  5. 所有输出流在close()之前一定要flush()否则在缓冲区中的一部分数据会丢失。
  6. 格式化输出流 PrintStream 和 PrintWrite 都有 print 方法 和 write 方法 这2种方法没有区别,但如果是println方法会调用newLine(),而newLine()会执行flush,因此如果执行print或者write后不执行flush那么有可能写入不了数据,而println一定会写入。
  7. InputStreamReader 和InputStreamWriter 为编码提供了支持。

5 自我独立的类:RandomAccessFile

  • RandomAccessFile 可以任意访问文件的某处,既可以读又可以写,功能比较强大。

  • RandomAccessFile(File file, String mode)
    创建一个任意访问文件流从File参数指定的文件中读取,可设置文件权限。
    RandomAccessFile(String name, String mode)
    创建一个任意访问文件流,以从中指定名称的文件读取,可设置文件权限。

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

  1. 从文件中读数据取数据,如果不是压缩文件那么文件中肯定都是各种字符所以使用字符流,使用字节流比较麻烦。

       /*

    * 字符流读取文件

    * 设置编码格式

    * BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));

    */

    BufferedReader in = new BufferedReader(new FileReader(file));

    String s;

    StringBuilder sb = new StringBuilder();

    while ((s = in.readLine()) != null){

    sb.append(s+"\n");

    }

    in.close();

    System.out.println(sb);

    /*

    * 字节流读取文件

    * 不使用缓冲区

    * FileInputStream in = new FileInputStream(file);

    *

    */

    //默认缓冲区大小8192字节=8M

    BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));

    byte[] bytes = new byte[1024];

    //读到一个字节数组中,然后转换字符串时可以指定编码

    // 如果使用read只读一个字节,单个字节没有编码,这样遇到中文无法处理

    while (in.read(bytes) != -1){

    System.out.print(new String(bytes,"UTF-8"));

    }

    in.close();

    View Code

  2. 基本文件输出

    /*字节流只能写入字节*/

    FileOutputStream out = new FileOutputStream(file2);

    BufferedOutputStream bo = new BufferedOutputStream(out);

    bo.write(98);

    bo.flush();

    bo.close();

    /*字符流可以写入 char string char[] */

    BufferedWriter bw = new BufferedWriter(new FileWriter(file));

    bw.write(85);

    bw.write("koko");

    bw.flush();

    bw.close();

    //快速写入文件

    PrintWriter p = new PrintWriter(file);

    p.write(9);

    p.write("koko");

    p.flush();

    p.close();

    View Code

  3. 读写随机访问文件

          //打开或创建一个文件,赋予读写权限

    RandomAccessFile accessFile = new RandomAccessFile(file3,"rw");

    for (int i = 0; i < 5; i++) {

    accessFile.writeDouble(i*1.314);

    }

    accessFile.close();

    accessFile = new RandomAccessFile(file3,"rw");

    /*seek()以字节定位到某一位置

    * 一个double8字节长,这里定位到第三个double开始

    * 重新写入一个并覆盖

    */

    accessFile.seek(2*8);

    accessFile.writeDouble(8888D);

    //重新执行开始位置

    accessFile.seek(0);

    for (int i = 0; i < 5; i++) {

    System.out.println(accessFile.readDouble());

    }

    accessFile.close();

    }

    /*output

    0.0

    1.314

    8888.0

    3.942

    5.256 */

    View Code

7 文件读写工具

8 标准 I/O 

  • 标准I/O就是程序使用的单一信息流。
  • Java 提供了三种 System.in   System.out   System.err

  1.  System.out   System.err 被包装成了PrintStream

  2.  System.in  是未经包装的InputStream。
  3. 标准I/O重定向

    1. System.setIn(InputStream) 从屏幕接收信息改为从文件接收
    2. System.setOut(PrintStream)输出到屏幕改为输出到文件
    3. System.setErr(PrintStream)输出错误信息到屏幕改为到文件

       PrintStream p  = System.out;// p 必须在重定向之前设定,

      PrintStream stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(file4)));

      System.setOut(stream);

      for (int i = 0; i < 100; i++) {

      System.out.println("Spring 实战第"+(i+1)+"页");

      }

      stream.close();

      System.setOut(p);//不能使用System.setOut(System.out);这样是无效的

      System.out.println("回来"); 

9 进程控制

 public abstract class Processextends Object;

ProcessBuilder.start()和Runtime.exec方法创建一个本机进程并返回一个Process子类的Process ,

可以用来控制进程并获取有关它的信息。

Process类提供了用于执行进程输入,

执行到进程的输出,等待进程完成,

检查进程的退出状态以及破坏(杀死)进程的方法

10 新 I/O

  • jdk1.4 的 java.nio.* 包中引入了新的I/O类,目的在于提高速度。
  • 速度的提高是由于这种I/O方式接近于操作系统的I/O方式: 通道和缓冲器。

  •  流 形式的I/O 是读取写入一个或多个字节,而nio是以块操作数据。

  1. Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

10.1 FileChannel  通道

  1. FileChannel  通道:数据好比自来水,通道就是一截自来水管,我能操作这截自来水管中的数据。
  2. ByteBuffer 缓冲器:我们操作数据是通过缓冲器来操作,缓冲器就是在我们和通道之间运输数据的对象。所以我们并不和通道直接交互。
  3. 获得FileChannel : IO中的三个字节流可以获得通道,调用getChannel()返回 FileChannel对象
    1. FileInputStream
    2. FileOutputStream
    3. RandimAccessFile

  4. FileChannel由于只和缓冲器交互,所以 通道的read()和write()方法只接受ByteBuffer和ByteBuffer[ ]类型的参数。
  5. read(ByteBuffer[] dsts) 从该通道读取数据到缓冲区。

  6. write(ByteBuffer[] srcs) 从给定的缓冲区向该通道写入一系列字节。

  7. 通道直接相连 in.transferTo(0, in.size(),out) 或者out.transferfrom(in ,  0, in.size()) ,这方法不稳定,有可能成功,有可能失败,有可能传一半,所以不常用。

10.2 缓冲器 ByteBuffer 

  1. 我们操作ByteBuffer 字节缓冲器 与通道交互,与通道并不直接交互。
  2.  创建缓冲器不能 new 而是调用静态方法allocate(size)来创建,size 是缓冲区大小。
  3. 静态方法ByteBuffer.wrap(byte[] b)将字符数组包装到缓冲器,此时b或其一部分就是缓冲器,用put(byte[ ] b) 把b添加到缓冲区会复制数组,不如wrap高效。
  4.  执行read(ByteBuffer[] dsts) 后应该立刻调用缓冲器的flip()它会重置缓冲器中的几个指针,让read之后的write从正确的地方开始和结束。

  5. 执行write(ByteBuffer[] srcs) 后应该立刻调用缓冲器的clean()方法将缓冲指针重置为初始态,为下一次read重写缓冲区做准备。

  6. hasRemaining()判断缓冲区是否还有字节,有则true

10.3 数据转换

  • 内存中的数据都是字节byte,要想读出字符,字符串,或者汉字则写入数据的编码要和读出时的编码一致,才能正确显示。
  • 如果不指定编码则使用默认编码。

  1. 对输入内存的数据编码:转为字节数组时指定编码,  "中国1994".getBytes("UTF-8")
  2. 解码: Charset.forName("UTF-8").decode(buffer) 把缓冲器按指定编码输出为字符缓冲器,相当于buffer.asCharBuffer()

10.4基本类型插入获取

  • ByteBuffer只能存储字节类型数据,但可以从容纳的字节中产生各种不同的基本类型值。也可以插入基本数据类型。

  1. 插入基本数据类型先要获取基本类型视图,调用asXXXBuffer()方法获取视图,再调用视图的put方法插入。获取直接调用缓冲器对应的get方法。

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    buffer.asIntBuffer().put(2018);

    int i = buffer.getInt();

  2. 插入short值时要将值强制转换,其他类型不用转换。

10.5视图缓冲器

  • 只有ByteBuffer能与通道交互其他不能,只能创建一个独立的基本类型视图缓冲器,底层还是ByteBuffer,通过它操作ByteBuffer内部数据。
  • 任何视图缓冲器底层还是ByteBuffer,任何对视图的修改都将修改底层ByteBuffer。
  • 不能将视图缓冲器强制转换为ByteBuffer。

  1. 获取视图缓冲器只需ByteBuffer调用asXXXBuff方法返回对应的视图缓冲器

    ByteBuffer bb= ByteBuffer.allocate(1024);

    CharBuffer charBuffer = bb.asCharBuffer();

    DoubleBuffer doubleBuffer = bb.asDoubleBuffer();

  2. 获得视图缓冲器之后就可以在该视图缓冲器上操作数据,但底层操作的还是ByteBuffer。
  3. 所有对ByteBuffer的方法对应的视图缓冲器都有,如array()返回视图的数组形式。
  4. 操作视图缓冲器时不应该操作ByteBuffer。

10.6 缓冲器的细节

  • 所有缓冲器都继承自Buffer, 缓冲器就是一段内存,它有四个索引,可以表示对缓冲器操作的位置和操作结束位置。

  1. mark 标记,调用bb.mark()可以将标记设置到当前position位置。mark 指针不一定存在。
  2. position 位置,当前位置就是pos位置。缓冲器的put(),get()都是在pos所指位置,之后pos向前移一字节。但是如果是get( i )或者put(i,  n)则直接在到 i 处操作,pos不会移动。
  3. limit 界限 , 这是安全操作的界限,如果读取值时pos超过了lim那么读取到的就是初始化值0
  4. capacity 容量,就是缓冲器的大小(字节)。
  5. mark( )  将mark 设置为当前pos位置
  6. position(n)将pos设置为n
  7. limit(n) 设置limit位置为n
  8. flip( ) 将lim设置为pos,再将pos设置到开始(0)。read()之后要调用flip(),因为read之后pos再数据末尾,flip之后读取范围就是pos到lim,不会超过缓冲器存有数据的范围。
  9. clean()将pos设置 为0,limit设置为缓冲器大小capacity。write之后要调用clean()这相当于清空了缓冲器,可以从头开始写入数据,当前位置有数据会直接覆盖。
  10. rewind( ) 间pos设置为0,其他不变,它可以重新获得数据。
  11. remaining( ) 返回 lim - pos 结果,-1 说明操作文件数据到了末尾。也可使用 hasRemaining( )
  12. 以上对索引的操作都不会改变ByteBuffer数据。

10.7 内存映射文件

  • 内存映射文件可以创建和操作那些太大而不能放入内存的文件。
  • 获得通道后调用map() 产生MappedByteBuffer,这是一个特殊的直接缓冲器它是继承自ByteBuffer,有ByteBuffer所有方法。可以像使用ByteBuffer一样使用它,只不过一般用它来映射大文件
  • map()方法必须指定映射文件的开始位置和长度。

     File file = new File("C:\\Users\\Administrator\\Desktop\\file\\com\\nio1.txt");

    File path = new File("H:\\WinXP-2018.07.GHO");

    PrintStream p = System.out;

    MappedByteBuffer map = new FileInputStream(path).getChannel().map(FileChannel.MapMode.READ_ONLY, 0, 0x8FFFFFF); //数据太大可以使用16进制

    System.setOut(new PrintStream(new BufferedOutputStream(new FileOutputStream(file))));

    for (int i = 0; i < 0x8FFFFFF; i++) {

    System.out.println(map.get());

    }

    数据太大可以使用16进制数。这里只使用了读通道,把结果重定向到其他文件了。

  • 最好使用RandomAccessFile来产生既可以读又可以写的通道。因为映射写必须要使用RandomAccessFile来产生写通道,不能使用FileOutputStream。

10.8文件加锁

  • jdk 1.4 引入文件加锁机制。
  • Java的文件加锁直接映射到操作系统的加锁工具,对所有线程可见。

  1. 通过调用FileChannel的tryLock()或者lock()来获取整个文件的FileLock,tryLock是非阻塞的拿不到锁就返回,lock()是阻塞式的直到拿到锁才返回。没有参数则对整个文件加锁,哪怕加锁后文件变大如此。
  2. lock(long position, long size, boolean shared) 可以给文件部分加锁,shared为true则加共享锁,但这需要操作系统支持,不支持则加独占锁。

  3. FileLock.release()可以释放锁,或者关闭通道或者lock线程中断都可以释放锁。
  4. 处理大文件的文件映射也可以加锁
  5. 加锁是在通道上加,缓冲器不能加锁。

11 压缩

  1. 压缩   GZIPOutputStream   ZIPOutputStream  
  2. 解压   GZIPInputStream    ZIPInputStream  
  3. 压缩流属于字节流,但是字节流无法指定字符集,如果内容有中文会出现压缩乱码,使用InputStreamReader OutputStreamWriter 可以设置字符集。

     BufferedReader in  = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));

    //BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(file2)));

    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(file2)),"UTF-8"));

    int c;

    while ((c = in.read()) != -1){

    out.write(c);

    }

    in.close();

    out.flush();

    out.close();

    BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file2)),"UTF-8"));

    String s;

    while ((s = reader.readLine()) != null){

    System.out.println(s);

    }

    reader.close();

  4. 压缩多个文件,基本就这四步

      ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file3));

    BufferedOutputStream out = new BufferedOutputStream(zip);

    zip.putNextEntry(new ZipEntry(file));

    BufferedReader in = new BufferedReader(new FileReader(file)); 

12 对象序列化

  • 对象在程序结束后就不复存在,对象序列化就是把对象转化成一个字节序列保存在某处,下次要使用时反序列化成原来保存前的对象。

  1. 一个对象要能序列化必须是实现 Serializable接口,这个接口没有任何方法。
  2. 序列化的信息可以保存在任何地方,可以在流中可以在文件中,可以通过网络传递。
  3. 序列化和反序列化需要2个流 ObjectOutputStream 和 ObjectInputStream,调用writeObject()序列化,readObject()反序列化。

     People people = new People();

    people.setName("序列化到文件");

    people.setAge(20);

    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./people.out"));

    out.writeObject(people);

    people.setName("序列化到流");

    people.setAge(15);

    ByteArrayOutputStream bo = new ByteArrayOutputStream();

    ObjectOutputStream oi = new ObjectOutputStream(bo);

    oi.writeObject(people);

    ObjectInputStream bytes = new ObjectInputStream(new ByteArrayInputStream(bo.toByteArray()));

    ObjectInputStream in = new ObjectInputStream(new FileInputStream("./people.out"));

    People p = (People) in.readObject();

    System.out.println(p);

    p = (People)bytes.readObject();

    System.out.println(p);

    View Code

  4. 序列化可以保存对象的全景图,如果对象中有其他对象,那么也会保存其他对象相关信息,如果其他对象还引用了其他对象,仍然会保,它会保存整个对象网。
  5. 反序列化时是从字节序列中直接恢复的,并不会调用对象的构造器
  6. 反序列化对象时必须确保jvm可以找到对应对象的 .class文件,否则抛 ClassNotFoundException 

12.1 序列化控制

  • 如果对序列化有要求可以实现Externlizable接口代替Serializable接口,而且增加了2个方法会在序列化和反序列化时自动调用。

  1. writeExternal(ObjectOutput out) 序列化时自动调用,可以将一些属性的信息通过out.writeXXX()保存
  2. readExternal(ObjectInput in) 反序列化时自动调用,相当于构造器给某些属性赋值,可以通过in.readXXX()将保存的信息赋值给某些属性。
  3. 实现Externlizable接口的对象反序列化时会调用对象的默认构造器,因此默认构造器必须是public

  4. Externlizable 与 Serializable 接口区别

    1. Serializable 将初始化完成的对象写入二进制文件,故无需构造器。

    2. Externlizable 默认不保存对象字段属性,对象的属性只有通过writeExternal 方法才可以保存。

  5. Serializable 想要做到控制序列化可以对不想被序列化的字段使用 transient 关键字,使用transient 声明就不会被保存值。

  6. 如果不想使用Externlizable 就是使用Serializable 也可以做到同样的序列化,那就是 添加2个和Externlizable 方法很像的方法,序列化时会执行这两个方法而不会执行默认的序列化动作。

    1. private void  writeObject(ObjectOutputStream stream) throws IOExpection
    2. private void readObject(ObjectInputStream streamthrows IOExpection,ClassNotFoundException 
    3. 2个方法作用和上面一模一样,要注意 方法签名一点不能错
    4. 所有的非 transient 字段要保存在 stream.defaultWriteObject() 中且在2个方法的第一句调用。
    5. transient 字段要逐个使用stream.writerObject()保存。
    6. 本质:序列化对象时即ObjectOutputStream.writeObject(b)时 通过反射检查是否有这两个方法如果有则放弃默认序列化执行这两个方法,没有则执行默认序列化。

      public class SerializableDemo {

      public static void main(String[] args) throws Exception{

      People people = new People();

      people.setName("序列化到文件");

      people.setAge(20);

      people.setSex("难");

      ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./people.out"));

      out.writeObject(people);

      ObjectInputStream in = new ObjectInputStream(new FileInputStream("./people.out"));

      People p = (People) in.readObject();

      System.out.println(p);

      }

      }

      class People implements Serializable {

      String name;

      transient String sex;

      transient int age;

      Date date;

      private void writeObject(ObjectOutputStream stream) throws IOException{

      stream.defaultWriteObject();

      stream.writeObject(sex);

      stream.writeInt(age);

      }

      private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException{

      stream.defaultReadObject();

      sex = (String) stream.readObject();

      age = stream.readInt();

      }

      public String getSex() {

      return sex;

      }

      public void setSex(String sex) {

      this.sex = sex;

      }

      public People() {

      this.date = new Date();

      }

      public String getName() {

      return name;

      }

      public void setName(String name) {

      this.name = name;

      }

      public int getAge() {

      return age;

      }

      public void setAge(int age) {

      this.age = age;

      }

      @Override

      public String toString() {

      return "People{" +

      "name='" + name + '\'' +

      ", sex='" + sex + '\'' +

      ", age=" + age +

      ", date=" + date +

      '}';

      }

      }

      View Code

12.2 持久性

  • 深度复制deep copy 会复制整个对象网,对象序列化就属于深度复制。

  • 对象序列化到不同流,反序列化出来的对象地址也就不同,同一流反序列化出来的对象地址相同。

  1. 如何序列化一个小系统

    • 对象创建需要.class文件支持,.class文件最终会产生class对象,class对象创建实例对象。
    • class对象是实现了Serializable 接口的,所以可以把所有系统需要的class对象放在单一容器内,序列化容器。
    • 把容器反序列化出来自然 jvm 自然也就加载了所有class对象,创建实例对象时就会由被反序列化的Class对象创建,因为它们使用的是同一个类加载器。
    • 实例对象的静态变量就是类成员变量就是Class对象的成员变量。
    • 序列化反序列化之后容器内元素顺序是不会变得。通过get(n)可以拿出放进去的class对象。

      public class SyStemSerDemo {

      public SyStemSerDemo() {

      }

      public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

      List<Shape> shapes = new ArrayList<Shape>();

      for (int i = 0; i < 10; i++) {

      shapes.add(Shape.randomFactory());

      }

      for (int i = 0; i < 10; i++) {

      shapes.get(i).setColor(Shape.YELLOW);

      }

      List<Class<? extends Shape>> shapeTypes = new ArrayList<Class<? extends Shape>>();

      shapeTypes.add(Circle.class);

      shapeTypes.add(Square.class);

      shapeTypes.add(Line.class);

      ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./rest.lpk"));

      out.writeObject(shapeTypes);

      Line.writeStatic(out);

      out.writeObject(shapes);

      System.out.println(shapes);

      }

      }

      abstract class Shape implements Serializable {

      public static int RED = 1, BLUE = 2, YELLOW = 3;

      private int xPos, yPos, dimension;

      private static Random random = new Random(47);

      private static int count = 0;

      public abstract void setColor(int newcoloe);

      public abstract int getColor();

      public Shape(int xPos, int yPos, int dimension) {

      this.xPos = xPos;

      this.yPos = yPos;

      this.dimension = dimension;

      }

      public String toString() {

      return getClass().hashCode() + "--" + getClass() +

      "color【" + getColor() + "】 xPos【" +

      xPos + "】 yPos【" +

      yPos + "】 dimension【" +

      dimension + "】\n";

      }

      public static Shape randomFactory() {

      int xVal = random.nextInt(100);

      int yVal = random.nextInt(100);

      int dim = random.nextInt(100);

      switch (count++ % 3) {

      default:

      case 0:

      return new Circle(xVal, yVal, dim);

      case 1:

      return new Square(xVal, yVal, dim);

      case 2:

      return new Line(xVal, yVal, dim);

      }

      }

      }

      class Circle extends Shape {

      private static int color = RED;

      public Circle(int xVal, int yVal, int dim) {

      super(xVal, yVal, dim);

      }

      public void setColor(int newColor) {

      color = newColor;

      }

      public int getColor() {

      return color;

      }

      }

      class Square extends Shape {

      private static int color;

      public Square(int xVal, int yVal, int dim) {

      super(xVal, yVal, dim);

      color = RED;

      }

      public void setColor(int newColor) {

      color = newColor;

      }

      public int getColor() {

      return color;

      }

      }

      class Line extends Shape {

      private static int color = RED;

      public Line(int xVal, int yVal, int dim) {

      super(xVal, yVal, dim);

      }

      public void setColor(int newColor) {

      color = newColor;

      }

      public int getColor() {

      return color;

      }

      static void writeStatic(ObjectOutputStream stream) throws IOException {

      stream.writeInt(color);

      }

      static void readStatic(ObjectInputStream stream) throws IOException, ClassNotFoundException {

      color = stream.readInt();

      }

      }

      序列化系统

      //用不同于序列化的类加载器反序列化,即 其他类中反序列化

      public class Rests {

      public static void main(String[] args) throws IOException, ClassNotFoundException {

      ObjectInputStream in = new ObjectInputStream(new FileInputStream("./rest.lpk"));

      List<Class<? extends Shape>> shapeTypes = (List<Class<? extends Shape>>) in.readObject();

      System.out.println(Line.class.hashCode());

      System.out.println(shapeTypes.get(2).hashCode());

      Line.readStatic(in);

      List<Shape> shapes = (List<Shape>) in.readObject();

      System.out.println(shapes);

      }

      }

      还原系统

  2. 存在的问题

    • 序列化Class对象,实际序列化的就是类成员变量也就是静态成员。
    • 加载Class对象因为不会执行构造器,所以序列化时只会保存那些定义时就被赋值的静态变量,在构造器中被赋值的静态变量只能是初始化值0或者null.
    • 所以序列化对象时静态成员只会保存加载类时的初始值,不会保存之后被改变或者赋值的结果

      [621009875--class Circlecolor【3】 xPos【58】 yPos【55】 dimension【93】

      , 1846274136--class Squarecolor【3】 xPos【61】 yPos【61】 dimension【29】

      , 1360875712--class Linecolor【3】 xPos【68】 yPos【0】 dimension【22】

      , 621009875--class Circlecolor【3】 xPos【7】 yPos【88】 dimension【28】

      , 1846274136--class Squarecolor【3】 xPos【51】 yPos【89】 dimension【9】

      , 1360875712--class Linecolor【3】 xPos【78】 yPos【98】 dimension【61】

      , 621009875--class Circlecolor【3】 xPos【20】 yPos【58】 dimension【16】

      , 1846274136--class Squarecolor【3】 xPos【40】 yPos【11】 dimension【22】

      , 1360875712--class Linecolor【3】 xPos【4】 yPos【83】 dimension【6】

      , 621009875--class Circlecolor【3】 xPos【75】 yPos【10】 dimension【42】

      ]

      序列化前结果

      2129789493

      2129789493

      [931919113--class Circlecolor【1】 xPos【58】 yPos【55】 dimension【93】

      , 764977973--class Squarecolor【0】 xPos【61】 yPos【61】 dimension【29】

      , 2129789493--class Linecolor【3】 xPos【68】 yPos【0】 dimension【22】

      , 931919113--class Circlecolor【1】 xPos【7】 yPos【88】 dimension【28】

      , 764977973--class Squarecolor【0】 xPos【51】 yPos【89】 dimension【9】

      , 2129789493--class Linecolor【3】 xPos【78】 yPos【98】 dimension【61】

      , 931919113--class Circlecolor【1】 xPos【20】 yPos【58】 dimension【16】

      , 764977973--class Squarecolor【0】 xPos【40】 yPos【11】 dimension【22】

      , 2129789493--class Linecolor【3】 xPos【4】 yPos【83】 dimension【6】

      , 931919113--class Circlecolor【1】 xPos【75】 yPos【10】 dimension【42】

      ]

      还原结果与保存结果不同

  3. 由1,2 可知某些情况下无法正确保存对象的静态变量。对于静态变量要另想其法。
  4. static 数据要想正确被序列化,就要创建2个静态方法,并传入序列化流作为参数,和反序列化流作为参数,把静态成员保存到流和从流读取出来,在执行序列化和反序列化时显式调用。

    1. Line 对象就使用了这种办法正确保存了对象,这样就可以不用序列化Class对象了,可以把关于Class对象的代码都删了,使用别的Class对象也能反序列化出正确结果。

      import java.io.*;

      import java.util.ArrayList;

      import java.util.List;

      import java.util.Random;

      public class SyStemSerDemo {

      public SyStemSerDemo() {

      }

      public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

      List<Shape> shapes = new ArrayList<Shape>();

      for (int i = 0; i < 10; i++) {

      shapes.add(Shape.randomFactory());

      }

      for (int i = 0; i < 10; i++) {

      shapes.get(i).setColor(Shape.YELLOW);

      }

      ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./rest.lpk"));

      Circle.writeStatic(out);

      Square.writeStatic(out);

      Line.writeStatic(out);

      out.writeObject(shapes);

      System.out.println(shapes);

      }

      }

      abstract class Shape implements Serializable {

      public static int RED = 1, BLUE = 2, YELLOW = 3;

      private int xPos, yPos, dimension;

      private static Random random = new Random(47);

      private static int count = 0;

      public abstract void setColor(int newcoloe);

      public abstract int getColor();

      public Shape(int xPos, int yPos, int dimension) {

      this.xPos = xPos;

      this.yPos = yPos;

      this.dimension = dimension;

      }

      public String toString() {

      return getClass().hashCode() + "--" + getClass() +

      "color【" + getColor() + "】 xPos【" +

      xPos + "】 yPos【" +

      yPos + "】 dimension【" +

      dimension + "】\n";

      }

      public static Shape randomFactory() {

      int xVal = random.nextInt(100);

      int yVal = random.nextInt(100);

      int dim = random.nextInt(100);

      switch (count++ % 3) {

      default:

      case 0:

      return new Circle(xVal, yVal, dim);

      case 1:

      return new Square(xVal, yVal, dim);

      case 2:

      return new Line(xVal, yVal, dim);

      }

      }

      }

      class Circle extends Shape {

      private static int color = RED;

      public Circle(int xVal, int yVal, int dim) {

      super(xVal, yVal, dim);

      }

      public void setColor(int newColor) {

      color = newColor;

      }

      public int getColor() {

      return color;

      }

      static void writeStatic(ObjectOutputStream stream) throws IOException {

      stream.writeInt(color);

      }

      static void readStatic(ObjectInputStream stream) throws IOException, ClassNotFoundException {

      color = stream.readInt();

      }

      }

      class Square extends Shape {

      private static int color;

      public Square(int xVal, int yVal, int dim) {

      super(xVal, yVal, dim);

      color = RED;

      }

      public void setColor(int newColor) {

      color = newColor;

      }

      public int getColor() {

      return color;

      }

      static void writeStatic(ObjectOutputStream stream) throws IOException {

      stream.writeInt(color);

      }

      static void readStatic(ObjectInputStream stream) throws IOException, ClassNotFoundException {

      color = stream.readInt();

      }

      }

      class Line extends Shape {

      private static int color = RED;

      public Line(int xVal, int yVal, int dim) {

      super(xVal, yVal, dim);

      }

      public void setColor(int newColor) {

      color = newColor;

      }

      public int getColor() {

      return color;

      }

      static void writeStatic(ObjectOutputStream stream) throws IOException {

      stream.writeInt(color);

      }

      static void readStatic(ObjectInputStream stream) throws IOException, ClassNotFoundException {

      color = stream.readInt();

      }

      }

      View Code

  5. 序列化写入流的顺序A-B-C 那么反序列化时 读取出来顺序也是A-B-C不能错。

13 XML

14 Preferences

知识点:

  1. public final class System   final类不能被实例化

  2. 字节存放次序

    1. 不同的机器可能使用不同的字节排序来存放数据。
    2. 对于byte  它只占一个字节没有高低位之分。对于其他类型如short占2字节,int 占4字节存储他们就有了高低位之分。
    3. ByteBuffer默认 和网络传输数据 都是高位优先存储 如 0 0 0 0 0 0 0 0    0 1 1 0 0 0 0 1  占2字节 (存储读取都是从左往右高地址到低地址)先存储高位8个0,再存储低位。以short类型读就是97
    4. 改变ByteBuffer存储方式为低位优先则为 0 1 1 0 0 0 0 1   0 0 0 0 0 0 0 0 以short类型读就是 24832 
    5. 在存放数据之前可以调用 bb.order(BIG_ENDIANLITTLE_ENDIAN) 来设定高位存储还是低位存储。

以上是 《Java编程思想》笔记 第十八章 Java I/O 系统 的全部内容, 来源链接: utcz.com/z/391035.html

回到顶部