LoginSignup
1
0

More than 1 year has passed since last update.

ByteBufferを使う

Last updated at Posted at 2023-05-01

はじめに

ByteBufferの使い方を説明します。

インスタンスをつくる

wrap

wrapはByteArrayのリファレンスを指定してByteBufferをつくります。
リファレンスを保持するだけでByteArrayをコピーしません
そのため元のByteArrayを変更するとByteBufferに反映します。
position = 0, limit = capacity = ByteArray.size, mark = 未設定 になります。

wrapにoffset / lengthを指定すると
position = offset, limit = offset + length, mark = 未設定 になります。

Test.kt
import java.nio.ByteBuffer

fun ByteBuffer.println() {
    print("$this ")
    repeat(times = this.limit()) {
        print(String.format(format = "%02x ", this[it]))
    }
    println("")
}

fun main() {
    val byteArray = byteArrayOf(1, 2, 3)
    ByteBuffer.wrap(byteArray).also {
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] 01 02 03
        byteArray[0] = 4 // (*1) ByteArrayを更新するとByteBufferに反映する
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] 04 02 03
    }

    ByteBuffer.wrap(byteArray, 1 /* offset */, 1 /* length */).also {
        it.println()
        // java.nio.HeapByteBuffer[pos=1 lim=2 cap=3] 04 02
    }
}

allocate / allocateDirect

capacityを指定してByteBufferをつくります。
position = 0, limit = capacity, mark = 未設定 になります。

allocateDirectについて[1]に次の記載があります。

It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.

サイズが大きくて、生存期間が長く、性能上のメリットがあることが明白な場合にのみ使いましょう。

Test.kt
import java.nio.ByteBuffer

fun ByteBuffer.println() {
    print("$this hasArray=${this.hasArray()} ")
    repeat(times = this.capacity()) {
        print(String.format(format = "%02x ", this[it]))
    }
    println("")
}

fun main() {
    ByteBuffer.allocate(3).also {
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] hasArray=true 00 00 00
    }

    ByteBuffer.allocateDirect(3).also {
        it.println()
        // java.nio.DirectByteBuffer[pos=0 lim=3 cap=3] hasArray=false 00 00 00
    }
}

mark / position / limit / capacity

mark / position / limit / capacityについて説明します。
常に以下が成り立ちます。

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

capacity

ByteBuffer全体のバイト長です。ByteBuffer生成時に決めます。

limit

読み書きできる上限です。limitを超えて読み書きするとExceptionが発生します。
capacityを超える値を設定できません。

position

次に読み書きをおこなう現在位置をあらわします。範囲は[0, limit]です。

mark / reset

markはpositionを記録します。初期値は未設定です。
resetはpositionをmark位置に戻します。markが未設定の時にresetを試みるとExceptionが発生します。

Test.kt
fun main() {
    val byteArray = byteArrayOf(1, 2, 3)
    ByteBuffer.wrap(byteArray).also {
        // it.reset() InvalidMarkExceptionが発生する
        it.mark()
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] 01 02 03
        it.position(1)
        it.println()
        // java.nio.HeapByteBuffer[pos=1 lim=3 cap=3] 01 02 03
        it.reset()
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] 01 02 03
    }
}

remaining

現時点で読み書きできる最大のバイト長です。
remaining = limit - poisitonです。capacityを基準としていないことに注意してください。

clear

position = 0 , limit = capacity にします。markを未設定にします。

Test.kt
fun main() {
    val byteArray = byteArrayOf(1, 2, 3)
    ByteBuffer.wrap(byteArray).also {
        it.mark()
        it.limit(1)
        it.position(1)
        it.println()
        // java.nio.HeapByteBuffer[pos=1 lim=1 cap=3] 01 
        it.clear()
        // it.reset() InvalidMarkExceptionが発生する
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] 01 02 03 
    }
}

flip

次の処理を実行します。markを未設定にします。

  1. limit = position
  2. position = 0

利用ケースを具体例で説明します。
ByteBufferに書き込みをおこなった後、読み込みのための準備に使用します。
同一のByteBufferに対して読み書きするwriterとreaderを考えます。

