【从入门到放弃-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

目录
相关文章
|
7天前
|
Java 程序员 开发者
深入理解Java并发编程:线程同步与锁机制
【4月更文挑战第30天】 在多线程的世界中,确保数据的一致性和线程间的有效通信是至关重要的。本文将深入探讨Java并发编程中的核心概念——线程同步与锁机制。我们将从基本的synchronized关键字开始,逐步过渡到更复杂的ReentrantLock类,并探讨它们如何帮助我们在多线程环境中保持数据完整性和避免常见的并发问题。文章还将通过示例代码,展示这些同步工具在实际开发中的应用,帮助读者构建对Java并发编程深层次的理解。
|
7天前
|
Java
Java并发编程:深入理解线程池
【4月更文挑战第30天】本文将深入探讨Java并发编程中的一个重要主题——线程池。我们将从线程池的基本概念入手,了解其工作原理和优势,然后详细介绍如何使用Java的Executor框架创建和管理线程池。最后,我们将讨论一些高级主题,如自定义线程工厂和拒绝策略。通过本文的学习,你将能够更好地理解和使用Java的线程池,提高你的并发编程能力。
|
7天前
|
安全 Java 调度
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第30天】本文将深入探讨Java并发编程的核心概念,包括线程安全、同步机制、锁优化以及性能调优。我们将通过实例分析如何确保多线程环境下的数据一致性,同时介绍一些常见的并发模式和最佳实践,旨在帮助开发者在保证线程安全的同时,提升系统的性能和响应能力。
|
19小时前
|
安全 Java
Java中的并发编程:理解并发性与线程安全
Java作为一种广泛应用的编程语言,在并发编程方面具有显著的优势和特点。本文将探讨Java中的并发编程概念,重点关注并发性与线程安全,并提供一些实用的技巧和建议,帮助开发人员更好地理解和应用Java中的并发机制。
|
4天前
|
Java 调度 开发者
Java 并发编程的探索与实践
【5月更文挑战第3天】在当今多核处理器普及的时代,并发编程已经成为提高程序性能的重要手段。本文将深入探讨 Java 并发编程的基本概念、原理及其在实际项目中的应用,帮助读者更好地理解和掌握 Java 并发编程技巧。
|
5天前
|
并行计算 安全 Java
Java 并发编程的探索之旅
【5月更文挑战第2天】 在多线程的世界里,程序的行为变得错综复杂。本文将带您走进 Java 并发编程的核心概念,通过深入分析并发工具的使用和原理,帮助您构建高效、安全且响应迅速的应用程序。我们将探讨线程的基本知识,同步机制,以及高级并发工具如 Executors、Futures 和 Streams。通过理论与实践相结合的方式,为开发者提供一份清晰、实用的并发编程指南。
12 2
|
6天前
|
存储 安全 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第1天】本文将深入探讨Java并发编程的核心概念,包括线程安全和性能优化。我们将详细分析线程安全问题的根源,以及如何通过合理的设计和编码实践来避免常见的并发问题。同时,我们还将探讨如何在保证线程安全的前提下,提高程序的并发性能,包括使用高效的同步机制、减少锁的竞争以及利用现代硬件的并行能力等技术手段。
|
6天前
|
安全 Java 程序员
Java并发编程:理解并应用ReentrantLock
【4月更文挑战第30天】 在多线程的世界中,高效且安全地管理共享资源是至关重要的。本文深入探讨了Java中的一种强大同步工具——ReentrantLock。我们将从其设计原理出发,通过实例演示其在解决并发问题中的实际应用,以及如何比传统的synchronized关键字提供更灵活的锁定机制。文章还将讨论在使用ReentrantLock时可能遇到的一些挑战和最佳实践,帮助开发者避免常见陷阱,提高程序性能和稳定性。
|
6天前
|
缓存 Java 调度
Java并发编程:深入理解线程池
【4月更文挑战第30天】 在Java并发编程中,线程池是一种重要的工具,它可以帮助我们有效地管理线程,提高系统性能。本文将深入探讨Java线程池的工作原理,如何使用它,以及如何根据实际需求选择合适的线程池策略。
|
6天前
|
Java
Java并发编程:深入理解线程池
【4月更文挑战第30天】 本文将深入探讨Java中的线程池,解析其原理、使用场景以及如何合理地利用线程池提高程序性能。我们将从线程池的基本概念出发,介绍其内部工作机制,然后通过实例演示如何创建和使用线程池。最后,我们将讨论线程池的优缺点以及在实际应用中需要注意的问题。