Skip to main content
  1. Java NIO (New I/O)/

ShortBuffer

10 mins

The ShortBuffer class is a specialized buffer for 16-bit short integer values, providing efficient storage and manipulation of short data. It’s commonly used for audio processing, image data, network protocols, and any application requiring compact integer storage.

Source Code #

View Source on GitHub

Core Implementation #

public abstract class ShortBuffer extends Buffer implements Comparable<ShortBuffer> {
    // Factory methods
    public static ShortBuffer allocate(int capacity) {
        return new HeapShortBuffer(capacity, capacity);
    }
    
    public static ShortBuffer wrap(short[] array) {
        return new HeapShortBuffer(array, 0, array.length);
    }
    
    public static ShortBuffer wrap(short[] array, int offset, int length) {
        return new HeapShortBuffer(array, offset, length);
    }
    
    // Short operations
    public abstract short get();
    public abstract short get(int index);
    public abstract ShortBuffer put(short s);
    public abstract ShortBuffer put(int index, short s);
    
    // Bulk operations
    public ShortBuffer get(short[] dst) {
        return get(dst, 0, dst.length);
    }
    
    public ShortBuffer get(short[] dst, int offset, int length) {
        checkBounds(offset, length, dst.length);
        if (length > remaining())
            throw new BufferUnderflowException();
        for (int i = offset; i < offset + length; i++)
            dst[i] = get();
        return this;
    }
    
    public ShortBuffer put(short[] src) {
        return put(src, 0, src.length);
    }
    
    public ShortBuffer put(short[] src, int offset, int length) {
        checkBounds(offset, length, src.length);
        if (length > remaining())
            throw new BufferOverflowException();
        for (int i = offset; i < offset + length; i++)
            put(src[i]);
        return this;
    }
    
    // View operations
    public abstract ShortBuffer duplicate();
    public abstract ShortBuffer slice();
    public abstract ShortBuffer slice(int index, int length);
    
    // Compact operation
    public abstract ShortBuffer compact();
}

Implementation Details #

16-bit Integer Data Processing #

ShortBuffer provides efficient short operations:

// Creating and populating ShortBuffer
ShortBuffer buffer = ShortBuffer.allocate(100);
buffer.put((short) 42);
buffer.put(Short.MAX_VALUE);
buffer.put(Short.MIN_VALUE);
buffer.flip();

// Reading short values
short first = buffer.get();          // 42
short max = buffer.get();            // Short.MAX_VALUE (32767)
short min = buffer.get(2);           // Short.MIN_VALUE (-32768)

// Bulk operations with short arrays
short[] data = {1, 2, 3, 4, 5};
ShortBuffer wrapped = ShortBuffer.wrap(data);  // Shares array
short[] copy = new short[5];
buffer.get(copy);                    // Bulk copy

View from ByteBuffer #

ShortBuffer can be created as a view of ByteBuffer (2 bytes per short):

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

// Create ShortBuffer view (2 bytes per short)
ShortBuffer shortBuffer = byteBuffer.asShortBuffer();

// The view shares underlying byte buffer
// Writing to shortBuffer affects byteBuffer
shortBuffer.put(0, (short) 0x1234);  // Writes 2 bytes
shortBuffer.put(1, (short) 0x5678);  // Writes next 2 bytes

// Byte order critically important for 16-bit values
byteBuffer.order(ByteOrder.BIG_ENDIAN);
ShortBuffer bigEndianView = byteBuffer.asShortBuffer();
// Same bytes, different short interpretation

Compact Operation for Short Data #

public ShortBuffer compact() {
    int pos = position();
    int lim = limit();
    int rem = (pos <= lim ? lim - pos : 0);
    
    // Copy remaining shorts to beginning
    for (int i = 0; i < rem; i++) {
        put(i, get(pos + i));
    }
    
    position(rem);
    limit(capacity());
    discardMark();
    return this;
}

// Usage pattern for short stream processing
ShortBuffer buffer = ShortBuffer.allocate(1024);
while (hasMoreData()) {
    // Fill buffer with shorts
    while (buffer.hasRemaining() && hasMoreData()) {
        buffer.put(nextShort());
    }
    
    buffer.flip();
    
    // Process complete batches
    while (buffer.remaining() >= BATCH_SIZE) {
        processBatch(buffer);
    }
    
    buffer.compact();  // Move remaining data to beginning
}

Performance Characteristics #

OperationHeapShortBufferDirectShortBuffer (via ByteBuffer)
AllocationO(1) fastO(n) slower (native allocation)
Short AccessO(1) direct array accessO(1) with bounds checking
Bulk OperationsFast (System.arraycopy)Slower (element-by-element)
Memory Usage2 bytes per short + overhead2 bytes per short + native overhead
Cache LocalityGood (heap memory)Variable (depends on allocation)

