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 #
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 #
| Operation | HeapShortBuffer | DirectShortBuffer (via ByteBuffer) |
|---|---|---|
| Allocation | O(1) fast | O(n) slower (native allocation) |
| Short Access | O(1) direct array access | O(1) with bounds checking |
| Bulk Operations | Fast (System.arraycopy) | Slower (element-by-element) |
| Memory Usage | 2 bytes per short + overhead | 2 bytes per short + native overhead |
| Cache Locality | Good (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 #
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; }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_ENDIANMemory 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 idealBulk 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 overheadRange 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 #
- Signed/Unsigned Confusion: Forgetting that Java shorts are signed (-32768 to 32767)
- Byte Order Errors: Wrong byte order corrupts 16-bit values
- Overflow/Underflow: Assigning values outside -32768..32767 range
- Buffer Position Errors: Forgetting to
flip()between write/read modes - Memory Alignment: Unaligned access can cause performance penalties
- Range Limitations: Using ShortBuffer for values outside 16-bit range
- 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);
}
}
}
Related Classes #
- ByteBuffer: Byte buffer with short view support
- IntBuffer: 32-bit integer buffer
- LongBuffer: 64-bit integer buffer
- CharBuffer: Character buffer (also 16-bit)
- MappedByteBuffer: Memory-mapped file buffer