18 Java I/O 系统 IO

对程序语言的设计者来说, 创建一个好的输入/输出(I/O)系统是一项艰难的任务

Java通过创建大量的类库来解决这个问题

  • File类

File类既能代表一个文件的名称, 又能代表一个目录下一组文件的名称

如果它指的是一个文件集, 我们可以通过调用list()方法, 返回一个字符数组

所以File类其实指代的是一个文件目录, 会带有一个目录路径

list()方法

//构造器给出一个目录, .表示当前目录, /表示根目录(我输出的是本计算机)

File path = new File("./src/containers");
String[] list;

//path.list(FilenameFilter filter);不加参数表示获取目录下所有文件名称

//加一个FilenameFilter类参数, 用正则表达式去筛选文件名称
list = path.list(new DirFilter("\\.java"));

//FilenameFilter是一个接口

interface FilenameFilter {

    boolean accept(File dir, String name);

}

实现这个接口的DirFilter类

class DirFilter implements FilenameFilter {
    private Pattern pattern;
    public DirFilter(String regex) {
        pattern = Pattern.compile(regex);
    }
    public boolean accept(File dir, String name) {
        System.out.println(name);
        return pattern.matcher(name).matches();
    }
}

这个接口唯一要实现的是一个accept()方法, 当把accept()方法提供给list()方法后, list()可以回调accept(), 

accept()方法接受一个代表某个目录的File对象, 对对象里的每个文件, 接受它的文件名String, 并一一调用, 

返回true的话, 就添加到list()的结果里, 也就是被筛选中

所以这个筛选方式可以通过给DirFilter对象传递一个正则, 用正则来筛选字符串

public static FilenameFilter filter(final String regex) {
        return new FilenameFilter() {
            private Pattern pattern = Pattern.compile(regex);
            public boolean accept(File dir, String name) {
                //System.out.println(name+" "+regex);
                //System.out.println(pattern.matcher(name).matches());
                return pattern.matcher(name).matches();
            }
        };
    }

public static void main(String[] args) throws Exception {
        File path = new File("./src/io");
        String[] list;
        list = path.list(filter("\\w+.java"));

}

可以定义内部匿名类, 匿名内部类的方法参数, 必须是final的

也可以把匿名类直接添加到方法执行的地方

list = path.list(new FilenameFilter() {

    private Pattern pattern = Pattern.compile("\\w+.java");

    public boolean accept(File dir, String name) {}

});

产生文件对象集

listFile()

前面的list()只是得到文件名字集, listFile()产生文件对象集合

File[] files = path.listFile(FilenameFilter filter);

用递归的方式, 遍历一个目录, 获取所有的目录和文件

目录和文件是不同的File对象, 可以通过isDirectory()方法判断

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 

                //如果是文件, 加入文件
                result.files.add(item);
        }
        return result;
}

查看文件信息

private static void fileData(File f) {
        System.out.println(
            "Absolute path: " + f.getAbsolutePath() + 
            "\n Can read: " + f.canRead() +
            "\n Can write: " + f.canWrite() +
            "\n getName: " + f.getName() +
            "\n getParent: " + f.getParent() +
            "\n getPath: " + f.getPath() +
            "\n length: " + f.length() +
            "\n lastModified: " + f.lastModified()
        );
        if(f.isFile())
            System.out.println("It's a file");
        else if(f.isDirectory())
            System.out.println("It's a directory");

}

创建目录

File newDir = new File("./src/io/newDir");
if(!newDir.exists())
     newDir.mkdirs();

删除文件

newDir.delete();

  • 输入和输出

流: 代表任何有能力产出数据源对象或者是有能力接受数据的接收端对象

流屏蔽了实际的I/O设备中处理数据的细节

任何继承自InputStream或者Reader类, 都包含read()方法, 用以读取单个字节或者字节数组

任何继承自OutputStream或者Writer类, 都包含write()方法, 用以写入单个字节或者字节数组

InputStream类型

InputStream类型表示从不同数据源产生输入的类

包括

1) 字节数组

2) 字符串数组

3) 文件

4) 管道(从一端输入, 从另一端输出)

5) 一个由其他种类的流组成的序列

每种数据源都有相应的InputStream子类

类                                       功能                                                构造器参数

ByteArrayInputStream       将缓冲区当作InputStream使用       缓冲区读出的字节

StringBufferInputStream    String转换为InputStream                字符串,底层使用StringBuffer

FileInputStream                 从文件中读取信息                           字符串, 表示文件名

PipeInputStream               产生用于PipeOutput流的数据          PipeOutputStream作为多线程的数据源

SequenceInputStream      将多个InputStream转化为一个        多个InputStream对象或者InputStream容器

FilterInputStream              抽象类, 作为装饰器接口, 为其他InputStream提供有用功能

FilterInputStream类为decorator装饰器类提供基类, 装饰器类可以把属性或者有用的接口与输入流连接在一起

OutputStream类型

该类别决定了输出所要去的地方

类                                    功能                                                  构造器参数

ByteArrayOutStream      在内存中创建缓冲区                         缓冲区初始化尺寸

FileOutStream                将信息写入文件                                字符串, 表示文件名

PipeOutStream               产生相关PipeInputStream的输出     PipeInputStream作为多线程的数据目的地

FilterOutStream              抽象类, 作为装饰器接口, 为其他OutStream提供有用功能

添加属性和有用的接口

Java I/O 需要多种不同功能协调组合, 所以用到了装饰器模式

