Think In Java 第18章 Java I/O

作者: KuTear | 来源:发表于2016-08-07 01:29 被阅读108次

    本文发表于KuTear's Blog,转载请注明

    I/O

    字节流与字符流的区别

    在字节流中输出数据主要是使用OutputStream完成
    输入使的是InputStream
    在字符流中输出主要是使用Writer类完成
    输入流主要使用Reader类完成。
    实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操作文件

      public static void test1() {
            File f = new File("G:" + File.separator + "test.txt"); // 声明File对象
            OutputStream out = null;
            try {
                out = new FileOutputStream(f);
                String str = "Hello World!!!";
                byte b[] = str.getBytes();
                out.write(b);
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
    //test1()正常输出到文件
    public static void test2() {
            File f = new File("G:" + File.separator + "test.txt"); // 声明File对象
            try {
                Writer out = new FileWriter(f);
                out.write("Hello World!!!");
                //out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
    //test2没有输出到文件
    

    所以在使用字符流的时候,是没有直接对文件或者其他的I/O设备进行直接的处理,而是先在内存中操作,待操作完成之后一次性输出到I/O设备(close()flush()).

    为什么匿名内部类和局部内部类只能访问final变量

    闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来源与创建它的作用域.

    下面通过例子说明

    public class Test{
       abstract class Run{
            abstract void run();
       }
       public void func(final/* Java 8 final 可以省 */ String str){
            new Run(){
              void run(){
                System.out.println(str);
              }
            };
       }
    }
    

    通过反编译最终可以得到3个类

    //Compiled from "Test.java"
    public class Test {
      public Test();
      public void func(java.lang.String);
    }
    
    //Compiled from "Test.java"
    abstract class Test$Run {
      final Test this$0;
      Test$Run(Test);
      abstract void run();
    }
    
    //Compiled from "Test.java"
    class Test$1 extends Test$Run {
      final java.lang.String val$str;
      final Test this$0;
      Test$1(Test, java.lang.String);
      void run();
    }
    

    我们的匿名内部类Test$1持有外部对象this$0的引用和内部方法要使用的参数val$str,但是是分离了外部类Test的,要想访问Test或方法中的参数,只有通过传值,所以在Test$1的构造器中就传递了必须的参数,并将其保存到它自己的内部.

    Java 常见类型的字节数

    类型 字节(byte) 比特位(bit)
    byte 1 8
    short 2 2*8
    int 4 4*8
    long 8 8*8
    float 4 4*8
    double 8 8*8
    char 2 2*8

    boolean 类型所占存储空间的大小没有明确指定,仅定义为能够取字面值true 或 false
    对于boolean,虽然一个bit 就能用,但可惜,最小的内存寻址单元就是byte,所以占用一个byte.

    System下的in/out/err的类型和I/O重定向

    public final static InputStream in = null;
    public final static PrintStream out = null;
    public final static PrintStream err = null;
    

    标准I/O重定向

    FileOutputStream fileOutputStream = new FileOutputStream("G:/text.txt");
    PrintStream fileOut = new PrintStream(fileOutputStream);
    System.setOut(fileOut);
    System.out.println("这是测试文字");
    

    运行之后会在G:/text.txt写入这是测试文字

    java调用系统命令

    Process process = new ProcessBuilder().command("ping", "www.kutear.com").start();
    InputStream inputStream = process.getInputStream();
    int c;
    while ((c = inputStream.read()) != -1) {
        System.out.print((char) (c));
    }
    

    文件通道与缓冲器


    文件读取

    //第一步:获取通道
    FileInputStream fin = new FileInputStream( "G:/test.txt" );
    //第二步:创建缓冲区
    FileChannel fc = fin.getChannel();  
    ByteBuffer buffer = ByteBuffer.allocate( 1024 );
    //第三步:将数据从通道读到缓冲区
    fc.read( buffer );
    buffer.flip();
    CharBuffer charBuffer = buffer.asCharBuffer();
    while (charBuffer.hasRemaining()) {
          System.out.print(charBuffer.get());
    }
    

    文件写入

    FileOutputStream outputStream = new FileOutputStream("G:/test.txt");
    FileChannel channel = outputStream.getChannel();
    ByteBuffer buffer = ByteBuffer.allocate(2);
    CharBuffer charBuffer = buffer.asCharBuffer();
    charBuffer.put('C');
    channel.write(buffer);
    buffer.flip();
    

    Buffer的结构



    基本函数说明

    • flip()

    此方法用于准备从缓冲区读取已经写入的数据

    public final Buffer flip() {
            limit = position;
            position = 0;
            mark = -1;
            return this;
    }
    
    • rewind()
    public final Buffer rewind() {
            position = 0;
            mark = -1;
            return this;
    }
    
    • clear()

    复写缓冲区,有下面的实现可以看见并没有清除数据

    public final Buffer clear() {
            position = 0;
            limit = capacity;
            mark = -1;
            return this;
    }
    
    • reset()
    public final Buffer reset() {
            int m = mark;
            if (m < 0)
                throw new InvalidMarkException();
            position = m;
            return this;
    }
    
    • mark()
    public final Buffer mark() {
            mark = position;
            return this;
    }
    

    由源码可知,reset()就是恢复到mark()的位置.有下面例子说明

    ByteBuffer buffer = ByteBuffer.wrap(new byte[]{1,2,3});
    buffer.mark(); //此时pos在0
    System.out.print(buffer.get()); //此时pos在1
    buffer.reset();//pos回到mark的0;
    System.out.print(buffer.get()+" ");
    

    输出: 1 1

    内存映射文件与文件锁

    内存映射简单的使用

    int LENGTH = 1 * 1024;
    MappedByteBuffer out = new RandomAccessFile("/home/kutear/test.txt", "rw")
                    .getChannel().map(FileChannel.MapMode.READ_WRITE, LENGTH / 2, LENGTH);
    for (int i = 0; i < LENGTH / 2; i++) {
          out.put((byte) 'A');
    }
    

    由于可以指定映射文件的初始位置和长度,意味着我们可以修改某个文件的其中一小部分,并且相比使用原始的流,映射在读写的性能上都有很大的提升。

    文件加锁
    文件锁主要涉及的类就是FileChannelFileLock,这里主要用到的就是FileChanneltryLock()或者lock()以及FileLockrelease()方法。下面是一个例子:

    FileInputStream f = new FileInputStream("/home/kutear/test.txt");
    FileChannel channel = f.getChannel();
    FileLock lock = channel.tryLock();
    if (lock != null) {
         //TODO 文件操作
          lock.release();
    }
    f.close();
    

    这里使用的tryLock()是非阻塞式的,而lock()是阻塞式的。另外还可以使用带参数的lock(...)trylock(...)来对文件的一部分加锁,而无参数的默认是对整个文件加锁。

    对象持久化

    持久化意味着一个对象的生存周期并不取决于程序是否正在执行,他可以生存于程序的调用之间。

    基本的使用方法在Android开发艺术探索-第二章-Android常用进程间通信这里有说过,在这里就不再说了。

    • Externalizable接口
        public static class Bean implements Externalizable {
            private String args;
    
            public Bean() {
                System.out.println("调默认构造器");
            }
            public void setArgs(String args) {
                this.args = args;
            }
    
            @Override
            public void writeExternal(ObjectOutput out) throws IOException {
                out.writeObject(args);
            }
    
            @Override
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
                this.args = (String) in.readObject();
            }
        }
    
        public static void write() throws Exception {
            Bean bean = new Bean();
            bean.setArgs("这是测试");
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("/home/kutear/test.txt"));
            out.writeObject(bean);
        }
    
        public static void read() throws Exception {
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream("/home/kutear/test.txt"));
            Bean bean = (Bean) in.readObject();
            System.out.println(bean.args); 
        }
    
        public static void main(String[] args) throws Exception{
            write();
            read();
        }
    

    输出如下

    调默认构造器
    调默认构造器
    这是测试

    由上面可以知道,在使用Externalizable反序列化的时候回调用默认的构造器。而使用Serializable则不会。

    • transient(瞬时)关键字

    使用该关键字来使某些字段不序列化

        public static class Bean implements Serializable {
    
            private String args;
            private transient String args2;
    
            public Bean(String args, String args2) {
                this.args = args;
                this.args2 = args2;
            }
       }
        public static void write() throws Exception {
            Bean bean = new Bean("111","222");
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("/home/kutear/test.txt"));
            out.writeObject(bean);
        }
    
        public static void read() throws Exception {
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream("/home/kutear/test.txt"));
            Bean bean = (Bean) in.readObject();
            System.out.println(bean.args);
            System.out.println(bean.args2);
        }
    

    输出

    111
    null

    但是如果对上面的Bean做一定的修改,就可以得到不同的结果

        public static class Bean implements Serializable {
    
            private String args;
            private String args2;
    
            public Bean(String args, String args2) {
                this.args = args;
                this.args2 = args2;
            }
            
            private void writeObject(ObjectOutputStream out) throws IOException{
                System.out.println("write called");
            }
    
            private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
                System.out.println("read called");
            }
        }
    

    此时输出

    write called
    read called
    null
    null

    非常奇怪,添加这两个与类无关的方法为什么会使序列化失效,原因就是在ObjectOutputStream.writeObject()中会通过反射检测类中是否含有签名是

    private void writeObject(ObjectOutputStream out) throws IOException{}
    

    的函数,如果有,会主动调用。接着我们在做一下修改

            private void writeObject(ObjectOutputStream out) throws IOException{
                System.out.println("write called");
                out.defaultWriteObject();
            }
    
            private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
                System.out.println("read called");
                in.defaultReadObject();
            }
    

    此时得到的结果是

    write called
    read called
    111
    222

    这说明通过out.defaultWriteObject()会写入数据。但是不会写入带有transient的字段。这点可以通过修改字段

            private String args;
            private transient  String args2;
    

    得到的结果证实

    write called
    read called
    111
    null

    继续再此基础上修改

            private void writeObject(ObjectOutputStream out) throws IOException{
                System.out.println("write called");
                out.defaultWriteObject();
                out.writeObject(args2);
            }
    
            private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
                System.out.println("read called");
                in.defaultReadObject();
                args2 = (String) in.readObject();
            }
    

    即主动将带有transient关键字的字段保存,可以看见结果是真的可以保存的

    write called
    read called
    111
    222

    反序列化后的数据测试

            Bean bean = new Bean("111", "222");
            System.out.println("原始数据:" + bean);
            ByteArrayOutputStream out1 = new ByteArrayOutputStream();
            ObjectOutputStream objOut1 = new ObjectOutputStream(out1);
            objOut1.writeObject(bean);
            objOut1.writeObject(bean);
            ByteArrayOutputStream out2 = new ByteArrayOutputStream();
            ObjectOutputStream objOut2 = new ObjectOutputStream(out2);
            objOut2.writeObject(bean);
            ObjectInputStream objIn1 = new ObjectInputStream(new ByteArrayInputStream(out1.toByteArray()));
            System.out.println("1号数据流-1:" + objIn1.readObject());
            System.out.println("1号数据流-2:" + objIn1.readObject());
            ObjectInputStream objIn2 = new ObjectInputStream(new ByteArrayInputStream(out2.toByteArray()));
            System.out.println("2号数据流:" + objIn2.readObject());
    

    输出

    原始数据:com.kutear.design.pattern.IO$Bean@7f31245a
    1号数据流-1:com.kutear.design.pattern.IO$Bean@61bbe9ba
    1号数据流-2:com.kutear.design.pattern.IO$Bean@61bbe9ba
    2号数据流:com.kutear.design.pattern.IO$Bean@610455d6

    由上可以看出,在同一流中的反序列化,只会出现一个对象,而不同流之间反序列化后的结果是不同的,这里对于单例需要特别注意。

    Tips

    • 记得在finally子句中调用close()函数

    参考

    相关文章

      网友评论

        本文标题:Think In Java 第18章 Java I/O

        本文链接:https://www.haomeiwen.com/subject/vsqlsttx.html