Tải bản đầy đủ - 0 (trang)
Table?2-1. Primitive data types and sizes

Table?2-1. Primitive data types and sizes

Tải bản đầy đủ - 0trang

Figure 2-15. Little-endian byte order



The way multibyte numeric values are stored in memory is commonly referred to as

endian-ness. If the numerically most-significant byte of the number, the big end, is at the

lower address, then the system is big-endian (Figure 2-14). If the least-significant byte

comes first, it's little-endian (Figure 2-15).

Endian-ness is rarely a choice for software designers; it's usually dictated by the hardware

design. Both types of endian-ness, sometimes known as byte sex, are in wide-spread use

today. There are good arguments for both approaches. Intel processors use the

little-endian design. The Motorola CPU family, Sun Sparc, and PowerPC CPU

architectures are all big-endian.

The question of byte order even transcends CPU hardware design. When the architects of

the Internet were designing the Internet Protocol (IP) suite to interconnect all types of

computers, they recognized the problem of exchanging numeric data between systems

with differing internal byte orders. Therefore, the IPs define a notion of network byte

order,[3] which is big-endian. All multibyte numeric values used within the protocol

portions of IP packets must be converted between the local host byte order and the

common network byte order.

[3] Internet terminology refers to bytes as octets. As mentioned in the sidebar, the size of

a byte can be ambiguous. By using the term "octet," the IP specifications explicitly

mandate that bytes consist of eight bits.

In java.nio, byte order is encapsulated by the ByteOrder class:

package java.nio;

public final class ByteOrder

{

public static final ByteOrder BIG_ENDIAN

public static final ByteOrder LITTLE_ENDIAN

public static ByteOrder nativeOrder()

public String toString()

}



The ByteOrder class defines the constants that determine which byte order to use when

storing or retrieving multibyte values from a buffer. The class acts as a type-safe

49



enumeration. It defines two public fields that are preinitialized with instances of itself.

Only these two instances of ByteOrder ever exist in the JVM, so they can be compared

using the == operator. If you need to know the native byte order of the hardware platform

the JVM is running on, invoke the nativeOrder() static class method. It will return one of

the two defined constants. Calling toString() returns a String containing one of the two

literal strings BIG_ENDIAN or LITTLE_ENDIAN.

Every buffer class has a current byte-order setting that can be queried by calling order():

public abstract class CharBuffer extends Buffer

implements Comparable, CharSequence

{

// This is a partial API listing

public final ByteOrder order()

}



This method returns one of the two constants from ByteOrder. For buffer classes other

than ByteBuffer, the byte order is a read-only property and may take on different values

depending on how the buffer was created. Except for ByteBuffer, buffers created by

allocation or by wrapping an array will return the same value from order(), as does

ByteOrder.nativeOrder(). This is because the elements contained in the buffer are

directly accessed as primitive data within the JVM.

The ByteBuffer class is different: the default byte order is always

ByteOrder.BIG_ENDIAN regardless of the native byte order of the system. Java's default

byte order is big-endian, which allows things such as class files and serialized objects to

work with any JVM. This can have performance implications if the native hardware byte

order is little-endian. Accessing ByteBuffer content as other data types (to be discussed

shortly) can potentially be much more efficient when using the native hardware byte

order.

Hopefully, you're a little puzzled at this point as to why the ByteBuffer class would need a

byte order setting at all. Bytes are bytes, right? Sure, but as you'll soon see in Section

2.4.4, ByteBuffer objects possess a host of convenience methods for getting and putting

the buffer content as other primitive data types. The way these methods encode or decode

the bytes is dependent on the ByteBuffer's current byte-order setting.

The byte-order setting of a ByteBuffer can be changed at any time by invoking order()

with either ByteOrder.BIG_ENDIAN or ByteOrder.LITTLE_ENDIAN as an argument:

public abstract class ByteBuffer extends Buffer

implements Comparable

{

// This is a partial API listing

public final ByteOrder order()

public final ByteBuffer order (ByteOrder bo)

}



50



If a buffer was created as a view of a ByteBuffer object (see Section 2.4.3), then the value

