java编程思想--IO

JAVA I/O系统读(read) 入(input) 写(write) 出(output)相对于内存的读和写,从硬盘读入,向硬盘写出

18.1 File类

public class DirList {
    public static void main(String[] args) {
        File path = new File("E:\\IDEA\\zhuofai729\\src\\cn\\itcast\\zhuofai729\\demo7_6");
        String[] list ;
        args = new String[]{"\\w+.java"};
        if (args.length==0){
            list = path.list();
        }else
            list = path.list(new DirFilter(args[0]));
        Arrays.sort(list,String.CASE_INSENSITIVE_ORDER);
        for (String dirItem :
                list) {
            System.out.println(dirItem);
        }
    }

}
class DirFilter implements FilenameFilter{
    private Pattern pattern;
    public DirFilter(String regex){
        pattern = Pattern.compile(regex);
    }
    public boolean accept (File dir,String name){
        return pattern.matcher(name).matches();
    }
}
  1. DirFilter这个类存在的唯一原因就是accept()方法.创建这个类的目的在于把accept()方法提供给list()使用,使list()可以回调accept(),进而一句顶那些文件包含在列表中.因此这种结构也常常称为回调.

过滤器方法简介

//这个程序厉害,遍历的很有水平,PPrint只是一个用来格式化的工具
public final class Directory {
  public static File[]
  local(File dir, final String regex) {
    return dir.listFiles(new FilenameFilter() {
      private Pattern pattern = Pattern.compile(regex);
      public boolean accept(File dir, String name) {
        return pattern.matcher(
          new File(name).getName()).matches();
      }
    });
  }
  public static File[]
  local(String path, final String regex) { // Overloaded
    return local(new File(path), regex);
  }
  // A two-tuple for returning a pair of objects:
  public static class TreeInfo implements Iterable<File> {
    public List<File> files = new ArrayList<File>();
    public List<File> dirs = new ArrayList<File>();
    // The default iterable element is the file list:
    public Iterator<File> iterator() {
      return files.iterator();
    }
    void addAll(TreeInfo other) {//精髓之处
      files.addAll(other.files);
      dirs.addAll(other.dirs);
    }
    public String toString() {
      return "dirs: " + PPrint.pformat(dirs) +
        "\n\nfiles: " + PPrint.pformat(files);
    }
  }
  public static TreeInfo
  walk(String start, String regex) { // Begin recursion
    return recurseDirs(new File(start), regex);
  }
  public static TreeInfo
  walk(File start, String regex) { // Overloaded
    return recurseDirs(start, regex);
  }
  public static TreeInfo walk(File start) { // Everything
    return recurseDirs(start, ".*");
  }
  public static TreeInfo walk(String start) {
    return recurseDirs(new File(start), ".*");
  }
  static TreeInfo recurseDirs(File startDir, String regex){
    TreeInfo result = new TreeInfo();
    for(File item : startDir.listFiles()) {
      if(item.isDirectory()) {
        result.dirs.add(item);
        result.addAll(recurseDirs(item, regex));//精髓之处
      } else // Regular file
        if(item.getName().matches(regex))
          result.files.add(item);
    }
    return result;
  }
  // Simple validation test:
  public static void main(String[] args) {
    if(args.length == 0)
      System.out.println(walk("."));
    else
      for(String arg : args)
       System.out.println(walk(arg));
  }
} ///:~
  1. File目录的检查及创建

编程语言的I/O类中常用流这个抽象概念,它代表任何有能力产出数据的数据源对象或者有能力接收数据的接收端对象.我们很少使用单一的类来创建刘对象,而是通过叠合多个对象来提供所期望的功能.

18.2输入和输出

  1. InputStream的作用是用来表示那些从不同数据源产生输入的类.数据源包括:
    • 字节数组
    • String对象.
    • 文件.
    • “管道”,工作方式与实际管道相似,即,从一端输入,从另一端输出.
    • 一个由其他类的流组成的序列,以便我们可以将它们收集合并到一个流内.
    • 其他数据源

InputStream类型

OutputStream类型

18.3添加属性和有用的接口

  1. javaI/O类库需要多种不同功能的组合,这正是使用装饰器模式的理由所在.这也是JavaI/O类库里存在filter(过滤器)类的原因所在抽象类filter是所有装饰器类的基类.装饰器必须具有和它所装饰的对象相同的接口,但它也可以拓展接口,而这种情况只发生在个别filter类中.
  2. 装饰器模式也有一个缺点:在编写程序时他给我们提供相当多的灵活性,但是它同时也增加了代码的复杂性.Java I/O类库操作不便的原因在于:我们必须创建许多类–”核心”I/O类型加上所有的装饰器,才能得到我们所希望的单个I/O对象.
  3. FilterInputStream和FilterOutputStream是用来提供装饰器类接口以控制特定输入流(InputStream)和输出流(OutputStream)的两个类.这两个类是装饰器的必要条件.