装饰器必须要有和被它装饰的对象一样的接口

FilterInputStream和FilterOutStream分别从InputStream和OutStream继承而来

FilterInputStream类

类                                           功能                                                                 构造器参数

DataInputStream                    从流读取基本类型以及String类型                   InputStream包含用于读取基本类型的全部接口

BufferedInputStream              防止每次读取都要实际操作, 使用缓冲区        InputStream,指定缓冲区大小

LineNumberInputStream        跟踪输入流中的信号                                       InputStream, 仅增加行号

PushbackInputStream            将读到的最后一个字节回退输入流                 InputStream, 作为编译器的扫描器

FilterOutputStream类

类                                           功能                                                                 构造器参数

DataOutputStream                 向流中写入基本类型以及String类型               OutputStream包含用于写入基本类型的全部接口

PrintSream                             用于产生格式化输出                                       OutputStream,可以指示是否清空缓冲区

BufferedOutStream                防止每次写入都要实际操作, 使用缓冲区        OutputStream,指定缓冲区大小

  • Reader和Writer

InputStream和OutputStream针对的是字节类型的I/O

Reader和Writer提供面向Unicode与面向字符的I/O功能

为了把字节层次结构和字符层次结构的类结合起来使用,  要用到适配器类

InputStreamReader可以把InputStream转为Reader

OutputStreamReader可以把OutputStream转为Reader

尽量使用Reader与Writer类, 因为它们是16位的, 而字节流是8位的

InputStream                        Reader

OutputStream                     Writer

FileInputStream                  FileReader

FileOutputStream               FileWriter

StringBufferInputStream    StringReader

                                           StringWriter

ByteArrayInput                   CharArrayReader

ByteArrayOutput                CharArrayWriter

PipeInputStream                PipedReader

PipeOutputStream             PipeWriter

过滤器也类似

FilterInputStream              FilterReader

FilterOutputStream           FilterWriter(抽象类, 没有子类)

BufferedInputStream        BufferedReader

BufferedOutputStream     BufferedWriter

DataInputStream              BufferedReader

PrintStream                      PrintWriter

LineNumberInputStream  LineNumberReader

StreamTokenizer              StreamTokenizer

PushbackInputStream      pushbackReader

  • RandomAccessFile

RandomAccessFile适用于用由大小已知的记录组成的文件,它是一个完全独立的类

它可以通过seek()把记录从一处转移到另一处, 可以在一个文件内向前或向后移动

  • I/O的使用方式

缓冲输入文件

read(String filename) throws IOException {
        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();
}

BufferedReader装饰器把读文件的FileReader的内容放在缓冲区, 用readLine()读出字符串的形式

从内存输入

public static void main(String[] args) throws IOException {
        StringReader in = new StringReader(BufferedInputFIle.read("./src/io/MemoryInput.java"));
        int c;
        while((c = in.read()) != -1)
            System.out.print((char)c);
}

读出内存中上一个例子里产生的字符串, 用StringReader把字符串变成InputStream

格式化的内存读入

public static void main(String[] args) throws IOException {
        try {
            DataInputStream in = new DataInputStream(
                new ByteArrayInputStream(
                    BufferedInputFIle.read("./src/io/FormattedMemoryInput.java").getBytes()
                )
            );
            while(true)
                System.out.print((char)in.readByte());
        } catch(EOFException e) {
            System.err.println("End of stream");
        }
}

DataInputStream是面向字节的包装器, 把内存中的字符串通过getBytes()变成字节数组, 用ByteArrayInputStream把字节数组变成

InputStream, 提供给DataInputStream装饰器

读字节的结束判断

public static void main(String[] args) throws Exception {
        DataInputStream in = new DataInputStream(
            new BufferedInputStream(new FileInputStream("./src/io/TestEOF.java"))
        );
        while(in.available() != 0)
            System.out.print((char)in.readByte());

}

available()方法用来判断字节输入的结束

基本的文件输入

/*6*/    public static void main(String[] args) throws Exception {
/*7*/          BufferedReader in = new BufferedReader(
/*8*/            new StringReader(
/*9*/                BufferedInputFIle.read(file)
/*10*/            )
/*11*/        );
/*12*/        PrintWriter out = new PrintWriter(
/*13*/            new BufferedWriter(new FileWriter(file))
/*14*/        );
/*15*/        int lineCount = 1;
/*16*/        String s;
/*17*/        while((s = in.readLine()) != null) 
/*18*/            out.println("/*"+lineCount++ + "*/" +s);
/*19*/         out.close();
/*20*/         System.out.println(BufferedInputFIle.read(file));
/*21*/   }

首先要创建一个文件写入FileWriter, 再用BufferedWriter包装起来以缓存输出, 结果就是在每一行加上了行标

存储和恢复数据

public static void main(String[] args) throws Exception {
        DataOutputStream out = new DataOutputStream(
            new FileOutputStream("./src/io/Data.txt")
        );
        out.writeDouble(3.1415926);
        out.writeUTF("That was pi");
        out.writeDouble(1.41413);
        out.writeUTF("Square root of 2");
        out.close();
        DataInputStream in = new DataInputStream(
            new FileInputStream("./src/io/Data.txt")
        );
        System.out.println(in.readDouble());
        System.out.println(in.readUTF());
        System.out.println(in.readDouble());
        System.out.println(in.readUTF());

}

DataInputStream和DataOutputStream可以格式化地输入和输出数据

