package utils.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Externalizable; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Arrays; import org.springframework.util.Base64Utils; import utils.ByteSequence; import utils.codec.Base58Utils; import utils.codec.HexUtils; /** * ByteArray 二进制字节块是对字节数组的包装,目的是提供一种不可变的二进制数据结构; * * @author huanghaiquan * */ public class ByteArray implements Externalizable, ByteSequence { public static final ByteArray EMPTY = ByteArray.wrap(new byte[0]); private byte[] bytes; private int hashCode; private ByteArray readonlyWrapper = null; private ByteArray(byte[] bytes) { this(bytes, false); } private ByteArray(byte[] bytes, boolean readonly) { this.bytes = bytes; this.hashCode = Arrays.hashCode(bytes); // this.readonly = readonly; } public int size() { return bytes.length; } public byte get(int i) { return bytes[i]; } // public boolean isReadonly() { // return readonly; // } public void copy(int srcPos, byte[] dest, int destPos, int length) { System.arraycopy(bytes, srcPos, dest, destPos, length); } public ByteArray() {} @Override public boolean equals(Object obj) { if (super.equals(obj)) { return true; } if (obj == null) { return false; } if (!(obj instanceof ByteArray)) { return false; } ByteArray target = (ByteArray) obj; if (this.bytes == target.bytes) { return true; } return BytesUtils.equals(target.bytes, this.bytes); } @Override public int hashCode() { return Arrays.hashCode(bytes); } /** * 对指定的字节数组的副本进行包装; * * @param bytes bytes * @return ByteArray */ public static ByteArray wrapCopy(byte[] bytes) { byte[] replica = Arrays.copyOf(bytes, bytes.length); return new ByteArray(replica); } /** * 对指定的字节数组直接进行包装; * * @param bytes bytes * @return ByteArray */ public static ByteArray wrap(byte[] bytes) { return new ByteArray(bytes); } /** * 对指定的字节数组直接进行包装; * * @param oneByte oneByte * @return ByteArray */ public static ByteArray wrap(byte oneByte) { return new ByteArray(new byte[] { oneByte }); } /** * 对指定的字节数组的副本进行包装; * * @param bytes bytes * @return ByteArray */ public static ByteArray wrapCopyReadonly(byte[] bytes) { byte[] replica = Arrays.copyOf(bytes, bytes.length); return new ByteArray(replica, true); } /** * 对指定的字节数组直接进行包装; * * @param bytes bytes * @return ByteArray */ public static ByteArray wrapReadonly(byte[] bytes) { return new ByteArray(bytes, true); } /** * 对指定的字节数组直接进行包装; * * @param oneByte oneByte * @return ByteArray */ public static ByteArray wrapReadonly(byte oneByte) { return new ByteArray(new byte[] { oneByte }, true); } /** * 将字节内容完整写入指定的输出流; * @param out out * @return int */ public int writeTo(OutputStream out) { try { out.write(bytes); return bytes.length; } catch (IOException e) { throw new RuntimeIOException(e.getMessage(), e); } } public InputStream asInputStream() { return new ByteArrayInputStream(bytes); } public ByteBuffer asReadOnlyBuffer() { return ByteBuffer.wrap(bytes).asReadOnlyBuffer(); } // public ByteArray asReadonly() { // if (readonly) { // return this; // } // if (readonlyWrapper == null) { // readonlyWrapper = new ByteArray(bytes, true); // } // return readonlyWrapper; // } /** * 返回原始的二进制数组的副本;
* * 注:使用者要注意此操作代理的内存性能的影响; * * @return byte array; */ public byte[] bytesCopy() { return Arrays.copyOf(bytes, bytes.length); } @Override public int copyTo(int sourceOffset, byte[] dest, int destOffset, int length) { System.arraycopy(bytes, sourceOffset, dest, destOffset, length); return length; } /** * 返回原始的字节数组; * *
* 如果字节数组被标识为只读,调用此方法将引发异常 IllegalStateException; * * 注:应谨慎操作此方法返回的字节数组,通常的原则是不应更改返回的字节数组,用于只读的情形; * * @return byte array; */ public byte[] bytes() { // if (readonly) { // throw new IllegalStateException("This byte array is readonly!"); // } return bytes; } public String toHex() { return toHex(bytes); } public String toBase64() { return toBase64(bytes); } public String toBase58() { return toBase58(bytes); } /** * 返回 ByteArray 的 Base58 字符; */ @Override public String toString() { return toBase58(); } public static ByteArray parseHex(String hexString) { return wrap(fromHex(hexString)); } public static ByteArray parseBase58(String base58String) { return wrap(fromBase58(base58String)); } public static ByteArray parseBase64(String base64String) { return wrap(fromBase64(base64String)); } public static ByteArray parseString(String str, String charset) { return wrap(fromString(str, charset)); } public static String toHex(byte[] bytes) { return HexUtils.encode(bytes); } public static String toBase58(byte[] bytes) { return Base58Utils.encode(bytes); } public static String toBase64(byte[] bytes) { return Base64Utils.encodeToUrlSafeString(bytes); } public String toString(String charset) { return BytesUtils.toString(bytes, charset); } public static byte[] fromHex(String hexString) { return HexUtils.decode(hexString); } public static byte[] fromBase58(String base58String) { return Base58Utils.decode(base58String); } public static byte[] fromBase64(String base64String) { return Base64Utils.decodeFromUrlSafeString(base64String); } public static byte[] fromString(String str, String charset) { return BytesUtils.toBytes(str, charset); } /** * The object implements the writeExternal method to save its contents * by calling the methods of DataOutput for its primitive values or * calling the writeObject method of ObjectOutput for objects, strings, * and arrays. * * @param out the stream to write the object to * @throws IOException Includes any I/O exceptions that may occur * @serialData Overriding methods should use this tag to describe * the data layout of this Externalizable object. * List the sequence of element types and, if possible, * relate the element to a public/protected field and/or * method of this Externalizable class. */ @Override public void writeExternal(ObjectOutput out) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); writeTo(os); byte[] bts = os.toByteArray(); out.writeInt(bts.length); out.write(bts); } /** * The object implements the readExternal method to restore its * contents by calling the methods of DataInput for primitive * types and readObject for objects, strings and arrays. The * readExternal method must read the values in the same sequence * and with the same types as were written by writeExternal. * * @param in the stream to read data from in order to restore the object * @throws IOException if I/O errors occur * @throws ClassNotFoundException If the class for an object being * restored cannot be found. */ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { int len = in.readInt(); byte[] bts = new byte[len]; in.readFully(bts); this.bytes = bts; } @Override public byte byteAt(int index) { return bytes[index]; } @Override public boolean equal(byte[] data) { return Arrays.equals(bytes, data); } @Override public ByteSequence subSequence(int start, int end) { return new BytesSlice(bytes, start, end - start); } }