FilterInputStrem装饰器接口

FilterInputStrem包含其他一些输入流,他将这些流用作其基本数据源,他可以直接传输数据或提供一些额外的功能.FilterInputStream类本身只是简单地重写那些将所有请求传递给所包含输入流的InputStream的所有方法.FilterInputStream的子类可以进一步重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段

面向字节和面向字符过滤器的变化

左右之间的对应关系近似度更加粗略一些.因为类组织形式不同,尽管BufferedOutputStream时FilterOuputStream的子类,但是BUfferedWriter并不是FilterWriter的子类,它放在那里只是把它作为一个占位符,或仅仅让我们不会对它所在的地方产生疑惑).然而这些类的接口却十分相似.
有一点很清楚:无论我们何时使用readLine(),都不应该使用DataInputStream(这会遭到编译器的强烈反对),而应该使用BUfferedReader.除了这一点,DataInputStream仍是I/O类库的首选成员.

自我独立的类:RandomAccessFile

public class RandomAccessFileextends Objectimplements DataOutput, DataInput, Closeable
RandomAccessFile适用于由大小已知的记录组成的文件,使用我们可以使用seek()将记录从一处转移到另一处,然后读取或者修改记录.文件中记录的大小不一定都相同,只要我们能够确定那些记录有多大以及他们呢在文件中的位置即可.
它是一个完全独立地类,从类开始编写其所有的方法(大多数都是本地的).这么做是因为RandomAccessFile拥有和别的I/O类型本质不同的行为,因为我们可以在一个文件内向前和向后移动.
在jdk1.4中,RandomAccessFile的大多数功能(但不是全部)由nio存储映射文件所取代.

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

尽管可以通过不同的方式组合I/O流类,但我们可能也就用到其中的几种组合.在这些示例中,异常处理都被简化为将异常传递给控制台,但是这只有在小型示例和工具才适用.在代码中,你需要考虑更加复杂的错误处理方式.

  1. 缓冲输入文件
public class BufferedInputFile {
  // Throw exceptions to console:
  public static String
  read(String filename) throws IOException {
    // Reading input by lines:
    BufferedReader in = new BufferedReader(//提供文件进行缓冲的功能
      new FileReader(filename));
    String s;
    StringBuilder sb = new StringBuilder();
    while((s = in.readLine())!= null)
      sb.append(s + "\n");
    in.close();
    return sb.toString();
  }
  public static void main(String[] args)
  throws IOException {
    System.out.print(read("BufferedInputFile.java"));
  }
} /* (Execute to see output) *///:~
  1. 从内存输入
public class MemoryInput {
  public static void main(String[] args)
  throws IOException {
    StringReader in = new StringReader(
      BufferedInputFile.read("MemoryInput.java"));
    int c;
    while((c = in.read()) != -1)
      System.out.print((char)c);
  }
} /* (Execute to see output) *///:~

注意:read()是以int形式返回下一字节.

  1. 格式化的内存输入

要读取格式化数据,可以使用DataInputStream,它们还有个面向字节的I/O类(不是面向字符的)因此我们必须使用InputStream类而不是Reader类.当然我们可以用InputStream以字节的形式读取任何数据(例如一个文件)

public class FormattedMemoryInput {
  public static void main(String[] args)
  throws IOException {
    try {
      DataInputStream in = new DataInputStream(
        new ByteArrayInputStream(
         BufferedInputFile.read(
          "FormattedMemoryInput.java").getBytes()));
      while(true)
        System.out.print((char)in.readByte());
    } catch(EOFException e) {//通过捕获异常来检测输入的末尾:这通常被认为时对异常特性的错误使用
      System.err.println("End of stream");
    }
  }
} /* (Execute to see output) *///:~

**ByteArrayInputStream**public class ByteArrayInputStreame xtends InputStreamByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException
ByteArrayInputStream(byte[] buf) 创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组.

DataInputStream专有的readbyte()方法

可以看到该方法与read方法不同在最后一个字符不是返回null而是抛出一个EOFException异常(书上讲因为任何字节的值都是合法的所以不能用来检测输入是否结束)可以用available()方法来检测后面还有几个字节来确定是否结束
注意,available()的工作方式会随着所读取的媒介类型的不同而有所不同,字面意思就是”在没有阻塞的情况下所能读取的字节数”

  1. 基本文件输出

首先,创建一个与制定文件连接的FileWriter.实际上我们通常会用BufferedWriter将其包装起来用以缓冲输出.