WriteUTF和ReadUTF是适用于Java的UTF-8变体, 如果用一个非Java程序读取WriteUTF写的程序, 就要编写一些特殊的

转换编码

读写随机访问文件

public class UsingRandomAccessFile {
    static String file = "./src/io/data.txt";
    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 Exception {
        RandomAccessFile rf = new RandomAccessFile(file, "rw");
        for(int i = 0;i < 7;i++)
            rf.writeDouble(i*1.414);
        rf.writeUTF("The end of file");
        rf.close();
        display();
        rf = new RandomAccessFile(file, "rw");
        rf.seek(5*8);
        rf.writeDouble(47.0001);
        rf.close();
        display();
    }
}

new RandomAccessFile(String filename, String wr);

构造器传递文件名, 修改方式("r"只读, "rw"读写)

rf.seek(5*8);这里按字节移动指针, 一个double是8字节, 这里去找第6个双精度值

  • 文件读写的实用工具

public static String read(String fileName) {
        StringBuilder sb = new StringBuilder();
        try {
            BufferedReader in = new BufferedReader(
                new FileReader(
                    new File(fileName).getAbsolutePath()
                )
            );
            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();
}

封装的读方法, 把每一行文件内容添加到字符串中, 读的时候换行符被删去, 所以要手动添加上
    public static void write(String fileName, String text) {
        try {
            PrintWriter out = new PrintWriter(
                new File(fileName).getAbsolutePath()
            );
            try {
                out.print(text);
            } finally {
                out.close();
            }
        } catch(IOException e) {
            throw new RuntimeException(e);
        }
}

封装的写方法, 将文本写入到文件中去

每当创建一个读或写的对象时, 都应该在finally子句里加上close()方法的调用, 关闭文件

读取二进制文件

public class BinaryFile {
    public static byte[] read(File bFile) throws IOException {

        //读取二进制, 用到InputStream
        BufferedInputStream bf = new BufferedInputStream(
            new FileInputStream(bFile)
        );
        try {
            byte[] data = new byte[bf.available()];
            //将bf中读取的二进制内容填充到一个字节数组中
            bf.read(data);
            return data;
        } finally {
            bf.close();
        }
    }
    public static byte[]
    read(String bFile) throws IOException {
        return read(new File(bFile).getAbsolutePath());
    }
}

  • 标准I/O

程序所有的输入都可以来自标准输入

所有的输出都可以来自标准输出

所有的错误信息都可以发送到标准错误

从标准输入中读取

System.in

System.out(被PrintStream封装)

System.err(被PrintStream封装)

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);

}

System.in和大多数流一样, 通常要对它进行缓冲

PrintWriter out = new PrintWriter(System.out, true);
out.println("Hello World");

将System.out转化成PrintWriter

  • 重定向标准I/O

System.setIn(InputStream);

System.setOut(PrintStream);

System.setErr(PrintStream);

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

        //预存标准输出
        PrintStream console = System.out;

        //标准I/O重定向接收的是字节流
        BufferedInputStream in = new BufferedInputStream(
            new FileInputStream("./src/io/Redirecting.java")    
        );
        PrintStream out = new PrintStream(new BufferedOutputStream(
            new FileOutputStream("./src/io/test1.txt")
        ));
        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);

        //恢复原来的标准输入
        System.setOut(console);
    }

这个程序把标准输入附接到文件上, 把标准输出和标准错误附接到另一个文件上

  • 进程控制

public class OSExecute {
    public static void command(String command) {
        boolean err = false;
        try {
            Process process =
                new ProcessBuilder(command.split(" ")).start();
            BufferedReader results = new BufferedReader(

                //进程的getInputStream捕获程序执行时产生的标准输出

               //因为InputStream是我们可以从中读出信息的流
                new InputStreamReader(process.getInputStream())
            );
            String s;
            while((s = results.readLine()) != null)
                System.out.println(s);
            BufferedReader errors = new BufferedReader(

                //捕获程序输出的错误流
                new InputStreamReader(process.getErrorStream())
            );
            while((s = errors.readLine()) != null)
                System.out.println(s);
        } catch(Exception e) {
            if(!command.startsWith("CMD /C"))
                command("CMD /C " + command);
            else 
                throw new RuntimeException(e);
        }
        if(err)
            throw new OSExecuteException("Errors executing " + command);
    }
}

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

        //javap 反编译.class文件 输出源文件的代码 然后用command从输出的结果中读出来
        OSExecute.command("javap ./bin/io/OSExecuteDemo");
    }
}

  • 新I/O

java.nio提高了速度, 但是老的io也用nio实现过了

速度的提高来自使用的结构更接近于操作系统执行I/O的方式: 通道和缓冲器

通道是一个煤矿 而缓冲器是运煤的卡车 我们从卡车获得煤  (数据)

唯一直接与通道交互的缓冲器是ByteBuffer

它通过告知分配多少存储空间来创建一个ByteBuffer对象, 只能输出或读取原始的字节数据

有三个java.io中的类可以产生文件通道FileChannel, 这三个类是FileInputStream, FileOutputStream, RandomAccessFile

用getChannel()方法得到这些IO流的通道

注意这些类都是字节操纵流, Reader和Writer这种字符操作流不能这样产生通道

