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

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 #

View Source on GitHub

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 #

OperationHeapFloatBufferDirectFloatBuffer (via ByteBuffer)
AllocationO(1) fastO(n) slower (native allocation)
Float AccessO(1) direct array accessO(1) with bounds checking
Bulk OperationsFast (System.arraycopy)Slower (element-by-element)
Memory Usage4 bytes per float + overhead4 bytes per float + native overhead
Numerical PerformanceGood (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 #

  1. 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
    }
    
  2. 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);
            }
        }
    }
    
  3. 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();
    
  4. 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 overhead
    
  5. Byte 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 #

  1. Floating-Point Precision: Direct equality comparisons of float values
  2. NaN Propagation: NaN values can silently propagate through calculations
  3. Byte Order Errors: Wrong byte order corrupts float values completely
  4. Buffer Position Errors: Forgetting to flip() between write/read modes
  5. Memory Alignment: Unaligned access can cause performance penalties or crashes
  6. Special Value Handling: Not checking for NaN/infinity in calculations
  7. 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);
        }
    }
}