public class BasicFileOutput {
  static String file = "BasicFileOutput.out";
  public static void main(String[] args)
  throws IOException {
    BufferedReader in = new BufferedReader(
      new StringReader(
        BufferedInputFile.read("BasicFileOutput.java")));
    PrintWriter out = new PrintWriter(
      new BufferedWriter(new FileWriter(file)));
    int lineCount = 1;
    String s;
    while((s = in.readLine()) != null )
      out.println(lineCount++ + ": " + s);
    out.close();
    // Show the stored file:
    System.out.println(BufferedInputFile.read(file));
  }
} /* (Execute to see output) *///:~
  1. 存储和恢复数据
public class StoringAndRecoveringData {
  public static void main(String[] args)
  throws IOException {
    DataOutputStream out = new DataOutputStream(
      new BufferedOutputStream(
        new FileOutputStream("Data.txt")));
    out.writeDouble(3.14159);
    out.writeUTF("That was pi");
    out.writeDouble(1.41413);
    out.writeUTF("Square root of 2");
    out.close();
    DataInputStream in = new DataInputStream(
      new BufferedInputStream(
        new FileInputStream("Data.txt")));
    System.out.println(in.readDouble());
    // Only readUTF() will recover the
    // Java-UTF String properly:
    System.out.println(in.readUTF());
    System.out.println(in.readDouble());
    System.out.println(in.readUTF());
  }
} /* Output:
3.14159
That was pi
1.41413
Square root of 2
*///:~
  1. 如果我们使用DataOutputStream写入数据,Java保证我们可以使用DataInputStream准确地读取数据–无论读和写数据的平台多么不同.
  2. 当我们使用DataOutputStream时,写字符串并且让DataInputStream能够恢复它的唯一可靠的做法就是使用UTF-8编码.用writeUTF()和readUTF()来实现的.

  3. 读写随机访问文件

public class UsingRandomAccessFile {
  static String file = "rtest.dat";
  static void display() throws IOException {
    RandomAccessFile rf = new RandomAccessFile(file, "r");
    for(int i = 0; i < 7; i++)
      System.out.println(
        "Value " + i + ": " + rf.readDouble());
    System.out.println(rf.readUTF());
    rf.close();
  }
  public static void main(String[] args)
  throws IOException {
    RandomAccessFile rf = new RandomAccessFile(file, "rw");
    for(int i = 0; i < 7; i++)
      rf.writeDouble(i*1.414);
    rf.writeUTF("The end of the file");
    rf.close();
    display();
    rf = new RandomAccessFile(file, "rw");
    rf.seek(5*8);//double总是8字节长,所以5*8
    rf.writeDouble(47.0001);
    rf.close();
    display();
  }
} /* Output:
Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 7.069999999999999
Value 6: 8.484
The end of the file
Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 47.0001
Value 6: 8.484
The end of the file
*///:~

18.7文件读写的实用工具

public class TextFile extends ArrayList<String> {
  // Read a file as a single string:
  public static String read(String fileName) {
    StringBuilder sb = new StringBuilder();
    try {
      BufferedReader in= new BufferedReader(new FileReader(
        new File(fileName).getAbsoluteFile()));
      try {
        String s;
        while((s = in.readLine()) != null) {
          sb.append(s);
          sb.append("\n");
        }
      } finally {
        in.close();
      }
    } catch(IOException e) {
      throw new RuntimeException(e);
    }
    return sb.toString();
  }
  // Write a single file in one method call:
  public static void write(String fileName, String text) {
    try {
      PrintWriter out = new PrintWriter(
        new File(fileName).getAbsoluteFile());
      try {
        out.print(text);
      } finally {
        out.close();
      }
    } catch(IOException e) {
      throw new RuntimeException(e);
    }
  }
  // Read a file, split by any regular expression:
  public TextFile(String fileName, String splitter) {
    super(Arrays.asList(read(fileName).split(splitter)));
    // Regular expression split() often leaves an empty
    // String at the first position:
    if(get(0).equals("")) remove(0);
  }
  // Normally read by lines:
  public TextFile(String fileName) {
    this(fileName, "\n");
  }
  public void write(String fileName) {
    try {
      PrintWriter out = new PrintWriter(
        new File(fileName).getAbsoluteFile());
      try {
        for(String item : this)
          out.println(item);
      } finally {
        out.close();
      }
    } catch(IOException e) {
      throw new RuntimeException(e);
    }
  }
  // Simple test:
  public static void main(String[] args) {
    String file = read("TextFile.java");
    write("test.txt", file);
    TextFile text = new TextFile("test.txt");
    text.write("test2.txt");
    // Break into unique sorted list of words:
    TreeSet<String> words = new TreeSet<String>(
      new TextFile("TextFile.java", "\\W+"));
    // Display the capitalized words:
    System.out.println(words.headSet("a"));//获取比a(97)小的set视图
  }
} /* Output:
[0, ArrayList, Arrays, Break, BufferedReader, BufferedWriter, Clean, Display, File, FileReader, FileWriter, IOException, Normally, Output, PrintWriter, Read, Regular, RuntimeException, Simple, Static, String, StringBuilder, System, TextFile, Tools, TreeSet, W, Write]
*///:~

