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

CharBuffer

8 mins

The CharBuffer class is a character buffer that implements CharSequence, making it suitable for text processing operations. It provides character-level I/O operations and can be used with charset encoders/decoders.

Source Code #

View Source on GitHub

Core Implementation #

public abstract class CharBuffer extends Buffer implements CharSequence, Comparable<CharBuffer> {
    // Factory methods
    public static CharBuffer allocate(int capacity) {
        return new HeapCharBuffer(capacity, capacity);
    }
    
    public static CharBuffer wrap(CharSequence csq) {
        return wrap(csq.toString());
    }
    
    public static CharBuffer wrap(char[] array) {
        return new HeapCharBuffer(array, 0, array.length);
    }
    
    public static CharBuffer wrap(char[] array, int offset, int length) {
        return new HeapCharBuffer(array, offset, length);
    }
    
    // CharSequence implementation
    public abstract char charAt(int index);
    public abstract CharSequence subSequence(int start, int end);
    public abstract String toString();
    
    // Character operations
    public abstract char get();
    public abstract char get(int index);
    public abstract CharBuffer put(char c);
    public abstract CharBuffer put(int index, char c);
    
    // Bulk operations
    public CharBuffer get(char[] dst) {
        return get(dst, 0, dst.length);
    }
    
    public CharBuffer get(char[] 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;
    }
    
    // Append and read operations
    public CharBuffer append(CharSequence csq) {
        return put(csq.toString());
    }
    
    public CharBuffer append(char c) {
        return put(c);
    }
    
    public CharBuffer append(CharSequence csq, int start, int end) {
        return put(csq.subSequence(start, end).toString());
    }
}

Implementation Details #

CharSequence Implementation #

CharBuffer implements CharSequence, allowing it to be used wherever CharSequence is expected:

CharBuffer buffer = CharBuffer.allocate(100);
buffer.put("Hello, World!").flip();

// CharSequence methods
char ch = buffer.charAt(5);           // ','
int length = buffer.length();         // 13
CharSequence sub = buffer.subSequence(0, 5);  // "Hello"
String str = buffer.toString();       // "Hello, World!"

// Can be used with APIs expecting CharSequence
StringBuilder sb = new StringBuilder();
sb.append(buffer);
Pattern pattern = Pattern.compile("Hello");
Matcher matcher = pattern.matcher(buffer);

Character Encoding and Decoding #

CharBuffer works with charset encoders and decoders:

// Encoding characters to bytes
Charset charset = StandardCharsets.UTF_8;
CharBuffer charBuffer = CharBuffer.wrap("Hello, World!");
ByteBuffer byteBuffer = charset.encode(charBuffer);

// Decoding bytes to characters
ByteBuffer source = ByteBuffer.wrap(bytes);
CharBuffer decoded = charset.decode(source);

// Direct encoding/decoding
CharsetEncoder encoder = charset.newEncoder();
CharsetDecoder decoder = charset.newDecoder();

CoderResult result = encoder.encode(charBuffer, byteBuffer, true);
if (result.isUnderflow()) {
    // All input processed
}

Text Processing Operations #

// Reading lines from buffer
CharBuffer buffer = CharBuffer.allocate(1024);
// Fill buffer with text...

buffer.flip();
while (buffer.hasRemaining()) {
    // Find end of line
    int start = buffer.position();
    int end = start;
    while (end < buffer.limit() && buffer.get(end) != '\n') {
        end++;
    }
    
    if (end < buffer.limit()) {
        // Extract line (excluding newline)
        CharBuffer line = buffer.slice(start, end - start);
        buffer.position(end + 1);  // Skip newline
        // Process line...
    }
}

Compact Operation for Text #

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

// Usage for text processing
CharBuffer buffer = CharBuffer.allocate(1024);
Reader reader = new FileReader("text.txt");
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
    buffer.flip();
    
    // Process complete lines
    processLines(buffer);
    
    // Compact remaining partial line
    buffer.compact();
}
reader.close();

View Operations #

CharBuffer can be created from ByteBuffer:

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.order(ByteOrder.BIG_ENDIAN);

// Create CharBuffer view
CharBuffer charBuffer = byteBuffer.asCharBuffer();

// The view shares the underlying byte buffer
// Changes affect both buffers
charBuffer.put('A');  // Writes 2 bytes to byteBuffer

Performance Characteristics #