public class GetChannel {
    private static final int BSIZE = 1024;
    public static void main(String[] args) throws Exception {

        //产生FileOutputStream的通道
        FileChannel fc = 
            new FileOutputStream("./src/io/data.txt").getChannel();

        //用缓冲器写字节流到通道中去
        fc.write(ByteBuffer.wrap("Some text ".getBytes()));
        fc.close();

        //产生RandomAccessFile的通道
        fc = new RandomAccessFile("./src/io/data.txt","rw").getChannel();

        //移动到文件末尾
        fc.position(fc.size());

        用缓冲器写字节到通道中去
        fc.write(ByteBuffer.wrap("Some more".getBytes()));
        fc.close();

        //产生FileInputStream的通道
        fc = new FileInputStream("./src/io/data.txt").getChannel();

        //产生指定大小的缓冲器
        ByteBuffer buff = ByteBuffer.allocate(BSIZE);

        //从通道中读字节到缓冲器中去
        fc.read(buff);
        buff.flip();
        while(buff.hasRemaining())
            System.out.print((char)buff.get());
    }
}

由于限定了缓冲器的大小, 提高了I/O的速度

public class ChannelCopy {
    private static final int BSIZE = 1024;
    public static void main(String[] args) throws Exception {
        FileChannel
            in = new FileInputStream("./src/io/ChannelCopy.java").getChannel(),
            out = new FileOutputStream("./src/io/data.txt").getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(BSIZE);

        //从输入通道里读到缓冲器
        while(in.read(buffer) != -1) {

            //filp()缓冲器做好写的准备
            buffer.flip();

            //把缓冲器写到输出通道
            out.write(buffer);
            buffer.clear();
        }
    }
}

这是一个简单的开启两个通道和一个直接缓冲器, 实现从一个文件到另一个文件内容拷贝的程序

 

两个通道也可以不通过缓冲器, 直接相连

public class TransferTo {
    public static void main(String[] args) throws Exception {
        FileChannel 
            in = new FileInputStream("./src/io/TransferTo.java").getChannel(),
            out = new FileOutputStream("./src/io/data.txt").getChannel();
        in.transferTo(0, in.size(), out);
        //out.transferFrom(in, 0, in.size());
    }
}

直接使用

in.transferTo(long position, long count,WritableByteChannel target);

写内容到目标通道, 或者

out.transferFrom(ReadableByteChannel src, long position, long count);

从源通道读取内容

 

缓冲器中容纳的是普通的字节, 为了把它们转换成字符, 要么在输入到缓冲器的时候对其进行编码, 要么在从缓冲器输出时对它们进行解码

fc.read(buffer);

String encoding = System.getProperty("file.encoding");
        System.out.println("Decoded using " + encoding + ": " 
        + Charset.forName(encoding).decode(buffer));

对普通字节的buffer, 直接输出不能正常显示, 通过System.getProperty发现默认字符集, 对缓冲器内的数据进行解码, 从这个默认字符集解码为可打印的字符集

fc.write(ByteBuffer.wrap("Some text".getBytes("UTF-16BE")));

在向buffer写入数据的时候, 用能够打印的字符集对其进行编码, 之后再从文件读出来就是可打印的形式, 只要转化成CharBuffer就

可以输出了

public class GetData {
    private static final int BSIZE = 1024;
    public static void main(String[] args) throws Exception {
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        int i = 0;
        while(i++ < bb.limit())
            if(bb.get() != 0)
                print("nonzero");
            print("i = " + i);
            bb.rewind();
            bb.asCharBuffer().put("Howdy!");
            char c;
            while((c = bb.getChar()) != 0)
                printnb(c + " ");
            print();
            bb.rewind();
            bb.asShortBuffer().put((short)471142);
            print(bb.getShort());
            bb.rewind();
            bb.asIntBuffer().put(877689);
            print(bb.getInt());
            bb.rewind();
            bb.asLongBuffer().put((long)11111);
            print(bb.getLong());
            bb.rewind();
    }
}

ByteBuffer可以从保存的字节数组中提取出各种基本类型的数据

视图缓冲器

如上面的例子, 可以通过asIntBuffer, asCharBuffer获取一个ByteBuffer缓冲器上的基本类型视图, 调用它的put的方法向ByteBuffer

中添加实际的数据, 我们还可以利用基本类型视图成批地从ByteBuffer中读取基本类型数据

public class IntBufferDemo {
    private static final int BSIZE = 1024;
    public static void main(String[] args) throws Exception {
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);

        //创建一个IntBuffer视图对象, 为ByteBuffer缓冲器的Int视图
        IntBuffer ib = bb.asIntBuffer();

        //用put()方法批量地向视图中添加基本类型数据
        ib.put(new int[]{11, 42, 47, 99, 143, 811, 1016});
        System.out.println(ib.get(3));
        ib.put(3,1811);
        ib.flip();

        //用hasRemaining()批量从视图中读取基本类型
        while(ib.hasRemaining()) {
            int i = ib.get();
            System.out.println(i);
        }
    }
}

把ByteBuffer转换成各种基本类型视图

