【从入门到放弃-Java】并发编程-NIO-Buffer

简介: 前言上篇【从入门到放弃-Java】并发编程-NIO-Channel中我们学习到channel是双向通道,数据通过channel在实体(文件、socket)和缓冲区(buffer)中可以双向传输。本文我们就来学习下buffer简介buffer即缓冲区,实际上是一块内存,可以用来写入、读取数据。

前言

上篇【从入门到放弃-Java】并发编程-NIO-Channel中我们学习到channel是双向通道,数据通过channel在实体(文件、socket)和缓冲区(buffer)中可以双向传输。

本文我们就来学习下buffer

简介

buffer即缓冲区,实际上是一块内存,可以用来写入、读取数据。是一个线性的、大小有限的、顺序承载基础数据类型的内存块。

buffer有三个重要的属性:

  • capacity:缓冲池大小,是不可变的。当buffer写满时,需要先清空才能继续写入。
  • limit:是buffer中不可以被读或者写的第一个元素的位置,limit的大小永远不会超过capacity(在写模式下,limit等于capacity)
  • position:是buffer中可以被读或者写的第一个元素的位置,position的大小永远不会超过limit

除了boolean外,每一个基础数据类型都有对应的buffer。如:ByteBuffer、CharBuffer、LongBuffer等

buffer不是线程安全的,如果要在多线程中使用 需要加锁控制

接下来以ByteBuffer为例开始学习。

ByteBuffer

allocateDirect

public static ByteBuffer allocateDirect(int capacity) {
    //会创建一个容量大小为capacity的DirectByteBuffer(ByteBuffer的子类)
    return new DirectByteBuffer(capacity);
}

allocate

public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw createCapacityException(capacity);
    //会创建一个容量大小为capacity的HeapByteBuffer(ByteBuffer的子类)
    return new HeapByteBuffer(capacity, capacity);
}

HeapByteBuffer和DirectByteBuffer的区别:

  • DirectByteBuffer是直接调用native方法在本机os::malloc()创建堆外内存;HeapByteBuffer是直接在jvm的堆中分配内存。
  • 当buffer中的数据和磁盘、网络等的交互都在操作系统的内核中发生时,使用DirectByteBuffer能避免从内核态->用户态->内核态的切换开销,所有的处理都在内核中进行,性能会比较好
  • 当频繁创建操作数据量比较小的buffer时,使用HeapByteBuffer在jvm堆中分配内存能抵消掉使用DirectByteBuffer带来的好处。

wrap

public static ByteBuffer wrap(byte[] array,
                                    int offset, int length)
{
    try {
        return new HeapByteBuffer(array, offset, length);
    } catch (IllegalArgumentException x) {
        throw new IndexOutOfBoundsException();
    }
}

public static ByteBuffer wrap(byte[] array) {
        return wrap(array, 0, array.length);
    }

将byte数组包装成一个ByteBuffer

读数据

  • 使用get方法从Buffer中读取数据
  • 从Buffer中读取数据到Channel即:Channel::write() (从buffer中读取数据写入到资源中,所以是write)

写数据

  • 使用put方法直接设置Buffer中的数据
  • 从Channel中读取数据到Buffer即:Channel::read() (从资源中读取数据写入到buffer中,所以是read)

position

//获取buffer中当前position的位置
public final int position() {
    return position;
}

//设置buffer的position为newPosition,注意newPosition要大于0且小于limit,如果remark大于newPosition则设置为-1
public Buffer position(int newPosition) {
    if (newPosition > limit | newPosition < 0)
         throw createPositionException(newPosition);
     position = newPosition;
     if (mark > position) mark = -1;
     return this;
}

limit

//获取buffer中当前limit的位置
public final int limit() {
    return limit;
}

//设置buffer的limit为newLimit,注意newLimit要大于0且小于capacity。如果position大于newLimit这设置为newLimit,如果remark大于newLimit则设置为-1
public Buffer limit(int newLimit) {
    if (newLimit > capacity | newLimit < 0)
        throw createLimitException(newLimit);
    limit = newLimit;
    if (position > limit) position = limit;
    if (mark > limit) mark = -1;
    return this;
}

mark

public Buffer mark() {
    //标记mark为当前position
    mark = position;
    return this;
}

将当前位置做标记,在使用reset方法时,可以回到当前mark的位置

reset

public Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    //设置position为当前mark
    position = m;
    return this;
}

回到之前设置mark的位置

clear

public Buffer clear() {
    //设置position为0
    position = 0;
    //limit设置为capacity大小
    limit = capacity;
    //mark设置为-1(初始化)
    mark = -1;
    return this;
}

读取完数据后调用clear,即将buffer逻辑上清空了,可以从0开始写入数据

flip

public Buffer flip() {
    //limit设置为当前位置
    limit = position;
    //position设置为0
    position = 0;
    //mark设置为-1(初始化)
    mark = -1;
    return this;
}

将buffer从写模式设置为读模式,limit设置为当前position的位置,即只能读取limit大小的数据

rewind

public Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}

将position设置为0,即从头开始读取

remaining

public final int remaining() {
    return limit - position;
}

返回buffer中还有多少byte是未读的

hasRemaining

public final boolean hasRemaining() {
    return position < limit;
}

是否已读完

compact

public ByteBuffer compact() {
    System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
    position(remaining());
    limit(capacity());
    discardMark();
    return this;
}

将position和limit直接的数据copy到byteBuffer的起始处,将已读数据清空,并将新的position设置为当前未读数据的末尾。这样能避免clear方法会将未读数据也清空的问题

slice

public ByteBuffer slice() {
    return new HeapByteBufferR(hb,
                                    -1,
                                    0,
                                    this.remaining(),
                                    this.remaining(),
                                    this.position() + offset);
}


ByteBuffer slice(int pos, int lim) {
    assert (pos >= 0);
    assert (pos <= lim);
    int rem = lim - pos;
    return new HeapByteBufferR(hb,
                                    -1,
                                    0,
                                    rem,
                                    rem,
                                    pos + offset);
}

新创建一个ByteBuffer,将缓存区分片,设置一个子缓冲区,实际上内存还是共享的,数据发生改变,两个缓冲区读取的数据都会是改变后的。

总结

Buffer最重要的三个属性:position、limit、capacity。牢记这三个属性的含义及读写切换时,设置值是如何变化的,Buffer的核心知识点就掌握了。

更多文章见:https://nc2era.com

目录
相关文章
|
29天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
31 0
|
2天前
|
自然语言处理 Java
Java中的字符集编码入门-增补字符(转载)
本文探讨Java对Unicode的支持及其发展历程。文章详细解析了Unicode字符集的结构,包括基本多语言面(BMP)和增补字符的表示方法,以及UTF-16编码中surrogate pair的使用。同时介绍了代码点和代码单元的概念,并解释了UTF-8的编码规则及其兼容性。
74 60
|
1月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
58 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
11天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
26 3
|
18天前
|
存储 监控 Java
Java的NIO体系
通过本文的介绍,希望您能够深入理解Java NIO体系的核心组件、工作原理及其在高性能应用中的实际应用,并能够在实际开发中灵活运用这些知识,构建高效的Java应用程序。
31 5
|
29天前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
2月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
157 6
|
2月前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
2月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
2月前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
47 2