前言

最近在重拾Java网络编程,想要了解一些JAVA语言基本的实现,这里记录一下学习的过程。

阅读之前,你需要知道

网络节点(node):位于网络上的互相连通的设备,通常为计算机,也可以是打印机,网桥,路由器等等烧入网卡,从而确保没有任何两台设备的MAC地址是相同的。
IP地址:网络地址,由ISP供应商决定。
包交换网络(packet-switched network):数据被拆解为多个包并分别在线路上传输,每个包包含发送者和接收者的信息。
协议:节点之间进行交流所遵循的规定
网络分层模型

当我们试图访问一个网站时,浏览器实际上直接访问的是本地的传输层(Transport Layer)。传输层将HTTP报文分段为TCP报文并继续向下传递给IP层。IP层封装信息并且将其传送到物理链路上。服务器对收到的数据以相反的顺序解包并读取里面的请求内容。

端口:一台计算机在传输层的每一个协议上通常有65,535个逻辑端口。HTTP通常使用80端口
C/S模型:客户端与服务器模型。通常是客户端向服务器主动发送请求并等待服务器的响应。

基于流的JAVA Socket 通信

Java的IO最初是基于流的。从输入流中读取数据,向输出流中写入数据。不同类型的流入java.io.FileInputStreamsun.net.TelnetOutput Stream往往对应于不同类型的流数据。但是,读取和写入流的方法本质上是相同的。Java还提供了ReadersWriters系列来支持对字符流的输入输出。

流是同步的。同步的流是指当程序(通常是某个线程)要求从流中读取或是向流中写入一段数据时,在获取到数据或是完成数据写入之前,该程序将会一直停在这一步,不会进行任何工作。Java也提供了非阻塞的I/O。我们将在后面继续了解。

OutputStream

先看一下所有输出流的父类OutputStream的API

public abstract class OutputStream{//核心方法//各个不同的流将实现具体的写入//如ByteArrayOutputStream可以直接写入内存,而FileOutputStream则需要根据操作系统调用底层函数来写入文件public abstract void write(int b);public void write(byte[] data) throws IOException;public void write(byte[] data, int offset, int length) throws IOException;//将缓存的内容强制写入目标地点public void flush() throws IOException;public void close() throws IOException;
}

在这里write(int)是核心方法,它会将一个个ASCII码写入输出流中。但是,每写一个字节就传送的浪费是巨大的,因为一次TCP/UDP传输需要携带额外的控制信息和路由信息。所以通常会将字节缓存到一定数量后再发送。

使用完输出流后,及时的关闭它是一个很好的习惯,否则可能会造成资源的浪费或是内存泄漏。我们通常使用finally块来实现:

OutputStream os = null;
try{os = new FileOutputStream("/tmp/data.txt");
}catch(IOException e){System.err.println(ex.getMessage());
}finally{if(os!=null){try{os.close();}catch(IOException e){//处理异常}}
}

上面的代码风格被称为dispose pattern,在使用需要回收资源的类时都会使用这个模式。Java7以后我们可以用另一种更加简洁的方式来实现这个代码:

try(OutputStream os = new FileOutputStream("/tmp/data.txt")){...
}catch(IOException e){System.err.println(ex.getMessage());
}

Java会自动调用实现AutoCloseable对象的close()方法,因此我们无需再写ugly的finally块。

InputStream

同样,看一下输入流的基类InputStream的API

public abstract class InputStream{//核心方法//从输入流中读取一个字节并将其作为int值(0~255)返回//当读到输入流末尾时,会返回-1public abstract int read() throws IOException;public int read(byte[] input) throws IOException;public int read(byte[] input, int offset, int length) throws IOException;public long skip(long n) throws IOException;public int available() throws IOException;public void close() throws IOException;
}

输入流也是阻塞式的,它在读取到字节之前会等待,因此其后的代码将不会执行知道读取结束。
为了解决阻塞式读写的问题,可以将IO操作交给单独的线程来完成。

read()方法返回的应该是一个byte,但是它却返回了int类型,从而导致其返回值变为-128~127之间而不是0~255。我们需要通过一定的转化来获取正确的byte类型:

byte[] input = new byte[10];
for (int i = 0; i < input.length; i++) {int b = in.read(); if (b == -1) break; input[i] = (byte) (b>=0? b : b+256);
}

有时候我们无法在一次读取中获得所有的输入,所以最好将读取放在一个循环中,在读取完成之后再跳出循环。

int bytesToRead = 1024;
int bytesRead = 0;
byte[] buffer = new byte[bytesToRead];
while(bytesRead < bytesToRead){int tmp = inputStream.read(buffer, bytesRead, bytesToRead-bytesRead);//当流结束时,会返回-1if(tmp == -1) break;bytesRead += tmp;
}

Filters

Java IO采用了装饰者模式,我们可以将一个又一个装饰器加到当前流上,赋予该流新的解析。

在这里,先通过TelnetInputStream从网络上获取Telnet数据流,然后再逐个经过多个过滤器从而获得流中的数据。将过滤器相连的方法很简单:

FileInputStream fin = new FileInputStream("data.txt"); BufferedInputStream bin = new BufferedInputStream(fin);

Buffered Stream
先将数据缓存至内存,再在flush或是缓存满了以后写入底层。
大多数情况下,缓存输出流可以提高性能,但是在网络IO的场景下不一定,因为此时的性能瓶颈取决于网速,而不是网卡将数据传到上层的速度或是应用程序运行的速度。

构造器如下:

public BufferedInputStream(InputStream in);
public BufferedInputStream(InputStream in, int bufferSize);
public BufferedOutputStream(OutputStream out);
public BufferedOutputStream(OutputStream out, int bufferSize);

PrintStream
输出流,System.out就是一个PrintStream。默认情况下,我们需要强制flush将PrintStream中的内容写出。但是,如果我们在构造函数中将自动flush设置为true,则每次写入一个byte数组或是写入换行符或是调用println操作都会flush该流。

public PrintStream(OutputStream out)
public PrintStream(OutputStream out, boolean autoFlush)

但是,在网络环境中应当尽可能不使用PrintStream,因为在不同的操作系统上,println的行为不同(因为换行符的标记不同)。因此同样的数据在不同的操作系统上可能不一致。

除此以外,PrintStream依赖于平台的默认编码。但是,这个编码和服务器端期待的编码格式很可能是不一样的。PrintStream不提供改变编码的接口。

而且,PrintStream会吞掉所有的异常。我们无法对异常进行相应的编码。

Date Stream
以二进制数据的格式读取和写入Java的基本数据类型和String类型。

DataOutputStream的API:

public final void writeBoolean(boolean b) throws IOException
public final void writeByte(int b) throws IOException
public final void writeShort(int s) throws IOException
public final void writeChar(int c) throws IOException
public final void writeInt(int i) throws IOException
public final void writeLong(long l) throws IOException
public final void writeFloat(float f) throws IOException
public final void writeDouble(double d) throws IOException //根据UTF-16编码将其转化为长度为两个字节的字符
public final void writeChars(String s) throws IOException //只存储关键的信息,任何超出Latin-1编码范围的内容都将会丢失
public final void writeBytes(String s) throws IOException //上面两个方法都没有将字符串的长度写入输出流,所以无法分辨究竟原始字符还是构成字符串的最终字符
//该方法采用UTF-8格式编码,并且记录的字符串的长度
//它应当只用来和其它Java的程序交换信息
public final void writeUTF(String s) throws IOException

DataInputStream的API:

public final boolean readBoolean() throws IOException
public final byte readByte() throws IOException
public final char readChar() throws IOException
public final short readShort() throws IOException
public final int readInt() throws IOException
public final long readLong() throws IOException
public final float readFloat() throws IOException
public final double readDouble() throws IOException
public final String readUTF() throws IOException//读取别的程序写的unsigned类型数据,如C
public final int readUnsignedByte() throws IOException
public final int readUnsignedShort() throws IOExceptionpublic final int read(byte[] input) throws IOException
public final int read(byte[] input, int offset, int length) throws IOException//完整的读取一定长度的字符串,如果可读的长度不足,将抛出IOException
//可用于已知读取长度的场景,如读取HTTP报文。
我们可以使用Header中的content-length属性来读取相应长度的body
public final void readFully(byte[] input) throws IOException
public final void readFully(byte[] input, int offset, int length) throws IOException//读取一行 但是最好不要使用,因为它无法正确的将非ASCII码转化为字符串
public final String readLine() throws IOException

这里需要强调一下为什么不要使用readLine()方法。因为readLine方法识别一行末尾的方法是通过\r或是\r\n。当readLine遇到\r时,它会判断下一个字符是不是\n。如果是,则将两个标记都抛弃并且将之前的内容作为一行返回。如果不是,则抛弃\r并将之前的内容返回。问题在于,如果流中最后一个字符为\r,那么读取一行的方法会挂起,并等待下一个字符。

这个问题在网络IO中特别明显,因为当一次数据发送结束之后,客户端在关闭连接之前会等待服务器端的响应。服务器端却在等待一个不存在的输入。因此二者陷入死锁。如果幸运的话,客户端会因为超时断开连接,使得死锁结束,同时你丢失了最后一行数据。也有可能这个程序无限死锁下去。

Readers Writers

文本中的字符并不能和ASCII码完全划等号。很多国家的语言如中文,日文,韩文等都远远超出了ASCII码编码的范围。用ASCII码是无法识别这些字节的。因此JAVA推出了Reader和Writer类。它将根据特定的编码来解读字节。

Writer类API

protected Writer()
protected Writer(Object lock)
public abstract void write(char[] text, int offset, int length) throws IOException
public void write(int c) throws IOException
public void write(char[] text) throws IOException
public void write(String s) throws IOException
public void write(String s, int offset, int length) throws IOException public abstract void flush() throws IOException
public abstract void close() throws IOException

这里和之前的区别在于将根据选择的编码转化为相应的byte。

OutputStreamWriter
将字节流根据选择的编码转化为字符流。

public OutputStreamWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
public void write(String s) throws IOException;

Reader类API

protected Reader()
protected Reader(Object lock)
public abstract int read(char[] text, int offset, int length) throws IOException//返回unicode对应的0~65535之间的整数
//如果到达了流的末尾,则返回-1
public int read() throws IOException
public int read(char[] text) throws IOException //跳过n个字符
public long skip(long n) throws IOException
public boolean ready()
public boolean markSupported()//设置标记与重置下标至标记处
public void mark(int readAheadLimit) throws IOException
public void reset() throws IOException
public abstract void close() throws IOException

InputStreamReader

public InputStreamReader(InputStream in)//如果没有可以匹配的编码,则抛出UnsupportedEncodingException
public InputStreamReader(InputStream in, String encoding)
throws UnsupportedEncodingException

使用InputStreamReader的范例:

public static String getMacCyrillicString(InputStream in) throws IOException {InputStreamReader r = new InputStreamReader(in, "MacCyrillic");            StringBuilder sb = new StringBuilder();int c;while ((c = r.read()) != -1) sb.append((char) c);return sb.toString();
}

PrintWriter
PrintStream的字符形式阅读,尽量使用PrintWriter而非PrintStream因为正如前面提到的,PrintStream依赖于当前平台的编码,并且无法修改。

public PrintWriter(Writer out)
public PrintWriter(Writer out, boolean autoFlush)
public PrintWriter(OutputStream out)
public PrintWriter(OutputStream out, boolean autoFlush)
public void flush()
public void close()
public boolean checkError()
public void write(int c)
public void write(char[] text, int offset, int length)
public void write(char[] text)
public void write(String s, int offset, int length)
public void write(String s)
public void print(boolean b)
public void print(char c)
public void print(int i)
public void print(long l)
public void print(float f)
public void print(double d)
public void print(char[] text)
public void print(String s)
public void print(Object o)
public void println()
public void println(boolean b)
public void println(char c)
public void println(int i)
public void println(long l)
public void println(float f)
public void println(double d)
public void println(char[] text)
public void println(String s)
public void println(Object o)

参考书籍

Java Network Prograing 4th edition
HTTP权威指南


想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注我的微信公众号!将会不定期的发放福利哦~

重拾Java Network Programming(一)IO流相关推荐

  1. 重拾Java基础知识:IO流

    I0流 前言 字节流 InputStream OutputStream 字符流 Reader Writer 缓存流 转换流 序列化流 数据流 字节数组流 打印流 校验流 数据压缩 ZIP压缩和解压 G ...

  2. php byte stringbuffer,重拾java基础(十三):String姐妹StringBuffer、StringBuilder总结

    重拾java基础(十三):String姐妹StringBuffer.StringBuilder总结 一.StringBuffer类概述buffer:缓冲 2. 字符串缓冲区,跟String非常相似,都 ...

  3. JAVA学生信息管理系统IO流版

    JAVA学生信息管理系统IO流版 1. Student类 public class Student implements Serializable{//学号private int sid;//姓名pr ...

  4. Java学习之路1——安装JDK1.8||安装idea2022||Java项目创建【重拾Java】

    Java学习之路1--安装JDK1.8||安装idea2022[重拾Java] 前言 安装 安装JDK1.8 安装idea2022(JetBrains Toolbox) Java项目创建 创建 项目结 ...

  5. 【Java网络编程与IO流】Java之Java Servlet详解

    Java网络编程与IO流目录: [Java网络编程与IO流]Java中IO流分为几种?字符流.字节流.缓冲流.输入流.输出流.节点流.处理流 [Java网络编程与IO流]计算机网络常见面试题高频核心考 ...

  6. Java学习之路3——方法定义、调用【重拾Java】

    Java学习之路3--方法定义.调用[重拾Java] 方法定义 为什么要写方法 方法完整的定义形式.调用 方法定义的格式 修饰符 返回值类型 返回值 调用格式 方法重载 方法定义 为什么要写方法 对于 ...

  7. 【Java网络编程与IO流】Java中IO流分为几种?字符流、字节流、缓冲流、输入流、输出流、节点流、处理流

    Java网络编程与IO流目录: [Java网络编程与IO流]Java中IO流分为几种?字符流.字节流.缓冲流.输入流.输出流.节点流.处理流 [Java网络编程与IO流]计算机网络常见面试题高频核心考 ...

  8. java字节流读取文件_字节流读取文件 java的几种IO流读取文件方式

    java字节流怎么读取数据 字节流读取数据例子如下: import java.io.File;import java.io.FileInputStream;import java.io.FileNot ...

  9. 打怪升级之小白的大数据之旅(二十五)<Java面向对象进阶之IO流三 其他常见流>

    打怪升级之小白的大数据之旅(二十五) Java面向对象进阶之IO流三 其他常见流 上次回顾 上一章,我们学习了常用的字节流与字符流,本章,我会将其他的一些常见的流进行分享,IO流很多,我介绍不完,就挑 ...

最新文章

  1. 射频,系带,调制解调器
  2. 深究AngularJS——$sce的使用
  3. 人工智能实践之旅 —— 简单说说主要内容和安排
  4. python 连接mysql_python连接MySQL
  5. 【java设计模式之Command(菜单命令) 】
  6. java反射的field.get(null)
  7. pads导出坐标文件html,【教程】PADS如何导出SMT贴片机用的坐标文件
  8. php:修改目录下文档权限(777,644 )
  9. 掌握后可为孩子收藏的MySQL入门全套
  10. 华为云基于云原生媒体网络,又出重磅新品
  11. [泛读]4篇Web Service Replication方面论文
  12. 推荐几个前端模板下载站
  13. Odin Inspector 系列教程 --- 初识Odin序列化
  14. PR字幕不显示的问题(已解决)
  15. VGA , CVBS , HDMI 三者的区别
  16. Windows两台服务器之间实现文件共享
  17. Guava--Splitter使用方式
  18. PDMS二次开发之PML开发一些常见查询语句
  19. 原生实现freeCodeCamp上的Build a Personal Portfolio Webpage
  20. 货拉拉客户端通用日志组件 - Glog

热门文章

  1. python实现:用类实现一个图书馆,实现借书,入库,还书,查书,等功能,要求数据可以保存到文件中,退出后下次可以找回数据...
  2. Python递归、反射、2分查找、冒泡排序
  3. PHP curl函数制 exec_ch和get_headers
  4. Myeclipse修改jdk版本流程
  5. NSS_08 extjs表单验证
  6. PHP 输入一棵二叉树和一个数字n,要求找出路径和为n的所有路径
  7. AAC Explicit or Implicit SBR PS issue
  8. 如何用T—SQL命令查询一个数据库中有哪些表?
  9. yolov3前向传播(一)-- darknet53网络解析与实现
  10. 抽象工厂模式 Abstract Factory Pattern