OperationHeapCharBufferDirectCharBuffer (via ByteBuffer)
AllocationO(1) fastO(n) slower (native allocation)
Character AccessO(1) direct array accessO(1) with bounds checking
Bulk OperationsFast (System.arraycopy)Slower (element-by-element)
I/O OperationsRequires copyingZero-copy possible
Memory OverheadLow (char[] + object)High (native memory + view overhead)

Common Patterns #

Text File Processing #

CharBuffer buffer = CharBuffer.allocate(8192);
FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();

while (channel.read(byteBuffer) != -1) {
    byteBuffer.flip();
    
    // Decode bytes to characters
    CoderResult result = decoder.decode(byteBuffer, buffer, false);
    
    buffer.flip();
    
    // Process text
    while (buffer.hasRemaining()) {
        char ch = buffer.get();
        // Process character...
    }
    
    buffer.compact();
    byteBuffer.compact();
}

// Flush decoder
decoder.decode(byteBuffer, buffer, true);
decoder.flush(buffer);
buffer.flip();
// Process remaining text

String Building with CharBuffer #

CharBuffer buffer = CharBuffer.allocate(1024);

// Append multiple strings
buffer.append("Hello");
buffer.append(", ");
buffer.append("World!");
buffer.append('\n');

// Convert to string
buffer.flip();
String result = buffer.toString();

// Reset for reuse
buffer.clear();

Pattern Matching on Buffer #

CharBuffer buffer = CharBuffer.allocate(1024);
// Fill buffer with text...
buffer.flip();

// Use regex on buffer
Pattern pattern = Pattern.compile("\\b\\w+\\b");
Matcher matcher = pattern.matcher(buffer);

while (matcher.find()) {
    int start = matcher.start();
    int end = matcher.end();
    CharBuffer word = buffer.slice(start, end - start);
    // Process word...
}

// Reset matcher for new data
buffer.compact();
// Fill with new data...
buffer.flip();
matcher.reset(buffer);

Character Stream Processing #

class CharBufferProcessor {
    private CharBuffer buffer = CharBuffer.allocate(4096);
    
    public void process(ReadableByteChannel channel, Charset charset) 
            throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4096);
        CharsetDecoder decoder = charset.newDecoder();
        
        while (channel.read(byteBuffer) != -1) {
            byteBuffer.flip();
            
            boolean endOfInput = false;
            while (!endOfInput) {
                CoderResult result = decoder.decode(byteBuffer, buffer, false);
                
                buffer.flip();
                processBuffer(buffer);
                buffer.compact();
                
                if (result.isUnderflow()) {
                    break;
                } else if (result.isOverflow()) {
                    // Buffer full, process and continue
                    buffer.flip();
                    processBuffer(buffer);
                    buffer.compact();
                }
            }
            
            byteBuffer.compact();
        }
        
        // Flush decoder
        decoder.decode(byteBuffer, buffer, true);
        decoder.flush(buffer);
        buffer.flip();
        processBuffer(buffer);
        buffer.clear();
    }
    
    private void processBuffer(CharBuffer buffer) {
        // Process text in buffer...
    }
}

Best Practices #

  1. Choose Appropriate Capacity:

    • For line-oriented text: 4KB-8KB buffers
    • For whole document processing: match expected document size
    • For streaming: 1KB-4KB buffers with compact() reuse
  2. Use CharSequence Features:

    • Leverage CharSequence implementation for interoperability
    • Use subSequence() for zero-copy substring extraction
    • Implement text algorithms directly on buffer
  3. Handle Encoding Properly:

    • Always specify charset when encoding/decoding
    • Use StandardCharsets constants for common charsets
    • Handle CoderResult for robust error handling
  4. Reuse Buffers:

    // Thread-local buffer for text processing
    private static final ThreadLocal<CharBuffer> TEXT_BUFFER = 
        ThreadLocal.withInitial(() -> CharBuffer.allocate(4096));
    
    public void processText(String text) {
        CharBuffer buffer = TEXT_BUFFER.get();
        buffer.clear();
        buffer.put(text);
        buffer.flip();
        // Process...
    }
    
  5. Monitor Buffer Limits:

    • Check remaining() before bulk operations
    • Use hasRemaining() in loops
    • Handle buffer overflow/underflow gracefully

