javaNIO 学习笔记(二)

javaNIO 学习笔记(二)

参考文档:http://tutorials.jenkov.com/java-nio/channels.html

Java NIO Channel

和传统的IO中的 InputStreamOutputStream相比,主要区别就是传统IO中的stream是单向的。但是在NIO中的channel是可读可写的。并且读写是可以异步进行的。另外channel都是从buffer中读取或者写入数据的

上一篇学习笔记中的主要实现的管道类型

  • FileChannel 从文件和文件读取数据

  • DatagramChannel 可以通过UDP在网络上读写数据。

  • SocketChannel 可以通过TCP在网络上读写数据。

  • ServerSocketChannel 允许您侦听传入的TCP连接,就像web服务器一样。为每个传入连接创建SocketChannel。

这4个类在idea中可以查看相应的UML图

FileChannel

先看下面的程序

读取文件数据:

public class FileChannelPrc {

    public static void main(String[] args) throws IOException {
        // 创建一个rw模式的随机文件
        RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\nioFile.txt", "rw");
        // 获取fileChinnel
        FileChannel fileChannel = randomAccessFile.getChannel();

        // 设定缓冲区大小48
        ByteBuffer buf = ByteBuffer.allocate(48);
        
        int bytesRead = fileChannel.read(buf);
        while(bytesRead !=-1){
            System.out.println("\nRead " + bytesRead);
            
            buf.flip();

            while(buf.hasRemaining()){
                System.out.print((char) buf.get());
            }

            buf.clear();
            bytesRead = fileChannel.read(buf);
        }
        randomAccessFile.close();
    }
}

// 运行结果:
Hi, I am jimmy. 

上面程序要正确执行需要,现在本地对应文件夹中创建相应文件。我在文件中写入的是Hi, I am jimmy.这里就可以看到读取出来并且打印了。

然后我们将文件内容修改为Hi, I am jimmy. I am learning Java NIO. Hope I can hold it.(大于48字符)。在运行程序,此时返回值

Read 48
Hi, I am jimmy. I am learning Java NIO. Hope I
Read 13
can hold it.

通过上面的测试我们大概可以猜测。

// 通过fileChannel将数据读取到缓冲区,返回值是缓冲区读取的字节数
int bytesRead = fileChannel.read(buf);
// 判断缓冲区是否还有数据
buf.hasRemaining()
// 获取当前缓冲区的第一个元素
buf.get()
// 切换缓冲区读写模式    
buf.flip();
// 清空缓存区
buf.clear();

我们现在先只来看channel相关的猜测。改造程序(改造部分如下)

// 设定缓冲区大小
ByteBuffer buf = ByteBuffer.allocate(48);
ByteBuffer buf2 = ByteBuffer.allocate(48);
int bytesRead = fileChannel.read(buf);
int bytesRead2 = fileChannel.read(buf);
System.out.println("bytesRead" + bytesRead + "  bytesRead2" + bytesRead2);

此时走debug或者查看打印语句就可以看出来,bytesRead = 16 bytesRead2 = -1(文件信息Hi, I am jimmy.)。那么这样就可以得出一个结论

fileChannel.read(buf);会将channel的数据flush到缓冲区,而不是复制。

下面是程序则是使用fileChannel写信息到文件中

public class FileChannelPrc {

    public static void main(String[] args) throws IOException {
       
        ...
        // 写文件
//        ByteBuffer buf2 = ByteBuffer.allocate(48);
        String newData = "do not worry, be happy 时间:" + System.currentTimeMillis();
        buf.clear();
        buf.put(newData.getBytes());
        while(buf.hasRemaining()) {
             buf.flip();
            fileChannel.write(buf);
        }

        fileChannel.close();
        randomAccessFile.close();
    }
}

查看文件,文件文本显示

Hi, I am jimmy. do not worry, be happy 时间:1591970658685

看这样的结果可以看出来,fileChannel写数据的时候是在文件结尾处增加写入数据,而非覆盖。

那么根据上面的例子我们先简单的总结下fileChannel简单使用方法。

  • 获取fileChannel的实例(注意public abstract class FileChannel这是个抽象类),获得方式

可以使用RandomAccessFile的实例来获取

  • 读取数据的方法使用read将内容读取到ByteBuffer,当然这里也设计ByteBuffer的使用,这个后面在学习总结
  • 写过程呢则是使用write方法向文件写入ByteBuffer的信息。
  • 当然最后和传统IO一样,需要调用close来关闭fileChannel

接下来稍微看下fileChannel的源码(这里也算是写一个备忘录,以后遇到相关问题的时候可以回来看下,或许会有帮助)

// ----   read方法   -------
public abstract int read(ByteBuffer dst) throws IOException;
// 从指定位置开始读取数据
public abstract int read(ByteBuffer dst, long position) throws IOException;
public final long read(ByteBuffer[] dsts) throws IOException {
        return read(dsts, 0, dsts.length);
}
public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;

