NIO与Socket编程技术指南

java的技术点不止是ssh、ssm,更应该落脚在多线程、并发处理、NIO以及Socket技术上

多线程

高性能的解决方案离不开多线程,使1个cpu运行更多的任务,使用Socket实现某些功能时是需求借助于多线程

并发处理

concurrent并发包是对多线程技术的封装

Socket

高性能的服务器的架构设计离不开集群,集群同样离不开Socket,Socket技术可以实现不同计算机间的数据通信,从而实现在集群中的服务器之间进行数据交换

NIO介绍

a、NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。

b、NIO系统的核心在于通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。

第一章:缓冲区的使用

1、缓冲区介绍

a、缓冲区是一个用于特定基本类型的容器。由java.nio 包定义,所有缓冲区都是 Buffer 抽象类的子类。

b、Java NIO 中的 Buffer ,主要用于与NIO 通道进行交互。数据从通道存入缓冲区,从缓冲区取出到通道中。

  • 容量(capacity)

capacity指的是缓冲区能够容纳元素的最大数量,这个值在缓冲区创建时被设定,而且不能够改变,如下,我们创建了一个最大容量为10的字节缓冲区;

ByteBuffer bf = ByteBuffer.allocate(10);

  • 上界(limit)

limit指的是缓冲区中第一个不能读写的元素的数组下标索引,也可以认为是缓冲区中实际元素的数量;

  • 位置(position)

position指的是下一个要被读写的元素的数组下标索引,该值会随get()和put()的调用自动更新;

  • 标记(mark)

一个备忘位置,调用mark()方法的话,mark值将存储当前position的值,等下次调用reset()方法时,会设定position的值为之前的标记值;

  • 四个属性值之间的关系

0 <= mark <= position <= limit <= capacity 

 1、创建一个容量大小为10的字符缓冲区

ByteBuffer bf = ByteBuffer.allocate(10);

此时:mark = -1; position = 0; limit = 10; capacity = 10;

2、往缓冲区中put()字节

bf.put((byte)'H').put((bte)'e').put((byte)'l').put((byte)'l').put((byte)'0');

注意这里一个字符是占用两个字节的,但是英文字符只占用一个字节,所以这样是可以实现储存效果的;

此时:mark = -1; position = 5; limit = 10; capacity = 10;

3、翻转:调用flip()方法,切换为读就绪状态

bf.flip();

此时:mark = -1; position = 0; limit = 5; capacity = 10;

 

 4、读取两个元素

System.out.println("" + (char) bf.get() + (char) bf.get());

 此时:mark = -1; position = 2; limit = 5; capacity = 10;

5、标记此时的position位置

bf.mark();

此时:mark = 2; position = 2; limit = 5; capacity = 10;

6、回到之前mark的位置处

System.out.println("" + (char) bf.get() + (char) bf.get());
bf.reset();

属性变化情况:

执行完第一行代码:mark = 2; position = 4; limit = 5; capacity = 10;

执行完第二行代码:mark = 2; position = 2; limit = 5; capacity = 10;

7、调用compact()方法,将limit-position的部分挪到开始处

bf.compact();

此时:mark = 2; position = 3; limit = 10; capacity = 10;

 注意观察数组中元素的变化,实际上进行了数组拷贝,抛弃了已读字节元素,保留了未读字节元素;

2、创建/复制缓冲区

字节缓冲区要么是 直接缓冲区,要么是 非直接缓冲区。

  • 非直接缓冲区

属于常规操作,传统的 IO 流和 allocate() 方法分配的缓冲区都是非直接缓冲区,建立在 JVM 内存中。磁盘上的文件在与应用程序交互的过程中会在两个缓存中来回进行复制拷贝。

  • 直接缓冲区

用于显著提升性能,缓冲区直接建立在物理内存(相对于JVM 的内存空间)中,省去了在两个存储空间中来回复制的操作,可以通过调用 ByteBuffer 的 allocateDirect() 工厂方法来创建。

a、直接缓冲区中的内容可以驻留在常规的垃圾回收堆之外

b、直接缓冲区还可以通过 FileChannel 的 map() 方法将文件直接映射到内存中来创建,该方法将返回 MappedByteBuffer 。

c、直接或非直接缓冲区只针对字节缓冲区而言。字节缓冲区是那种类型可以通过 isDirect() 方法来判

注意!!!