Common Patterns #

Audio Processing #

// Process 16-bit PCM audio data
ShortBuffer processAudio(ShortBuffer pcmData, float volume) {
    ShortBuffer processed = ShortBuffer.allocate(pcmData.remaining());
    
    // Apply volume adjustment
    for (int i = 0; i < pcmData.remaining(); i++) {
        short sample = pcmData.get(i);
        float adjusted = sample * volume;
        
        // Clamp to 16-bit range
        if (adjusted > Short.MAX_VALUE) {
            adjusted = Short.MAX_VALUE;
        } else if (adjusted < Short.MIN_VALUE) {
            adjusted = Short.MIN_VALUE;
        }
        
        processed.put((short) adjusted);
    }
    
    processed.flip();
    return processed;
}

// Mix multiple audio tracks
ShortBuffer mixAudioTracks(List<ShortBuffer> tracks, float[] volumes) {
    if (tracks.isEmpty()) {
        return ShortBuffer.allocate(0);
    }
    
    int length = tracks.get(0).remaining();
    ShortBuffer mix = ShortBuffer.allocate(length);
    
    for (int i = 0; i < length; i++) {
        float mixedSample = 0.0f;
        
        for (int t = 0; t < tracks.size(); t++) {
            ShortBuffer track = tracks.get(t);
            if (i < track.remaining()) {
                mixedSample += track.get(i) * volumes[t];
            }
        }
        
        // Clamp to 16-bit range
        if (mixedSample > Short.MAX_VALUE) {
            mixedSample = Short.MAX_VALUE;
        } else if (mixedSample < Short.MIN_VALUE) {
            mixedSample = Short.MIN_VALUE;
        }
        
        mix.put((short) mixedSample);
    }
    
    mix.flip();
    return mix;
}

Image Processing (16-bit grayscale) #

// Process 16-bit grayscale image data
ShortBuffer adjustBrightness(ShortBuffer imageData, int width, int height, int brightness) {
    ShortBuffer adjusted = ShortBuffer.allocate(imageData.remaining());
    
    for (int i = 0; i < imageData.remaining(); i++) {
        short pixel = imageData.get(i);
        int adjustedPixel = pixel + brightness;
        
        // Clamp to 16-bit range (0-65535 for unsigned)
        if (adjustedPixel > 65535) {
            adjustedPixel = 65535;
        } else if (adjustedPixel < 0) {
            adjustedPixel = 0;
        }
        
        adjusted.put((short) adjustedPixel);
    }
    
    adjusted.flip();
    return adjusted;
}

// Apply convolution filter to 16-bit image
ShortBuffer applyConvolution(ShortBuffer image, int width, int height, 
                            float[][] kernel) {
    int kernelSize = kernel.length;
    int kernelRadius = kernelSize / 2;
    
    ShortBuffer result = ShortBuffer.allocate(image.remaining());
    
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            float sum = 0.0f;
            
            for (int ky = 0; ky < kernelSize; ky++) {
                for (int kx = 0; kx < kernelSize; kx++) {
                    int imgX = x + kx - kernelRadius;
                    int imgY = y + ky - kernelRadius;
                    
                    // Handle edges (clamp to edge)
                    if (imgX < 0) imgX = 0;
                    if (imgX >= width) imgX = width - 1;
                    if (imgY < 0) imgY = 0;
                    if (imgY >= height) imgY = height - 1;
                    
                    short pixel = image.get(imgY * width + imgX);
                    sum += pixel * kernel[ky][kx];
                }
            }
            
            // Clamp result
            if (sum > 65535) sum = 65535;
            if (sum < 0) sum = 0;
            
            result.put((short) sum);
        }
    }
    
    result.flip();
    return result;
}

Network Protocol Processing #

// Process network packet with 16-bit fields
class NetworkPacketProcessor {
    private ShortBuffer buffer;
    
    public NetworkPacketProcessor(int bufferSize) {
        buffer = ShortBuffer.allocate(bufferSize);
    }
    
    public void processPacket(ByteBuffer packet) {
        packet.order(ByteOrder.BIG_ENDIAN);  // Network byte order
        ShortBuffer shortView = packet.asShortBuffer();
        
        // Extract header fields
        short sourcePort = shortView.get(0);
        short destPort = shortView.get(1);
        short length = shortView.get(2);
        short checksum = shortView.get(3);
        
        // Process payload
        short payloadStart = 4;
        for (int i = payloadStart; i < shortView.remaining(); i++) {
            short data = shortView.get(i);
            processData(data);
        }
    }
    
