Java网络编程的Java流介绍
前言
网络程序所做的很大一部分工作都是简单的输入输出:将数据字节从一个系统移动到另一个系统。Java的I/O建立于流(stream)之上。输入流读取数据,输出流写入数据。过滤器流(filter)流可以串联到输入或输出流上。读写数据时过滤器可以修改数据(加密或压缩),或者只是提供额外的方法,将读/写的数据转换为其他格式。阅读器(reader)和书写器(writer)可以串链到输入流和输出流上,允许程序读/写文本而不是字节。
输出流
Java的基本输出流类是:java.io.OutputStream;
这个类中提供了写入数据所需的基本方法,如下:
- public abstract void write(int b) throws IOException;
- public void write(byte b[]) throws IOException
- public void write(byte b[], int off, int len) throws IOException
- public void flush() throws IOException
- public void close() throws IOException
但是我们平时使用它的子类来实现向某种特定介质写入数据。例如:FileOutputStream等,它的子类都是通过装饰模式来实现一些特定的功能的。OutputStream的基本方法是write(int b)。这个方法接受一个0到255之间的整数作为参数,将对应的字节写入到输出流中。虽然此方法接受一个int作为参数,但它实际上会写入一个无符号字节,因为java没有无符号字节数据类型,所以这要使用int来代替。无符号字节和有符号字节之间唯一的真正区别在于解释。它们都由8个二进制组成,write方法将int写入一个网络连接时,线缆上只会放8个二进制位。如果将一个超出0~255的int传入write方法,将协议这个数的最低字节,其他3个字节将被忽略。因为每次写入一个字节效率不高,所以就又提供了两个可以传入字节数组的方法,write(byte[])、write(byte b[],int off,int len)。
与网络硬件中缓存一样,流还可以在软件中得到缓冲,即直接用java代码缓存。在写入数据完成后,刷新(flush)输出流非常重要。因为flush()方法可以强迫缓冲的流发送数据,即使缓冲区还没有满,以此来打破流一直等待着缓冲区满了才会发送数据的状态。
最后,当结束一个流操作时,要通过调用它的close()方法将其关闭。关闭流会释放与整个流关联的所有资源,如果流来自网络连接,这个连接也会被关闭。长时间未关闭一个流,可能会泄漏文件句柄、网络端口和其他资源。所以在Java6以及更早的版本中,是在一个finally块中关闭流。但是Java7引入了try width resources 可以简化关闭流的操作,只需要把流定义在try的参数中即可。
如下所示:
- try(OutputStream out = new FileOutputStream("D:/temp/test.txt")){
- // 处理输出流
- }catch (IOException e){
- e.printStackTrace();
- }
因为Java会对try块参数表中 声明的所有AutoCloseable对象自动调用close()。Java中的流相关的类基本上都直接或间接的实现了AutoCloseable接口。
输入流
Java的基本输出流类是:java.io.InputStream;
这个类提供了将数据读取为原始字节所需要的基本方法。如下:
- public abstract int read() throws IOException;
- public int read(byte b[]) throws IOException
- public int read(byte b[], int off, int len) throws IOException
- public long skip(long n) throws IOException
- public int available() throws IOException
- public void close() throws IOException
InputStream的基本方法是没有参数的read()方法。此方法从输入流的源中读取1字节数据,作为一个0到255的int返回,流的结束通过返回-1来表示。read()方法会等待并阻塞其后任何代码的执行,直到有1字节的数据可供读取。输入和输出可能很慢,所以如果成行在做其他重要工作,要尽量将I/O放在单独的线程中。
一次读取1字节的效率也不高,因此,有两个重载的read()方法,可以用从流中读取的多字节的数据填充一个指定的数组:read(byte[] input)和read(byte[] input, int offset,int length)。当read的时候如果遇到IOException或网络原因只读取到了一部分,这个时候就会返回实际读取到的字节数。
例如:
- int bytesRead = 0;
- int bytesToRead = 1024;
- byte[] input = new byte[bytesToRead];
- while (bytesRead<bytesToRead){
- bytesRead += in.read(input,bytesRead,bytesToRead - bytesRead);
- }
上面这段代码就是没有考虑到有可能流会中断导致读取的数据永远读不出来,所以要防止这种事情出现需要先测试read()的返回值,然后再增加到byteRead中
如下所示:
- int bytesRead = 0;
- int bytesToRead = 1024;
- byte[] input = new byte[bytesToRead];
- while (bytesRead<bytesToRead){
- int result = in.read(input,bytesRead,bytesToRead - bytesRead);
- if(result == -1) break;
- bytesRead += result;
- }
可以使用available()方法来确定不阻塞的情况下有多少字节可以读取。它会返回可读取的最少字节数。事实上还能读取更多字节,至少可以读取available()建议的字节数。
如下:
- int bytesAvailable = in.available();
- byte[] input = new byte[bytesAvailable];
- int bytesRead = in.read(input,0,bytesAvailable);
- //读取到数据后,去执行其他部分
在少数情况下,你可能希望跳过数据不进行读取。skip()方法会完成这项任务。
与输出流一样,一旦结束对输入流的操作,应当调用close()方法将其关闭。这会释放这个流关联的所有资源。
InputStream类中还有3个不经常用的方法,
- public synchronized void mark(int readlimit)
- public synchronized void reset() throws IOException
- public boolean markSupported()
为了重新读取数据,要用mark()方法标记流的当前位置,在以后某个时刻可以用reset()方法把流重置到之前标记的位置。在尝试使用标记和重置之前,要坚持markSupported()方法是否返回true。如果返回true,那么这个流确实支持标志和重置,否则,mark()会什么都不做,而reset()将抛出一个IOException异常。
过滤器流
过滤器由两个版本:过滤器流(filte stream)以及阅读器(reader)和书写器(writer)
每个过滤器输出流都有与java.io.OutputStream相同的write()、close()和flush()方法。每个过滤器输入流都有与java.io.InputStream相同的read()、close()和available()方法。
过滤器通过其构造函数与流连接。
- FileInputStream iin = new FileInputStream("test.txt");
- BufferedInputStream bin = new BufferedInputStream(iin);
这种情况下如果混合调用连接到同一个源的不同流,这可能会违反过滤器流的一些隐含约定。大多数情况下应当只使用链中最后一个过滤器进行实际的读/写。
可以用如下方式:
- InputStream iin = new FileInputStream("test.txt");
- iin = new BufferedInputStream(iin);
缓冲流
BufferedOutputStream类将写入的数据存储在缓冲区中,直到缓冲区满了或者执行了flush方法。然后将数据一次全部写入底层输出流。在网络连接中,缓冲网络输出通常会带来巨大的性能提升。
BufferedInputStream类也有一个作为缓冲区的保护字节数组,当调用某个流的read()方法时,它首先尝试从缓冲区获得请求的数据。当缓冲区没有数据时,流才从底层的源中读取数据。这时,它会读取尽可能多的数据存入缓冲区,而不管是否马上需要所有这些数据。不会立即用到的数据可以在以后调用read()时读取。当从本地磁盘中读取文件时,从底层流中读取几百字节的数据与读取1字节数据几乎一样快。因此,缓冲可以显著提升性能。
BufferedOutputStream有两个构造函数,BufferedInputStream也是有两个构造函数:
- public BufferedInputStream(InputStream in)
- public BufferedInputStream(InputStream in, int size)
- public BufferedOutputStream(OutputStream out)
- public BufferedOutputStream(OutputStream out, int size)
PrintStream
PrintStream类是大多数程序员都会遇到的第一个过滤器输出流,因为System.out就是一个PrintStream。还可以使用下面两个构造函数将其他输出流串链到打印流:
- public PrintStream(OutputStream out)
- public PrintStream(OutputStream out, boolean autoFlush)
如果autoFlush参数为true,那么每次写入1字节数组或换行,或者调用println()方法时,都会刷新输出流。除了平常的write()、flush()和close()方法,PrintStream还有9个重载的print()方法和10个重载的println方法:
- 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 s[])
- public void print(String s)
- public void print(Object obj)
- public void println()
- public void println(boolean x)
- public void println(char x)
- public void println(int x)
- public void println(long x)
- public void println(float x)
- public void println(double x)
- public void println(char x[])
- public void println(String x)
- public void println(Object x)
每个print()方法都将其参数以可见的方式转换为一个字符串,再用默认的编码方式把字符串写入底层输出流。println()方法也完成相同操作,但会在所写的行末尾追加一个与平台有关的行分隔符。
- 在网络编程中应尽量避免使用PrintStream。
PrintStream第一个问题,println()输出是与平台有关的。
PrintStream第二个问题,会假定使用所在平台的默认编码方式。
PrintStream第三个问题,会吞掉了所有异常。
数据流
DataInputStream和DataOutputStream类提供了一些可以用二进制格式读/写Java的基本数据类型和字符串。
DataOutputStream类提供下面11种方法,可以写入特定的Java数据类型。
- public final void writeBoolean(boolean v) throws IOException
- public final void writeByte(int v) throws IOException
- public final void writeShort(int v) throws IOException
- public final void writeChar(int v) throws IOException
- public final void writeInt(int v) throws IOException
- public final void writeLong(long v) throws IOException
- public final void writeFloat(float v) throws IOException
- public final void writeDouble(double v) throws IOException
- public final void writeBytes(String s) throws IOException
- public final void writeChars(String s) throws IOException
- public final void writeUTF(String str) throws IOException
前面的8个方法,都按照实际参数的类型长度来写数据的,最后三个方法有些特别,writeChars()方法只是对String参数迭代处理,将各个字符按顺序写为一个2字节的big-endian Unicode字符。writeBytes()方法迭代处理String参数,但只是写入每个字符的低字节。
writeChars和writeBytes都不会对输出流的字符串的长度编码。因此,你无法真正区分原始字符和作为字符串一部分的字符。writeUTF()方法则包括了字符串的长度。它将字符串本身用Unicode UTF-8编码的一个变体进行编码。
除了这些写入二进制数字和字符串的方法,DataOutputStream当然还有所有OutputStream类都有的平常的write()、flush()、和close()方法。
DataInputStream与DataOutputStream是互补的。DataOutputStream写入的每一种数据格式,DataInputStream都可以读取。此外DataInputStream还有通常read()、available()、skip()和close()方法,以及读取整个字节数组和文本行的方法。所以DataInputStream的内容就不写了。
书写器
Writer是以字符流的方式书写数据,它是一个抽象类,有两个保护类型的构造函数。与OutputStream类似,Writer类从不直接使用;相反,会通过他的某个子类以多态方式使用。它有5个write()方法,另外还有flush()和close()方法。
- protected Writer()
- protected Writer(Object lock)
- abstract public void write(char cbuf[], int off, int len) throws IOException
- public void write(int c) throws IOException
- public void write(char cbuf[]) throws IOException
- public void write(String str) throws IOException
- public void write(String str, int off, int len) throws IOException
- bstract public void flush() throws IOException
- abstract public void close() throws IOException
abstract public void write(char cbuf[], int off, int len)方法是基础方法,其他四个write()都是根据它实现的。子类至少覆盖整个方法以及flush()和close(),
但是为了提供更高效的实现方法,大多数子类还覆盖了其他一些write()方法。
例如:给定一个Writer对象w,可以这样写入字符串“Jimoer”:
- char[] jimoer = {'J','i','m','o','e','r'};
- w.write(jimoer,0,jiomer.length);
也可以用其他write()方法完成同样的任务:
- Writer w = new OutputStreamWriter(out);
- w.write(jiomer);
- for(int i=0;i<jimoer.length;i++){
- w.write(jiomer[i]);
- }
- w.write("Jimoer");
- w.write("Jimoer",0,5);
所有这些例子表述都是同样的事情,只不过方式有所不同。
书写器可以缓冲,有可能直接串链到BufferedWriter,也有可能直接链入。为了强制将一个写入提交给输出介质,需要调用flush()方法。
OutputStreamWriter
OutputStreamWriter是Writer的最重要的具体子类。OutputStreamWriter会从Java程序中接收字符。会根据指定的编码方式将这些字符转换为直接,并写入底层输出流。
构造函数指定了要写入的输出流和使用的编码方式:
- public OutputStreamWriter(OutputStream out, String charsetName)
- throws UnsupportedEncodingException
- {
- super(out);
- if (charsetName == null)
- throw new NullPointerException("charsetName");
- se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
- }
除了构造函数,OutputStream只有通常的Writer方法,还有一个返回对象编码方式的方法:
- public String getEncoding()
阅读器
Reader是一个抽象类,从不直接使用,只通过子类来使用。有三个read()方法,另外还有skip()、close()、ready()、mark()、reset()和markSupported()方法:
- protected Reader()
- protected Reader(Object lock)
- abstract public int read(char cbuf[], int off, int len) throws IOException
- public int read() throws IOException
- public int read(char cbuf[]) throws IOException
- public long skip(long n) throws IOException
- public boolean ready() throws IOException
- public boolean markSupported()
- public void mark(int readAheadLimit) throws IOException
- public void reset() throws IOException
- abstract public void close() throws IOException
这里面的方法大多数都与Writer中方法能对应上,read()方法以int型(从0到65,535)返回一个单一的Unicode字符或读到流结束时返回-1。
InputStreamReader是Reader的最重要的具体子类。InputStreamReader从其底层输入流中读取字节。然后根据指定的编码发那个还是将字节转为字符,并返回这些字符。
构造函数如下:
- public InputStreamReader(InputStream in)
- public InputStreamReader(InputStream in, String charsetName)
- throws UnsupportedEncodingException
- public InputStreamReader(InputStream in, Charset cs)
- public InputStreamReader(InputStream in, CharsetDecoder dec)
如果没有指定编码方式,就使用平台的默认编码方式。如果指定了一个位置的编码方式,会抛出UnsupportedEncodingException异常。
过滤阅读器和书写器
InputStreamReader和OutputStreamWriter类就相当于输入和输出流之上的装饰器,把面向字节的接口改为面向字符的接口。完成之后就可以将其他面向字符的过滤器放在使用java.io.FilterReader和java.io.FilterWriter类的阅读器或书写器上。
BufferedReader和BufferedWriter也有与阅读器和书写器关联的常用方法,如read()、ready()、write()和close()。这两个类都有两个构造函数,可以将BufferedReader或BufferedWriter串链到一个底层阅读器或书写器,并设置缓冲区的大小。如果没有设置大小,则使用默认的大小8192字符:
- public BufferedReader(Reader in, int sz)
- public BufferedReader(Reader in)
- public BufferedWriter(Writer out)
- public BufferedWriter(Writer out, int sz)
BufferedReader类还有一个readLine()方法,它读取一行文本,并作为一个字符串返回:
- public String readLine() throws IOExceptioin
这个方法可以替代DataInputStream中国已经废弃的readLine()方法,它与该方法的行为基本相同。主要区别在于,通过BufferedReader串链到InputStreamReader,可以用正确的字符集读取行,而不是采用平台的默认编码方式。
BufferedWriter类增加了一个其超类所没有的新方法,名为newLine(),也用于写入一行:
- public void newLine() throws IOException
这个方法向输出插入一个与平台有关的行分隔符字符串。
PrintWriter
PrintWriter类用户取代Java1.0的PrintStream类,它能正确地处理多字节字符集和国际化文本。除了构造函数,PrintWriter类也有与PrintStream几乎相同的方法集。
- public PrintWriter (Writer out)
- public PrintWriter(Writer out,boolean autoFlush)
- public PrintWriter(OutputStream out)
- public PrintWriter(OutputStream out, boolean autoFlush)
- public PrintWriter(String fileName) throws FileNotFoundException
- private PrintWriter(Charset charset, File file) throws FileNotFoundException
- public PrintWriter(String fileName, String csn)
- throws FileNotFoundException, UnsupportedEncodingException
- public PrintWriter(File file) throws FileNotFoundException
- public PrintWriter(File file, String csn)
- throws FileNotFoundException, UnsupportedEncodingException
- public void flush()
- public void close()
- public boolean checkError()
- public void write(int c)
- public void write(char buf[], int off, int len)
- public void write(char buf[])
- public void write(String s, int off, int len)
- 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 s[])
- public void print(String s)
- public void print(Object obj)
- public void println()
- public void println(boolean x)
- public void println(char x)
- public void println(int x)
- public void println(long x)
- public void println(float x)
- public void println(double x)
- public void println(char x[])
- public void println(String x)
- public void println(Object x)
- public PrintWriter printf(String format, Object ... args)
这些方法的行为大多数与PrintStream中相同。只有4个write()方法有所例外,它们写入字符而不是字节。
Java网络编程的Java流介绍的更多相关文章
- java网络编程TCP传输—流操作—拿到源后的写入动作
在网络编程中的TCP传输里,拿到Socket的源后,应该怎么进行读写操作呢,下面我列举了两种方法,希望大家帮忙补充···· 1.利用byte数组作为一个缓冲区进行读写 客户端上传 //获取socket ...
- java网络编程TCP传输—流操作—服务端反馈与客户端接收
在读取完流后,服务端会向客户端返回一些数据,告诉客户端,已经写完了. 在这里和”流操作—拿到源后的写入动作“差不多,客户端同样以byte与Buffered两种缓冲读取作为例子,同时,.也是希望大家给补 ...
- Java 网络编程学习总结
新手一枚,Java学习中,把自己学习网络编程的知识总结一下,梳理下知识,方便日后查阅,高手莫进. 本文的主要内容: [1] 网络编程认识 [2] TCP/IP编程 ...
- JAVA网络编程【转】出处不详
网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...
- 【转】JAVA 网络编程
网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...
- Java网络编程和NIO详解开篇:Java网络编程基础
Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...
- java网络编程+通讯协议的理解
参考: http://blog.csdn.net/sunyc1990/article/details/50773014 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很 ...
- Java工程师学习指南第5部分:Java网络编程与NIO
本文整理了微信公众号[Java技术江湖]发表和转载过的Java网络编程相关优质文章,想看到更多Java技术文章,就赶紧关注本公众号吧. 深度解读 Tomcat 中的 NIO 模型 [Java基本功]浅 ...
- Java - 网络编程完全总结
本文主要是自己在网络编程方面的学习总结,先主要介绍计算机网络方面的相关内容,包括计算机网络基础,OSI参考模型,TCP/IP协议簇,常见的网络协议等等,在此基础上,介绍Java中的网络编程. 一.概述 ...
随机推荐
- [转]数据库中间件 MyCAT源码分析——跨库两表Join
1. 概述 2. 主流程 3. ShareJoin 3.1 JoinParser 3.2 ShareJoin.processSQL(...) 3.3 BatchSQLJob 3.4 ShareDBJo ...
- 代码图片转文本--阿里VS度娘
最近看vue的书,居然没有提供源代码,一堆的CSS真不想手工录入,于是用手机找APP去转.发现广告普遍,于是找找网上相关的API,结果百度和阿里都有在线的API提供,于是好奇其能力如何.如于用以下两图 ...
- ADO.NET MSSSQLServer 操作简要总结
1).数据库操作首先应该是链接,链接的方式有多种,不论怎样实现最终都是把链接字符串赋值给实现了 继承自DbConnection 类的SqlConnection类实例的ConnectionString属 ...
- 创建JUtil
这里拿Dynamic项目来演示,首先创建一个Dynamic项目,起名,点next, 继续点next, 将web.xml文件勾选,finish, 接下来在Java Resources->src下创 ...
- 基于fpga的vga学习(3)
本次学习如何通过vga发送数字.文字.字母, 首先利用建模软件,将想要发送的数据通过数学建模转换,这里我用的软件是PCtoLCD,具体效果如下 这里可以看出,建模将数据装换成0和1,一个字母用16x8 ...
- 程序员基层知识程序与cpu【更新1】
我是一个小白,但我通过学习我慢慢的懂了一些事.分享出来有错误的望大咖指导. 我们要很明确的明白我们学的是编程而不是编程语言,编程语言只是工具用来方便我们做很多编程有关的事. 首先我在学习的过程中,第一 ...
- ORACLE知识点总结
一.ORACEL常用命令 1.解锁账户:ALTER USER username ACCOUNT UNLOCK; 2.查看数据库字符集:SELECT USERENV ('language') FROM ...
- Python小练习之判断一个日期是一年的第几天
python练手遇到的一个问题写了个统一公式,不用麻烦的分各种类,如果有人测试出错误请评论通知. #分单双月 def dayNum(month,day,isLeap): if month % 2 != ...
- java数据结构面试问题—快慢指针问题
上次我们学习了环形链表的数据结构,那么接下来我们来一起看看下面的问题, 判断一个单向链表是否是环形链表? 看到这个问题,有人就提出了进行遍历链表,记住第一元素,当我们遍历后元素再次出现则是说明是环形链 ...
- Java面试中的“劲敌”线程,9个疑问全面解析
作者:我是攻城师 (一)创建线程的方式 (1)实现Runnable接口 (2)继承Thread类 推荐使用接口,能够做到定义与实现分离,耦合更低 (二)关于线程的优先级 thread2.setPrio ...