returned by the order() method is the byte-order setting of the originating ByteBuffer at

the time the view was created. The byte-order setting of the view cannot be changed after

it's created and will not be affected if the original byte buffer's byte order is changed later.

2.4.2 Direct Buffers

The most significant way in which byte buffers are distinguished from other buffer types

is that they can be the sources and/or targets of I/O performed by Channels. If you were

to skip ahead to Chapter 3 (hey! hey!), you'd see that channels accept only ByteBuffers as

arguments.

As we saw in Chapter 1, operating systems perform I/O operations on memory areas.

These memory areas, as far as the operating system is concerned, are contiguous

sequences of bytes. It's no surprise then that only byte buffers are eligible to participate in

I/O operations. Also recall that the operating system will directly access the address space

of the process, in this case the JVM process, to transfer the data. This means that memory

areas that are targets of I/O operations must be contiguous sequences of bytes. In the

JVM, an array of bytes may not be stored contiguously in memory, or the Garbage

Collector could move it at any time. Arrays are objects in Java, and the way data is stored

inside that object could vary from one JVM implementation to another.

For this reason, the notion of a direct buffer was introduced. Direct buffers are intended

for interaction with channels and native I/O routines. They make a best effort to store the

byte elements in a memory area that a channel can use for direct, or raw, access by using

native code to tell the operating system to drain or fill the memory area directly.

Direct byte buffers are usually the best choice for I/O operations. By design, they support

the most efficient I/O mechanism available to the JVM. Nondirect byte buffers can be

passed to channels, but doing so may incur a performance penalty. It's usually not

possible for a nondirect buffer to be the target of a native I/O operation. If you pass a

nondirect ByteBuffer object to a channel for write, the channel may implicitly do the

following on each call:

1.

2.

3.

4.



Create a temporary direct ByteBuffer object.

Copy the content of the nondirect buffer to the temporary buffer.

Perform the low-level I/O operation using the temporary buffer.

The temporary buffer object goes out of scope and is eventually garbage

collected.



This can potentially result in buffer copying and object churn on every I/O, which are

exactly the sorts of things we'd like to avoid. However, depending on the implementation,

things may not be this bad. The runtime will likely cache and reuse direct buffers or

perform other clever tricks to boost throughput. If you're simply creating a buffer for

one-time use, the difference is not significant. On the other hand, if you will be using the



51



buffer repeatedly in a high-performance scenario, you're better off allocating direct

buffers and reusing them.

Direct buffers are optimal for I/O, but they may be more expensive to create than

nondirect byte buffers. The memory used by direct buffers is allocated by calling through

to native, operating system-specific code, bypassing the standard JVM heap. Setting up

and tearing down direct buffers could be significantly more expensive than heap-resident

buffers, depending on the host operating system and JVM implementation. The

memory-storage areas of direct buffers are not subject to garbage collection because they

are outside the standard JVM heap.

The performance tradeoffs of using direct versus nondirect buffers can vary widely by

JVM, operating system, and code design. By allocating memory outside the heap, you

may subject your application to additional forces of which the JVM is unaware. When

bringing additional moving parts into play, make sure that you're achieving the desired

effect. I recommend the old software maxim: first make it work, then make it fast. Don't

worry too much about optimization up front; concentrate first on correctness. The JVM

implementation may be able to perform buffer caching or other optimizations that will

give you the performance you need without a lot of unnecessary effort on your part.[4]

[4] "We should forget about small efficiencies, say about 97% of the time: premature

optimization is the root of all evil." — Donald Knuth

A direct ByteBuffer is created by calling ByteBuffer.allocateDirect() with the desired

capacity, just like the allocate() method we covered earlier. Note that wrapped buffers,

those created with one of the wrap() methods, are always non-direct.

public abstract class ByteBuffer

extends Buffer implements Comparable

{

// This is a partial API listing

public static ByteBuffer allocate (int capacity)

public static ByteBuffer allocateDirect (int capacity)

public abstract boolean isDirect();

}



All buffers provide a boolean method named isDirect() to test whether a particular