Common Pitfalls #

  1. Forgetting to Flip: Reading from buffer after writing without flip()
  2. Character Encoding Issues: Assuming default platform charset
  3. Buffer Overflow: Writing beyond capacity without checking remaining()
  4. View Buffer Confusion: CharBuffer views of ByteBuffer have independent position/limit
  5. Line Ending Differences: Platform-specific line endings (\n, \r\n, \r)
  6. Unicode Surrogate Pairs: Multi-char code points may span buffer boundaries
  7. Thread Safety: CharBuffer is not thread-safe for concurrent access

Internal Implementation #

HeapCharBuffer #

class HeapCharBuffer extends CharBuffer {
    final char[] hb;       // The underlying char array
    final int offset;      // The offset into the array
    
    HeapCharBuffer(int cap, int lim) {
        super(-1, 0, lim, cap, null);
        hb = new char[cap];
        offset = 0;
    }
    
    public char get() {
        return hb[ix(nextGetIndex())];
    }
    
    public CharBuffer put(char x) {
        hb[ix(nextPutIndex())] = x;
        return this;
    }
    
    public char charAt(int index) {
        return hb[ix(checkIndex(index))];
    }
    
    public CharSequence subSequence(int start, int end) {
        int pos = position();
        return new HeapCharBuffer(hb, 
            -1, 
            pos + start, 
            pos + end, 
            capacity(), 
            offset);
    }
    
    int ix(int i) {
        return i + offset;
    }
}

StringCharBuffer #

// Read-only CharBuffer backed by String
class StringCharBuffer extends CharBuffer {
    private final String str;
    
    StringCharBuffer(String str, int start, int end) {
        super(-1, start, end, str.length(), null);
        this.str = str;
    }
    
    public char get() {
        return str.charAt(nextGetIndex());
    }
    
    public CharBuffer put(char c) {
        throw new ReadOnlyBufferException();
    }
    
    public char charAt(int index) {
        return str.charAt(index);
    }
    
    public CharSequence subSequence(int start, int end) {
        return new StringCharBuffer(str, start, end);
    }
    
    public String toString() {
        return str.substring(position(), limit());
    }
}

DirectCharBuffer (via ByteBuffer view) #

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

Performance Optimization #

Buffer Pooling for Text Processing #

public class CharBufferPool {
    private final BlockingQueue<CharBuffer> pool;
    
    public CharBufferPool(int size, int bufferSize) {
        pool = new ArrayBlockingQueue<>(size);
        for (int i = 0; i < size; i++) {
            pool.add(CharBuffer.allocate(bufferSize));
        }
    }
    
    public CharBuffer acquire() throws InterruptedException {
        CharBuffer buffer = pool.take();
        buffer.clear();
        return buffer;
    }
    
    public void release(CharBuffer buffer) {
        buffer.clear();
        pool.put(buffer);
    }
}

Zero-Copy Text Extraction #

// Extract substrings without copying
CharBuffer buffer = CharBuffer.allocate(1024);
buffer.put("The quick brown fox jumps over the lazy dog");
buffer.flip();

// Zero-copy substring using slice()
int wordStart = 4;  // "quick"
int wordEnd = 9;    // "brown"
buffer.position(wordStart).limit(wordEnd);
CharBuffer word = buffer.slice();  // No data copying
buffer.clear();

// word contains "brown" (shared backing array)

Efficient Line Reading #

public class LineReader {
    private CharBuffer buffer = CharBuffer.allocate(8192);
    private StringBuilder lineBuilder = new StringBuilder();
    
    public String readLine(ReadableByteChannel channel, Charset charset) 
            throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(2048);
        CharsetDecoder decoder = charset.newDecoder();
        
        while (true) {
            // Read bytes
            int bytesRead = channel.read(byteBuffer);
            if (bytesRead == -1) {
                return lineBuilder.length() > 0 ? lineBuilder.toString() : null;
            }
            
            byteBuffer.flip();
            
            // Decode to characters
            decoder.decode(byteBuffer, buffer, bytesRead == -1);
            
            buffer.flip();
            
            // Look for newline
            while (buffer.hasRemaining()) {
                char ch = buffer.get();
                if (ch == '\n') {
                    String line = lineBuilder.toString();
                    lineBuilder.setLength(0);
                    buffer.compact();
                    byteBuffer.compact();
                    return line;
                } else if (ch != '\r') {
                    lineBuilder.append(ch);
                }
            }
            
            buffer.compact();
            byteBuffer.compact();
        }
    }
}