Java基本数据类型与字节数组的相互转换
C/C++的相互转换
作为一名C/C++语言开发者,熟悉各种数据类型的底层表示,很容易就会把它们的内存地址直接进行字节型的读写。
比如我们定义了一个int的类型,在不考虑大小端的情况下,就可以直接通过按照变量地址进行读写:
// 写入一个int值到unsigned char *
void writeInt(int iv, unsigned char *p, size_t psize)
assert (sizeof (iv) <= psize);
memcpy (p, &iv, sizeof (iv));
}
// 读取一个unsigned char *到int
void readInt(int *iv, const unsigned char *p, size_t psize)
{
assert (psize >= sizeof (*iv));
*iv = * (int *) p;
}
通过DataInputStream与DataOutputStream
在Java中,这一点却没有那么简单直接。
因为Java一切皆对象,这些对象有的是值类型,有的是指针类型,但是无论是什么类型,都不支持直接对底层内存地址的数据进行读写。
虽然,比较新的Java有unsafe实现,但是unsafe也只能对对象的属性,进行偏移量级别的读写,仍然没法对底层的数据进行操作。
好在作为一个非常成熟的语言,Java在这方面包装了各种类来实现。其中,DataInputStream/DataOutputStream就是一个读写数据类型的好方法(虽然性能不佳)。
DataInputStream是DataInput接口的一个实现。为了篇幅简短,以下是DataInput去掉注释以后的源代码:
public interface DataInput {
void readFully(byte[] b) throws IOException;
void readFully(byte[] b, int off, int len) throws IOException;
int skipBytes(int n) throws IOException;
boolean readBoolean() throws IOException;
byte readByte() throws IOException;
int readUnsignedByte() throws IOException;
short readShort() throws IOException;
int readUnsignedShort() throws IOException;
char readChar() throws IOException;
int readInt() throws IOException;
long readLong() throws IOException;
float readFloat() throws IOException;
double readDouble() throws IOException;
String readLine() throws IOException;
String readUTF() throws IOException;
}
DataInputStream类是一个包装类,可以包装在底层的InputStream上。如果包装一个ByteArrayInputStream,就可以实现对byte[]的读取了。
同理,使用DataOutputStream包装在ByteArrayOutputStream上,就可以实现对byte[]的写入。
因为DataOutputStream实现了DataOutput接口。与DataInput类似,DataOutput接口如下:
public interface DataOutput {
void write(int b) throws IOException;
void write(byte[] b) throws IOException;
void write(byte[] b, int off, int len) throws IOException;
void writeBoolean(boolean v) throws IOException;
void writeByte(int v) throws IOException;
void writeShort(int v) throws IOException;
void writeChar(int v) throws IOException;
void writeInt(int v) throws IOException;
void writeLong(long v) throws IOException;
void writeFloat(float v) throws IOException;
void writeDouble(double v) throws IOException;
void writeBytes(String s) throws IOException;
void writeChars(String s) throws IOException;
void writeUTF(String s) throws IOException;
}
以下两个函数片段演示了具体的数据类型与字节数组的交互:
public byte[] ToByteArray() throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
dataOutputStream.writeBoolean(true);
dataOutputStream.writeByte(1);
dataOutputStream.writeShort(1);
dataOutputStream.writeInt(1);
dataOutputStream.writeLong(1);
dataOutputStream.writeFloat(1.0f);
dataOutputStream.writeDouble(1.0d);
dataOutputStream.writeUTF("hello,world");
byteArrayOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
public void fromByteArray(byte[] bytes) throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
DataInputStream dataInputStream = new DataInputStream(byteArrayInputStream);
boolean bo = dataInputStream.readBoolean();
byte b = dataInputStream.readByte();
short s = dataInputStream.readShort();
int i = dataInputStream.readInt();
long l = dataInputStream.readLong();
float f = dataInputStream.readFloat();
double d = dataInputStream.readDouble();
String str = dataInputStream.readUTF();
dataInputStream.close();
}
通过ByteBuffer
伴随NIO一起被引入Java语言的,还有ByteBuffer。
ByteBuffer是方便做io的缓冲区读写的,我们可以使用它的读写基本类型的接口,来完成byte[]的操作。
比如,我们如果要把基本类型转成byte[],可以先按照每种类型的大小,创建一个ByteBuffer,然后设置值之后,返回它的arrry,即byte[]。
以下是short、int、long、float、double转成byte[]的转换函数:
public static byte[] shortToBytes(short s) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(Short.BYTES);
byteBuffer.putShort(s);
return byteBuffer.array();
}
public static byte[] intToBytes(int i) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(Integer.BYTES);
byteBuffer.putInt(i);
return byteBuffer.array();
}
public static byte[] longToBytes(long l) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(Long.BYTES);
byteBuffer.putLong(l);
return byteBuffer.array();
}
public static byte[] floatToBytes(float f) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(Float.BYTES);
byteBuffer.putFloat(f);
return byteBuffer.array();
}
public static byte[] doubleToBytes(double d) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(Double.BYTES);
byteBuffer.putDouble(d);
return byteBuffer.array();
}
如果要反过来转化,则可以先把确定大小的byte[]数组,转成ByteBuffer,之后再调用ByteBuffer的读取方法。
如:
public static short shortFromBytes(byte[] bytes) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return byteBuffer.getShort();
}
public static int intFromBytes(byte[] bytes) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return byteBuffer.getInt();
}
public static long longFromBytes(byte[] bytes) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return byteBuffer.getLong();
}
public static float floatFromBytes(byte[] bytes) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return byteBuffer.getFloat();
}
public static double doubleFromBytes(byte[] bytes) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return byteBuffer.getDouble();
}
直接转化
上面两种转换方法,都引入了中间对象,有了对象创建与释放的开销,如果追求高性能,还是直接转化比较简单直接。
因为short、int、long类型,不像浮点数那样,有具体语言实现的存储方式,所以可以直接按照字节做运算转化。而float、double都有转换成int、long的实现,可以先转换成int、long再转化成byte[]。
比如:
public static byte[] directShortToBytes(short s) throws IOException {
byte[] bytes = new byte[Short.BYTES];
bytes[0] = (byte) (s >> 8 & 0xFF);
bytes[1] = (byte) (s & 0xFF);
return bytes;
}
public static byte[] directIntToBytes(int i) throws IOException {
byte[] bytes = new byte[Integer.BYTES];
bytes[0] = (byte) (i >> 24 & 0xFF);
bytes[1] = (byte) (i >> 16 & 0xFF);
bytes[2] = (byte) (i >> 8 & 0xFF);
bytes[3] = (byte) (i & 0xFF);
return bytes;
}
public static byte[] directLongToBytes(long l) throws IOException {
byte[] bytes = new byte[Long.BYTES];
bytes[0] = (byte) (l >> 56 & 0xFF);
bytes[1] = (byte) (l >> 48 & 0xFF);
bytes[2] = (byte) (l >> 40 & 0xFF);
bytes[3] = (byte) (l >> 32 & 0xFF);
bytes[4] = (byte) (l >> 24 & 0xFF);
bytes[5] = (byte) (l >> 16 & 0xFF);
bytes[6] = (byte) (l >> 8 & 0xFF);
bytes[7] = (byte) (l & 0xFF);
return bytes;
}
public static byte[] directFloatToBytes(float f) throws IOException {
int i = Float.floatToIntBits(f);
return directIntToBytes(i);
}
public static byte[] directDoubleToBytes(double d) throws IOException {
long l = Double.doubleToLongBits(d);
return directLongToBytes(l);
}
以下是相反的实现:
public static short directShortFromBytes(byte[] bytes) throws IOException {
short s = (short) (bytes[0] << 8);
s += bytes[1];
return s;
}
public static int directIntFromBytes(byte[] bytes) throws IOException {
int i = bytes[0] << 24;
i += bytes[1] << 16;
i += bytes[2] << 8;
i += bytes[3];
return i;
}
public static long directLongFromBytes(byte[] bytes) throws IOException {
long l = ((long) bytes[0]) << 56;
l += ((long) bytes[1]) << 48;
l += ((long) bytes[2]) << 40;
l += ((long) bytes[3]) << 32;
l += bytes[4] << 24;
l += bytes[5] << 16;
l += bytes[6] << 8;
l += bytes[7];
return l;
}
public static float directFloatFromBytes(byte[] bytes) throws IOException {
int i = directIntFromBytes(bytes);
return Float.intBitsToFloat(i);
}
public static double directDoubleFromBytes(byte[] bytes) throws IOException {
long l = directLongFromBytes(bytes);
return Double.longBitsToDouble(l);
}