Android/Java学习日记Java知识收集

Think In Java 第18章 Java I/O

2016-08-07  本文已影响108人  KuTear

本文发表于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的结构



基本函数说明

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

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

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

public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
}
public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
}
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常用进程间通信这里有说过,在这里就不再说了。

    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则不会。

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

    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

参考

上一篇 下一篇

猜你喜欢

热点阅读