public class ViewBuffer {
    public static void main(String[] args) throws Exception {
        ByteBuffer bb = ByteBuffer.wrap(
            new byte[] { 0, 0, 0, 0, 0, 0, 0, 'a' }
        );
        bb.rewind();
        printnb("Byte Buffer ");
        while(bb.hasRemaining())
            printnb(bb.position() + " -> " + bb.get() + ", ");
        print();
        CharBuffer cb = 
            ((ByteBuffer)bb.rewind()).asCharBuffer();
        printnb("Char Buffer ");
        while(cb.hasRemaining())
            printnb(cb.position() + " -> " + cb.get() + ", ");
        print();
        FloatBuffer fb = 
                ((ByteBuffer)bb.rewind()).asFloatBuffer();
        printnb("Float Buffer ");
        while(fb.hasRemaining())
            printnb(fb.position() + " -> " + fb.get() + ", ");
        print();
        IntBuffer ib = 
                ((ByteBuffer)bb.rewind()).asIntBuffer();
        printnb("Int Buffer ");
        while(ib.hasRemaining())
            printnb(ib.position() + " -> " + ib.get() + ", ");
        print();
        LongBuffer lb = 
                ((ByteBuffer)bb.rewind()).asLongBuffer();
        printnb("Long Buffer ");
        while(lb.hasRemaining())
            printnb(lb.position() + " -> " + lb.get() + ", ");
        print();
        ShortBuffer sb = 
                ((ByteBuffer)bb.rewind()).asShortBuffer();
        printnb("Short Buffer ");
        while(sb.hasRemaining())
            printnb(sb.position() + " -> " + sb.get() + ", ");
        print();
        DoubleBuffer db = 
                ((ByteBuffer)bb.rewind()).asDoubleBuffer();
        printnb("Double Buffer ");
        while(db.hasRemaining())
            printnb(db.position() + " -> " + db.get() + ", ");
        print();
    }
}

这里原来是8个字节的字节数组, 占64位

转换成char, short就是4个元素,每个16位

转换成int, float就是2个元素, 每个32位

转换成long, double就是1个元素, 每个64位

高位优先是把最重要的字节存储在存储器的最低位

低位优先是把最重要的字节存储在存储器的最高位

ByteBuffer默认是高位优先的, 可以通过bb.order(ByteBuffer.BIG_ENDIAN), bb.order(ByteBuffer.LITTLE_ENDIAN)

改变存储方式

用缓冲器操纵数据

缓冲器的方法

capacity()      返回缓冲器容量

clear()            清空缓冲区, 将position设置为0, limit设置为容量

flip()               将limit设置为position, position设置为0, 用于准备从缓冲区向通道写入数据

rewind           将position设置为0

limit()             limit值

limit(int)         设置limit值

position()       返回position值

position(int)   设置position值

mark()           将mark设置为position

reset()           将position设置为mark

remaining()    返回limit-position

hasRemaining()   如果有介于position和limit的值, 返回true

每次打印只打印position和limit之间的值, 所以当position遍历到limith后, 通过rewind()将position设置为0来打印全部的值

映射文件访问

映射文件允许我们创建和修改那些因为太大而不能放入内存的文件, 我们可以假定整个文件都被读入内存, 把它当作非常大

的数组来访问

这是通过指定映射文件的初始位置和映射区域的长度来实现的, 我们可以映射某个大文件的较小的部分

public class LargeMappedFiles {
    static int length = 0x8FFFFFF;
    public static void main(String[] args) throws Exception {
        MappedByteBuffer out = 
            new RandomAccessFile("./src/io/test1.txt", "rw").getChannel().

            //初始化MappedByteBuffer
            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));
    }
}

先创建RandomAccessFile获取该文件上的通道

再调用map()产生MappedByteBuffer(), 这是一种特殊的缓冲器, 它是由ByteBuffer继承而来, 因此也具有它的所有方法

通过底层操作系统的文件映射工具是用来最大化地提高性能的方法

映射写用到了FileInputStream, 但是映射文件的所有输出都必须使用RandomAccessFile

文件加锁

FileOutputStream fos = new FileOutputStream("./src/io/data.txt");
FileLock fl = fos.getChannel().tryLock();
if(fl != null) {
       System.out.println("Locked File");
       TimeUnit.MILLISECONDS.sleep(100);
       fl.release();
       System.out.println("Release Lock");
}
fos.close();

用tryLock()或者lock()给文件加锁, tryLock()是非阻塞式的, lock()是阻塞式的

trtLock()请求失败会直接返回, lock()则会阻塞进程直至锁可以获得

用FileLock.release()可以释放锁

也可以对文件的一部分上锁

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

lock(long position, long size, boolean shared)

第三个参数表示是否共享锁

共享锁是由底层的操作系统决定的

对映射文件的部分加锁

我们可能需要对比较大的映射文件进行部分加锁, 以便其他进程可以修改文件中未被加锁的部分

  • 压缩

有一些支持读写压缩格式的数据流, 它们继承自InputStream和OutputStream, 是按照字节的方式处理的

类                                                             功能

CheckedInputStream                               GetCheckSum()为任何InputStream产生校验和

CheckedOutputStream                            GetCheckSum()为任何OutputStream产生校验和

DeflaterOutputStream                              将数据压缩成Zip格式

GZIPOutputStream                                  将数据压缩成GZIP格式

InflaterInputStream                                  解压缩的基类

ZipInputStream                                        解压缩Zip文件格式的数据

GZIPInputStream                                     解压缩GZIP文件格式的数据

用GZIP进行简单的压缩

public class GZIPcompress {
    public static void main(String[] args) 
    throws IOException {
        BufferedReader in = new BufferedReader(
            new FileReader("./src/io/data.txt")
        );

        //把输出封装成GZIPOutputStream
        BufferedOutputStream out = new BufferedOutputStream(
            new GZIPOutputStream(new FileOutputStream("./src/io/test.gz"))
        );
        System.out.println("Writing file");
        int c;
        while((c = in.read()) != -1)
            out.write(c);
        in.close();
        out.close();
        System.out.println("Reading file");

        //把输入封装成GZIPInputStream
        BufferedReader in2 = new BufferedReader(
            new InputStreamReader(new GZIPInputStream(
                new FileInputStream("./src/io/test.gz")
            ))
        );
        String s;
        while((s = in2.readLine()) != null)
            System.out.println(s);
        in2.close();
    }
}