18.8 标准I/O

标准I/O这个术语参考的是Unix中”程序所使用的单一信息流”这个概念.程序的所有输入都可以来自与标准输入,它的所有输出也都可以发送到标准输出,以及所有的错误信息都可以发送到标准错误.
标准I/O的意义在于:我们可以很容易地把程序串联起来,一个程序的标准输出可以称为另一个程序的标准输入.强大的工具.

  1. Java提供了System.in,System.out和System.err.其中System.out和System.err已经事先被包装成了printStream对象.但是System.in却是一个没有被包装过的未经加工的InputStream.这意味着我们可以立即使用System.out和System.err,但是在读取Ssytem.in之前必须对其进行包装.
  2. 通常我们会用readLine()一次一行地读取输入,为此,我们将System.in包装成BufferedReader来使用,这要求我们必须用InputStreamReader把System.in转换成Reader.
public class Echo {
  public static void main(String[] args)
  throws IOException {
    BufferedReader stdin = new BufferedReader(
      new InputStreamReader(System.in));
    String s;
    while((s = stdin.readLine()) != null && s.length()!= 0)
      System.out.println(s);
    // An empty line or Ctrl-Z terminates the program
  }
} ///:~

字节流转字符流

现在来探究一下标准IO流的源码

public final class System{
    public static final InputStream in = null;
    public static final PrintStream out = null;
    public static final PrintStream err = null;

    private static native void setIn0(InputStream in);
    private static native void setOut0(PrintStream out);
    private static native void setErr0(PrintStream err);//重新制定流
}
public class Redirecting {
    public static void main(String[] args) throws IOException {
        PrintStream console = System.out;
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(".\\zhuofai814\\src\\cn\\itcast\\zhuofai\\demo18_8\\Redirecting.java"));
        PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("test.out")));
        System.setIn(in);
        System.setOut(out);
        System.setErr(out);
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s;
        while((s = br.readLine())!=null){
            System.out.println(s);
        }
        out.close();
        System.setOut(console);
    }
}
  1. 这个程序将标准输入附接到文件上,并将标准输入和标准错误重定向到另一个文件.注意:它在程序开头存储了对最初的System.out对象的引用,并且在结尾处将系统输出恢复到了该对象上
  2. I/O重定向操纵的是字节流,而不是字符流,因此我们使用的是InputStream和OutputStream,而不是Reader和Writer.

18.9进程控制

18.10新IO

jdk1.4的java.nio.*包引入了新JavaI/O类库,其目的在于提高速度.实际上,旧的I/O包已经使用nio重新实现过,以便充分利用这种速度提高,因此,即使我们不显示地用nio编写代码,也能从中受益.速度提高在文件I/O和网络I/O都有可能发生,我们在这里只研究前者,对于后者,在\
速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器.

  1. 唯一直接与通道交互的缓冲器时ByteBuffer–也就是说,可以存储未加工字节的缓冲器.
  2. 旧I/O类中有三个类被修改了,用以产生FileChannel.这三个被修改的类是FIleInputStream.FileOutputStream 以及用于既读又写的RandomAccessFile.注意这些是字节操纵流,与底层的nio性质一致.Reader和Writer这种字符模式类不能用于产生通道,但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Writer.
public class GetChannel {
    private static final int BSIZE = 1024;

    public static void main(String[] args) throws IOException {
        //Write a file
        FileChannel fc = new FileOutputStream("data.txt").getChannel();
        fc.write(ByteBuffer.wrap("Some text ".getBytes()));
        fc.close();
        //Add to the end of the file
        fc = new RandomAccessFile("data.txt","rw").getChannel();
        fc.position(fc.size());
        fc.write(ByteBuffer.wrap("Some more".getBytes()));
        fc.close();
        //Read the file
        fc = new FileInputStream("data.txt").getChannel();
        ByteBuffer buff = ByteBuffer.allocate(BSIZE);
        fc.read(buff);
        buff.flip();
        while(buff.hasRemaining())
            System.out.print((char)buff.get());
    }
}

wrap方法

  1. 对于这里所展示的任何流类,getChannel()将会产生一个FileChannel.通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问.
  2. 将字节放于ByteBuffer的方法之一是:使用”put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值.如果使用wrap()方法,它将已存在的字节数组”包装”到ByteBuffer中.一旦如此,就不再复制底层的数字,而是把它作为所产生的ByteBuffer的存储器,我们称之为数组支持的ByteBuffer.
  3. 对于只读访问,我们必须显式地使用静态allocate()方法来分配ByteBuffer.nio的目标就是快速移动大量数据,因此,ByteBuffer的大小就显得尤为重要
  4. 甚至达到更高的速度也有可能,方法就是使用allocateDirect()而不是allocate(),以产生一个与操作系统有更高耦合性的”直接”缓冲器.但是这种分配的开支会更大.
  5. 一旦调用read()来告知FileChannel向ByteBUffer存储字节,就必须调用缓冲器上的flip(),让它做好让人读取字节的准备(使得,则似乎有一点拙略,但是请记住,他是很拙略的,但却适用于获取最大速度).如果我们打算使用缓冲器执行进一步的read()操作(read下一个位置),我们也必须得调用clear()来为每个read()做好准备.(clear()清空此缓冲区,将位置设置为 0,将限制设置为容量,并丢弃标记。)