// ------  write方法  ------
public abstract int write(ByteBuffer src) throws IOException;
public abstract int write(ByteBuffer src, long position) throws IOException;
public final long write(ByteBuffer[] srcs) throws IOException {
    return write(srcs, 0, srcs.length);
}
public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException;

// ---- open方法 ------  @since   1.7  这个方法可以获取fileChannel
public static FileChannel open(Path path, OpenOption... options) throws IOException
{
    Set<OpenOption> set = new HashSet<OpenOption>(options.length);
    Collections.addAll(set, options);
    return open(path, set, NO_ATTRIBUTES);
}
public static FileChannel open(Path path,  Set<? extends OpenOption> options,
                                   FileAttribute<?>... attrs) throws IOException
{
    FileSystemProvider provider = path.getFileSystem().provider();
    return provider.newFileChannel(path, options, attrs);
}

// ---  position  --
// 获取一个新的fileChannel
 public abstract FileChannel position(long newPosition) throws IOException;
// 返回buffer读取的位置
public abstract long position() throws IOException;
// --- force  ----
// 强制将channel的数据写入,根据metaData决定是否写入本地磁盘存储
public abstract void force(boolean metaData) throws IOException;
// Returns the current size of this channel's file. 返回当前通道中的文件大小
public abstract long size() throws IOException;
// 截取指定长度的数据,从开始进行截取长度为 size的
public abstract FileChannel truncate(long size) throws IOException;

当然还有一些其他方法,这些等以后涉及使用的时候在补充吧。这里附上测试的时候的代码(非全部,有的API直接可使用idea的片段执行查看结果。)

public class FileChannelPrc {

    public static void main(String[] args) throws IOException {
        // 创建一个rw模式的随机文件
        RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\nioFile.txt", "rw");
        // 获取fileChinnel
        FileChannel fileChannel = randomAccessFile.getChannel();

//        fileChannel.position();
        FileChannel fileChannel2 = fileChannel.truncate(10);
        // 设定缓冲区大小
        ByteBuffer buf = ByteBuffer.allocate(48);

//        int bytesRead = fileChannel.read(buf,10L);
        int bytesRead = fileChannel2.read(buf);

//        ByteBuffer buf2 = ByteBuffer.allocate(48);

//        int bytesRead = fileChannel.read(buf);
        /*int bytesRead2 = fileChannel.read(buf);
        System.out.println("bytesRead" + bytesRead + "  bytesRead2" + bytesRead2);*/
        while(bytesRead !=-1){
            System.out.println("Read " + bytesRead);
            buf.flip();

            while(buf.hasRemaining()){
                System.out.print((char) buf.get());
            }

            buf.clear();
//            bytesRead = fileChannel.read(buf);
            bytesRead = fileChannel2.read(buf);
        }

        // 写文件
//        ByteBuffer buf2 = ByteBuffer.allocate(48);
       /* String newData = "do not worry, be happy 时间:" + System.currentTimeMillis();
        buf.clear();
        buf.put(newData.getBytes());
        while(buf.hasRemaining()) {
            buf.flip();
            fileChannel.write(buf);
        }*/

        fileChannel.close();
        randomAccessFile.close();
    }
}

SocketChannel 和 ServerSocketChannel

socketChannel是一个用来处理TCP网络套接字的通道。有俩种方式来获取SocketChannel

  • 使用open()可以直接获取一个实例
  • 通过ServerSocketChannel来获取
SocketChannel socketChannel = SocketChannel.open();

ServerSocketChannel类似于标准Java网络中的ServerSocket一样,是一个可以侦听传入TCP连接的通道.

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

no talk,show code

package jniolearn.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/**
 * @Author: jimmy
 * @Date: 2020/6/13 15:03
 * @Description:
 *
 * 理解为server
 */
public class ServerSocketChannelPrc {

    public static void main(String[] args) throws IOException {

        // 获取实例
        ServerSocketChannel server = ServerSocketChannel.open();
        // 绑定ip和端口
        server.socket().bind(new InetSocketAddress("127.0.0.1",2999));
        //设置为非阻塞模式
        server.configureBlocking(false);
        // 获取 SocketChannel
        while(true){
            SocketChannel serverChannel =  server.accept();

            if(serverChannel != null) {
                ByteBuffer buf = ByteBuffer.allocate(512);
                int byteRead = serverChannel.read(buf);

                StringBuilder req = new StringBuilder();
                buf.flip();
                while(buf.hasRemaining()){
                    req.append((char) buf.get());
                }
                buf.clear();

                System.out.println("客户端发来消息" + req);

                buf.put("hi,this is jimmy. be happy ".getBytes());
                buf.flip();
                serverChannel.write(buf);

    //        serverChannel.close();
            }
        }
    }
}