用Zip进行多文件保存

public class ZipCompress {
    public static void main(String[] args) 
    throws IOException {

        //创建Zip文件输出流
        FileOutputStream f = new FileOutputStream("./src/io/test.zip");

        //用Checksum类包装输出流, 用以计算和校验文件的校验和方法

        //Adler32()速度较快    CRC32()慢一些, 但更准确
        CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());

        //用ZipOutputStream再包装
        ZipOutputStream zos = new ZipOutputStream(csum);

        //用BufferedOutputStream包装
        BufferedOutputStream out = 
            new BufferedOutputStream(zos);
        zos.setComment("A test of Java Zipping");
        String[] args1 = {"./src/io/ZipCompress.java","./src/io/test2.txt"};
        for(String arg : args1) {
            print("Writing file " + arg);

            //把将要压缩的文件一个个读出来到输入流
            BufferedReader in = new BufferedReader(
                new FileReader(arg)
            );

            //每一个将要加入到压缩档案的文件, 都必须调用putNextEntry(), 并将其传递给一个ZipEntry对象, 存到输出流里面
            zos.putNextEntry(new ZipEntry(arg.replace("./src/io/","")));
            int c;

           //把当前这个文件写到压缩文件当前创建的这个文件中去
            while((c = in.read()) != -1) 
                out.write(c);
            in.close();
            out.flush();
        }
        out.close();

        //校验和可以从getChecksum方法中得到
        print("Checksum: " + csum.getChecksum().getValue());
        print("Reading file");
        FileInputStream fi = new FileInputStream("./src/io/test.zip");
        CheckedInputStream csumi =
            new CheckedInputStream(fi, new Adler32());
        ZipInputStream in2 = new ZipInputStream(csumi);
        BufferedInputStream bis = new BufferedInputStream(in2);
        ZipEntry ze;

       //getNextEntry()之前创建的时候存到输出流里面的一个个ZipEntry, 从输入流里面可以读出来

      //用于解压缩文件
        while((ze = in2.getNextEntry()) != null) {
            print("Reading file " + ze);
            int x;

            //从输入流的当前ZipEntry中读出文件内容 写到标准输出中去
            while((x = bis.read()) != -1)
                System.out.write(x);
        }
        print("Checksum: " + csumi.getChecksum().getValue());
        bis.close();

        //利用ZipFile对象读文件, 它的entries()方法可以获取一个枚举
        ZipFile zf = new ZipFile("./src/io/test.zip");
        Enumeration e = zf.entries();
        while(e.hasMoreElements()) {
            ZipEntry ze2 = (ZipEntry)e.nextElement();
            print("File: " + ze2);
        }
    }
}

对于每一个要加入压缩档案的文件, 都必须调用putNextEntry(), 并将其传递给一个ZipEntry()对象, ZipEntry()对象包含了一个功能

很宽泛的接口

getNextEntry()方法可以返回ZipInputStream输入流中下一个ZipEntry, 从而解压Zip档案

也可以通过创建ZipFile对象, 它的entries()方法包含了里面文件的枚举集合

可以遍历枚举得到里面的文件, 实现解压

JAR

这种文件也是把一组文件压缩到单个压缩文件中, JAR文件也式跨平台的

可以使用命令行来自动压缩文件

c                                                      创建一个新的或空的压缩文档

t                                                       列出目录表

x                                                      解压所有文件

x file                                                解压该文件

f                                                      打算指定一个文件名

m                                                    表示第一个参数将是用户自建的清单文件的名字

v                                                     产生详细输出,描述jar的工作

O                                                    只储存文件, 不压缩文件

M                                                    不自动创建文件清单

jar cf myJarFile.jar *.class

表示创建了一个名为myJarFile.jar文件, 里面包含了当前目录下所有的.class文件

jar cmf myJarFile.jar myMainfestFile.mf *.class

与第一条类似, 另外添加了一个名为myMainfestFile的用户自建清单

jar tf myJarFile.jar

产生myJarFile.jar下所有文件的目录表

jar tvf myJarFile.jar

提供有关myJarFile的更多信息

jar cvf myApp.jar audio classes image

将子目录audio, classes, image都合并到myApp.jar文件中去

CLASSPATH="lib1.jar;lib2.jar;"

使Java可以再lib1.jar和lib2.jar中搜索文件

  • 对象序列化

让对象在程序不运行的时候仍然能够存在, 将对象变成持久性的

Java的对象序列化将实现了Serializable接口的对象转换成一个字节序列, 并能在以后把这个字节序列完全恢复

它可以跨平台保存这个序列, 也能通过网络来传输

利用序列化可以实现轻量级的持久性

对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize)

对象序列化是为了支持语言的两种特性,

一是远程方法调用(RMI)

而是对于Java Beans, 在配置阶段保存一个Bean的信息, 并在程序后期恢复

只要实现了Serializable接口, 序列化就很简单

要序列化一个对象, 首先要创建一个OutputStream对象, 将其封装在一个ObjectOutputStream对象里面, 调用writeObject()即可

将它序列化

要反序列化还原, 只要创建一个InputStream对象,  将其封装在ObjectInputStream内, 然后调用readObject(), 就可以获得一个引用

