io
  
    
    
      只要是把数据从程序外部拷贝到程序内部,都叫输入,包括但不限于,硬盘文件数据,网络数据,其他应用程序的数据
只要是把数据从程序内部拷贝到程序外部,都叫输入,包括但不限于,硬盘文件数据,网络数据,其他应用程序的数据
     
   
我们对于 Stream 这类 Closable 对象都知道需要调用 close 函数,就比如 kotlin 的 use 函数,那么我们该如何把编写正确的作用域调用呢?
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | val inPutStream = FileInputStream("./asd.text")
 inPutStream.use {
 try {
 val char = inPutStream.read().toChar()
 println(char)
 } catch (e: Exception) {
 e.printStackTrace()
 }
 }
 
 
 try {
 val inPutStream = FileInputStream("./asd.text")
 inPutStream.use {
 println(inPutStream.read())
 }
 } catch (e: Exception) {
 e.printStackTrace()
 }
 
 | 
因为FileInputStream函数会抛出异常
use函数在最后调用的Closable.close()也会抛出异常
所以这两个函数都应该在 try 的作用域里
| 12
 3
 4
 5
 6
 7
 
 | public FileOutputStream(String name) throws FileNotFoundException {
 this(name != null ? new File(name) : null, false);
 }
 
 
 public void close() throws IOException;
 
 | 
FileOutPutStream
同样, 使用OutPutStream也是这样,应该将初始化和use都放进try的作用域
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | try {val outPutStream = FileOutputStream("./asd.text")
 outPutStream.use {
 val byteArray = byteArrayOf('a'.code.toByte())
 outPutStream.write(byteArray)
 }
 } catch (e: Exception) {
 e.printStackTrace()
 }
 
 | 
Reader & Writer
Reader
reader: 一次只能读一个字符,需要关闭!!!
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | val inPutStream = FileInputStream("./asd.text")
 inPutStream.use {
 val reader = InputStreamReader(inPutStream)
 reader.use {
 println(reader.read().toChar())
 println(reader.read().toChar())
 }
 }
 
 
 val inPutStream = FileInputStream("./asd.text")
 val reader = InputStreamReader(inPutStream)
 reader.use {
 println(reader.read().toChar())
 println(reader.read().toChar())
 }
 
 | 
    
    
      由于 InputStreamReader 在调用 close 的时候,调用 InputStream 的 close,所以只需调用一次 InputStreamReader.close 即可
     
   
  
    
    
      如果你习惯用kotlin你会发现还有一个方法reader.readLines(),这个方法可以把所有内容都读出来,返回一个文件的字符串数组
这是kotlin官方写的扩展函数,其实内部实现是把reader又套了一层,变成了BufferedReader,并不是 reader的成员方法
     
   
Writer
Writer 和 Reader 一样,同样注意 close 一次
Writer.flush() 是冲洗,效果就是将缓冲区的数据立刻写入文件
在你刚调用 Writer.write() 的时候,文件里并不会有你写入的字符,需要调用 Writer.flush() 或者 Writer.close 才会写进去,这是因为你的缓冲区没有写满,当你的缓冲区写满的时候,会自动 flush()
  
    
    
      所以 flush() 的应用场景就是在你缓冲区没写满的时候,并且不再写入或者有其他需要立刻写入文件的需求时,你可以将调用此方法,立刻写入文件
     
   
在你调用 Writer.close 的时候也会将缓冲区立刻写入文件,只不过它并不是调用 Writer.flush(),而是另外的原理,这里就不展开讲述了
  
    
    
      
- OutPutStream.flush() 不做任何操作
- flush() 类似的调用,并不能确保文件的写入,他只是将这部分操作通知给操作系统,并不会进行写入检查
 
   
BufferedReader
  
    
      频繁的io(input&output)造成的性能消耗
     
    
      在程序执行过程中,频繁进行 io 是会造成很多性能消耗