public class BufferToText {
  private static final int BSIZE = 1024;
  public static void main(String[] args) throws Exception {
    FileChannel fc =
      new FileOutputStream("data2.txt").getChannel();
    fc.write(ByteBuffer.wrap("Some text".getBytes()));
    fc.close();
    fc = new FileInputStream("data2.txt").getChannel();
    ByteBuffer buff = ByteBuffer.allocate(BSIZE);
    fc.read(buff);
    buff.flip();
    // Doesn't work:
    System.out.println(buff.asCharBuffer());
    // Decode using this system's default Charset:
    buff.rewind();
    String encoding = System.getProperty("file.encoding");
    System.out.println("Decoded using " + encoding + ": "
      + Charset.forName(encoding).decode(buff));
    // Or, we could encode with something that will print:
    fc = new FileOutputStream("data2.txt").getChannel();
    fc.write(ByteBuffer.wrap(
      "Some text".getBytes("UTF-16BE")));
    fc.close();
    // Now try reading again:
    fc = new FileInputStream("data2.txt").getChannel();
    buff.clear();
    fc.read(buff);
    buff.flip();
    System.out.println(buff.asCharBuffer());
    // Use a CharBuffer to write through:
    fc = new FileOutputStream("data2.txt").getChannel();
    buff = ByteBuffer.allocate(24); // More than needed
    buff.asCharBuffer().put("Some text");
    fc.write(buff);
    fc.close();
    // Read and display:
    fc = new FileInputStream("data2.txt").getChannel();
    buff.clear();
    fc.read(buff);
    buff.flip();
    System.out.println(buff.asCharBuffer());
  }
} /* Output:
????
Decoded using Cp1252: Some text
Some text
Some text
*///:~
  1. 缓冲容器容纳的是普通的字节,为了把它们转换成字符,我们要么在输入它们的时候对其进行编码(这样,它们输出时才具有意义),要么将其从缓冲器输出时对他们进行编码,要么使用java.nio.cahrset.Charset类实现这些功能,该类提供了把数据编码成多种不同类型的字符集的工具.
public class GetData {
  private static final int BSIZE = 1024;
  public static void main(String[] args) {
    ByteBuffer bb = ByteBuffer.allocate(BSIZE);
    // Allocation automatically zeroes the ByteBuffer:
    int i = 0;
    while(i++ < bb.limit())
      if(bb.get() != 0)
        print("nonzero");
    print("i = " + i);
    bb.rewind();
    // Store and read a char array:
    bb.asCharBuffer().put("Howdy!");
    char c;
    while((c = bb.getChar()) != 0)
      printnb(c + " ");
    print();
    bb.rewind();
    // Store and read a short:
    bb.asShortBuffer().put((short)471142);
    print(bb.getShort());
    bb.rewind();
    // Store and read an int:
    bb.asIntBuffer().put(99471142);
    print(bb.getInt());
    bb.rewind();
    // Store and read a long:
    bb.asLongBuffer().put(99471142);
    print(bb.getLong());
    bb.rewind();
    // Store and read a float:
    bb.asFloatBuffer().put(99471142);
    print(bb.getFloat());
    bb.rewind();
    // Store and read a double:
    bb.asDoubleBuffer().put(99471142);
    print(bb.getDouble());
    bb.rewind();
  }
} /* Output:
i = 1025
H o w d y !
12390
99471142
99471142
9.9471144E7
9.9471142E7
*///:~
  1. 想ByteBuffer插入基本类型数据的简单的方法是:利用asCharBuffer() asShortBuffer()等获得该缓冲器上的试图,然后使用视图的put()方法.

public final Buffer rewind()重绕此缓冲区。将位置设置为 0 并丢弃标记。
在一系列通道写入或获取 操作之前调用此方法(假定已经适当设置了限制)。例如:
out.write(buf); // Write remaining data
buf.rewind(); // Rewind buffer
buf.get(array); // Copy data into array
返回:此缓冲区

  1. 试图缓冲器可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer.ByteBuffer依然是实际存储数据的地方,”支持”这前面的视图,因此对视图的任何修改都会映射称为对ByteBuffer中数据的修改.