    private void processData(short data) {
        // Process 16-bit data word
    }
}

// Calculate 16-bit checksum
short calculateChecksum(ShortBuffer data) {
    int sum = 0;
    
    for (int i = 0; i < data.remaining(); i++) {
        sum += data.get(i) & 0xFFFF;  // Treat as unsigned
    }
    
    // Fold carries
    while ((sum >> 16) != 0) {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }
    
    return (short) ~sum;
}

Embedded Systems Data Processing #

// Process 16-bit sensor data
ShortBuffer calibrateSensors(ShortBuffer rawData, short[] offsets, short[] scales) {
    ShortBuffer calibrated = ShortBuffer.allocate(rawData.remaining());
    
    for (int i = 0; i < rawData.remaining(); i++) {
        short raw = rawData.get(i);
        short offset = offsets[i % offsets.length];
        short scale = scales[i % scales.length];
        
        // Apply calibration: (raw - offset) * scale
        int calibratedValue = (raw - offset) * scale;
        
        // Handle overflow
        if (calibratedValue > Short.MAX_VALUE) {
            calibratedValue = Short.MAX_VALUE;
        } else if (calibratedValue < Short.MIN_VALUE) {
            calibratedValue = Short.MIN_VALUE;
        }
        
        calibrated.put((short) calibratedValue);
    }
    
    calibrated.flip();
    return calibrated;
}

// Convert 12-bit ADC data to 16-bit
ShortBuffer convert12BitTo16Bit(ShortBuffer adc12BitData) {
    ShortBuffer converted = ShortBuffer.allocate(adc12BitData.remaining());
    
    for (int i = 0; i < adc12BitData.remaining(); i++) {
        short adcValue = adc12BitData.get(i);
        
        // 12-bit ADC value (0-4095) to 16-bit (0-65535)
        // Scale by 16 (2^4) since 16/12 = 1.333, actually 65535/4095 ≈ 16
        int scaledValue = adcValue * 16;
        
        if (scaledValue > 65535) {
            scaledValue = 65535;
        }
        
        converted.put((short) scaledValue);
    }
    
    converted.flip();
    return converted;
}

Best Practices #

  1. Signed vs Unsigned Handling:

    // Java shorts are signed, but many applications use unsigned
    ShortBuffer buffer = ShortBuffer.allocate(100);
    
    // Convert signed to unsigned
    short signed = buffer.get(0);
    int unsigned = signed & 0xFFFF;  // Mask to get unsigned value
    
    // Convert unsigned to signed (clamp to valid range)
    int unsignedValue = 40000;
    short signedValue;
    if (unsignedValue > 32767) {
        signedValue = (short) (unsignedValue - 65536);  // Wrap around
    } else {
        signedValue = (short) unsignedValue;
    }
    
  2. Byte Order Sensitivity:

    // 16-bit values are highly sensitive to byte order
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
    byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // x86 native order
    ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
    
    // Always specify byte order explicitly
    // Network protocols typically use BIG_ENDIAN
    
  3. Memory Efficiency:

    // Short buffers use 2 bytes per element - efficient for large datasets
    // Consider using ShortBuffer over IntBuffer when range allows
    ShortBuffer efficientBuffer = ShortBuffer.allocate(1000000);  // ~2MB
    
    // For even more efficiency with unsigned data up to 255, use ByteBuffer
    // For values 0-65535, ShortBuffer is ideal
    
  4. Bulk Operations for Performance:

    // Use bulk operations for better performance
    short[] batch = new short[100];
    buffer.get(batch);  // Single call vs 100 individual gets
    
    // For maximum performance with heap buffers:
    short[] data = new short[1000];
    ShortBuffer buffer = ShortBuffer.wrap(data);  // Zero overhead
    
  5. Range Checking:

    // Always validate short values when converting from other types
    ShortBuffer buffer = ShortBuffer.allocate(100);
    
    int largeValue = 100000;
    if (largeValue > Short.MAX_VALUE || largeValue < Short.MIN_VALUE) {
        // Handle overflow/underflow
        largeValue = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, largeValue));
    }
    buffer.put((short) largeValue);
    

Common Pitfalls #

  1. Signed/Unsigned Confusion: Forgetting that Java shorts are signed (-32768 to 32767)
  2. Byte Order Errors: Wrong byte order corrupts 16-bit values
  3. Overflow/Underflow: Assigning values outside -32768..32767 range
  4. Buffer Position Errors: Forgetting to flip() between write/read modes
  5. Memory Alignment: Unaligned access can cause performance penalties
  6. Range Limitations: Using ShortBuffer for values outside 16-bit range
  7. Performance Bottlenecks: Using single-element operations instead of bulk ops

