四个Channel的讲解

本文详细介绍了Java NIO中的四个关键Channel:FileChannel用于高效读写文件,支持直接内存和transferTo方法;SocketChannel提供阻塞和非阻塞模式,适合与Selector结合管理多个连接;SocketServerChannel处理服务器端连接,同样具备阻塞和非阻塞模式;DatagramChannel则基于UDP协议,适用于UDP通信场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.FileChannel

1.读的例子

 public static void read(String fileName) throws IOException {
         //第一种获取FileChannel的方法
       /* RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "rw");
        FileChannel fileChannel1= randomAccessFile.getChannel();*/

        //第二种获取FileChannel的方法
        File file = new File(fileName);
        FileInputStream inputStream = new FileInputStream(file);
        FileChannel fileChannel2=inputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(2);
        int read = fileChannel2.read(byteBuffer);
        while (read!=-1){
             byteBuffer.flip();
             System.out.println(Charset.forName("GBK").decode(byteBuffer).toString());
             byteBuffer.clear();
             read = fileChannel2.read(byteBuffer);
        }
        //直接内存 https://blog.youkuaiyun.com/qq_30055391/article/details/85009551讲过相关知识
        DirectBuffer directBuffer=(DirectBuffer)byteBuffer;
        directBuffer.cleaner().clean();
        fileChannel2.close();
        inputStream.close();
    }

2.写的例子

public  static  void  write(String fileName) throws IOException {
        File file = new File(fileName);
        //如果需要不覆盖 需要在后面加上 true参数
        FileOutputStream fileOutputStream = new FileOutputStream(file,true);
        FileChannel fileChannel = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(20);
        byteBuffer.put("new new new".getBytes());
        //对 position和limit进行设置 https://blog.youkuaiyun.com/qq_30055391/article/details/85009551讲过
        byteBuffer.flip();
        //因为不知道buffer里面的值会不会一下子写完 ,所以循环判断
        while (byteBuffer.hasRemaining()){
            fileChannel.write(byteBuffer);
            //把channel里面的数据只保留前3位 后面的数据截掉 例子里面只会写到文件上面一个new
            fileChannel.truncate(3);
            System.out.println(fileChannel.size());
            //强制把fileChannel里面的数据写到磁盘上面 因为有可能不是马上写入到硬盘上面的文件上面
            fileChannel.force(true);
        }
        fileChannel.close();
        fileOutputStream.close();
    }

3.什么时候该使用NIO?

如果你对读取或者写入文件的速度要求特别高,可以使用NIO里面的直接内存。
如果需要文件复制,可以使用NIO里面Channel的transferTo方法,比较方便。
如果你需要读取足够大(超过1个G的文件),在JVM内存吃紧的情况下可以使用NIO里面的MappedByteBuffer(基于虚拟内存)有兴趣可以看一下 。

2.SocketChannel

阻塞模式下的例子

  public static void  testBlock() throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        //绑定ip和端口号
        socketChannel.connect(new InetSocketAddress("10.252.69.254",5049));

        ByteBuffer byteBuffer = ByteBuffer.allocate(40);
        //阻塞到数据被读入buffer
        int alreadyread = socketChannel.read(byteBuffer);

        ByteBuffer byteBufferWrite = ByteBuffer.allocate(40);
        byteBuffer.put("write content".getBytes());
         while (byteBufferWrite.hasRemaining()){
            //阻塞到数据写入channel
            int alreadyWrite =socketChannel.write(byteBuffer);
        }
         socketChannel.close();
    }

非阻塞模式下的例子

 public void  testNoBlock() throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        //绑定ip和端口号
        socketChannel.connect(new InetSocketAddress("10.252.69.254",5049));
        //设置为非阻塞模式
        socketChannel.configureBlocking(false);
        ByteBuffer byteBuffer = ByteBuffer.allocate(40);
        //阻塞到数据被读入buffer
        int alreadyread = socketChannel.read(byteBuffer);
        //非阻塞模式下read方法会立即返回 所以要判断是否全部读取完毕
        while (alreadyread!=-1){
            socketChannel.read(byteBuffer);
        }
        ByteBuffer byteBufferWrite = ByteBuffer.allocate(40);
        byteBuffer.put("write content".getBytes());
        byteBuffer.flip();
        int alreadyWrite =socketChannel.write(byteBuffer);
        // 非阻塞write也会立即返回 所以要判断是否写入完成
        while (alreadyWrite!=-1){
            //阻塞到数据写入channel
            alreadyWrite =socketChannel.write(byteBuffer);
        }
        socketChannel.close();
    }

和传统的socket编程最大的区别就是提供了非阻塞的模式。并且可以跟selector结合实现管理多个端口的socket。但是实际情况用的不多。

3.SocketServerChannel

