1、字符流读取

字符流读取的所有类都是从Reader这个超类继承的,都是用于读取字符的,这些类分别是InputSteamReader(从字符流读取)、FileReader(继承与InputStreamReader,读取文件流)StringReader(读取字符串)、PipedReader(读取管道,管道的上端来自于一个PipedWriter)、CharArrayReader(读取字符数组),还有两个比较特殊的类,一个是FileterReader,这是个抽象类,目前只有PushbackReader类实现了它,从名字就可以看出PushbackReader类允许把读出来的数据推回流中(但能不能推到源中,还有待于验证);一个是BufferedReader,这个是一个为了提高字符流读取的性能而提供的带有缓存功能的类,是一个代理类,能代理前面提到的所有类,也就是说,它可以读取所有来源的数据。

InputStreamReader和FileReader

这个类的字符流是从字节流读取字节数据,并且把字节数据转化为字符的,也就是说字符流的读操作其实是基于字节流读操作之上的,内部调取了字节流的读操作。所以很重要的两个传入参数就是字节流字符编码集。因为文件操作很重用,所有从InputStreamReader里面有专门派生出了一个FileReader,FileReader因为读取的是文件,字符流可以用文件或者文件名代替,这样源就是且只能是一个文件了。

这个类与其他读字符流读取类最大的区别,就是它没有mark、skip、reset等和读取位置有关的方法,这是因为他是从流中读取字节数据的,因此没有办法对流进行标记,只能老老实实重头到尾按顺序取字节流。所以他只有close,read,ready,getEncoding四个函数

PushbackReader

这个类主要是提供了unread()操作,可以把字符写回。

PipedReader

这个类从和它相连(snc.connect(src)的PipedWriter中读取字节流。

BufferedReader

这个类主要是提供一个其它类的代理功能,并且提供一个缓存,尤其是用在InputStream时,更能提高效率,因为他可以提前把字节流读取到缓存中,所以其他类一般都不会直接用,而是用BufferedReader包装一下再用。特别需要注意的是,这个类没有提供直接读取字节流的构造函数,也就是说它必须从字符流中读取数据。与它对应的Buffer额度Writer也是这样,只能把字符写到字符流中,而不能直接写到字节流中去。也就是说,它没有提供字符到字节的转码功能。

注意:为了优化性能,所有的数据字符流读取类从字节流中读取数据时不是一个一个字符的数据量读的,而是一下读取很多数据,然后把这些数据放在缓存里面,再根据需求从缓存里面把字符读出来。所以,当有两个字符流读取对象对同一个block字节流进行读取时,虽然前一个字符流只读取了一个字符出来,但是因为字节的预读取,所以后一个对象可能就无法从字节流中读取数据了,如果此时后面这个对象的缓存里面也没有数据,它就会一直卡在那里,等待字节流的数据。

 1 public class InputStreamIO {
2 public static void main(String args[]) {
3 try {
4 int count = 0;
5 InputStreamReader inputStreamReader = new InputStreamReader(System.in);
6 PushbackReader pushbackReader = new PushbackReader(new InputStreamReader(System.in));
7 System.out.println("the ecoding of inputStreamReader is :" + inputStreamReader.getEncoding());
8 char mychar;
9 do {
10 mychar = (char)inputStreamReader.read();
11 // pushbackReader.unread(mychar);
12 System.out.println("char from inputStreadReader:"+mychar);
13 System.out.println("char from pushbackReader:"+(char)pushbackReader.read());
14 } while (mychar!='q');
15 }
16 catch (IOException e){
17 System.out.println(e.getMessage());
18 e.printStackTrace();
19 }
20 }
21 }

上面的代码是从标准输入流中读取数据的,结果如下:

the ecoding of inputStreamReader is :GBK

char from inputStreadReader:1

char from pushbackReader:1
char from inputStreadReader:2
char from pushbackReader:2
char from inputStreadReader:3
char from pushbackReader: char from inputStreadReader:4

上面的结果中,当输入123456之后,inputStreadReader读取了一个字符到mychar中,虽然只读取了一个字符,但是它把标准输入流中的所有数据都读完了,存在它的缓存里面,后面的pushbackReader因为自己的缓存里面没有数据,所以它试图从标准输入中获取字节,但此时标准输入中所有的字节都被前面的inputStreadReader读取完了,标准输入流里已经没有数据了,所以后面的pushbackReader就只能一直等在那里。

第二次输入12之后,标准输入流里面就有了“12\n“(注意,因为标准输入流以换行符提交刷新,即flush,所以最后肯定会有一个换行符),这是pushbackReader便有了字节可以读取,它一下子把“12\n”读取完了,存在缓存里,并且从缓存里读取出1,被打印了出来

之后循环继续,到InputStreamReader时,它的缓存里面还有"23456\n",所以它不需硬要从标准输入流中读如字节数据,所以它不会卡在那里,他顺利的读取了2;同样,后面的pushbackReader它也还有“2\n”,它把2读了出来了。这样顺利的循环下去,知道pushbackReader读取我它缓存里的最后一个数据“\n”之后,它又卡在了那里。

2、字符流写

字符流写就是把字符写到相应的目的地,并且和字符流读是对应的,基本上就是把原来的xxxReader变成xxxWriter,然后功能就由读变成了写,源也就变成了对应的目的地。如果目的地是字符流(这种情况只有OutputStreamWriter以及它的子类FileWriter),则会把字符自动编码成字节后再写入流当中。

PrintWriter这个类比较特殊,下面重点说说它。

PrintWriter

PrintWriter其实是OutputStreamWriter和FileWriter的增强版,它包括了格式化输出,按行输出以及自动添加换行符等功能(println)。另外有一点需要注意,它添加的换行符是和底层操作系统有关的,在Windows下,它添加的是回车换行符”\r\n“,在GNU下,他添加的是换行符“\n“。PrintWriter还能自动刷新缓存,每当调用了println, printf, 或者format中的任何一个函数之后,就自动flush。

我们还将马上看到,在字节流输出中,也有一个和PrintWriter比较像的叫PrintStream的对象,它也实现了把字符输出到字节流并提供编码功能的方法(所以说它比较特殊),但是它的换行符总是“\n”,且遇到“\n”就调用flush。

3、重点分析分析PipedReader和PipedWriter

PipedReader和PipedWriter是连起来用的,用于线程间的IO通信。PipedWriter是PipedReader的src(源),PipedReader是PipedWriter的snk(目标),他们通过二者任何一个connect函数连接,也可以在通过构造函数参数建立连接,并且src和snk是一对一的关系,否则会抛出IOException("Already connected")异常。

其实src和snk通信挺简单的,就是src的writer向snk的buffer里面写数据,具体的方式这是调用snk的receive函数把字符传给snk的buffer。下面重点看看PipedReader的receive函数的源码:

  /**
* Receives a char of data. This method will block if no input is
* available.
*/
synchronized void receive(int c) throws IOException {
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByWriter || closedByReader) {
throw new IOException("Pipe closed");
} else if (readSide != null && !readSide.isAlive()) {
throw new IOException("Read end dead");
} writeSide = Thread.currentThread();
while (in == out) {
if ((readSide != null) && !readSide.isAlive()) {
throw new IOException("Pipe broken");
}
/* full: kick any waiting readers */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
/* 说明此PipedReader空了,此时把in和out都归位到0*/
if (in < 0) {
in = 0;
out = 0;
}
buffer[in++] = (char) c;
if (in >= buffer.length) {
in = 0;
}
}

还有PipedReader的的read函数的源码:

    public synchronized int read()  throws IOException {
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByReader) {
throw new IOException("Pipe closed");
} else if (writeSide != null && !writeSide.isAlive()
&& !closedByWriter && (in < 0)) {
throw new IOException("Write end dead");
} readSide = Thread.currentThread();
int trials = 2;
while (in < 0) {
if (closedByWriter) {
/* closed by writer, return EOF */
return -1;
}
if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
throw new IOException("Pipe broken");
}
/* might be a writer waiting */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
int ret = buffer[out++];
if (out >= buffer.length) {
out = 0;
}
if (in == out) {
/* now empty */
in = -1;
}
return ret;
}

可以看到,PipedReader是通过in和out两个指针来确定写入和读出的,如果有数据且不满,那么in!=out;当in==out且不等于-1时,表示buffer满了;当in=-1时,表示buffer空了。buffer是一个首尾相接的数组,从out到in是缓存的数据。从receive函数的源码中可以很容易地看出,当buffer满了(in==out且in>0),receive函数会唤醒其它线程,并等待1000ms让其它线程(读线程)有机会把数据读走。并且注意到,receive函数是同步的,也就是说只能有有个线程在写数据,并且可以肯定的是,这个线程是个写数据线程(snk的receive函数一般只由src的write函数调用,因为receive函数的访问权限是默认访问权限,只有同一个包小面的类才能访问它)。

另外,从PipedReader的read函数也可以看出,当buffer空了的时候(in<0),他也会唤醒其它线程,并且等待1000ms以期待其它线程写入数据到buffer。