这取决于很多原因,不过关键因素在于 io 操作会依赖于外部了————也就是硬盘(这里指的不仅是物理硬盘,也有虚拟硬盘,或者操作系统优化过的硬盘,比如 nas)或者网络,也就是并不全都是在内存里进行操作了
而且,开始读写文件到读写第一个字符的时间 要比 读写第一个字符到读写第二个字符的时间 要长的多!!!
- 物理设备速度限制: 硬盘和网络等IO设备相比于内存和CPU速度较慢。 
- IO操作的机制: 读写文件涉及操作系统的多个层面。这包括用户空间和内核空间之间的数据传输,文件系统的操作(比如定位文件位置、权限检查等),以及物理设备之间的数据传输。这些操作都需要系统资源,并且可能涉及上下文切换和系统调用,从而引入额外的开销。 
- 数据传输的开销: 数据传输本身有开销。在磁盘上,数据可能被分散存储在不同的位置(磁盘碎片化),导致需要更多的寻道时间。此外,网络IO也会受到带宽限制和延迟的影响。 
- 系统等待时间: 在进行IO操作时,CPU可能会处于空闲状态,因为IO操作通常会阻塞进程直到完成。这样的等待时间可能会降低系统的整体利用率,特别是在单线程或同步IO的情况下。 
 
   
BufferedReader就可以一定程度上减少 io 操作,因为他可以一次读写一行数据
它继承自Reader所以也需要关闭
| 12
 3
 4
 5
 6
 
 | val inPutStream = FileInputStream("./asd.text")val reader = InputStreamReader(inPutStream)
 val bufferedReader = BufferedReader(reader)
 bufferedReader.use {
 println(bufferedReader.readLine())
 }
 
 | 
复制文件
了解了前面的操作,复制操作就会很简单了
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | fun copyFile(srcFilePath: String, dstFilePath: String) {try {
 val inputStream = FileInputStream(srcFilePath)
 val outputStream = FileOutputStream(dstFilePath)
 val data = ByteArray(1024)
 var len: Int
 while (`is`.read(data).also { len = it } != -1) {
 os.write(data, 0, len)
 }
 inputStream.close()
 outputStream.close()
 } catch (e: Exception) {
 e.printStackTrace()
 }
 }
 
 | 
kotlin 的 use() 会增加嵌套所以有时候,并不一定要使用 use(),用 close() 反而更简洁
如果这里使用 use(),反而会减少可读性
| 12
 3
 4
 5
 6
 7
 
 | inputStream.use {`is`->outputStream.use { os ->
 while (`is`.read(data).also { len = it } != -1) {
 os.write(data, 0, len)
 }
 }
 }
 
 | 
网络 io
bufferedReader 和 bufferedWriter 在这里是主要的应用场景
我这里列举一个非常简陋的服务器代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | fun networkIO() {try {
 val serverSocket = ServerSocket(8080)
 val socket = serverSocket.accept()
 val bufferedReader = BufferedReader(InputStreamReader(socket.getInputStream()))
 val bufferedWriter = BufferedWriter(OutputStreamWriter(socket.getOutputStream()))
 while (true) {
 val requireString = bufferedReader.readLine()
 if (requireString == "exit") {
 break
 }
 bufferedWriter.write("response: $requireString\n")
 bufferedWriter.flush()
 }
 bufferedReader.close()
 bufferedWriter.close()
 socket.close()
 } catch (e: Exception) {
 e.printStackTrace()
 }
 }
 
 | 
我们通过 ServerSocket.accept() 来等待建立连接,并会将客户端发的信息返回去,在客户端输入 exit 的时候,结束接收新的字符并关闭服务
| 12
 3
 4
 5
 6
 7
 8
 
 | $ telnet localhost 8080Trying ::1...
 Connected to localhost.
 Escape character is '^]'.
 hello
 response: hello
 exit
 Connection closed by foreign host.
 
 | 
nio (强制使用 Buffer)
Buffer 操作细节
我们以一个读文件的代码来讲解 nio 的原理
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | try {val randomAccessFile = RandomAccessFile("./asd.txt", "rw")
 val channel = randomAccessFile.channel
 val byteBuffer = ByteBuffer.allocate(1024)
 channel.read(byteBuffer)
 byteBuffer.flip()
 println(Charset.defaultCharset().decode(byteBuffer))
 } catch (e: Exception) {
 e.printStackTrace()
 }
 
 | 

我画了一个图,蓝色为 buffer,capacity 是 buffer 的容量,limit 是限制符,position 是当前位置
可以看到,当 Channel 读取字符到 Buffer 里面的时候,position 一直在记录当前位置
ByteBuffer.flip() 是切换读模式,其实就是 limit 记录 position 位置,position 重置为 0,mark 重置(mark 后面会提到)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | ByteBuffer flip() {
 super.flip();
 return this;
 }
 
 
 
 public Buffer flip() {
 limit = position;
 position = 0;
 mark = -1;
 return this;
 }
 
 | 
