java特种兵读书笔记(4-1)——java通信之概述

java设计的初衷就是为了网络,网络的基础就源自于通信。

java进程并不真正参与网络通信,而只是将数据交由Kernel去处理。

两组接口


InputStream和OutputStream

这两个接口是操作字节的。Reader和Writer操作的是字符,其实是通过对前者的封装得到的。

BufferInputStream和BufferOutputStream

有缓冲作用,这两个所谓的缓冲区是java堆中的一个byte数组,默认是8192个字节,每次通过写操作时,如果没有发起flush、close操作,而且byte数组没有全满,则不会讲数据真正发送出去。真正发送时依然是将java区域的一个byte数组调用write方法交给kernel。

如果有可控大小的byte数组,也可以选择不使用Buffer来控制。

FileOutputStream和FileInputStream

SocketOutput/InputStream的父类,并没有实现Buffer功能,发送数据是直接基于默认的OutputStream的flush方法,它是空方法,不做任何动作,所以使用这一组接口的时候,不需要调用flush(只是在内核中有一些buffer机制)。

有些容器和第三方框架,通过修饰继承等方式实现了对BufferStream的间接使用,或者自定义一种Buffer机制,因为不能简单根据看到拿到的类型来判断它是否使用了Buffer。

真正IO的时候,操作的是字节,计算机中只有数字存在,字符只是某种字符集下由数字表示出来的而已。

编码解码


计算机中的char也是由数字组成的,java的char是UTF-16编码,每个字符占用两个字节的宽度。传送中需要约定字符集,发送发用什么方式编码,接收方就用什么方式来解码。

如果编码解码字符集不同,结果就会错误。转换字节很简单,例如,发送方发送两个汉字,用utf-8编码,接收方不知道,用GBK编码的,首先接收方会得到一堆字符的字符串,调用getByte("GBK"),就把这些字符以GBK编码变为字节,然后再用new String(byte[], "utf-8")重新得到汉字。

当然这不一定正确,utf-8编码得到的6个字节,当按照每2个字节组成编码时,未必在GBK的编码范围之内,这时会用一个?来表示,由于?本身也是一个字符,这样就无法正确转换了。

IO交互过程中,会在协议的头部中告诉对方使用什么编码,对方按照这个格式来解码。在HTTP协议中,浏览器以contentType的方式告诉服务器端本次传送的字符集是什么(字符集的概念是系统中有字符与字节转换才有的)。服务端输出数据时也会以这种方式告诉浏览器。可以在浏览器的debug下面看到相应的头部信息。

utf-8编码只是针对汉字用3个字节来存放,其他语言有可能大于或者小于3个字节。

协议


数据传输中除了数据本身,还有在数据之前的头部,用来描述连接操作,数据格式相关的内容。例如HTTP网络协议编解码就在头信息的content-type项,另外user-agent,referer等信息都可能包装在协议头部。

java的socket是基于TCP/IP协议的通信,他没有自己想要告诉对方的一些头部信息(例如内容的长度和格式等等)。

服务端和客户端程序


打开Socket端口,用java的SocketServer,指定一个端口,然后socketServer调用accept方法返回一个socket,然后通过这个socket的输入输出流进行通信。

注意:要有用户验证,用户名密码要做加密(不要用Base64,相当于明文)。

log4j与system.out


对屏幕输出来说,log4j就是封装system.out/err来实现的,没有任何优化,所以性能两者一致。而且这个对象JVM进程全局唯一,所以输出的时候还会用synchronized来串行化。

如果是写日志文件的话,log4j会提供Buffer机制,会在默认的文件输出上套一层BufferWriter,这样日志会先写入内存的Buffer中,当Buffer不够用的时候,才写文件,提升了系统系能。

java的IO流


java的IO最终会使用字节来操作,所以首先要知道的是Input/OutputStream接口,这是最基本的接口。

FileInput/OutputStream是一组实现,实现了对文件的输入输出操作。

FileReader和FileWriter是字符流操作的类,内在通过sun的StreamDecoder/Encoder来对字符集进行处理。但最终还是通过字节来发送和接收的。

Reader和Writer是字节字符转换的桥梁,在实例化时最好自己指定字符集

流的套用


网络通信用到的SocketXXXStream继承与FileXXXStream,只是前者的缓冲区是在内核中完成的。

当希望API处理流中的不同数据类型时,需要使用DataXXXStream。

压缩数据使用ZipXXXStream和GZipXXXStream。

希望通过内存的某些数据得到流,会使用ByteArrayXXXStream,例如用字符串获得流,使用new ByteArrayXXXStream("xxx".getBytes("GBK"))

需要对自定义对象做输入输出时,需要使用ObjectXXXStream。

ServletXXXStream是web层对原始socket流做的多层包装。

需要对数据做Buffer时,需要使用BufferedXXXStream。

当两个流的功能十分相似时,用继承来完成的代价很小。如果不是,用嵌套(修饰)的方式来完成。从原理上讲,它是将一个Stream的对象引用设置为另一个Stream的属性,逐层向下调用,实现功能合并。

比如读取文件使用FileXXXStream,可以用BufferedXXXStream在外面嵌套一层,那么它就有了Buffer的功能来读取文件了。

FilterXXXStream是最简单的一种流嵌套的方式,它仅仅做了一个引用的保存,所有功能都是间接调用构造方法中传入的Stream来完成的。

这是一个中间过滤层,它将接口的方法实现了一层通用的代理处理,即默认与原来对象的功能保持一致,许多具体功能修饰可以基于该类通过继承的方式来完成,不用重写所有的方法了。

Reader和Writer


只有处理文本类数据时才会用到Reader和Writer。

字节流读取一个字节就是一个字节,字符流读取一个字符可能要读取多个字节,会根据字符集的不同取不同长度字节转换为对应字符(或者将字符转换为不同长度的字节来发送)。

比如BufferedWriter和BufferedReader,它们都是数组实现,只不过相比于BufferedXXXStream,它们使用char[]来代替byte[]。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值