白话Java I/O模型

2018-12-08  本文已影响0人  kid551

I/O的很多操作和使用,其实并不是一个非常直观的概念,特别是打开文件、创建buffer。这对于终端用户来讲是个非常奇葩和奇怪的过程。我只是想要从一个文件里读取内容,从过程上来讲,我只需要知道:

那我干嘛要去关心神马打开文件、创建stream和buffer?!

所以,要理解I/O这一套东西以及它所涉及的stream、buffer,你必须先理解计算机的底层是如何工作的。如果没有这一步的底层基础理论做支撑,所有的I/O操作将无法变得直观。

为理解I/O所需要用到的底层知识并不算多,就几点:

面对以上这些现实,你不得不在programming时考虑上述问题。因为你不是终端用户只需要一个简单的接口。你是细节的操作者,必须对以上限制做出具体的可操作性的回应。

有了这部分的知识,我们再来看“Java中的NIO是如何读取文件的”就不会变得怪异了。

RandomAccessFile aFile = new RandomAccessFile("test.txt", "rw");
FileChannel inChannel = aFile.getChannel();

//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {

  buf.flip();  //make buffer ready for read

  while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
  }

  buf.clear(); //make buffer ready for writing
  bytesRead = inChannel.read(buf);
}
aFile.close();

让我们将之前讨论的过程复现一遍:

接下来可以深入更多的细节。

由于buffer是被重复利用的部分,所以这涉及到清理buffer的概念buf.clear()。那你可能会说,为什么不可以自动地清理buffer?因为这里的buffer是一个底层的基础服务。对于上层的应用来讲,有些场景是需要清理buffer,有些场景是需要复用buffer的内容。你怎么可以一概而论地认为所有应用场景都是只需要消费一次buffer中的数据呢?所以,作为底层设施来讲,你必须提供足够的灵活性,让developer自己决定是否需要清理buffer。

再来比较奇怪的是flip()这部分,为什么需要对buffer做flip操作呢?这就涉及到内存中buffer的管理问题。

Buffer Model

这里的管理方式其实很常见,在内存中,基本上都是以数组的形式提供堆栈结构来管理数据。那么,这就涉及到对这个数组的操作问题。你要有一个表征position的指针来指导数据的写入方向。

如果多走一步,为了验证flip()是否真的在让position指针复原,你还可以使用以下代码:

System.out.println("Read " + bytesRead);

// switch the buffer from writing mode into reading mode
buf.flip();

while (buf.hasRemaining()) {
    System.out.println((char) buf.get());
}

System.out.println("---------------------------");

// reset the pointer back to original point again
buf.flip();

while (buf.hasRemaining()) {
    System.out.println((char) buf.get());
}

可以看到,从buffer中又一次获取到了同样的信息。

从这个例子可以看到很多深层次的东西。例如,为什么你必须了解底层的内存运作机制、操作系统的运转机制?因为这些细节决定了你该以什么样的方式去设计你的编程模式,也决定了你应该如何去理解编程语言中提供的一些机制,或者为什么一个库应该会这样设计。这是一切具体行动的现实。

“具备什么功能”是这底层基础设施提供的封装好的API。但你要做的是programming的工作,不得不利用底层的基础设施去构建新的服务和产品。如果没办法理解这些底层机制,你就没办法真正地去构建东西。很多的问题,其实可以被绕过又或是不可能被实现,不在于逻辑有问题,而是单纯的信息差,你并不知道这个构建出的抽闲概念下面隐藏的真实东西。

如果能够理解内存的利用方式,那么,“分片、以stack的形式来做操作”的模式将成为你本能的一部分。进而,涉及到的position移动或者flip()的指针回调问题,就会成为你的直观。

当然,积累这部分的基础知识是非常枯燥和乏味的。但如同所有的基本功,它们不会在短期内为你提供足够的回报,但却会为你将来形成正确的“直觉”和“直观”做出巨大的贡献。

任何的抽象概念都具备直观,只不过这个直观所依赖的基础不同。抽象如“概率论基础”,其“形象”的直观,其实是数学系本科所学的“经典概率论”,否则你会迷失在“测度论”的细节里。但这个“直观”对于其它专业的人来讲,并不直观,甚至是异常复杂。而这个就是所谓的牢固的前提基础知识。进一步,如果你想要学好“概率论基础”这样高度抽象的topic,你必须先夯实“经典概率论”这个基础,必须先建立对它的深刻认知。否则,你对“概率论基础”的理解根本无从谈起。

同样的,如果你希望理解类似于编程语言中I/O库的设计、理解各类缓存中间件、消息队列中间件的设计,你必须要先建立“计算机如何运作”这个前提基础。只有你熟悉了计算机的运作方式,能够以计算机底层的习惯去思考问题、处理问题,你才能够看到各种组件设计的直观,才会看到各种莫名其妙的“绕路”到底是在解决什么、是为了什么。

所以,这样一个学习过程是任何抽象技能所避免不了的。你必须通过反复的阅读和练习来掌握第一层的基础概念,熟悉到让这一层的抽象变成你脑海中的一个条件反射式的直观。再这个新建立的直观本能基础上,你才可以去理解更高层次的抽象。

上一篇下一篇

猜你喜欢

热点阅读