{0,0,0,0,0,0,0,'a'}

  1. 字节存放次序 不同的机器可能会使用不同的字节排序方法来存储数据.”big endian”(高位优先)将最重要的字节存放在地址最低的存储器单元.而”little endian”(低位优先)则是将最重要的字节放在地址最高的存储器单元.
  2. 用缓冲器操纵数据 注意:ByteBuffer是将数据移进通道的唯一方式,并且我们只能创建一个独立的基本类型缓冲器,或者使用”as”方法从ByteBuffer中获得.也就是说我们不能把基本类型的缓冲器转换成ByteBuffer.然而由于我们可以经由视图缓冲器将基本类型数据移进移出ByteBuffer,所以这也就不是什么真正的限制了.

缓冲器的细节
缓冲器的细节

public class UsingBuffers {
  private static void symmetricScramble(CharBuffer buffer){
    while(buffer.hasRemaining()) {
      buffer.mark();
      char c1 = buffer.get();
      char c2 = buffer.get();
      buffer.reset();
      buffer.put(c2).put(c1);
    }
  }
  public static void main(String[] args) {
    char[] data = "UsingBuffers".toCharArray();
    ByteBuffer bb = ByteBuffer.allocate(data.length * 2);
    CharBuffer cb = bb.asCharBuffer();
    cb.put(data);
    print(cb.rewind());
    symmetricScramble(cb);
    print(cb.rewind());
    symmetricScramble(cb);
    print(cb.rewind());
  }
} /* Output:
UsingBuffers
sUniBgfuefsr
UsingBuffers
*///:~

過程解析

rewind()把position设置到缓冲器的开始位置.

  1. 内存映射文件:为了既能读又能写,我们先由RandomAccessFile开始,获得该文件上的通道,然后调用map()产生MappedByteBuffer.
public class LargeMappedFiles {
  static int length = 0x8FFFFFF; // 128 MB
  public static void main(String[] args) throws Exception {
    MappedByteBuffer out =
      new RandomAccessFile("test.dat", "rw").getChannel()
      .map(FileChannel.MapMode.READ_WRITE, 0, length);
    for(int i = 0; i < length; i++)
      out.put((byte)'x');
    print("Finished writing");
    for(int i = length/2; i < length/2 + 6; i++)
      printnb((char)out.get(i));
  }
} ///:~
  1. mappedByteBuffer是一种特殊类型的直接缓冲器.我们必须指定映射文件的初始位置和映射区域的长度,这意味着我们可以映射某个大文件的较小部分.

The file appears to be accessible all at once because only portions of it are brought into memory, and other parts are swapped out. This way a very large file (up to 2 GB) can easily be modified.

//性能比较,注意test()方法包括初始化各种IO对象的时间,因此,即使建立映射文件的花费很大,但是整体受益比起I/O流来说还是很显著的.
/* Output: (90% match)
Stream Write: 0.56
Mapped Write: 0.12
Stream Read: 0.80
Mapped Read: 0.07
Stream Read/Write: 5.32
Mapped Read/Write: 0.02
*///:~

18.11压缩

  1. 利用GZIP进行简单压缩,如果我们只想对单个数据流(而不是一系列互异数据)进行压缩,那么它可能是比较适合的选择.
  2. 用Zip进行多文件保存.

18.2对象序列化(Object serialization)

并不是字面意思上的序列化,类似于把对象保存起来编码,在需要的时候再编码出来.
当你创建对象时,只要你需要,它就会一直存在,但是在程序终止时,无论如何他都不会继续存在.如果对象能够在程序不运行的情况下仍能存在并保存其信息,那将非常有用.当然你可以通过将信息写入文件或数据库来达到相同的效果,但是在使万物都成为对象的精神中,如果能够讲一个对象声明为是”持久性”的,并为我们处理掉所有的细节,那将会显得十分方便.
java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后这个字节序列完全恢复为原来的对象.(甚至可以通过网络进行;意味着序列化机制能够自动弥补不同操作系统之间的差异)
“持久性”意味着一个对象的生存周期并不取决与程序是否正在执行,他可以生存与程序的调用之间.通过讲一个序列化对I型昂写入磁盘,然后再重新调用程序时恢复该对象,就能够实现持久性的效果.
“轻量级”,是因为不能用某种”persistent”(持久)关键字来简单定义一个对象,并让系统自动维护其他细节问题(尽管将来有可能实现),相反,对象必须在程序中显示地序列化(serialize)和反序列化还原(deserialize)
对象序列化时为了支持两种主要特征.一是java的远程方法调用(Remote Method Invocation,RMI),它使存活于其他计算机上的对象使用起来就像是存活与本机上一样.再者对java Beans来说,对象的序列化也是必需的.使用一个bean时,一般情况下是在设计阶段对它的状态信息进行配置.这种信息必须保存下来,并在程序自动时进行后期恢复;这种具体工作就是由对象序列化完成的.
序列化过程:只要对象实现了Serializable接口(该接口仅是一个标记接口,不包括任何方法),对象的序列化处理就会非常简单.要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内.这时,只需调用writeObject()即可将对象许泪花,并将其发送给OutputStream(对象序列化是基于字节的),要反向进行该过程需要讲一个Inputstream封装在ObjectInputStream内,然后调用readObject().和往常一样,我们获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们.
对象序列化特别”聪明”的一个地方是它不仅保存了对象的”全景图”,而且能追踪对象内所包含的所有引用,并保存那些对象;接着又能对对象内每个这样的引用进行追踪;依此类推

  1. 下面这个例子通过对链接的对象生成一个worm(蠕虫)对序列化机制进行了测试.每个对象都与worm中的下一段链接,同时又与属于不同类(Data)的对象引用数组链接:
class Data implements Serializable {
  private int n;
  public Data(int n) { this.n = n; }
  public String toString() { return Integer.toString(n); }
}
//这段程序中包含了一个private的Worm的next即next存储了下一个的next对象经由调式之后可以看到它的内存构图
public class Worm implements Serializable {
  private static Random rand = new Random(47);
  private Data[] d = {
    new Data(rand.nextInt(10)),
    new Data(rand.nextInt(10)),
    new Data(rand.nextInt(10))
  };
  private Worm next;
  private char c;
  // Value of i == number of segments
  public Worm(int i, char x) {
    print("Worm constructor: " + i);
    c = x;
    if(--i > 0)
      next = new Worm(i, (char)(x + 1));
  }
  public Worm() {
    print("Default constructor");
  }
  public String toString() {
    StringBuilder result = new StringBuilder(":");
    result.append(c);
    result.append("(");
    for(Data dat : d)
      result.append(dat);
    result.append(")");
    if(next != null)
      result.append(next);
    return result.toString();
  }
  public static void main(String[] args)
  throws ClassNotFoundException, IOException {
    Worm w = new Worm(6, 'a');
    print("w = " + w);
    ObjectOutputStream out = new ObjectOutputStream(
      new FileOutputStream("worm.out"));
    out.writeObject("Worm storage\n");
    out.writeObject(w);
    out.close(); // Also flushes output
    ObjectInputStream in = new ObjectInputStream(
      new FileInputStream("worm.out"));
    String s = (String)in.readObject();
    Worm w2 = (Worm)in.readObject();
    print(s + "w2 = " + w2);
    ByteArrayOutputStream bout =
      new ByteArrayOutputStream();
    ObjectOutputStream out2 = new ObjectOutputStream(bout);
    out2.writeObject("Worm storage\n");
    out2.writeObject(w);
    out2.flush();
    ObjectInputStream in2 = new ObjectInputStream(
      new ByteArrayInputStream(bout.toByteArray()));
    s = (String)in2.readObject();
    Worm w3 = (Worm)in2.readObject();
    print(s + "w3 = " + w3);
  }
} /* Output:
Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w2 = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w3 = :a(853):b(119):c(802):d(788):e(199):f(881)
*///:~

调式图

  1. 序列化的控制在特殊情况下,可通过实现Externalizable接口–代替实现Serializable接口–来对序列化过程进行控制.这个Externalizable接口继承了Serializable接口,同时增添了两个方法:writeExternal()和readExternal().这两个方法会在序列化和反序列化还原的过程汇总自动被调用,以便执行一些操作.
  2. 恢复b1后会调用默认构造器.这与恢复一个Serializable对象不同,对于Serializable对象,对象完全以它存储的二进制位基础来构造,而不调用构造器.而对于一个Externalizable对象,所有普通的默认构造器都会被迪欧暗涌(包括在字段定义时的初始化),然后调用readExternal()必须注意这一点–所有默认的构造器都会被调用,才能使Externalizable对象产生正确的行为.
//完整保存和恢复一个Externalizable对象:
//: io/Blip3.java
// Reconstructing an externalizable object.
import java.io.*;
import static net.mindview.util.Print.*;