buffer is direct. While ByteBuffer is the only type that can be allocated as direct, isDirect()

could be true for nonbyte view buffers if the underlying buffer is a direct ByteBuffer.

This leads us to...

2.4.3 View Buffers

As we've already discussed, I/O basically boils down to shuttling groups of bytes around.

When doing high-volume I/O, odds are you'll be using ByteBuffers to read in files,

receive data from network connections, etc. Once the data has arrived in your ByteBuffer,



52



you'll need to look at it to decide what to do or manipulate it before sending it along. The

ByteBuffer class provides a rich API for creating view buffers.

View buffers are created by a factory method on an existing buffer object instance. The

view object maintains its own attributes, capacity, position, limit, and mark, but shares

data elements with the original buffer. We saw the simple form of this in Section 2.3, in

which buffers were duplicated and sliced. But ByteBuffer allows the creation of views to

map the raw bytes of the byte buffer to other primitive data types. For example, the

asLongBuffer() method creates a view buffer that will access groups of eight bytes from

the ByteBuffer as longs.

Each of the factory methods in the following listing create a new buffer that is a view into

the original ByteBuffer object. Invoking one of these methods will create a buffer of the

corresponding type, which is a slice (see Section 2.3) of the underlying byte buffer

corresponding to the byte buffer's current position and limit. The new buffer will have a

capacity equal to the number of elements remaining in the byte buffer (as returned by

remaining()) divided by the number of bytes comprising the view's primitive type (refer

to Table 2-1). Any remaining bytes at the end of the slice will not be visible in the view.

The first element of the view will begin at the position (as returned by position()) of the

ByteBuffer object at the time the view was created. View buffers with data elements that

are aligned on natural modulo boundaries may be eligible for optimization by the

implementation.

public abstract class ByteBuffer

extends Buffer implements Comparable

{

// This is a partial API listing

public

public

public

public

public

public



abstract

abstract

abstract

abstract

abstract

abstract



CharBuffer asCharBuffer();

ShortBuffer asShortBuffer();

IntBuffer asIntBuffer();

LongBuffer asLongBuffer();

FloatBuffer asFloatBuffer();

DoubleBuffer asDoubleBuffer();



}



The following code creates a CharBuffer view of a ByteBuffer, as shown in Figure 2-16.

(Example 2-2 puts this fragment into a larger context.)

ByteBuffer byteBuffer = ByteBuffer.allocate (7).order

(ByteOrder.BIG_ENDIAN);

CharBuffer charBuffer = byteBuffer.asCharBuffer();

Figure 2-16. A CharBuffer view of a ByteBuffer



53



Example 2-2. Creating a char view of a ByteBuffer

package com.ronsoft.books.nio.buffers;

import

import

import

import



java.nio.Buffer;

java.nio.ByteBuffer;

java.nio.CharBuffer;

java.nio.ByteOrder;



/**

* Test asCharBuffer view.

*

* Created May 2002

* @author Ron Hitchens (ron@ronsoft.com)

*/

public class BufferCharView

{

public static void main (String [] argv)

throws Exception

{

ByteBuffer byteBuffer =

ByteBuffer.allocate (7).order (ByteOrder.BIG_ENDIAN);

CharBuffer charBuffer = byteBuffer.asCharBuffer();

// Load the ByteBuffer with some bytes

byteBuffer.put (0, (byte)0);

byteBuffer.put (1, (byte)'H');

byteBuffer.put (2, (byte)0);

byteBuffer.put (3, (byte)'i');

byteBuffer.put (4, (byte)0);

byteBuffer.put (5, (byte)'!');

byteBuffer.put (6, (byte)0);

println (byteBuffer);

println (charBuffer);

}

// Print info about a buffer

private static void println (Buffer buffer)

{

System.out.println ("pos=" + buffer.position()

+ ", limit=" + buffer.limit()

+ ", capacity=" + buffer.capacity()

+ ": '" + buffer.toString() + "'");

}



54



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Table?2-1. Primitive data types and sizes

Tải bản đầy đủ ngay(0 tr)

×