流:字节流和字符流
字节流:处理二进制数据,用于处理原始数据,不应该用来处理文本内容
字符流:处理字符数据,自动转换本地字符集
行尾结束符: \r\n 或者\r 或者\n
对象流:处理对象二进制数据
需要对流进行关闭:
字符流操作:Reader,Writer
字节流操作:InputStream,OutputStream
Channel,GatheringByteChannel,InterruptibleChannel
ReadableByteChannel,ScatteringByteChannel,WritableByteChannel
Channels, DatagramChannel,FileChannel,FileLock,Pipe
SelectableChannel,SelectionKey,Selector,ServerSocketChannel,SocketChannel
中文两个字节,英文1个字节,一个字节8bit,高位补零
字节流不会使用内存缓冲,文件本身直接操作
字符流操作使用内存缓冲,用缓冲操作文件,字符流在输出前将内容保存在内容中
主要有四类操作:
1:字节操作:InputStream和OutputStream
2:字符操作:Reader和Writer
3:磁盘操作:File
4:网络操作:Socket
无论是网络还是磁盘,存储单元都是字节,字符是在字节的基础上进行了转换:
InputStreamReader
拿到字节数据后,要指定编码字符集,否则采用的是系统默认的字符集
磁盘IO机制:
系统在内核空间中加入了缓存机制,如果用户访问的是同一磁盘地址的空间数据,那么会从缓存中返回
标准访问方式:
调用read时,操作系统检查内核缓存中有没有数据,如果没有则从磁盘读取并缓存到内核缓存
调用write时:先复制到缓存中,对于用户来说操作已经完成,什么时候写入磁盘由操作系统决定
直接IO方式:
应用直接访问磁盘,不经过系统内核数据缓存区,通常使用在数据库管理系统中
同步访问方式:读取和写入都是同步操作,写入经过高速缓存然后进入磁盘,读取经过磁盘到高速缓存
异步访问方式:发起读写请求后,线程去处理其他的,等待高速缓存从磁盘读取回来后再操作
内存映射方式:把内存的一块区域和文件关联起来,文件的数据就是内存中的数据
FileInputStream会创建一个FileDescriptor对象,这个对象是真正代表一个存在的文件对象的描述
可以通过getFD犯法获取真正操作与底层操作系统相关的文件的描述
传入文件名时,创建一个File对象。读取File对象内容时,创建一个FileDescriptor操作,使用StreamDecoder类将byte解码成char格式。然后返回char数据
Java序列化技术:java序列化就是将一个对象转换成一串二进制字节数组。这样,对象就能保存和传输,进行持久化。对象需要实现Serializable接口
序列化的二进制数组主要包含5个部分内容:
1:序列化文件头:使用的序列化协议,序列化协议,该数组的类型(比如说是一个对象)
2:序列化类的描述,比如是Serialize类。包含一个类声明,class名字长度,完整类名,序列化ID,如果没有指定则生成一个8字节的ID,还有该类的字段个数
3:对象中各个属性项的描述
4:该对象的父类信息描述
5:该对象的属性项的实际值。
序列化的复杂情况:
1:父类实现Serializable接口时,所有子类都可以被序列化
2:子类实现Serializable接口,而父类没有实现,则父类属性不能被序列化,不报错,数据丢失,子类仍然能序列化。
3:如果序列化的对象的属性也是对象,那么子对象也需要实现序列化接口,否则报错
4:反序列化时,对象的属性有修改或删减,则修改部分的属性会丢失,但不会报错
5:反序列化时,如果serialVersionUID被修改,则反序列化时会失败
TCP连接:
TCP状态:
CLOSED:
LISTEN:等待连接状态
SYN-SENT:客户端发起连接,发送SYN给服务器,如果接受不到则直接进入CLOSED
SYN-RCVD:服务器接收请求,返回ACK响应
ESTABLISHED:服务器端和客户端在完成3次握手后进入该状态,说明可以传输数据了
FIN-WAIT-1:发送FIN给对方
FIN-WAIT-2:接收FIN ACK数据
CLOSE-WAIT:
LAST-ACK:发起关闭请求
CLOSING
TIME-WAIT:
closed--->listen :被动打开
closed--->syn-send:主动打开,发送SYN
listen->syn-received:收到syn,发送syn-ack
syn-send->syn-received:同时打开,收到syn,发送syn-ack
syn-received-------->established:收到ack
syn-send---------->established:收到syn-ack,发送ack
established-------->FIN-WAIT1:关闭,发送FIN
FIN-WAIT1-------->FIN-WAIT2:收到对FIN的ACK
FIN-WAIT2------->TIME-WAIT:收到FIN,发送ACK
FIN-WAIT1------>CLOSING:收到FIN,发送ACK,同时关闭
CLOSING------->TIME-WAIT:收到对FIN的应答
ESTABLISHED-------->CLOSE-WAIT:收到FIN,发送ACK
CLOSE-WAIT--------->LAST-ACK:等待应用程序关闭,发送FIN
LAST-ACK------------>CLOSED:收到对FIN的ACK
影响网络传输因素:
1:带宽:1秒传输最大比特数
2:传输距离:光纤的折射率导致有传输延迟。
3:TCP拥堵:停-等-停-等
NIO工作方式:非阻塞IO
一旦发生阻塞,线程就会失去CPU的使用权,在大规模访问下是不能允许的。
工作过程:
0:创建一个ByteBuffer来获取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
1:Selector静态工厂方法创建一个选择器selector
Selector selecotr = Selector.open();
2:创建一个服务器端的Channel,然后绑定到一个Socket对象,并注册到selector上
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);//非阻塞模式
ssc.socket.bind(new InetSocketAddress(8080));
ssc.register(selector,SelectionKey.OP_ACCEPT);//注册监听事件
3:调用selector的selectedKeys方法来检查是否有事件发生,如果有事件发生则返回SelectionKey,通过这个对象的Channel方法获得通信信道对象:
Set selectedKeys = selector.selectedKeys();
SelectionKey key = 遍历selectedKeys
if(key.readOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT){
ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
SocketChannel sc = ssChannel.accept();//
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ)
} else if((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
SocketChannel sc = (SocketChannel) key.channel();
sc.read(buffer);
buffer.flip();
}
RAID策略:
RAID0:数据被平均写入多个磁盘阵列,写数据和读数据都是并行的,所以磁盘的IOPS可以提高一倍
RAID1:将一份数据复制到多个磁盘阵列中,不能提高IOPS,但能提高安全性
RAID5:前两种方式的折中。将数据平均写到所有磁盘阵列总数减1的磁盘中,往另外一个磁盘中写入这份数据的奇偶校验信息。如果其中一个磁盘数据损坏,可以通过其他磁盘的数据和这个数据的奇偶校验信息来恢复这份数据。
RAID0+1:根据数据的备份情况进行分组,一份数据同时写入多个备份磁盘分组中,同时多个分组也会进行并行读写。
java中操作字符的Reader和Writer中有StreamDecoder,StreamEncoder,负责字符和字节的转换。
内存中的编码:在内存中进行从字符到字节的转换。String类提供了getBytes(字符集)来转换
和new String(bytes, 字符集);
已经废弃的ByteToCharConverter和CharToByteConverter,使用Charset取代,Charset提供encode和decode方法
还有一个ByteBuffer类,提供char和byte的软交换,他们之间的转换不需要编码,仅仅只是把一个16bit的char拆分成2个8bit的byte表示。
java内存编码使用的是utf-16编码,效率高,但不利于网络之前传输。
因为网络传输容易损坏字节流,字节流损坏将很难恢复,所以相比较utf-8更适合网络传输。
utf-8对ascii字符使用单字节存储,另外单个字符损坏也不会影响后面的其他字符。是理想的编码方式。
大多数IO都是没有缓冲的,这意味着每个读写请求都是底层OS直接处理的。这让程序比较低效,因为每次请求都会触发磁盘访问,网络活动或一些相对昂贵的操作
为了减少这种类型的开销,java平台实现缓冲IO stream。缓冲的输入流从内存区域读取数据作为buffer,仅当buffer为空的时候,才会调用原生输入api。类似的,缓冲的输出流写入数据到一个buffer,当buffer满了的时候原生输出api才会被调用。
flush缓冲流:在关键点写出缓冲区而不需要等它填充是有意义的。这个被称为flush
一些缓冲输出类支持自动flush,通过构造参数指定一个选项。当自动刷新开启,特定关键事件会导致缓冲会被flush
NIO:
标准IO是面向字节流和字符流的,而NIO是面向通道和缓冲区的,数据总是从通道中读到buffer缓冲区内,或者从buffer写入通道
核心组件:
Channels:Buffer的数据从Channel中读取或写入
FileChannel,DatagramChannel,SocketChannel,ServerSocketChannel
Buffers:实际就是一个块内存,有三个主要属性:capacity容量,position位置,limit限制
ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer
Selectors:选择器允许单线程操作多个通道,Channel需要注册到Selector上
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
buf.flip(); //make buffer ready for read
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // read 1 byte at a time
}
buf.clear(); //make buffer ready for writing
bytesRead = inChannel.read(buf);
FileChannel类型可以把数据直接传输到另一个channel上:
两个都是FileChannel:
aChannel.transferFrom(bChannel);
aChannel.transferTo(bChannel);
注册selector:
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Unrecognized Windows Sockets error: 0: recv failed:
socket连接中有数据未处理
=================
编写Socket服务器:
只需要返回http协议中的内容即可
BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream());
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
writer.write("HTTP/1.1 200 OK");
writer.newLine();
writer.write("Server: Apache-Coyote/1.1");
writer.newLine();
writer.write("Content-Type: text/html;charset=UTF-8");
writer.newLine();
String body = "
writer.write("Content-Length: " + body.length());
writer.newLine();
writer.write("Date: Mon, 03 Nov 2014 06:37:28 GMT");
writer.newLine();
writer.write("\n" + body);
writer.flush();
writer.close();
在body处需要\n,否则浏览器不能正确识别响应实体
在读取请求的数据时,只能判断最后的内容为空字符串,不能判断为null
ServerSocket serverSocket = new ServerSocket(1099);
Socket socket = serverSocket.accept();
BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String lineStr = null;
while (!(lineStr=reader.readLine()).equals("")){
System.out.println(lineStr);
}