在做一个Socket通信的项目的时候使用了BufferedReader,并用readLine函数读取信息,但是遇到了一个问题,就是把readLine放到了读取消息的循环外面,然后程序就再也执行不到下面一句了,对应GitHub连接(以后更新会在GitHub博客更新,以GitHub博客为准)。
public void run() {
try {
String LineString = null;
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "UTF-8"));
LineString = br.readLine();
while (LineString!=null) {
System.out.println(LineString);
ChatManager.getChatManager().PublishInf(this, LineString);
}
br.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
看上面的第6句,程序运行到这里就没法运行下去了,然后在android客户端中也出现了这种问题
messagebean.setBr(new BufferedReader(new InputStreamReader(socket.getInputStream())));
while ((line = messagebean.getBr().readLine()) != null) {
Message message = new Message();
message.what = msgWhat;
Bundle bundle = new Bundle();
bundle.putString("message", line);
message.setData(bundle);
handler.sendMessage(message);
}
这里把readLine放到了while循环中,解决了上面段服务端出现的无法执行后面代码的问题,但是这里的handler却又出现了问题
handler在这个类中是一个全局变量,由主线程传过来的值,在android中我给每个聊天界面设置了不同得handler,所以当while循环执行一次,handler值却是上一次保存的值,是不是很奇怪,这个问题我是在几年前遇到的,当时也没去了解底层的原理,这次我要仔细研究下readLine实现原码,readLine读取的是BufferedReader 中的信息,所以先来看看BufferedReader 这个类。
因为用BufferedReader时候也调用了InputStreamReader和InputStream首先来看下InputStream和OutputStream。
InputStream,OutputStream
InputStream 是字节输入流的所有类的超类,一般我们使用它的子类,如FileInputStream等。
OutputStream是字节输出流的所有类的超类,一般我们使用它的子类,如FileOutputStream等。
package java.io;
public abstract class InputStream implements Closeable {
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
public int available() throws IOException {
return 0;
}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public boolean markSupported() {
return false;
}
}
以上是InputStream的源码,大家可以去JDK(这里用的是1.8版本的)中找,我把里面的注释都删了,可以看到InputStream是调用了Closeable接口,而Closeable接口又继承了AutoCloseable类,再底层就不说下去了,可以再看下Closeable接口源码。
package java.io;
import java.io.IOException;
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}
这个接口里面就一句话,也就是close这个方法,所以这个方法就是用来关闭流的,并且会释放与其相关的任何方法,如果流已经关闭了,那么调用这个方法也就没什么意义了。而InputStream调用这个接口也只是为了实现close方法。
下面再继续来看看InputStream这个类,上面的源码中可以看到这是一个抽象类。里面主要是read方法,还有一个skip方法,首先来看下read方法,发现这里有两个read方法,一个是抽象方法,还有一个read则是重载了上面这个read抽象方法,对其进行了具体实现,read方法主要是把输入流中读取的数据存到一个数组中,具体的就不再一一分析了。然后skip方法相信大家应该也很少用到,作用就是跳过或者舍弃输入流数据中的n个字节。另一个类OutputStream就先不分析了,这里这要分析read。
然后再来看InputStreamReader和OutputStreamWriter这两个类
package java.io;
import java.nio.charset.Charset;
public class InputStreamReader extends Reader {
private final StreamDecoder sd;
public InputStreamReader(InputStream in) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
} catch (UnsupportedEncodingException e) {
// The default encoding should always be available
throw new Error(e);
}
}
public InputStreamReader(InputStream in, String charsetName)
throws UnsupportedEncodingException
{
super(in);
if (charsetName == null)
throw new NullPointerException("charsetName");
sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
}
public InputStreamReader(InputStream in, Charset cs) {
super(in);
if (cs == null)
throw new NullPointerException("charset");
sd = StreamDecoder.forInputStreamReader(in, this, cs);
}
public InputStreamReader(InputStream in, CharsetDecoder dec) {
super(in);
if (dec == null)
throw new NullPointerException("charset decoder");
sd = StreamDecoder.forInputStreamReader(in, this, dec);
}
public String getEncoding() {
return sd.getEncoding();
}
public int read() throws IOException {
return sd.read();
}
public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}
public boolean ready() throws IOException {
return sd.ready();
}
public void close() throws IOException {
sd.close();
}
}
这个 InputStreamReader类的作用就是把字节流转换成了字符流,可以看到InputStreamReader继承了Reader类,那么就再来看看Reader类是怎么实现的
package java.io;
public abstract class Reader implements Readable, Closeable {
protected Object lock;
protected Reader() {
this.lock = this;
}
protected Reader(Object lock) {
if (lock == null) {
throw new NullPointerException();
}
this.lock = lock;
}
public int read(java.nio.CharBuffer target) throws IOException {
int len = target.remaining();
char[] cbuf = new char[len];
int n = read(cbuf, 0, len);
if (n > 0)
target.put(cbuf, 0, n);
return n;
}
public int read() throws IOException {
char cb[] = new char[1];
if (read(cb, 0, 1) == -1)
return -1;
else
return cb[0];
}
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
abstract public int read(char cbuf[], int off, int len) throws IOException;
private static final int maxSkipBufferSize = 8192;
private char skipBuffer[] = null;
public long skip(long n) throws IOException {
if (n < 0L)
throw new IllegalArgumentException("skip value is negative");
int nn = (int) Math.min(n, maxSkipBufferSize);
synchronized (lock) {
if ((skipBuffer == null) || (skipBuffer.length < nn))
skipBuffer = new char[nn];
long r = n;
while (r > 0) {
int nc = read(skipBuffer, 0, (int)Math.min(r, nn));
if (nc == -1)
break;
r -= nc;
}
return n - r;
}
}
public boolean ready() throws IOException {
return false;
}
public boolean markSupported() {
return false;
}
public void mark(int readAheadLimit) throws IOException {
throw new IOException("mark() not supported");
}
public void reset() throws IOException {
throw new IOException("reset() not supported");
}
abstract public void close() throws IOException;
}
可以看到 Reader和InputStream有点相似,比如也实现了Closeable 这个接口,也有read方法,read方法有三种实现方法,也有skip方法,它们的作用也是差不多的,只不过这里的read方法读取的是字符流中的数据了。再仔细看skip和前面的InputStream中的skip有些不同了,这里的skip加锁了,可见这个skip多线程方法是线程安全的,不多说了,再回过去看下上面的
InputStreamReader,InputStreamReader类除了重载了四次构造方法,就剩下两个read方法,ready和close方法了,这里的read应该就是父类Reader中的,进行了覆盖,也就是重写。所以这里的InputStreamReader就是处于Reader和InputStream之间,起到适配的作用,它把字节流转化成了字符流。
接下来再来看重点BufferedReader和BufferedWriter
package java.io;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class BufferedReader extends Reader {
private Reader in;
private char cb[];
private int nChars, nextChar;
private static final int INVALIDATED = -2;
private static final int UNMARKED = -1;
private int markedChar = UNMARKED;
private int readAheadLimit = 0; /* Valid only when markedChar > 0 */
/** If the next character is a line feed, skip it */
private boolean skipLF = false;
/** The skipLF flag when the mark was set */
private boolean markedSkipLF = false;
private static int defaultCharBufferSize = 8192;
private static int defaultExpectedLineLength = 80;
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;
}
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
//此处省略一些方法
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
for (;;) {
if (nextChar >= nChars) {
fill();
if (nextChar >= nChars)
return -1;
}
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
continue;
}
}
return cb[nextChar++];
}
}
}
private int read1(char[] cbuf, int off, int len) throws IOException {
if (nextChar >= nChars) {
/* If the requested length is at least as large as the buffer, and
if there is no mark/reset activity, and if line feeds are not
being skipped, do not bother to copy the characters into the
local buffer. In this way buffered streams will cascade
harmlessly. */
if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
return in.read(cbuf, off, len);
}
fill();
}
if (nextChar >= nChars) return -1;
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
if (nextChar >= nChars)
fill();
if (nextChar >= nChars)
return -1;
}
}
int n = Math.min(len, nChars - nextChar);
System.arraycopy(cb, nextChar, cbuf, off, n);
nextChar += n;
return n;
}
public int read(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int n = read1(cbuf, off, len);
if (n <= 0) return n;
while ((n < len) && in.ready()) {
int n1 = read1(cbuf, off + n, len - n);
if (n1 <= 0) break;
n += n1;
}
return n;
}
}
String readLine(boolean ignoreLF) throws IOException {
StringBuffer s = null;
int startChar;
synchronized (lock) {
ensureOpen();
boolean omitLF = ignoreLF || skipLF;
bufferLoop:
for (;;) {
if (nextChar >= nChars)
fill();
if (nextChar >= nChars) { /* EOF */
if (s != null && s.length() > 0)
return s.toString();
else
return null;
}
boolean eol = false;
char c = 0;
int i;
/* Skip a leftover '\n', if necessary */
if (omitLF && (cb[nextChar] == '\n'))
nextChar++;
skipLF = false;
omitLF = false;
charLoop:
for (i = nextChar; i < nChars; i++) {
c = cb[i];
if ((c == '\n') || (c == '\r')) {
eol = true;
break charLoop;
}
}
startChar = nextChar;
nextChar = i;
if (eol) {
String str;
if (s == null) {
str = new String(cb, startChar, i - startChar);
} else {
s.append(cb, startChar, i - startChar);
str = s.toString();
}
nextChar++;
if (c == '\r') {
skipLF = true;
}
return str;
}
if (s == null)
s = new StringBuffer(defaultExpectedLineLength);
s.append(cb, startChar, i - startChar);
}
}
}
public String readLine() throws IOException {
return readLine(false);
}
//后面一些方法省略了,有需要的可以去JDK中看源码
}
上面分析了那么多终于看到了重点,从BufferedReader源码中可以看到BufferedReader里面也有read方法和skip方法,并且多了readLine这个方法,同时BufferedReader也是继承了Reader类,那么现在来具体看看readLine这个方法。
可以看到里面有一句synchronized (lock)很明显这个readLine方法加锁了但是这个不是重点,再看下面的bufferLoop这个是重点,bufferLoop里面有个for (;;)这是一个死循环,所以导致每次读取完数据都会阻塞,那么怎么解决第一个问题呢,就是如同在第二个问题中那样,把readLine放到循环中,这样每读取到一次'\n'或者'\r',readline就会返回一个值,接着进入while循环,循环中的代码也会在读取后进行执行,这里注意readline这个方法每次读取到回车符或者换行符才会进行返回,如果在客户端发送消息的末尾没有加回车符或者换行符,那么readline就无法返回值。第二个问题,我是把handler设置成了静态变量才解决的,android客户端每次readLine读取数据后都要把消息传会给主线程(UI线程),然后开始都是调用前一次的handler值,所以后面把handler设置成了静态,问题就解决了,为什么会这样呢?
这个涉及到Java中内存分配了,在Java中内存一般分为方法区,堆,JVM方法栈,本地方法栈,寄存器(cache),一般的静态变量都存在方法区中,而new出来的对象都是在堆内存中,如果handler设置成了全局变量,那么这个变量的值就和new出来的对象一样一起存在堆内存中。而每个线程都是有自己的JVM栈内存的,一个线程访问不到其他线程的JVM栈中的变量和方法的,说到这里又会有疑问了,怎么又是变量值存在堆内存中,又是变量在JVM栈中。
其实在Java类中定义出一个全局变量,就会把这个变量的指针存放在栈中,然后这个全局变量指向的内存块则是在堆中的,也就是真是数据是存放在堆里面的(因为全局变量在对象中),栈中只是存放了这个数据的地址而已,这样才能找到这个值,如果这个指针指向了别的内存地址了,那么前面那个内存块就再也无法访问到它了,然后JVM就会利用垃圾回收机制清理掉它。
JVM会为每个新创建的线程都分配一个堆栈,每个java应用都对应一个JVM实列,每个实例对应一个堆,也就是java应用中的每个线程各自的堆都是在java应用这个大堆里面的,而静态变量一般都是存放在方法区中,方法区也是全局共享的,也就是所有线程共享的,当然java应用中的那个堆也是全局共享的,JVM栈,本地方法栈和寄存器是非共享的。
所以回到题目中的问题,把handler设置为静态变量的时候,它就是全局共享了,所有线程都能访问到它,所以就解决了while循环中把readLine读取的信息通过handler发送的主线程,handler的值还是前面那个对象中值的问题。另外的就不再展开分析了,再展开我怕我要绕晕了,以上都是个人理解,如果有错请指正。