package jniolearn.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @Author: jimmy
 * @Date: 2020/6/13 12:04
 * @Description:
 *
 * 可以理解为client
 */
public class SocketChannelPrc {

    /**
     * 为了方便,这里直接将异常抛出,实际开发要进行异常捕捉
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        //  获取 SocketChannel 的实例
        SocketChannel client = SocketChannel.open();
        /**
         * 连接服务端,这里面传入的参数对象为 SocketAddress
         * 查看源码可以找到一个实现类 InetSocketAddress 构造方法 public InetSocketAddress(String hostname, int port)
         * 看到这个构造就很熟悉了,
         *
         * 连接服务  127.0.0.1 和 2999 端口
         */
        client.connect(new InetSocketAddress("127.0.0.1",2999));

        // 有channel就需要有缓冲区buffer
        ByteBuffer buf = ByteBuffer.allocate(512);
        buf.put("hi, jimmy,this is client.".getBytes());
        // 切换读写模式
        buf.flip();
        // 写数据去服务器
        client.write(buf);
        // 清空buffer,此时相当于将buffer重置为读模式
        buf.clear();
        int byteRead =  client.read(buf);
        buf.flip();
        StringBuilder stringBuffer=new StringBuilder();
        while (buf.hasRemaining()){
            stringBuffer.append((char) buf.get());
        }
        buf.clear();

        System.out.println("从服务端接收到的数据:"+stringBuffer);

        // 关闭 socketChannel
        client.close();
    }
}

先启动服务端ServerSocketChannel,然后U启动SocketChannel.返回结果如下:

客户端发来消息hi, jimmy,this is client.

从服务端接收到的数据:hi,this is jimmy. be happy

整体看来和fileChannel差不太多,基本都是要先有一个打开的channel(有一个方法是isOpen()可以获取是否打开)。然后使用相应的缓冲区来将数据从通道写入缓冲区或者从缓冲区读取数据。(关于buffer相关的方法后面在学习)。不过要注意一些特别的地方SocketChannel``ServerSocketChannel的一个方法configureBlocking()这个方法表示这个SocketChannel``ServerSocketChannel是阻塞还是非阻塞的,具体使用方式等学习了selector的时候在进行补充。当然上面程序是不完善的,接受放缓冲区不够大的时候会报错。具体可以改下代码看看。

DatagramChannel

DatagramChannel类似于java 网络编程的DatagramSocket类;使用UDP进行网络传输

同样的可以使用open方法获取一个实例

package jniolearn.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

/**
 * @Author: jimmy
 * @Date: 2020/6/13 17:32
 * @Description:
 * server
 */
public class DatagramChannelPrcS {

    public static void main(String[] args) throws IOException {
        DatagramChannel s = DatagramChannel.open();
        s.bind(new InetSocketAddress("127.0.0.1",3999));
        ByteBuffer buf = ByteBuffer.allocate(48);
        s.receive(buf);
        buf.clear();
        while(buf.hasRemaining()){
            System.out.print((char)buf.get());
        }

    }
}

package jniolearn.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

/**
 * @Author: jimmy
 * @Date: 2020/6/13 17:26
 * @Description:
 * client
 */
public class DatagramChannelPrc {

    public static void main(String[] args) throws IOException {

        DatagramChannel channel = DatagramChannel.open();
        // 绑定端口
//        channel.bind(new InetSocketAddress(3999));

        ByteBuffer buf = ByteBuffer.allocate(48);
        buf.put("datagrameChannel info".getBytes());
        // datagrameChannel不需要使用这个方法
//        channel.write(buf);
        buf.flip();
        int a = channel.send(buf,new InetSocketAddress("127.0.0.1",3999));
        channel.close();
    }
}

使用起来比较简单,服务端可以直接使用bind方法绑定端口、ip。客户端从缓冲区直接将数据发哦是那个至服务器。是否发送成功或者成功被接收到是没有保证的;发送消息通过send方法发出,改方法返回一个int值,表示成功发送的字节数:

那么这学习留下一个尾巴就是关于阻塞模式和非阻塞模式的区别以及应用。这个在学习完其他类之后在进行补充学习

buf = ByteBuffer.allocate(48);
buf.put(“datagrameChannel info”.getBytes());
// datagrameChannel不需要使用这个方法
// channel.write(buf);
buf.flip();
int a = channel.send(buf,new InetSocketAddress(“127.0.0.1”,3999));
channel.close();
}
}


使用起来比较简单,服务端可以直接使用bind方法绑定端口、ip。客户端从缓冲区直接将数据发哦是那个至服务器。是否发送成功或者成功被接收到是没有保证的;发送消息通过send方法发出,改方法返回一个int值,表示成功发送的字节数:



那么这学习留下一个尾巴就是关于阻塞模式和非阻塞模式的区别以及应用。这个在学习完其他类之后在进行补充学习











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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值