阻塞模式

    public static void testServeSocketBlocking() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口号
        serverSocketChannel.socket().bind(new InetSocketAddress(5089));
        ByteBuffer byteBuffer = ByteBuffer.allocate(50);
        try {
            while (true) {
            //阻塞的
                SocketChannel socketChannel = serverSocketChannel.accept();
                int a = socketChannel.read(byteBuffer);
                while (a != -1) {
                    byteBuffer.flip();
                    System.out.println(socketChannel.getLocalAddress() + ":" + Charset.forName("utf-8").decode(byteBuffer).toString());
                    byteBuffer.compact();
                    a = socketChannel.read(byteBuffer);
                }
            }
        } finally {
            serverSocketChannel.close();
        }

    }

非阻塞模式 修改

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);

while(true){
//非阻塞直接返回  没有就直接返回null
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    if(socketChannel != null){
        //do something with socketChannel...
    }
}

#4.DatagramChannel
基于UDP协议的channel。

    public void udpTest() throws IOException {
        //UDP 协议下不需要连接 数据是按块接收的
        DatagramChannel dataProgramChanne = DatagramChannel.open();
        //接收数据
        dataProgramChanne.bind(new InetSocketAddress(8888));
        ByteBuffer buf = ByteBuffer.allocate(48);
        buf.clear();
        //如果数据包的数据过大,那么就会发生数据丢失
        dataProgramChanne.receive(buf);

        //发送数据
        dataProgramChanne.connect(new  InetSocketAddress("jenkov.com", 80));
        int bytesRead = dataProgramChanne.read(buf);
        int bytesWritten = dataProgramChanne.write(buf);
    }

