Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

  




 

 

Thinking in Java
Prev Contents / Index Next

View buffers

A “view buffer” allows you to look at an underlying ByteBuffer through the window of a particular primitive type. The ByteBuffer is still the actual storage that’s “backing” the view, so any changes you make to the view are reflected in modifications to the data in the ByteBuffer. As seen in the previous example, this allows you to conveniently insert primitive types into a ByteBuffer. A view also allows you to read primitive values from a ByteBuffer, either one at a time (as ByteBuffer allows) or in batches (into arrays). Here’s an example that manipulates ints in a ByteBuffer via an IntBuffer:

//: c12:IntBufferDemo.java
// Manipulating ints in a ByteBuffer with an IntBuffer
import java.nio.*;
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;

public class IntBufferDemo {
  private static Test monitor = new Test();
  private static final int BSIZE = 1024;
  public static void main(String[] args) {
    ByteBuffer bb = ByteBuffer.allocate(BSIZE);
    IntBuffer ib = bb.asIntBuffer();
    // Store an array of int:
    ib.put(new int[] { 11, 42, 47, 99, 143, 811, 1016 });
    // Absolute location read and write:
    System.out.println(ib.get(3));
    ib.put(3, 1811);
    ib.rewind();
    while(ib.hasRemaining()) {
      int i = ib.get();
      if(i == 0) break; // Else we'll get the entire buffer
      System.out.println(i);
    }
    monitor.expect(new String[] {
      "99",
      "11",
      "42",
      "47",
      "1811",
      "143",
      "811",
      "1016"
    });
  }
} ///:~


The overloaded put( ) method is first used to store an array of int. The following get( ) and put( ) method calls directly access an int location in the underlying ByteBuffer. Note that these absolute location accesses are available for primitive types by talking directly to a ByteBuffer, as well.

Once the underlying ByteBuffer is filled with ints or some other primitive type via a view buffer, then that ByteBuffer can be written directly to a channel. You can just as easily read from a channel and use a view buffer to convert everything to a particular type of primitive. Here’s an example that interprets the same sequence of bytes as short, int, float, long, and double by producing different view buffers on the same ByteBuffer:

//: c12:ViewBuffers.java
import java.nio.*;
import com.bruceeckel.simpletest.*;

public class ViewBuffers {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    ByteBuffer bb = ByteBuffer.wrap(
      new byte[]{ 0, 0, 0, 0, 0, 0, 0, 'a' });
    bb.rewind();
    System.out.println("Byte Buffer");
    while(bb.hasRemaining())
      System.out.println(bb.position()+ " -> " + bb.get());
    CharBuffer cb =
      ((ByteBuffer)bb.rewind()).asCharBuffer();
    System.out.println("Char Buffer");
    while(cb.hasRemaining())
      System.out.println(cb.position()+ " -> " + cb.get());
    FloatBuffer fb =
      ((ByteBuffer)bb.rewind()).asFloatBuffer();
    System.out.println("Float Buffer");
    while(fb.hasRemaining())
      System.out.println(fb.position()+ " -> " + fb.get());
    IntBuffer ib =
      ((ByteBuffer)bb.rewind()).asIntBuffer();
    System.out.println("Int Buffer");
    while(ib.hasRemaining())
      System.out.println(ib.position()+ " -> " + ib.get());
    LongBuffer lb =
      ((ByteBuffer)bb.rewind()).asLongBuffer();
    System.out.println("Long Buffer");
    while(lb.hasRemaining())
      System.out.println(lb.position()+ " -> " + lb.get());
    ShortBuffer sb =
      ((ByteBuffer)bb.rewind()).asShortBuffer();
    System.out.println("Short Buffer");
    while(sb.hasRemaining())
      System.out.println(sb.position()+ " -> " + sb.get());
    DoubleBuffer db =
      ((ByteBuffer)bb.rewind()).asDoubleBuffer();
    System.out.println("Double Buffer");
    while(db.hasRemaining())
      System.out.println(db.position()+ " -> " + db.get());
    monitor.expect(new String[] {
      "Byte Buffer",
      "0 -> 0",
      "1 -> 0",
      "2 -> 0",
      "3 -> 0",
      "4 -> 0",
      "5 -> 0",
      "6 -> 0",
      "7 -> 97",
      "Char Buffer",
      "0 -> \0",
      "1 -> \0",
      "2 -> \0",
      "3 -> a",
      "Float Buffer",
      "0 -> 0.0",
      "1 -> 1.36E-43",
      "Int Buffer",
      "0 -> 0",
      "1 -> 97",
      "Long Buffer",
      "0 -> 97",
      "Short Buffer",
      "0 -> 0",
      "1 -> 0",
      "2 -> 0",
      "3 -> 97",
      "Double Buffer",
      "0 -> 4.8E-322"
    });
  }
} ///:~


The ByteBuffer is produced by “wrapping” an eight-byte array, which is then displayed via view buffers of all the different primitive types. You can see in the following diagram the way the data appears differently when read from the different types of buffers:

TIJ327.png

This corresponds to the output from the program.

Endians

Different machines may use different byte-ordering approaches to store data. “Big endian” places the most significant byte in the lowest memory address, and “little endian” places the most significant byte in the highest memory address. When storing a quantity that is greater than one byte, like int, float, etc., you may need to consider the byte ordering. A ByteBuffer stores data in big endian form, and data sent over a network always uses big endian order. You can change the endian-ness of a ByteBuffer using order( ) with an argument of ByteOrder.BIG_ENDIAN or ByteOrder.LITTLE_ENDIAN.

Consider a ByteBuffer containing the following two bytes:

TIJ328.png

If you read the data as a short (ByteBuffer.asShortBuffer( )), you will get the number 97 (00000000 01100001), but if you change to little endian, you will get the number 24832 (01100001 00000000).

Here’s an example that shows how byte ordering is changed in characters depending on the endian setting:

//: c12:Endians.java
// Endian differences and data storage.
import java.nio.*;
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;

public class Endians {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
    bb.asCharBuffer().put("abcdef");
    System.out.println(Arrays2.toString(bb.array()));
    bb.rewind();
    bb.order(ByteOrder.BIG_ENDIAN);
    bb.asCharBuffer().put("abcdef");
    System.out.println(Arrays2.toString(bb.array()));
    bb.rewind();
    bb.order(ByteOrder.LITTLE_ENDIAN);
    bb.asCharBuffer().put("abcdef");
    System.out.println(Arrays2.toString(bb.array()));
    monitor.expect(new String[]{
      "[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]",
      "[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]",
      "[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]"
    });
  } 
} ///:~


The ByteBuffer is given enough space to hold all the bytes in charArray as an external buffer so that that array( ) method can be called to display the underlying bytes. The array( ) method is “optional,” and you can only call it on a buffer that is backed by an array; otherwise, you’ll get an UnsupportedOperationException.

charArray is inserted into the ByteBuffer via a CharBuffer view. When the underlying bytes are displayed, you can see that the default ordering is the same as the subsequent big endian order, whereas the little endian order swaps the bytes.
Thinking in Java
Prev Contents / Index Next


 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire