被readLine()折腾了一把

虽然写IO方面的程序不多,但BufferedReader/BufferedInputStream倒是用过好几次的,原因是:

  • 它有一个很特别的方法:readLine(),使用起来特别方便,每次读回来的都是一行,省了很多手动拼接buffer的琐碎;
  • 它比较高效,相对于一个字符/字节地读取、转换、返回来说,它有一个缓冲区,读满缓冲区才返回;一般情况下,都建议使用它们把其它Reader/InputStream包起来,使得读取数据更高效。
  • 对于文件来说,经常遇到一行一行的,特别相符情景。

这次是在蓝牙开发时,使用两个蓝牙互相传数据(即一个发一个收),bluecove这个开源组件已经把数据读取都封装成InputStream了,也就相当于平时的IO读取了,很自然就使用起readLine()来了。

发数据:

[java] view plaincopy

  1. BufferedWriter output = new BufferedWriter(new OutputStreamWriter(conn.openOutputStream()));
  2. int i = 1;
  3. String message = "message " + i;
  4. while(isRunning) {
  5. output.write(message+"/n");
  6. i++;
  7. }

读数据:

[java] view plaincopy

  1. BufferedReader input = new BufferedReader(new  InputStreamReader(m_conn.openInputStream()));
  2. String message = "";
  3. String line = null;
  4. while((line = m_input.readLine()) != null) {
  5. message += line;
  6. }
  7. System.out.println(message);

上面是代码的节选,使用这段代码会发现写数据时每次都成功,而读数据侧却一直没有数据输出(除非把流关掉)。经过折腾,原来这里面有几个大问题需要理解:

  • 误以为readLine()是读取到没有数据时就返回null(因为其它read方法当读到没有数据时返回-1),而实际上readLine()是一个阻塞函数,当没有数据读取时,就一直会阻塞在那,而不是返回null;因为readLine()阻塞后,System.out.println(message)这句根本就不会执行到,所以在接收端就不会有东西输出。要想执行到System.out.println(message),一个办法是发送完数据后就关掉流,这样readLine()结束阻塞状态,而能够得到正确的结果,但显然不能传一行就关一次数据流;另外一个办法是把System.out.println(message)放到while循环体内就可以。
  • readLine()只有在数据流发生异常或者另一端被close()掉时,才会返回null值。
  • 如果不指定buffer大小,则readLine()使用的buffer有8192个字符。在达到buffer大小之前,只有遇到"/r"、"/n"、"/r/n"才会返回。

readLine()的实质(下面是从JDK源码摘出来的):

[java] view plaincopy

  1. String readLine(boolean ignoreLF) throws IOException {
  2. StringBuffer s = null;
  3. int startChar;
  4. synchronized (lock) {
  5. ensureOpen();
  6. boolean omitLF = ignoreLF || skipLF;
  7. bufferLoop:
  8. for (;;) {
  9. if (nextChar >= nChars)
  10. fill(); //在此读数据
  11. if (nextChar >= nChars) { /* EOF */
  12. if (s != null && s.length() > 0)
  13. return s.toString();
  14. else
  15. return null;
  16. }
  17. ......//其它
  18. }
  19. private void fill() throws IOException {
  20. ..../其它
  21. int n;
  22. do {
  23. n = in.read(cb, dst, cb.length - dst); //实质
  24. } while (n == 0);
  25. if (n > 0) {
  26. nChars = dst + n;
  27. nextChar = dst;
  28. }
  29. }

从上面看出,readLine()是调用了read(char[] cbuf, int off, int len) 来读取数据,后面再根据"/r"或"/n"来进行数据处理。

在Java I/O书上也说了:

public String readLine() throws IOException
This method returns a string that contains a line of text from a text file. /r, /n, and /r/n are assumed to be line breaks and are not included in the returned string. This method is often used when reading user input from System.in, since most platforms only send the user‘s input to the running program after the user has typed a full line (that is, hit the Return key).
readLine() has the same problem with line ends that DataInputStream‘s readLine() method has; that is, the potential to hang on a lone carriage return that ends the stream . This problem is especially acute on networked connections, where readLine() should never be used.

小结,使用readLine()一定要注意:

    1. 读入的数据要注意有/r或/n或/r/n
    2. 没有数据时会阻塞,在数据流异常或断开时才会返回null
    3. 使用socket之类的数据流时,要避免使用readLine(),以免为了等待一个换行/回车符而一直阻塞
时间: 2024-10-13 00:29:47

被readLine()折腾了一把的相关文章

关于expdp 中query用法小结

今天看到群里有人问到关于在使用expdp导出数据中使用query参数报错的解决方法,自己也出于好奇心瞎折腾了一把,现记录如下 1.第一次尝试的时候 [[email protected] ~]$ expdp scott/scott tables=emp1 dumpfile=emp1.dmp logfile=emp1.log query=emp1:"where rownum < 5"   Export: Release 11.2.0.4.0 - Production on 星期日 6

树莓派上使用蚂蚁矿机挖矿

家中以前一直使用的电脑+矿机挖矿,电脑24小时开着不说,声音还特大: 前两天折腾了一把,将挖矿整体转到了树莓派上,而且整个都放到了书柜上. 世界一下子安静了(电表也安静了)~~~~ 树莓派开机后,先安装CGMiner的关联组件(已经先执行过Update & Upgrade操作) sudo apt-get install libusb-1.0-0-dev libusb-1.0-0 libcurl4-openssl-dev libncurses5-dev libudev-dev screen lib

用 Vim 写 Python 的最佳实践

先来晒个图: 对于一些 Python 的小项目,使用 vim 是一个不错的选择.本文内容整理自我在知乎的回答 用用 Vim 写 Python 的最佳实践是什么?,下面的内容是对知乎旧有回答的一个补充,尤其有一些主要针对 vim8. 如果想要更多内容,可以查看知乎对于该问题的一些回答. 语法检查 如果用 vim8, 那么可以用异步检测的 w0rp/ale 代替 syntastic 了,再也不用羡慕 flycheck, 也不用因为语法检查而卡顿了. 关于 ale 这部分的个性化配置,其实有点 "吹毛

iOS In House方式发布

这两天In House签名的时候,提示SSL证书过期了.又折腾了一把iOS In House方式发布.这里开一篇文章记录一下过去折腾In House的经过,未来再修改也更新在这里. 最早在12年的时候,我们写了一个重签名的shell脚本 IPA="xxx.ipa" APPNAME="xxx.app" PROVISION="embedded.mobileprovision" CERTIFICATE="xxx" #must be

mysql5.1.73 Access denied for user &#39;root&#39;@&#39;localhost&#39; (using password: YES)

我的mysql是通过yum安装的,版本为5.1.73. 今天远程登陆mysql命令行时出现 Access denied for user 'root'@'localhost' (using password: YES) , 但在sqlyog和java程序中可以正常访问. 折腾了一把小时,解决办法如下: 1.停止mysql服务 : service mysqld stop 2.开启mysql服务时带上参数 : service mysqld start --skip-grant-tables skip

全面解读Python Web开发框架Django

全面解读Python Web开发框架Django Django是一个开源的Web应用框架,由Python写成.采用MVC的软件设计模式,主要目标是使得开发复杂的.数据库驱动的网站变得简单.Django注重组件的重用性和“可插拔性”,敏捷开发和DRY法则(Don’t Repeat Yoursef). 花了两周时间,利用工作间隙时间,开发了一个基于Django的项目任务管理Web应用.项目计划的实时动态,可以方便地被项目成员查看(^_^又 重复发明轮子了).从前台到后台,好好折腾了一把,用到:HTM

guake终端复制标签页

平时习惯用的终端是下拉式终端guake,开多个标签各司其职,热键一按,呼之即来,挥之即去. 但用一个东西,就难免会有不爽的地方,比如每次想复制一个标签页,就要3步,1 新建标签页, 2 切换到之前的路径, 3 改标签名 对于懒人来说,还是想懒一点的.然后就发现,这个东西是支持命令行参数的.具体参数请参考最后. 其中就有新建,重命名.那我就可以写个bash脚本,取得当前路径,新建一个标签页,cd切换路径,重命名标签页. 这个时候问题来了,原本的参数中,居然只有设置名字,没有取得标签页名字的,尴尬.

VNC connect:Connection refused(10061)

在Windows机器上使用VNC Viewer访问Linux服务器,有时候会遇到"connect:Connection refused(10061)"这个错误,导致这个错误出现的原因有多重,下面总结一下: 1:使用VNC Viewer时忘记加桌面号(一般为IP:桌面号,桌面号要看你自己的配置而定.如10.20.34.76:2) 错误使用方法: 正确使用方法: 2:Linux服务器的vncserver服务没有启动. 如果vncserver服务没有启动,使用VNC Viewer访问时,就会

【SaltStack】ZMQ版本引起的错误:Minion dit not return

很长时间都没接触saltstack了,近期由于需要上线的项目需要使用lvs+heartbeat,于是安装了5个虚拟机进行演练,为了适应项目,采用 了rhel5的ISO安装系统,同时想到不如趁此机会,再通过saltstack管理这些机器不是更好吗?于是折腾了一把,不聊遇到了一些问题: 环境: 查看master版本: [email protected]:~# salt-master  --version salt-master 2014.7.0 (Helium) 原因: RHEL5.6通过EPEL源