, 它将指向一个Object引用, 所以要将它向下转型

class Data implements Serializable {
    private int n;
    public Data(int n) { this.n = n; }
    public String toString() { return Integer.toString(n); }
}

//一个实现序列化Serializable的类
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;
    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创建输出流
        ObjectOutputStream out = new ObjectOutputStream(
            new FileOutputStream("worm.out")
        );

        //用writeObject()写序列化的数据到文件中去
        out.writeObject("Worm storage\n");
        out.writeObject(w);
        out.close();

        //用ObjectInputStream创建输入流
        ObjectInputStream in = new ObjectInputStream(
            new FileInputStream("worm.out")
        );    

        用readObject()读取反序列化数据对象, 并向下转型
        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);
    }
}

序列化的控制

 Externalizable接口

interface Externalizable {

    public void readExternal(ObjectInput in) throws IOException;

    public void writeExternal(ObjectOutput out) throws IOException;

}

通过实现Externalizable接口来实现对序列化的控制

class Blip1 implements Externalizable {
    public Blip1() {
        print("Blip1 Constructor");
    }
    public void writeExternal(ObjectOutput out) 
    throws IOException {
        print("Blip1.writeExternal");
    }
    public void readExternal(ObjectInput in) 
    throws IOException {
        print("Blip1.readExternal");
    }
}
class Blip2 implements Externalizable {
    Blip2() {
        print("Blip2 Constructor");
    }
    public void writeExternal(ObjectOutput out) 
    throws IOException {
        print("Blip2.writeExternal");
    }
    public void readExternal(ObjectInput in) 
    throws IOException {
        print("Blip2.readExternal");
    }
}
public class Blips {
    public static void main(String[] args) throws Exception {
        print("Constructing objects");
        Blip1 b1 = new Blip1();
        Blip2 b2 = new Blip2();
        ObjectOutputStream o = new ObjectOutputStream(
            new FileOutputStream("Blips.out")
        );
        print("Saving objects");
        o.writeObject(b1);
        o.writeObject(b2);
        o.close();
        ObjectInputStream in = new ObjectInputStream(
            new FileInputStream("Blips.out")
        );
        print("Recovering b1");
        b1 = (Blip1)in.readObject();
//        print("Recovering b2");
//        b2 = (Blip2)in.readObject();
    }
}

这里不能恢复Blip2对象, 因为它的默认构造器不是公共的, 对于一个Externaliable对象, 在恢复它的时候, 它所有的默认构造器都会

被调用, 然后再调用readExternal()方法

可以在接口的readExternal()和writeExternal()方法中利用参数中的ObjectInput和ObjectOutput对象, 来控制对象

public class Blip3 implements Externalizable {
    private int i;
    private String s;
    public Blip3() {
        print("Blip3 Constructor");
    }
    public Blip3(String x, int a) {
        print("Blip3(String x, int a)");
        s = x;
        i = a;
    }
    public String toString() { return s + i; }
    public void writeExternal(ObjectOutput out) 
    throws IOException {
        print("Blip3.writeExternal");
        out.writeObject(s);
        out.writeInt(i);
    }
    public void readExternal(ObjectInput in)
    throws IOException, ClassNotFoundException {
        print("Blip3.readExternal");
        s = (String)in.readObject();
        i = in.readInt();
    }
    public static void main(String[] args) throws Exception {
        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();
        ObjectInputStream in = new ObjectInputStream(
            new FileInputStream("Blip3.out")    
        );
        print("Recovering object:");
        b3 = (Blip3)in.readObject();
        print(b3);
    }
}

如果去掉writeExternal()和readExternal()里面writeObject(), writeInt(), readObject(), readInt()的语句, 最后输出的s和i就会是默认

初始化的null和0, 因为在创建对象的时候Externalizable会自动调用没有参数的构造器, 清空对象成员的数据

所以用Externalizable类的时候必须手动地存储和恢复成员的数据

transient(瞬时)关键字

使用Serializable的时候, 有一些对象成员不希望被序列化, 但是Serializable的序列化机制会自动周到地序列化所有的成员, 这里用

到transient关键字来标注特定的成员, 来关闭特定成员的序列化

下面是一个登陆程序, 为了保存登陆信息, 但是隐去登陆的密码, 用transient关闭密码的序列化

public class Logon implements Serializable {
    private Date date = new Date();
    private String username;

    //这里将密码设为transient的, 序列化的时候就不会把密码的值存到输出目的地去

    //读的时候也读不到
    private transient String password;
    public Logon(String name, String pwd) {
        username = name;
        password = pwd;
    }
    public String toString() {
        return "logon info: \n   username: " + username +
        "\n   date: " + date + "\n   password: " + password;
    }
    public static void main(String[] args) throws Exception {
        Logon a = new Logon("Hulk", "myLittlePony");
        print("logon a = " + a);
        ObjectOutputStream o = new ObjectOutputStream(
            new FileOutputStream("logon.out")
        );
        o.writeObject(a);
        o.close();
        TimeUnit.SECONDS.sleep(1);
        ObjectInputStream in = new ObjectInputStream(
            new FileInputStream("logon.out")
        );
        print("Recovering object at " + new Date());
        a = (Logon)in.readObject();
        print("logon a = " + a);
    }
}

在Serializable的实现类中, 添加readObject()或者writeObject()方法也能实现对序列化的控制, 这两个方法不是接口中要求实现的

方法, 不是重载或者覆盖

