A nice feature of the NIO Bufferclass is the ability to perform reads and writes of the numeric data types using either Big Endian or Little Endian byte order. For those not familiar with the difference, for multibyte data types like short (2 bytes), integer and float (4 bytes), and long and double (8 bytes), Little Endian stores the bytes starting from the least significant byte (“littlest” number) toward the most significant. Of course, Big Endian stores bytes in the opposite direction. Processors from Motorola and Sun use Big Endian order, while Intel uses Little Endian. Prior to JDK 1.4, you would have to perform the byte-swapping yourself. I created a class called LittleEndian- OutputStreamto do just that for a BMP Image encoder. Listing 2.2 demonstrates the byte swapping. This is necessary because the only order available for DataOutput- Streamis Big Endian.
008: class LittleEndianOutputStream extends OutputStream 009: {
010: OutputStream os; 011:
012: public LittleEndianOutputStream(OutputStream os) 013: {
014: this.os = os; 015: }
016:
017: public void write(int b) throws IOException 018: {
019: os.write(b); 020: }
021:
022: public void writeShort(short s) throws IOException 023: {
024: int is = (int) s; // promote 025: int maskB1 = 0xff;
026: int maskB2 = 0xff00; 027:
028: byte [] b = new byte[2]; 029: b[0] = (byte) (s & maskB1);
030: b[1] = (byte) ((s & maskB2) >>> 8); 031:
032: os.write(b); 033: }
034:
035: public void writeInt(int i) throws IOException 036: {
037: byte [] b = new byte[4]; 038: int maskB1 = 0xff; 039: int maskB2 = 0xff00; 040: int maskB3 = 0xff0000; 041: int maskB4 = 0xff000000; 042:
043: b[3] = (byte) ((i & maskB4) >>> 24); 044: b[2] = (byte) ((i & maskB3) >>> 16); 045: b[1] = (byte) ((i & maskB2) >>> 8); 046: b[0] = (byte) (i & maskB1);
047:
048: os.write(b); 049: }
050: }
The LittleEndianOutputStream was used in combination with a DataOut- putStreamto write integers and shorts in a BMP encoder. Note how we manually swap the bytes when writing an integer to the underlying OutputStream in the
writeInt()method. We swap the bytes by masking the byte in the original integer (Big Endian format), shifting it down to the lowest byte position and assigning it to its new byte position. LittleEndianOutputStream is now obsolete, as Listing 2.3 shows the encoder rewritten using NIO Buffers.
001: /** BmpWriter3.java */ 002: package org.javapitfalls.item2; 003: 004: import java.awt.*; 005: import java.awt.image.*; 006: import java.io.*; 007: import java.nio.*; 008: import java.nio.channels.*; 009:
010: public class BmpWriter3 011: {
012: // File Header - Actual contents (14 bytes): 013: short fileType = 0x4d42;// always “BM”
014: int fileSize; // size of file in bytes 015: short reserved1 = 0; // always 0
016: short reserved2 = 0; // always 0
017: int bitmapOffset = 54; // starting byte position of image data 018:
019: // BMP Image Header - Actual conents (40 bytes):
020: int size = 40; // size of this header in bytes 021: int width; // image width in pixels
022: int height; // image height in pixels (if < 0, “top-down”) 023: short planes = 1; // no. of color planes: always 1
024: short bitsPerPixel = 24;// number of bits per pixel: 1, 4, 8, Æ or 24 (no color map)
// Some data members omitted for brevity -- code available online. 037:
038: public void storeImage(Image img, String sFilename) throws Æ IOException
039: {
// ... getting Image width and height omitted for brevity ... 056:
057: width = imgWidth; 058: height = imgHeight; 059:
060: imgPixels = new int[imgWidth * imgHeight]; 061: // pixels are stored in rows
062: try 063: { 064: PixelGrabber pg = new Æ PixelGrabber(img,0,0,imgWidth,imgHeight,imgPixels, 065: 0,imgWidth); 066: pg.grabPixels(); 067: } catch (Exception e) 068: {
069: throw new IOException(“Exception. Reason: “ + e.toString());
070: } 071:
072: // now open the file
073: FileOutputStream fos = new FileOutputStream(sFilename); 074: FileChannel fc = fos.getChannel();
075:
076: // write the “header” 077: boolean padded=false;
078: // first calculate the scanline size 079: iScanLineSize = 3 * width; 080: if (iScanLineSize % 2 != 0) 081: { 082: iScanLineSize++; 083: padded = true; 084: } 085:
086: // now, calculate the file size
087: fileSize = 14 + 40 + (iScanLineSize * imgHeight); 088: sizeOfBitmap = iScanLineSize * imgHeight;
089:
090: // create a ByteBuffer
091: ByteBuffer bbuf = ByteBuffer.allocate(fileSize); 092: bbuf.order(ByteOrder.LITTLE_ENDIAN);
093: bbuf.clear();
094:
095: // now put out file header
096: bbuf.putShort(fileType); 097: bbuf.putInt(fileSize); 098: bbuf.putShort(reserved1); 099: bbuf.putShort(reserved2); 100: bbuf.putInt(bitmapOffset); 101:
// ... some output to buffer code omitted for brevity ...
113:
114: // put the pixels
115: for (int i= (imgHeight - 1); i >= 0; i--) 116: {
117: byte pad = 0;
118: for (int j=0; j < imgWidth; j++) 119: {
120: int pixel = imgPixels[(i * width) + j]; 121: byte alpha = (byte) ((pixel >> 24) & 0xff); 122: byte red = (byte) ((pixel >> 16) & 0xff); 123: byte green = (byte) ((pixel >> 8) & 0xff); 124: byte blue = (byte) ((pixel ) & 0xff); 125: 126: // put them bgr 127: bbuf.put(blue); 128: bbuf.put(green); 129: bbuf.put(red); 130: } 131: if (padded) 132: bbuf.put(pad); 133: } 134: 135: bbuf.flip(); 136: fc.write(bbuf); 137: fos.close(); 138: } 139:
140: public static void main(String args[]) 141: {
// method omitted for brevity - available on Web site. 171:
172: } 173:
Listing 2.3 (continued)
The key lines of Listing 2.3 are as follows:
■■ At lines 64 and 65, we grab the image pixels (as integers) using the Pixel- Grabberclass.
■■ At lines 73 and 74, we first create the FileOutputStreamfor the BMP output file and then get the FileChannelusing the getChannel()method. This demonstrates the integration between the existing IO packages (FileOutput- Stream) and the new IO packages (FileChannel).
■■ At line 91, we create a ByteBuffervia the static allocate()method. It is important to note that there is also an allocateDirect()method, which allows you to create DirectBuffers. ADirectBufferis a buffer allocated by the operating system to reduce the number of copies between the virtual machine and the operating system. How DirectBuffersare implemented differs for each operating system. Additionally, a direct buffer may be more expensive to create because of the interaction with the operating system, so they should be used for long-standing buffers.
■■ There are three values associated with all buffers: position, limit, and capacity. The position is the current location to read from or write to. The limit is the amount of data in the array. The capacity is the size of the underlying array.
■■ At line 92, we use the order()method to set the endian order to
LITTLE_ENDIAN.
■■ At lines 96 and 97, we use putInt()and putShort()to write integers and shorts, respectively, in Little Endian order, to the byte buffer.
■■ At line 135, we flip the buffer from reading to writing using the flip()
method. The flip()method sets the limit to the current position and the position back to 0.
■■ At line 136, we write the byte buffer to the underlying file.
Now we can finish our discussion of NIO by examining how the package imple- ments non-blocking IO.