Think In Java 第18章 Java I/O

本文发表于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()函数

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,182评论 11 349
  • 小编费力收集:给你想要的面试集合 1.C++或Java中的异常处理机制的简单原理和应用。 当JAVA程序违反了JA...
    八爷君阅读 4,573评论 1 114
  • 一、字词 1. I thought I could just crank out my thank-you car...
    小傅_ff94阅读 369评论 0 1