Internal Implementation #

HeapShortBuffer #

class HeapShortBuffer extends ShortBuffer {
    final short[] hb;       // The underlying short array
    final int offset;       // The offset into the array
    
    HeapShortBuffer(int cap, int lim) {
        super(-1, 0, lim, cap, null);
        hb = new short[cap];
        offset = 0;
    }
    
    HeapShortBuffer(short[] buf, int off, int len) {
        super(-1, off, off + len, buf.length, null);
        hb = buf;
        offset = 0;
    }
    
    public short get() {
        return hb[ix(nextGetIndex())];
    }
    
    public ShortBuffer put(short x) {
        hb[ix(nextPutIndex())] = x;
        return this;
    }
    
    int ix(int i) {
        return i + offset;
    }
}

DirectShortBuffer (via ByteBuffer view) #

// ShortBuffer view of direct ByteBuffer
class ByteBufferAsShortBufferB extends ShortBuffer {
    private final ByteBuffer bb;
    private final int offset;
    
    ByteBufferAsShortBufferB(ByteBuffer bb) {
        super(-1, 0, bb.remaining() >> 1, bb.remaining() >> 1);
        this.bb = bb.duplicate().order(ByteOrder.BIG_ENDIAN);
        this.offset = bb.position();
    }
    
    public short get() {
        return bb.getShort(ix(nextGetIndex()));
    }
    
    public ShortBuffer put(short x) {
        bb.putShort(ix(nextPutIndex()), x);
        return this;
    }
    
    int ix(int i) {
        return (i << 1) + offset;
    }
}

Performance Optimization #

Audio Stream Processing #

public class AudioStreamProcessor {
    private ShortBuffer buffer;
    private final int chunkSize;
    
    public AudioStreamProcessor(int bufferSize, int chunkSize) {
        this.buffer = ShortBuffer.allocate(bufferSize);
        this.chunkSize = chunkSize;
    }
    
    public void processStream(ShortSupplier supplier) {
        short[] chunk = new short[chunkSize];
        
        while (true) {
            // Fill buffer with audio chunks
            while (buffer.hasRemaining() >= chunkSize) {
                for (int i = 0; i < chunkSize; i++) {
                    short sample = supplier.getAsShort();
                    if (sample == Short.MIN_VALUE) {  // End sentinel
                        flush();
                        return;
                    }
                    buffer.put(sample);
                }
            }
            
            // Process full chunks
            buffer.flip();
            while (buffer.remaining() >= chunkSize) {
                buffer.get(chunk);
                processAudioChunk(chunk);
            }
            buffer.compact();
        }
    }
    
    private void processAudioChunk(short[] chunk) {
        // Audio processing (filtering, effects, etc.)
    }
}

Memory-Mapped Short File Processing #

// Process large short file with memory mapping
public void processShortFile(Path filePath) throws IOException {
    try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
        long fileSize = channel.size();
        
        // Ensure file size is multiple of 2 bytes
        if (fileSize % 2 != 0) {
            throw new IOException("File size not aligned to 2 bytes");
        }
        
        long position = 0;
        
        while (position < fileSize) {
            long size = Math.min(Integer.MAX_VALUE, fileSize - position);
            MappedByteBuffer mappedBuffer = channel.map(
                FileChannel.MapMode.READ_ONLY, position, size);
            
            mappedBuffer.order(ByteOrder.BIG_ENDIAN);
            ShortBuffer shortBuffer = mappedBuffer.asShortBuffer();
            
            // Process short buffer
            processShortBuffer(shortBuffer);
            
            position += size;
        }
    }
}

Short Buffer Pool for Real-Time Processing #

public class ShortBufferPool {
    private final ConcurrentMap<Integer, Queue<ShortBuffer>> pools = 
        new ConcurrentHashMap<>();
    
    public ShortBuffer acquire(int size) {
        Queue<ShortBuffer> pool = pools.computeIfAbsent(size, 
            k -> new ConcurrentLinkedQueue<>());
        
        ShortBuffer buffer = pool.poll();
        if (buffer == null) {
            buffer = ShortBuffer.allocate(size);
        }
        buffer.clear();
        return buffer;
    }
    
    public ShortBuffer acquireDirect(int size) {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size * 2);
        byteBuffer.order(ByteOrder.nativeOrder());
        return byteBuffer.asShortBuffer();
    }
    
    public void release(ShortBuffer buffer) {
        buffer.clear();
        Queue<ShortBuffer> pool = pools.get(buffer.capacity());
        if (pool != null) {
            pool.offer(buffer);
        }
    }
}