非阻塞网络 IO
阻塞式 IO
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | fun nioNetWorkIO() {try {
 val serverSocketChannel = ServerSocketChannel.open()
 serverSocketChannel.bind(InetSocketAddress(8080))
 val socketChannel = serverSocketChannel.accept()
 val byteBuffer = ByteBuffer.allocate(1024)
 while (socketChannel.read(byteBuffer) != -1) {
 byteBuffer.flip()
 val requireString = Charset.defaultCharset().decode(byteBuffer).toString()
 if (requireString == "exit\r\n") {
 break
 }
 byteBuffer.flip()
 socketChannel.write(byteBuffer)
 byteBuffer.clear()
 }
 } catch (e: Exception) {
 e.printStackTrace()
 }
 }
 
 | 
这段代码就能充分体现出来我们学习 Buffer 操作细节的作用了
第 9 行对 byteBuffer 进行了读取操作,此时 limit 和 position 应该相等了,如果需要再次读取,应该再次修改 position 的位置。
我们直接修改 position 也可以byteBuffer.position(0)重新调用一遍 flip 也可以
并且在第 15 行,我们执行 clear ,方便下次的写入
非阻塞式 IO
这一部分其实已经有点跑题了,看个乐呵,点击查看 
              
              | 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 
 | fun nioNetWorkIOunBlocking() {try {
 val serverSocketChannel = ServerSocketChannel.open()
 serverSocketChannel.bind(InetSocketAddress(8080))
 serverSocketChannel.configureBlocking(false)
 val selector = Selector.open()
 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT)
 while (true) {
 selector.select()
 handleRequireData(selector)
 }
 } catch (e: IOException) {
 e.printStackTrace()
 }
 }
 
 fun handleRequireData(selector: Selector) {
 val selectedKeys = selector.selectedKeys()
 val keyIterator = selectedKeys.iterator()
 while (keyIterator.hasNext()) {
 val key = keyIterator.next()
 if (key.isAcceptable) {
 val serverChannel = key.channel() as ServerSocketChannel
 val client = serverChannel.accept()
 client.configureBlocking(false)
 client.register(selector, SelectionKey.OP_READ)
 println("Client connected: " + client.remoteAddress)
 } else if (key.isReadable) {
 val client = key.channel() as SocketChannel
 val buffer = ByteBuffer.allocate(1024)
 val bytesRead = client.read(buffer)
 if (bytesRead != -1) {
 buffer.flip()
 val receivedData = String(buffer.array(), 0, bytesRead)
 println("Received data: " + receivedData + " from " + client.remoteAddress)
 buffer.clear()
 } else {
 key.cancel()
 client.close()
 println("Client disconnected: " + client.remoteAddress)
 }
 }
 keyIterator.remove()
 }
 }
 
 | 
 
            okio
简单用法
| 12
 3
 4
 5
 
 | fun okioBufferRead(file: File) {file.source().buffer().use { bufferedFileSource ->
 println(bufferedFileSource.readUtf8())
 }
 }
 
 | 
| 12
 3
 4
 5
 
 | fun okioBufferWrite(file: File) {file.sink().buffer().use { sink ->
 sink.writeUtf8("Hello World!")
 }
 }
 
 | 
    
    
      使用以下两种方法
- 使用 file.source() 获取 InputStreamSource ,然后调用 fileSource.read(buffer, 1024) 写入 Buffer,然后调用 buffer.readUtf8() 读取 buffer
- 使用 file.source().buffer() 获取 BufferedSource ,然后调用 bufferedFileSource.readUtf8() 读取
这两种其实是一样的,只不过第一种需要你指定 buffer 大小
但第二种是官方指定推荐的形式,并且更加智能\简洁
     
   
  
    
    
      同样,BufferedSource.close() 会调用 Source.close() ,所以只要调用 BufferedSource.close() 就可以了
     
   
buffer 作为输出对象
在传统 io 里面,Buffer 并不能作为输出位置,也就是说,我们可以有 FileOutPutStream 但是并没有  BufferOutPutStream
而 okio 给了我们机会,他直接在自己的 Buffer 上写了一个 OutPutStream 这样我们就可以就可以直接输出到 Buffer 里了
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | val buffer = Buffer()try {
 ObjectOutputStream(buffer.outputStream()).use { objectOutputStream ->
 objectOutputStream.writeUTF("abc")
 objectOutputStream.writeBoolean(true)
 objectOutputStream.writeChar('0'.code)
 objectOutputStream.flush()
 val objectInputStream = ObjectInputStream(buffer.inputStream())
 println(objectInputStream.readUTF())
 }
 } catch (e: Exception) {
 e.printStackTrace()
 }
 
 | 
同理 buffer.inputStream() 也是有的