这是我的一个作业,因为没有用了PrintWriter,但是没有及时flush,导致服务端一直收不到客户端的数据。可跳过这部分去看下面的。
package mysocket;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class ServerThread extends Thread{
@Override
public void run() {
try {
ServerSocket serverSocket=new ServerSocket(3999);
Socket socket=serverSocket.accept();
System.out.println(socket.getInetAddress()+" port:"+socket.getPort());
Scanner scan=new Scanner(socket.getInputStream());
PrintWriter printWriter=new PrintWriter(socket.getOutputStream());//,true);
scan.useDelimiter("\n");
int flag=1;
while(flag==1){
// System.out.println("1111111111111");
String val= scan.next().trim();
System.out.println(val);
if("byebye".equalsIgnoreCase(val)){
flag=0;
printWriter.println("ByeBye客户端");
}else{
printWriter.println("服务端已收到ECHO"+val);
}
printWriter.flush();
}
scan.close();
printWriter.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*********************************************************************************************/
package mysocket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class ClientThread extends Thread{
private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(
new InputStreamReader(System.in));
public String inputString() throws IOException {
System.out.print("客户端请输入:");
String v=KEYBOARD_INPUT.readLine();
return v;
}
@Override
public void run(){
try {
Socket socket = new Socket("127.0.0.1",3999);
PrintWriter printWriter=new PrintWriter(socket.getOutputStream());
Scanner scan=new Scanner(socket.getInputStream());
scan.useDelimiter("\n");
int flag=1;
while(flag==1){
String val=inputString();
printWriter.println(val);
printWriter.flush();
if("byebye".equals(val)){
flag=0;
}
if(scan.hasNext()){
System.out.println(scan.next());
}
}
scan.close();
printWriter.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*********************************************************************************************/
package mysocket;
public class Main {
public static void main(String[] args){
ServerThread server=new ServerThread();
server.start();
ClientThread client=new ClientThread();
client.start();
}
}
Bug:由于使用PrintWriter,不手动刷新,又不设置自动刷新 即new PrintWriter(OutputString out,autoFlush true),导致服务端一直阻塞,拿不到客户端发来的数据,程序无法按照正常走下去。
flush方法
另外,flush方法,在FileOutputStream中,没有重写,而OutputStream中的flush方法有是一个空的方法。其实之前有一个误区是,以为都要flush的。其实如果没有使用到缓冲区,根本就不需要使用flush,write方法直接将数据交个底层操作系统写入。只有使用了Buffer的才需要flush。
flush方法有三种情况。
1.OutputStream中的flush是一个空的方法。
2.有一些实现类中,没有重写flush方法,直接是继承父类的。
3.flush方法被重写,如BufferedOutputStream
PrintWriter中的自动刷新,println()会自动刷新。
public void println() {
newLine();
}
public void println(boolean x) {
synchronized (lock) {
print(x);
println();
}
}
private void newLine() {
try {
synchronized (lock) {
ensureOpen();
out.write(lineSeparator);
if (autoFlush)/*************************这里是关键********************/
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
PrintWriter PrintStream自动刷新
newline()方法中的,out.flush是自动刷新的关键。
out所指向的对象可能是下面两种情况(多态)
1.没有重写flush的对象(即是不用flush的,没写入一个,都会直接交个操作系统写入的,这种类型的输出流自然不需要flush的,使用了也相当于没有使用,效果是一样的)
2.重写了flush的对象,如果自动刷新为true,则自然会把缓冲区中的刷出去。
所以会自动刷新。
BufferOutputStream类详解
源码如下:
public
class BufferedOutputStream extends FilterOutputStream {
/**
* The internal buffer where data is stored.
*/
protected byte buf[];//缓冲区数组
/**
* The number of valid bytes in the buffer. This value is always
* in the range <tt>0</tt> through <tt>buf.length</tt>; elements
* <tt>buf[0]</tt> through <tt>buf[count-1]</tt> contain valid
* byte data.
*/
protected int count;//缓冲区有效字节数
/**
* Creates a new buffered output stream to write data to the
* specified underlying output stream.
*
* @param out the underlying output stream.
*/
public BufferedOutputStream(OutputStream out) {//默认定义一个8k的缓冲区
this(out, 8192);
}
/**
* Creates a new buffered output stream to write data to the
* specified underlying output stream with the specified buffer
* size.
*
* @param out the underlying output stream.
* @param size the buffer size.
* @exception IllegalArgumentException if size <= 0.
*/
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
/** Flush the internal buffer */
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);//真是有效的刷新写入。
count = 0;//清空缓冲区
}
}
/**
* Writes the specified byte to this buffered output stream.
*
* @param b the byte to be written.
* @exception IOException if an I/O error occurs.
*/
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();//如果缓冲区满,则交给底层操作系统写入。
}
buf[count++] = (byte)b;
}
/**
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @exception IOException if an I/O error occurs.
*/
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {//如果新来的要写入缓冲区的数据的长度大于缓冲区的长度
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();//先将缓冲去中的数据写入()
out.write(b, off, len);
//然后在用out(真实对象)的write方法写入。因为真实对象out的write方法,是直接交给操作系统写入的。
//数据都大于缓冲区了,直接交给操作系统最快了,不用经过缓冲区了。
return;
}
if (len > buf.length - count) {//如果剩余的缓冲区长度,不足以存放新来的数据。//先刷出
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);//这样缓冲区足以存放新来的数据,则将它复制进缓冲区。
count += len;
}
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();//暂时我的认知认为它没有用。
}
}
其实BufferOutputStream只是对其他类进行包装装饰一次。对于没有重写过fuush方法的类,里面的write方法是直接交给操作系统区写入的,如果频繁的交给操作系统区写入,对资源的消耗是相当大的,因为要启动磁盘,找扇区,寻道等会相当耗时,如果为了写入一个或者几个字节的数据而启动一次,是不值得。缓冲区的概念也并不是很深奥的概念,就是一个大一点的数组,暂时存放数据,称之为缓冲,等到数据满之后一次性写入到磁盘中,这样就可以减少磁盘的IO次数,性能大大提高。BufferOutputStream中的buf就是缓冲区,可以看到BufferOutputStream中的write方法并不是直接就交给操作系统写入的,而是等待缓冲区满的时候在一次性写入(不是全部情况,全部情况看上面的源码)。
为什么我说BufferOutStream只是对其他的OuputStream 的一次包装装饰,因为BufferOutputStream中的write方法还是需要调用它所装饰的对象out的write方法。如下:
/** Flush the internal buffer */
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);//真是有效的刷新写入。用的还是所装饰的对象out的write方式(是直接交个操作系统写入的)
count = 0;//清空缓冲区
}
}
平时我们说BufferOutputStream等缓冲流能够加快速度,就是上面所说的把原本需要很多次IO的的操作,通过缓冲区暂时存下来,一次写入,这提升了性能。一个例子就是下面。
FileOutputStream fos=new FileOutputStream(...);
fos.write(1);
fos,write("aa");
fos.write(2);
fos.write("bb");
这个过程需要四次IO操作。如果使用BufferOutputStream只需要一次IO操作(因为默认缓冲区为8k,足以暂时存放这些数据)。