java io 学习笔记(三) 字符流读写的更多相关文章

  1. Java IO学习笔记三

    Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...

  2. Java IO学习笔记三:MMAP与RandomAccessFile

    作者:Grey 原文地址:Java IO学习笔记三:MMAP与RandomAccessFile 关于RandomAccessFile 相较于前面提到的BufferedReader/Writer和Fil ...

  3. Java IO学习笔记:概念与原理

    Java IO学习笔记:概念与原理   一.概念   Java中对文件的操作是以流的方式进行的.流是Java内存中的一组有序数据序列.Java将数据从源(文件.内存.键盘.网络)读入到内存 中,形成了 ...

  4. Java IO学习笔记二

    Java IO学习笔记二 流的概念 在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成. 程序中的输入输 ...

  5. Java IO学习笔记总结

    Java IO学习笔记总结 前言 前面的八篇文章详细的讲述了Java IO的操作方法,文章列表如下 基本的文件操作 字符流和字节流的操作 InputStreamReader和OutputStreamW ...

  6. Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer

    作者:Grey 原文地址:Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer ByteBuffer.allocate()与ByteBuffer.allocateD ...

  7. Java IO学习笔记五:BIO到NIO

    作者:Grey 原文地址: Java IO学习笔记五:BIO到NIO 准备环境 准备一个CentOS7的Linux实例: 实例的IP: 192.168.205.138 我们这次实验的目的就是直观感受一 ...

  8. Java IO学习笔记四:Socket基础

    作者:Grey 原文地址:Java IO学习笔记四:Socket基础 准备两个Linux实例(安装好jdk1.8),我准备的两个实例的ip地址分别为: io1实例:192.168.205.138 io ...

  9. Java IO学习笔记六:NIO到多路复用

    作者:Grey 原文地址:Java IO学习笔记六:NIO到多路复用 虽然NIO性能上比BIO要好,参考:Java IO学习笔记五:BIO到NIO 但是NIO也有问题,NIO服务端的示例代码中往往会包 ...

  10. Java IO学习笔记七:多路复用从单线程到多线程

    作者:Grey 原文地址:Java IO学习笔记七:多路复用从单线程到多线程 在前面提到的多路复用的服务端代码中, 我们在处理读数据的同时,也处理了写事件: public void readHandl ...

随机推荐

  1. 客户端 post ,get 访问服务器

    private void sendReuestExpansion() { HttpRequest<T> req = this; HttpWebRequest request; try { ...

  2. IE11下使用fixed定位时鼠标滚动不平滑

    很久不用IE了,近期做兼容性测试发现一个fixed定位的问题,当元素使用fixed定位时,其应该不随页面滚动,在chrome/firefox/edge下都很完美,元素完全不动,但是使用IE11时,如果 ...

  3. Vue vue-resource发送Http请求

    vue-resource 1.cnpm install vue-resource --save 2.在main.js中import VueResource from 'vue-resource' 3. ...

  4. Linux中,关闭selinux

    首先我们可以用命令来查看selinux的状态getenforce 这个命令可以查看到selinux的状态,当前可以看到是关闭状态的. 还有一个命令也可以查看出selinux的状态.sestatus - ...

  5. 如何设置linux支持上传的文件中文不乱吗

    一.背景: 1.由于客户的需求,需要a链接打开的pdf文件,支持中文名称的 二.步骤 ①.查看当前编码 locale ②.编辑 vi  /etc/profile 打开后结尾处添加  export LA ...

  6. Windows bat脚步同步时间

    @echo onnet stop w32timew32tm /unregisterw32tm /registernet start w32timew32tm /config /manualpeerli ...

  7. c++11时间相关库(chrono)

    以下整理自:https://www.2cto.com/kf/201404/290706.html chrono 库主要包含了三种类型:时间间隔 Duration.时钟 Clocks 和时间点 Time ...

  8. 洛谷P4495 [HAOI2018]奇怪的背包(数论)

    题面 传送门 题解 好神仙的思路啊--orzyyb 因为不限次数,所以一个体积为\(V_i\)的物品可以表示出所有重量为\(\gcd(V_i,P)\)的倍数的物品,而所有物品的总和就是这些所有的\(\ ...

  9. Github 三种克隆模式

    1.我称为平常模式,用于项目的本地克隆使用.(无权限.无加密.ssh protocol) git clone http://github.com/username/exampleproject 2.我 ...

  10. Python之freshman05

    一:内建模块 time和datetime(http://www.jb51.net/article/49326.htm) 在Python中,通常有这几种方式来表示时间:1)时间戳 2)格式化的时间字符串 ...