FloatBuffer
9 mins
The FloatBuffer class is a specialized buffer for 32-bit floating-point values, providing efficient storage and manipulation of single-precision float data. It’s essential for scientific computing, graphics processing, and any application requiring floating-point arithmetic.
Source Code #
Core Implementation #
public abstract class FloatBuffer extends Buffer implements Comparable<FloatBuffer> {
// Factory methods
public static FloatBuffer allocate(int capacity) {
return new HeapFloatBuffer(capacity, capacity);
}
public static FloatBuffer wrap(float[] array) {
return new HeapFloatBuffer(array, 0, array.length);
}
public static FloatBuffer wrap(float[] array, int offset, int length) {
return new HeapFloatBuffer(array, offset, length);
}
// Float operations
public abstract float get();
public abstract float get(int index);
public abstract FloatBuffer put(float f);
public abstract FloatBuffer put(int index, float f);
// Bulk operations
public FloatBuffer get(float[] dst) {
return get(dst, 0, dst.length);
}
public FloatBuffer get(float[] 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 FloatBuffer put(float[] src) {
return put(src, 0, src.length);
}
public FloatBuffer put(float[] 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 FloatBuffer duplicate();
public abstract FloatBuffer slice();
public abstract FloatBuffer slice(int index, int length);
// Compact operation
public abstract FloatBuffer compact();
}
Implementation Details #
Floating-Point Data Processing #
FloatBuffer provides efficient float operations with IEEE 754 semantics:
// Creating and populating FloatBuffer
FloatBuffer buffer = FloatBuffer.allocate(100);
buffer.put(3.14159f);
buffer.put(Float.MAX_VALUE);
buffer.put(Float.NaN);
buffer.put(Float.POSITIVE_INFINITY);
buffer.flip();
// Reading float values
float pi = buffer.get(); // 3.14159f
float max = buffer.get(); // Float.MAX_VALUE
float nan = buffer.get(2); // Float.NaN
float infinity = buffer.get(3); // Float.POSITIVE_INFINITY
// Bulk operations with float arrays
float[] data = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
FloatBuffer wrapped = FloatBuffer.wrap(data); // Shares array
float[] copy = new float[5];
buffer.get(copy); // Bulk copy
View from ByteBuffer #
FloatBuffer can be created as a view of ByteBuffer (4 bytes per float):
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
// Create FloatBuffer view (4 bytes per float)
FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
// The view shares underlying byte buffer
// Writing to floatBuffer affects byteBuffer
floatBuffer.put(0, 3.14159f); // Writes 4 bytes
floatBuffer.put(1, Float.NaN); // Writes next 4 bytes
// Byte order affects float representation
byteBuffer.order(ByteOrder.BIG_ENDIAN);
FloatBuffer bigEndianView = byteBuffer.asFloatBuffer();
// Same bytes, different float interpretation
Floating-Point Special Values #
// Handling special float values
FloatBuffer buffer = FloatBuffer.allocate(10);
// Store special values
buffer.put(Float.NaN);
buffer.put(Float.POSITIVE_INFINITY);
buffer.put(Float.NEGATIVE_INFINITY);
buffer.put(Float.MIN_VALUE);
buffer.put(Float.MAX_VALUE);
buffer.put(Float.MIN_NORMAL);
buffer.flip();
// Check special values
while (buffer.hasRemaining()) {
float value = buffer.get();
if (Float.isNaN(value)) {
// Handle NaN
} else if (Float.isInfinite(value)) {
// Handle infinity
} else if (Float.isFinite(value)) {
// Handle finite value
}
}
Compact Operation for Float Data #
public FloatBuffer compact() {
int pos = position();
int lim = limit();
int rem = (pos <= lim ? lim - pos : 0);
// Copy remaining floats to beginning
for (int i = 0; i < rem; i++) {
put(i, get(pos + i));
}
position(rem);
limit(capacity());
discardMark();
return this;
}
// Usage pattern for float stream processing
FloatBuffer buffer = FloatBuffer.allocate(1024);
while (hasMoreData()) {
// Fill buffer with floats
while (buffer.hasRemaining() && hasMoreData()) {
buffer.put(nextFloat());
}
buffer.flip();
// Process complete batches
while (buffer.remaining() >= BATCH_SIZE) {
processBatch(buffer);
}
buffer.compact(); // Move remaining data to beginning
}
Performance Characteristics #
| Operation | HeapFloatBuffer | DirectFloatBuffer (via ByteBuffer) |
|---|---|---|
| Allocation | O(1) fast | O(n) slower (native allocation) |
| Float Access | O(1) direct array access | O(1) with bounds checking |
| Bulk Operations | Fast (System.arraycopy) | Slower (element-by-element) |
| Memory Usage | 4 bytes per float + overhead | 4 bytes per float + native overhead |
| Numerical Performance | Good (JIT optimized) | Slightly slower (JNI overhead) |
Common Patterns #
Scientific Computing #
// Vector operations on float buffers
FloatBuffer vectorAdd(FloatBuffer a, FloatBuffer b) {
if (a.remaining() != b.remaining()) {
throw new IllegalArgumentException("Vector size mismatch");
}
FloatBuffer result = FloatBuffer.allocate(a.remaining());
for (int i = 0; i < a.remaining(); i++) {
result.put(a.get(i) + b.get(i));
}
result.flip();
return result;
}
// Dot product calculation
float dotProduct(FloatBuffer a, FloatBuffer b) {
if (a.remaining() != b.remaining()) {
throw new IllegalArgumentException("Vector size mismatch");
}
float sum = 0.0f;
for (int i = 0; i < a.remaining(); i++) {
sum += a.get(i) * b.get(i);
}
return sum;
}
Graphics Processing #
// Process vertex data (x, y, z coordinates)
FloatBuffer processVertices(FloatBuffer vertices, float scale) {
FloatBuffer transformed = FloatBuffer.allocate(vertices.remaining());
// Scale each coordinate
for (int i = 0; i < vertices.remaining(); i++) {
transformed.put(vertices.get(i) * scale);
}
transformed.flip();
return transformed;
}
// Normalize vector data
FloatBuffer normalizeVectors(FloatBuffer vectors, int vectorSize) {
if (vectors.remaining() % vectorSize != 0) {
throw new IllegalArgumentException("Invalid vector count");
}
FloatBuffer normalized = FloatBuffer.allocate(vectors.remaining());
int vectorCount = vectors.remaining() / vectorSize;
for (int v = 0; v < vectorCount; v++) {
// Calculate magnitude
float magnitude = 0.0f;
for (int i = 0; i < vectorSize; i++) {
float component = vectors.get(v * vectorSize + i);
magnitude += component * component;
}
magnitude = (float) Math.sqrt(magnitude);
// Normalize components
for (int i = 0; i < vectorSize; i++) {
float component = vectors.get(v * vectorSize + i);
normalized.put(component / magnitude);
}
}
normalized.flip();
return normalized;
}
Signal Processing #
// Apply moving average filter
FloatBuffer movingAverage(FloatBuffer signal, int windowSize) {
if (signal.remaining() < windowSize) {
throw new IllegalArgumentException("Signal too short");
}
FloatBuffer filtered = FloatBuffer.allocate(signal.remaining() - windowSize + 1);
for (int i = 0; i <= signal.remaining() - windowSize; i++) {
float sum = 0.0f;
for (int j = 0; j < windowSize; j++) {
sum += signal.get(i + j);
}
filtered.put(sum / windowSize);
}
filtered.flip();
return filtered;
}
// Find peaks in float data
List<Integer> findPeaks(FloatBuffer data, float threshold) {
List<Integer> peaks = new ArrayList<>();
for (int i = 1; i < data.remaining() - 1; i++) {
float prev = data.get(i - 1);
float curr = data.get(i);
float next = data.get(i + 1);
if (curr > prev && curr > next && curr > threshold) {
peaks.add(i);
}
}
return peaks;
}
Neural Network Operations #
// Apply activation function
FloatBuffer applyActivation(FloatBuffer inputs, ActivationFunction func) {
FloatBuffer outputs = FloatBuffer.allocate(inputs.remaining());
for (int i = 0; i < inputs.remaining(); i++) {
float input = inputs.get(i);
float output = func.apply(input);
outputs.put(output);
}
outputs.flip();
return outputs;
}
// Matrix-vector multiplication (simplified)
FloatBuffer matrixVectorMultiply(FloatBuffer matrix, FloatBuffer vector,
int rows, int cols) {
if (matrix.remaining() != rows * cols || vector.remaining() != cols) {
throw new IllegalArgumentException("Dimension mismatch");
}
FloatBuffer result = FloatBuffer.allocate(rows);
for (int row = 0; row < rows; row++) {
float sum = 0.0f;
for (int col = 0; col < cols; col++) {
sum += matrix.get(row * cols + col) * vector.get(col);
}
result.put(sum);
}
result.flip();
return result;
}
Best Practices #
Floating-Point Precision:
// Be aware of floating-point precision issues FloatBuffer buffer = FloatBuffer.allocate(100); // Avoid direct equality comparisons float a = buffer.get(0); float b = buffer.get(1); // Use epsilon comparison float epsilon = 1e-6f; if (Math.abs(a - b) < epsilon) { // Values are considered equal }NaN and Infinity Handling:
// Always check for special values FloatBuffer data = readSensorData(); for (int i = 0; i < data.remaining(); i++) { float value = data.get(i); if (Float.isNaN(value)) { // Handle NaN (replace with default or skip) data.put(i, 0.0f); } else if (Float.isInfinite(value)) { // Handle infinity (clamp to max/min) if (value > 0) { data.put(i, Float.MAX_VALUE); } else { data.put(i, -Float.MAX_VALUE); } } }Memory Alignment for Performance:
// For native library integration, ensure proper alignment ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); // Ensure buffer is aligned to float size (4 bytes) if (byteBuffer.position() % 4 != 0) { byteBuffer.position(byteBuffer.position() + (4 - byteBuffer.position() % 4)); } FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();Bulk Operations for Performance:
// Use bulk operations for better performance float[] batch = new float[100]; buffer.get(batch); // Single call vs 100 individual gets // For maximum performance with heap buffers: float[] data = new float[1000]; FloatBuffer buffer = FloatBuffer.wrap(data); // Zero overheadByte Order Considerations:
// Float representation depends on byte order ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // x86 native order FloatBuffer floatBuffer = byteBuffer.asFloatBuffer(); // Always specify byte order explicitly
Common Pitfalls #
- Floating-Point Precision: Direct equality comparisons of float values
- NaN Propagation: NaN values can silently propagate through calculations
- Byte Order Errors: Wrong byte order corrupts float values completely
- Buffer Position Errors: Forgetting to
flip()between write/read modes - Memory Alignment: Unaligned access can cause performance penalties or crashes
- Special Value Handling: Not checking for NaN/infinity in calculations
- Performance Bottlenecks: Using single-element operations instead of bulk ops
Internal Implementation #
HeapFloatBuffer #
class HeapFloatBuffer extends FloatBuffer {
final float[] hb; // The underlying float array
final int offset; // The offset into the array
HeapFloatBuffer(int cap, int lim) {
super(-1, 0, lim, cap, null);
hb = new float[cap];
offset = 0;
}
HeapFloatBuffer(float[] buf, int off, int len) {
super(-1, off, off + len, buf.length, null);
hb = buf;
offset = 0;
}
public float get() {
return hb[ix(nextGetIndex())];
}
public FloatBuffer put(float x) {
hb[ix(nextPutIndex())] = x;
return this;
}
int ix(int i) {
return i + offset;
}
}
DirectFloatBuffer (via ByteBuffer view) #
// FloatBuffer view of direct ByteBuffer
class ByteBufferAsFloatBufferB extends FloatBuffer {
private final ByteBuffer bb;
private final int offset;
ByteBufferAsFloatBufferB(ByteBuffer bb) {
super(-1, 0, bb.remaining() >> 2, bb.remaining() >> 2);
this.bb = bb.duplicate().order(ByteOrder.BIG_ENDIAN);
this.offset = bb.position();
}
public float get() {
return bb.getFloat(ix(nextGetIndex()));
}
public FloatBuffer put(float x) {
bb.putFloat(ix(nextPutIndex()), x);
return this;
}
int ix(int i) {
return (i << 2) + offset;
}
}
Performance Optimization #
Vectorized Float Operations #
public class FloatVectorProcessor {
private FloatBuffer buffer;
private final int vectorSize;
public FloatVectorProcessor(int bufferSize, int vectorSize) {
this.buffer = FloatBuffer.allocate(bufferSize);
this.vectorSize = vectorSize;
}
public void processVectors(FloatSupplier supplier) {
float[] vector = new float[vectorSize];
while (true) {
// Fill buffer with vectors
while (buffer.hasRemaining() >= vectorSize) {
for (int i = 0; i < vectorSize; i++) {
float value = supplier.getAsFloat();
if (Float.isNaN(value)) { // End sentinel
flush();
return;
}
buffer.put(value);
}
}
// Process full vectors
buffer.flip();
while (buffer.remaining() >= vectorSize) {
buffer.get(vector);
processVector(vector);
}
buffer.compact();
}
}
private void processVector(float[] vector) {
// Vectorized operation...
}
}
Memory-Mapped Float File Processing #
// Process large float file with memory mapping
public void processFloatFile(Path filePath) throws IOException {
try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
long fileSize = channel.size();
// Ensure file size is multiple of 4 bytes
if (fileSize % 4 != 0) {
throw new IOException("File size not aligned to 4 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);
FloatBuffer floatBuffer = mappedBuffer.asFloatBuffer();
// Process float buffer
processFloatBuffer(floatBuffer);
position += size;
}
}
}
Float Buffer Pool for Scientific Computing #
public class FloatBufferPool {
private final ConcurrentMap<Integer, Queue<FloatBuffer>> pools =
new ConcurrentHashMap<>();
public FloatBuffer acquire(int size) {
Queue<FloatBuffer> pool = pools.computeIfAbsent(size,
k -> new ConcurrentLinkedQueue<>());
FloatBuffer buffer = pool.poll();
if (buffer == null) {
buffer = FloatBuffer.allocate(size);
}
buffer.clear();
return buffer;
}
public FloatBuffer acquireDirect(int size) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size * 4);
byteBuffer.order(ByteOrder.nativeOrder());
return byteBuffer.asFloatBuffer();
}
public void release(FloatBuffer buffer) {
buffer.clear();
Queue<FloatBuffer> pool = pools.get(buffer.capacity());
if (pool != null) {
pool.offer(buffer);
}
}
}
Related Classes #
- ByteBuffer: Byte buffer with float view support
- DoubleBuffer: Double-precision floating-point buffer
- IntBuffer: Integer buffer for numerical data
- LongBuffer: Long integer buffer
- MappedByteBuffer: Memory-mapped file buffer