private void writeObject(ObjectOutputStream stream)

throws IOException;

private void readObject(ObjectInputStream stream)

throws IOException;

在调用ObjectOutputStream.writeObject()时, 会检查所传递的Serializable对象是否实现了自己的writeObject()方法, 如果有的话,

就调用它自己实现的writeObject()方法

在你的writeObject()内部, 可以调用defaultWriteObject()来选择执行默认的writeObject()

private String a;
private transient String b;

//在writeObject中调用默认的writeObject

//对于transient的成员自己来完成序列化

private void writeObject(ObjectOutputStream out) 
    throws IOException {
        out.defaultWriteObject();
        out.writeObject(b);
}

//在readObject中调用默认的readObject

//对于transient的成员自己来完成恢复
private void readObject(ObjectInputStream in) 
    throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        b = (String)in.readObject();
}

static的数据并不能被保存, 想要保存下来, 必须手动对其进行操作, 在类中添加

serializeStaticState(ObjectOutputStream os);和

deserializeStaticState(ObjectInputStream os);方法, 并在程序中显式地调用它们

  • XML

为了将对象持久化, Java使用了序列化, 而XML也是一种持久化的好方式, 而且不像序列化只能应用于Java

//www.xom.nu从这个网站下载xom的源码, 获取xom-x.x.x.jar

下面是一个创建XML文件的程序
public class Person {
    private String first, last;
    public Person(String first, String last) {
        this.first = first;
        this.last = last;
    }

    //使用xom用来产生转化未XML的Element对象
    public Element getXML() {

        //初始化xml中标签名称为person
        Element person = new Element("person");
        Element firstName = new Element("first");
        firstName.appendChild(first);
        Element lastName = new Element("last");
        lastName.appendChild(last);
        person.appendChild(firstName);
        person.appendChild(lastName);
        return person;
    }
    public Person(Element person) {

        //getFirstChildElement按标签名返回第一个元素
        first = person.getFirstChildElement("first").getValue();
        last = person.getFirstChildElement("last").getValue();
    }
    public String toString() {
        return first + " " + last;
    }

    //format方法, 用XOM中的Serializer类, 将XML转化为更可读的格式
    public static void 
    format(OutputStream os, Document doc) 
    throws Exception, ClassNotFoundException 
    {
        Serializer serializer = new Serializer(os,"ISO-8859-1");
        serializer.setIndent(4);
        serializer.setMaxLength(60);
        serializer.write(doc);
        serializer.flush();
    }
    public static void main(String[] args) throws ClassNotFoundException, FileNotFoundException, Exception {
        List<Person> people = Arrays.asList(
            new Person("Dr. Bunsen", "Honeydew"),
            new Person("Gonzon","The Great"),
            new Person("Phillip J.","Fry")
        );
        System.out.println(people);
        Element root = new Element("people");
        for(Person p : people)
            root.appendChild(p.getXML());

        //把Element根元素放到Doc里
        Document doc = new Document(root);
        format(System.out, doc);

        //把Doc输出到xml文件里
        format(new BufferedOutputStream(new FileOutputStream("People.xml")), doc);
    }
}

下面是一个从XML程序中读取信息的程序

public class People extends ArrayList<Person> {
    public People(InputStream in) throws Exception {

        //使用Builder.builder()方法, 打开并读取一个文件, 获取一个Doc
        Document doc = new Builder().build(in);
        Elements elements = 
            doc.getRootElement().getChildElements();
        for(int i = 0;i < elements.size();i++)
            add(new Person(elements.get(i)));
    }
    public static void main(String[] args) throws Exception {
        BufferedInputStream in = new BufferedInputStream(
            new FileInputStream("People.xml")
        );
        People p = new People(in);
        System.out.println(p);
    }
}

  • Preferences

Preferences API用于储存和读取用户的偏好, 以及程序配置项的设置

Preferences是一个键-值集合, 存储在一个节点层次结构中

下面是一个使用Preferences的例子

public class PreferencesDemo {
    public static void main(String[] args) throws Exception {
        Preferences prefs = Preferences.userNodeForPackage(PreferencesDemo.class);
        prefs.put("Location", "Oz");
        prefs.put("aaa", "fff");
        prefs.put("dsad", "dasda");

        //可以置入不同基本类型的数据
        prefs.putInt("Companions", 333);
        prefs.putBoolean("bool", true);

        //get的第二个参数表示如果找不到的默认值
        int usageCount = prefs.getInt("UsageCount", 0);
        usageCount++;
        prefs.putInt("UsageCount", usageCount);

        //keys是用字符串来存储的
        for(String key : prefs.keys()) 
            print(key + ": " + prefs.get(key, null));
        print("How many companions does Dorothy have? " + 
            prefs.getInt("Companions", 0)
        );
    }
}

每次运行程序UsageCount都加了1, 但是Preferences并没有存储在任何的文件中

实际上Preferences会使用合适的系统资源去存储它们呢

比如Windows中会用注册表去存储它们

  • 总结

我们可以通过控制台, 文件, 内存块, 甚至网络进行Java的读写

通过继承, 我们可以创建新类型的输入和输出对象

使用重载的toString()方法, 可以对流接受的对象进行简单扩充, 当我们向一个希望收到字符串的方法传送一个对象时, 会自动调用

toString()方法

装饰器模式的应用, 使得在使用不同的装饰器类库来装饰I/O流时获得不同的灵活性

 

阅读更多
换一批

没有更多推荐了,返回首页