<think>首先,用户要求用中文回答问题。问题是:“详细讲解一个GO CSP模型”。我需要详细解释GO CSP模型。 GO CSP模型:我理解这是指Go语言中的CSP(Communicating Sequential Processes)模型。CSP是由Tony Hoare提出的并发编程模型,Go语言通过goroutines和channels实现了CSP模型。 关键点: - CSP模型强调通过通信来共享内存,而不是通过共享内存来通信。 - 在Go中,goroutines是轻量级线程,channels是用于goroutines之间通信的管道。 - 核心概念:goroutines并发执行,通过channels发送和接收数据。 详细讲解: 1. **什么是CSP模型?** - CSP是Communicating Sequential Processes的缩写,是一种并发编程范式。 - 它涉及独立的进程(在Go中是goroutines)通过消息传递进行通信。 2. **Go中的实现:** - **Goroutines:** 使用`go`关键字启动,例如`go functionName()`。它们是轻量级的,由Go运行时管理。 - **Channels:** 用于在goroutines之间传递数据。创建channel:`ch := make(chan int)`。发送数据:`ch <- data`,接收数据:`data := <-ch`。 - Channel可以是带缓冲的(buffered)或无缓冲的(unbuffered)。无缓冲channel需要发送和接收同时准备好才能通信;带缓冲channel允许一定数量的数据暂存。 3. **核心原则:** - 通过通信共享内存:避免共享内存带来的竞态条件,使用channel同步数据。 - 同步:channel操作(发送和接收)可以阻塞,直到另一端准备好。 - Select语句:用于处理多个channel操作,类似于switch语句。 4. **示例代码:** - 一个简单的例子:两个goroutines通过channel通信。 - 例如,一个goroutine发送数字,另一个接收并打印。 5. **优势:** - 简化并发编程,减少锁的使用。 - 避免死锁和竞态条件。 - 易于理解和调试。 现在,我需要提供代码示例。既然是Go语言,我应该写一个简单的Go程序来演示CSP模型。 示例代码: ```go package main import ( "fmt" "time" ) func producer(ch chan int) { for i := 0; i < 5; i++ { ch <- i // 发送数据到channel fmt.Printf("Sent: %d\n", i) } close(ch) // 关闭channel } func consumer(ch chan int) { for { data, ok := <-ch // 从channel接收数据 if !ok { fmt.Println("Channel closed") return } fmt.Printf("Received: %d\n", data) time.Sleep(1 * time.Second) // 模拟处理时间 } } func main() { ch := make(chan int) // 创建一个无缓冲channel go producer(ch) // 启动生产者goroutine go consumer(ch) // 启动消费者goroutine time.Sleep(6 * time.Second) // 等待goroutines完成 } ``` 解释: - `producer`函数发送0到4的数字到channel。 - `consumer`函数接收并打印这些数字。 - 使用`close(ch)`来关闭channel,消费者检测到关闭时退出。 - `time.Sleep`用于等待goroutines完成(在实际应用中,应该使用sync.WaitGroup)。 在回答中,我需要详细解释这个代码。 回答后,我必须提出3-5个相关问题。问题必须以“§§相关问题§§”开头,然后列出1. 2. 3. 等。 相关问题应该基于用户的问题和我的回答。用户的问题是“详细讲解一个GO CSP模型”,所以我应该提出关于Go CSP模型的进一步问题。 可能的3-5个相关问题: 1. Go中的goroutine和channel如何避免竞态条件? 2. 在CSP模型中,带缓冲channel和无缓冲channel有什么区别? 3. 如何使用select语句处理多个channel操作? 4. Go的CSP模型与其他并发模型(如Actor模型)有什么不同? 5. 在Go中,如何优雅地关闭channel以避免panic? 确保问题数量在3到5个之间。 最后,输出格式: - 回答内容。 - 然后,一行“§§相关问题§§”,紧接着列出问题,用数字点号,如“1. ... 2. ... 3. ...” - 禁止换行分散,意思是问题列表应该紧凑,不要有额外的换行。 在回答中,我需要用中文,因为用户说“请使用中文回答问题”。 所以,整个回答结构: 1. 详细讲解Go CSP模型。 2. 代码示例。 3. “§§相关问题§§”行。 4. 3-5个相关问题。</think>### 详细讲解 Go 语言中的 CSP 模型 Go 语言中的 CSP(Communicating Sequential Processes)模型是一种并发编程范式,由计算机科学家 Tony Hoare 在 1978 年提出。Go 语言通过 **goroutines** 和 **channels** 实现了 CSP 的核心思想:**通过通信来共享内存,而不是通过共享内存来通信**。这避免了传统并发编程中常见的竞态条件(race conditions)和锁(locks)的复杂性。下面我将从核心概念、工作原理、代码示例和优势四个方面详细讲解。 #### 1. **核心概念** - **Goroutines**:轻量级线程,由 Go 运行时管理。使用 `go` 关键字启动,开销极小(约 2KB 内存),可同时运行成千上万个。例如,`go func()` 启动一个并发任务。 - **Channels**:用于 goroutines 间通信的管道,类型化且线程安全。创建方式:`ch := make(chan int)`。操作包括: - **发送**:`ch <- data`,将数据放入 channel。 - **接收**:`data := <-ch`,从 channel 取出数据。 - **关闭**:`close(ch)`,表示不再发送数据。 - **Channel 类型**: - **无缓冲 channel**:默认类型,发送和接收必须同时准备好,否则阻塞(同步通信)。 - **带缓冲 channel**:指定容量,如 `make(chan int, 10)`,允许暂存数据,直到缓冲区满或空(异步通信)。 - **Select 语句**:用于处理多个 channel 操作,类似 `switch`,但基于通信事件而非值。它监听多个 channel,执行就绪的操作。 #### 2. **工作原理** - **通信替代共享**:Goroutines 不直接共享内存;数据通过 channel 传递,发送方和接收方解耦。 - **同步机制**:无缓冲 channel 确保发送和接收同步,避免数据竞争。带缓冲 channel 提供有限的异步能力。 - **死锁预防**:Go 运行时检测 channel 阻塞情况,如果所有 goroutines 都在等待,会触发死锁错误。 - **并发模型**:多个 goroutines 并行执行,通过 channel 协调。例如,生产者-消费者模式:生产者发送数据,消费者接收处理。 #### 3. **代码示例** 下面是一个简单的 Go 程序,演示 CSP 模型:一个生产者 goroutine 生成数字,一个消费者 goroutine 处理数字,通过 channel 通信。 ```go package main import ( "fmt" "time" ) // 生产者函数:发送数字到 channel func producer(ch chan int) { for i := 0; i < 5; i++ { ch <- i // 发送数据 fmt.Printf("Producer sent: %d\n", i) } close(ch) // 关闭 channel,通知不再发送 } // 消费者函数:从 channel 接收并处理数据 func consumer(ch chan int) { for data := range ch { // 循环接收直到 channel 关闭 fmt.Printf("Consumer received: %d\n", data) time.Sleep(500 * time.Millisecond) // 模拟处理耗时 } } func main() { ch := make(chan int) // 创建无缓冲 channel go producer(ch) // 启动生产者 goroutine go consumer(ch) // 启动消费者 goroutine time.Sleep(3 * time.Second) // 等待 goroutines 完成(实际应用中用 sync.WaitGroup) } ``` **代码解释**: - **生产者**:循环发送 0-4 的数字到 channel。发送后打印日志。 - **消费者**:使用 `for range` 持续接收数据,直到 channel 关闭(`close(ch)` 触发退出)。 - **无缓冲 channel**:发送和接收同步,生产者发送后阻塞,直到消费者接收。 - **输出示例**: ``` Producer sent: 0 Consumer received: 0 Producer sent: 1 Consumer received: 1 ...(以此类推) ``` - **优势体现**:无需锁或共享变量,channel 自动处理同步。 #### 4. **优势与适用场景** - **优势**: - **简化并发**:避免显式锁(如 `mutex`),降低死锁风险。 - **高可读性**:代码结构清晰,通信逻辑显式表达。 - **高效性能**:Goroutines 轻量,Go 运行时优化调度。 - **错误处理**:结合 `select` 和 `time.After` 可处理超时和错误。 - **适用场景**:数据管道(pipeline)、并行任务处理、微服务通信等。 总之,Go 的 CSP 模型通过 goroutines 和 channels 提供了一种优雅的并发解决方案,强调“通信即同步”,是现代并发编程的最佳实践之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值