1. writerはbufferにデータを書き込みます。limitまで書き込むとは限りません。
2. writerはbuffer.flipします。書き込んだところまでがlimitになります。
3. writerはreaderが読み込み完了するまで待ちます。readerに通知します。
4. readerはbufferからlimitまでデータを読み込みます。
5. readerはbuffer.clearします。writerに通知します。
6. 1.から5.を繰り返します。

rewind

flipに似ていますがlimitを変更しない点が違いです。
position=0 にします。markを未設定にします。

get

ByteBufferからデータを読みだします。
positionから1Byte読みだす get() と位置を指定して読みだす get(index) があります。
範囲[0, limit]を超えて読みだしを試みるとExceptionが発生します。

getXxx (Xxx: Char, Int, Long, Float, Double)があります。振る舞いはget系と同じです。
put/putXxxについては書き込みであることを除いてgetと考え方が類似であるため説明を省略します。

get()

1Byte読みだします。positionを+1します。

Test.kt
fun main() {
    val byteArray = byteArrayOf(1, 2, 3)
    ByteBuffer.wrap(byteArray).also {
        it.limit(1)
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=1 cap=3] 01 
        it.get()
        it.println()
        // java.nio.HeapByteBuffer[pos=1 lim=1 cap=3] 01 
        // it.get() BufferUnderflowExceptionが発生します
    }
}

get(index)

1Byte読みだします。positionを更新しません。
配列のように[index]と表記できます(*1)。

Test.kt
fun main() {
    val byteArray = byteArrayOf(1, 2, 3)
    ByteBuffer.wrap(byteArray).also {
        it.limit(1)
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=1 cap=3] 01
        it.get(0)
        it[0]  // get(0) と同じ (*1)
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=1 cap=3] 01
        // it[1] BufferUnderflowExceptionが発生します
    }
}

get(byteArray: ByteArray)

positionからbyteArray.sizeを読みだします。
positionを+byteArray.sizeします。

Test.kt
fun main() {
    val byteArray = byteArrayOf(1, 2, 3)
    ByteBuffer.wrap(byteArray).also {
        ByteArray(3).also { byteArray ->
            it.println()
            // java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] 01 02 03
            it.get(byteArray)
            it.println()
            // java.nio.HeapByteBuffer[pos=3 lim=3 cap=3] 01 02 03
            // it.get(byteArray) BufferUnderflowExceptionが発生します
        }
    }
}

slice / duplicate

元のByteBufferのByteArrayを共有する新しいByteBufferをつくります。
新しいByteBufferと元のByteBufferのposition / limit / capacity / markは独立しています。

sliceは元のByteBufferのpositionからlimitの間を切り出したByteBufferをつくります。
position = 0, limit = capacity = (original limit - original position), mark = 未設定 になります。

duplicateは元のByteBufferと同じposition / limit / capacity / markのByteBufferをつくります。

Test.kt
fun main() {
    val byteArray = byteArrayOf(1, 2, 3, 4)
    ByteBuffer.wrap(byteArray).also {
        it.println()
        // java.nio.HeapByteBuffer[pos=0 lim=4 cap=4] 01 02 03 04
        it.get()
        it.limit(2)
        it.println()
        // java.nio.HeapByteBuffer[pos=1 lim=2 cap=4] 01 02
        it.slice().also { it.println() }
        // java.nio.HeapByteBuffer[pos=0 lim=1 cap=1] 02
        it.duplicate().also { it.println() }
        // java.nio.HeapByteBuffer[pos=1 lim=2 cap=4] 01 02
    }
}

asReadOnlyBuffer

読み取り専用のByteBufferをつくります。
putなどの書き込み操作をするとExceptionが発生します。
ByteArrayをcopyしてwrapすること(*1)で読み取り専用を徹底することができます。

Test.kt
fun main() {
    val byteArray = byteArrayOf(1, 2, 3)
    ByteBuffer.wrap(byteArray.copyOf()).asReadOnlyBuffer().also { // (*1)
        it.println()
        // java.nio.HeapByteBufferR[pos=0 lim=3 cap=3] 01 02 03
        println("isReadOnly=${it.isReadOnly}")
        // isReadOnly=true
        byteArray[0] = 4 // copyしているのでByteBufferに影響しない
        it.println()
        // java.nio.HeapByteBufferR[pos=0 lim=3 cap=3] 01 02 03
        it.get()
        it.println()
        // it.put(1) ReadOnlyBufferException
    }
}

References

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0