public class Blip3 implements Externalizable {
  private int i;
  private String s; // No initialization
  public Blip3() {
    print("Blip3 Constructor");
    // s, i not initialized
  }
  public Blip3(String x, int a) {
    print("Blip3(String x, int a)");
    s = x;
    i = a;
    // s & i initialized only in non-default constructor.
  }
  public String toString() { return s + i; }
  public void writeExternal(ObjectOutput out)
  throws IOException {
    print("Blip3.writeExternal");
    // You must do this:
    out.writeObject(s);
    out.writeInt(i);
  }
  public void readExternal(ObjectInput in)
  throws IOException, ClassNotFoundException {
    print("Blip3.readExternal");
    // You must do this:
    s = (String)in.readObject();
    i = in.readInt();
  }
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    print("Constructing objects:");
    Blip3 b3 = new Blip3("A String ", 47);
    print(b3);
    ObjectOutputStream o = new ObjectOutputStream(
      new FileOutputStream("Blip3.out"));
    print("Saving object:");
    o.writeObject(b3);
    o.close();
    // Now get it back:
    ObjectInputStream in = new ObjectInputStream(
      new FileInputStream("Blip3.out"));
    print("Recovering b3:");
    b3 = (Blip3)in.readObject();
    print(b3);
  }
} /* Output:
Constructing objects:
Blip3(String x, int a)
A String 47
Saving object:
Blip3.writeExternal
Recovering b3:
Blip3 Constructor
Blip3.readExternal
A String 47
*///:~
  1. 字段s和i只在第二个构造器中初始化,而不是在默认的构造器中初始化.这意味这加入不在readExternal中初始化s和i,s就会为null,而i就会为零.
  2. 我们如果从一个Externalizable对象继承,通常需要调用基类版本的writeExternal()和readExternal()来为基类组件提供恰当的存储和恢复功能.
  3. transient(瞬时)关键字当我们不像让Java的序列化机制自动保存与恢复.一种办法就是实现Externalizable,并且可以在writeExternal()内部只对所需部分进行显示初始化.但是我们正在操作的是一个Serializable对象,那么所有序列化操作都会自动进行.为了能够予以控制,可以用transient(瞬时)关键字逐个字段地关闭序列化,他的意思是”不用麻烦你保存或恢复数据—我会自己处理”.

  4. 使用”持久性”

//: io/MyWorld.java
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;

class House implements Serializable {}

class Animal implements Serializable {
  private String name;
  private House preferredHouse;
  Animal(String nm, House h) {
    name = nm;
    preferredHouse = h;
  }
  public String toString() {
    return name + "[" + super.toString() +
      "], " + preferredHouse + "\n";
  }
}

public class MyWorld {
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    House house = new House();
    List<Animal> animals = new ArrayList<Animal>();
    animals.add(new Animal("Bosco the dog", house));
    animals.add(new Animal("Ralph the hamster", house));
    animals.add(new Animal("Molly the cat", house));
    print("animals: " + animals);
    ByteArrayOutputStream buf1 =
      new ByteArrayOutputStream();
    ObjectOutputStream o1 = new ObjectOutputStream(buf1);
    o1.writeObject(animals);
    o1.writeObject(animals); // Write a 2nd set
    // Write to a different stream:
    ByteArrayOutputStream buf2 =
      new ByteArrayOutputStream();
    ObjectOutputStream o2 = new ObjectOutputStream(buf2);
    o2.writeObject(animals);
    // Now get them back:
    ObjectInputStream in1 = new ObjectInputStream(
      new ByteArrayInputStream(buf1.toByteArray()));
    ObjectInputStream in2 = new ObjectInputStream(
      new ByteArrayInputStream(buf2.toByteArray()));
    List
      animals1 = (List)in1.readObject(),
      animals2 = (List)in1.readObject(),
      animals3 = (List)in2.readObject();
    print("animals1: " + animals1);
    print("animals2: " + animals2);
    print("animals3: " + animals3);
  }
} /* Output: (Sample)
animals: [Bosco the dog[Animal@addbf1], House@42e816
, Ralph the hamster[Animal@9304b1], House@42e816
, Molly the cat[Animal@190d11], House@42e816
]
animals1: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
animals2: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
animals3: [Bosco the dog[Animal@10d448], House@e0e1c6
, Ralph the hamster[Animal@6ca1c], House@e0e1c6
, Molly the cat[Animal@1bf216a], House@e0e1c6
]
*///:~
  1. 我们可以通过一个字节数组来使用对象序列化,从而实现对任何可Serializable对象的”深度复制”(deep copy)–深度赋值意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用.
  2. 在这个例子中我们可以看到,main()方法中创建了一个Animal列表并将其两次序列化,分别送至不同的流.档期被反序列化还原并打印时 每次运行时,对象将会处在不同的内存地址.
  3. 只要将热河对象序列化到单一流中,就可以恢复出我们写出时一样的对象网,并且没有任何意外重复复制出的对象.
  4. 如果我们想保存系统状态,最安全的做法时将其作为”原子”操作进行序列化.

18.13XML

  1. 对象序列化的一个重要限制时它只是java的解决方案:只有java程序才能反序列化这种现象.一中更具互操作性的解决方案是将数据转换为XML格式,这可以使其被各种各样的平台和语言使用.
  2. 使用Elliotte Rusy Harold开源XOM类库最直观的用java产生和修改XML的方式.另外,XOM还强调了XML的正确性.详细信息可以查看www.xom.nu

18.14Preferences

Preferences是一个键-值集合(类似映射),存储在一个节点层次结构中.尽管节点层次结构可用来创建更为复杂的结构,但是通常是创建以你的类名命名的单一节点,然后将信息存储其中.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值