直接缓冲区性能虽然好,但是缓冲区直接建立在物理内存中,无法由 GC来释放,可控性差,同时分配和销毁成本很高!在对性能不是特别依赖的场景不建议使用!

创建缓冲区的方式

  • allocate

还可以分配直接内存空间(如ByteBuffer,可以调用allocateDirect方法分配直接内存)

CharBuffer bf = CharBuffer.allocate(10);
  • wrap

与allocate方法的区别是,它的缓存存储空间是外部传入的;

        char[] myArray = new char[10];
        CharBuffer charbuffer = CharBuffer.wrap(myArray);
  • wrap (myArray,offset , length)

另外,wrap还有一个重载方法:带offset和length作为参数的wrap()方法,创建一个position = 2, limit = 5, capacity = 10的Buffer;

        char[] myArray = new char[10];
        CharBuffer charbuffer = CharBuffer.wrap (myArray, 2, 3);
 

复制缓冲区的方式

1、调用duplicate方法

2、调用asReadOnlyBuffer方法

3、调用slice方法

  • duplicate

不是深拷贝,是浅拷贝,每个缓存区的上界、容量、位置等属性是各自独立的,但这两个缓存区会共享数据元素修改其中一个缓存区的元素会影响另一个缓存区,如下示例:

        CharBuffer charbuffer1 = CharBuffer.allocate(10);
        CharBuffer charbuffer2 = charbuffer1.duplicate();

        charbuffer1.put('a').put('b').put('c');
        charbuffer1.flip();

        System.out.println(charbuffer1);
        System.out.println(charbuffer2);

结果打印如下:

abc
abc

  • asReadOnlyBuffer

调用asReadOnlyBuffer方法会生成一个只读缓存区,与调用duplicate方法基本一致,唯一的区别是这个缓存区是只读的,不能对其进行put操作,否则报错

        CharBuffer charbuffer1 = CharBuffer.allocate(10);
        CharBuffer charbuffer2 = charbuffer1.asReadOnlyBuffer();

        charbuffer1.put('a').put('b').put('c');
        charbuffer1.flip();

        System.out.println(charbuffer1);
        System.out.println(charbuffer2);

        charbuffer2.put('c');//ReadOnlyBufferException

输出结果:

abc
abc
Exception in thread "main" java.nio.ReadOnlyBufferException
    at java.nio.HeapCharBufferR.put(HeapCharBufferR.java:166)
    at nio.Main.main(Main.java:21)

 

  • slice

slice方法其实是用于分割缓存区的,该方法创建了一个从原始缓冲区的当前位置开始的新缓冲区,并且其容量是原始缓冲区的剩余元素数量(limit-position);

该缓存区与原始缓存区共享一段序列;

a、创建一个容量为10的缓存区charbuffer1

CharBuffer charbuffer1 = CharBuffer.allocate(10);
此时:mark = -1; position = 0; limit = 10; capacity = 10;

b、修改charbuffer1的position和limit值

charbuffer1.position(2).limit(5);

此时:mark = -1; position = 2; limit = 5; capacity = 10;

c、调用slice方法,对charbuffer1缓存区进行分割

CharBuffer charbuffer2 = charbuffer1.slice();
此时:

charbuffer1:mark = -1; position = 2; limit = 5; capacity = 10;

charbuffer2:mark = -1; position = 0; limit = 3; capacity = 3;

 

 

 

 

3、ByteBuffer类的使用

Buffer类的子类都定义了两种get(读)和put(写)操作,一种是相对位置一种是绝对位置

  • put(byte b)和get()方法

a、相对位置:对当前位置进行读写

b、在执行相对位置的读写操作以后,位置(position)呈现递增的状态,位置自动移动到下一个位置上,以便进行下一次读或者写的操作。

  • put(byte[] src , int offset , int length)和get(byte[] dst , int offset , int length)

把给定原数组中的字节传输到此缓冲区当前位置中

dst.put(src , offset , length)等同于
for(int i = offset ; i < offset+length ; i++){
    dst.put(src[i])
}
src.get(dst,offset,length)等同于
for(int i = offset ; i < offset+length ; i++){
	dst[i] = src.get();
}
  • put(byte[] src )和get(byte[] dst )
  • put(int index , byte b)和get(int index)

指定位置

第二章:通道与FileChannel

1、通道基础

什么是通道Channel

a、通道主要用于传输数据,从缓冲区的一侧传到另一侧

b、通道是访问IO服务的导管,通过通道,我们可以以最小的开销来访问操作系统的I/O服务;

c

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值