序列化
1、把变量从内存中变成可存储或传输的过程称之为序列化。https://www.liaoxuefeng.com/wiki/1016959663602400/1017624706151424
2、一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
https://www.runoob.com/java/java-serialization.html
Java序列化实现两种方式
- Serializable (通过transit关键字来使得变量不被序列化,有缓存机制, 重复对象会直接输出所在位置)
- Externalizable(必须完全由自己来实现序列化规则,自己决定输出内容)
持久化
把 数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)
序列化和持久化的区别
ObjectOutputStream
- 内部类:Caches 用于安全审计缓存
private static class Caches {
/** cache of subclass security audit results */
static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits =
new ConcurrentHashMap<>();
/** queue for WeakReferences to audited subclasses */
static final ReferenceQueue<Class<?>> subclassAuditsQueue =
new ReferenceQueue<>();
}
-
private final BlockDataOutputStream bout; 下层输出流
两个表是用于记录已输出对象的缓存便于之前说的重复输出的时候输出上一个相同内容的位置 -
writeObject 记录上下文
-
private final DebugTraceInfoStack debugInfoStack; 存储错误信息
-
构造函数
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
writeStreamHeader();
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
private void verifySubclass() {
Class<?> cl = getClass();
if (cl == ObjectOutputStream.class) {
return; //不是子类直接返回
}
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
return;//没有安全管理器直接返回
}
processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits); //从弱引用队列中出队所有类,并移除缓存中相同的类
WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue);
Boolean result = Caches.subclassAudits.get(key);//缓存中是否已有这个类
if (result == null) {
result = Boolean.valueOf(auditSubclass(cl));
Caches.subclassAudits.putIfAbsent(key, result);
}
if (result.booleanValue()) {
return;
}
sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
1、安全检查 verifySubclass
2、sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); 检查序列化的类是否重写了readObject and writeObject
- writeStreamHeader()
final static short STREAM_MAGIC = (short)0xaced;
/**The writeStreamHeader method is provided so subclasses can append or prepend their own header to the stream. It writes the magic number and version to the stream.
*/
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);//流魔数
bout.writeShort(STREAM_VERSION);//流版本信息
}
- writeObject
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);//如果流子类重写了writeObject则调用这里的方法
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
// BEGIN Android-changed: Ignore secondary exceptions during writeObject().
// writeFatalException(ex);
try {
writeFatalException(ex);
} catch (IOException ex2) {
// If writing the exception to the output stream causes another exception there
// is no need to propagate the second exception or generate a third exception,
// both of which might obscure details of the root cause.
}
// END Android-changed: Ignore secondary exceptions during writeObject().
}
throw ex;
}
}
- writeObject0
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);//将输出流设置为非块模式
depth++;//增加递归深度
try {
// handle previously written and non-replaceable objects
int h;
if ((obj = subs.lookup(obj)) == null) {
writeNull(); //替代对象映射中这个对象为null时,写入null代码
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h); //不是非共享模式且这个对象在对句柄的映射表中已有缓存,写入该对象在缓存中的句柄值
return;
// BEGIN Android-changed: Make Class and ObjectStreamClass replaceable.
/*
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared); //写类名
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared); //写类描述
return;
*/
// END Android-changed: Make Class and ObjectStreamClass replaceable.
}
// check for replacement object
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
// BEGIN Android-changed: Make only one call to writeReplace.
/*
for (;;) {
// REMIND: skip this check for strings/arrays?
Class<?> repCl;
desc = ObjectStreamClass.lookup(cl, true);
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
desc = ObjectStreamClass.lookup(cl, true);
break;
}
*/
// Do only one replace pass
Class repCl;
desc = ObjectStreamClass.lookup(cl, true);
if (desc.hasWriteReplaceMethod() &&
(obj = desc.invokeWriteReplace(obj)) != null &&
(repCl = obj.getClass()) != cl)
{
cl = repCl;
desc = ObjectStreamClass.lookup(cl, true);
}
// END Android-changed: Make only one call to writeReplace.
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
// if object replaced, run through original checks a second time
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
// BEGIN Android-changed: Make Class and ObjectStreamClass replaceable.
/*
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
*/
// END Android-changed: Make Class and ObjectStreamClass replaceable.
}
}
// remaining cases
// BEGIN Android-changed: Make Class and ObjectStreamClass replaceable.
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed: Make Class and ObjectStreamClass replaceable.
} else if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
- writeOrdinaryObject
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
如果实现了Externalizable接口并且类描述符非动态代理,则执行writeExternalData,否则执行writeSerialData
- writeExternalData
private void writeExternalData(Externalizable obj) throws IOException {
PutFieldImpl oldPut = curPut;
curPut = null;
if (extendedDebugInfo) {
debugInfoStack.push("writeExternal data");
}
SerialCallbackContext oldContext = curContext;//存储上下文
try {
curContext = null;
if (protocol == PROTOCOL_VERSION_1) {
obj.writeExternal(this);
} else {//默认协议是2,所以会使用块输出流
bout.setBlockDataMode(true);
obj.writeExternal(this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
}
} finally {//恢复上下文
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
}
- writeSerialData
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {//重写了writeObject方法
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this); //调用writeObject方法
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);//如果没有重写writeObject则输出默认内容
}
}
}
- defaultWriteFields
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals);//将基本类型数据的字段值存入缓冲区
bout.write(primVals, 0, primDataSize, false);//输出缓冲区内容
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];//获取非基本数据类型对象
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
if (extendedDebugInfo) {
debugInfoStack.push(
"field (class \"" + desc.getName() + "\", name: \"" +
fields[numPrimFields + i].getName() + "\", type: \"" +
fields[numPrimFields + i].getType() + "\")");
}
try {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
- 类描述信息writeClassDesc
private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
int handle;
if (desc == null) {
writeNull();//描述符不存在时写null
} else if (!unshared && (handle = handles.lookup(desc)) != -1) {
writeHandle(handle);//共享模式且缓存中已有该类描述符时,写对应句柄值
} else if (desc.isProxy()) {
writeProxyDesc(desc, unshared);//描述符是动态代理类时
} else {
writeNonProxyDesc(desc, unshared);//描述符是标准类时
}
}
BlockDataOutputStream
private static class BlockDataOutputStream
extends OutputStream implements DataOutput
{
/** maximum data block length 最大数据块长度1K*/
private static final int MAX_BLOCK_SIZE = 1024;
/** maximum data block header length 最大数据块头部长度*/
private static final int MAX_HEADER_SIZE = 5;
/** (tunable) length of char buffer (for writing strings) 字符缓冲区的可变长度用于写字符串*/
private static final int CHAR_BUF_SIZE = 256;
/** buffer for writing general/block data 用于写一般/块数据的缓冲区*/
private final byte[] buf = new byte[MAX_BLOCK_SIZE];
/** buffer for writing block data headers 用于写块数据头部的缓冲区*/
private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
/** char buffer for fast string writes 用于写快速字符串的缓冲区*/
private final char[] cbuf = new char[CHAR_BUF_SIZE];
/** block data mode 块数据模式*/
private boolean blkmode = false;
/** current offset into buf */
private int pos = 0;
/** underlying output stream 下层输出流*/
private final OutputStream out;
/** loopback stream (for data writes that span data blocks) 回路流用于写跨越数据块的数据*/
private final DataOutputStream dout;
// BEGIN Android-added: Warning if writing to a closed ObjectOutputStream.
/**
* Indicates that this stream was closed and that a warning must be logged once if an
* attempt is made to write to it and the underlying stream does not throw an exception.
*
* <p>This will be set back to false when a warning is logged to ensure that the log is not
* flooded with warnings.
*
* http://b/28159133
*/
private boolean warnOnceWhenWriting;
// END Android-added: Warning if writing to a closed ObjectOutputStream.
/**
* Creates new BlockDataOutputStream on top of given underlying stream.
* Block data mode is turned off by default.
*/
BlockDataOutputStream(OutputStream out) {
this.out = out;
dout = new DataOutputStream(this);
}
/**
* Sets block data mode to the given mode (true == on, false == off)
* and returns the previous mode value. If the new mode is the same as
* the old mode, no action is taken. If the new mode differs from the
* old mode, any buffered data is flushed before switching to the new
* mode.
*/
boolean setBlockDataMode(boolean mode) throws IOException {
if (blkmode == mode) {
return blkmode;
}
drain();
blkmode = mode;
return !blkmode;
}
/**
* Returns true if the stream is currently in block data mode, false
* otherwise.
*/
boolean getBlockDataMode() {
return blkmode;
}
// BEGIN Android-added: Warning about writing to closed ObjectOutputStream
/**
* Warns if the stream has been closed.
*
* <p>This is called after data has been written to the underlying stream in order to allow
* the underlying stream to detect and fail if an attempt is made to write to a closed
* stream. That ensures that this will only log a warning if the underlying stream does not
* so it will not log unnecessary warnings.
*/
private void warnIfClosed() {
if (warnOnceWhenWriting) {
System.logW("The app is relying on undefined behavior. Attempting to write to a"
+ " closed ObjectOutputStream could produce corrupt output in a future"
+ " release of Android.", new IOException("Stream Closed"));
// Set back to false so no more messages are logged unless the stream is closed
// again.
warnOnceWhenWriting = false;
}
}
// END Android-added: Warning about writing to closed ObjectOutputStream
/* ----------------- generic output stream methods ----------------- */
/*
* The following methods are equivalent to their counterparts in
* OutputStream, except that they partition written data into data
* blocks when in block data mode.
*/
public void write(int b) throws IOException {
if (pos >= MAX_BLOCK_SIZE) {
drain();
}
buf[pos++] = (byte) b;
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length, false);
}
public void write(byte[] b, int off, int len) throws IOException {
write(b, off, len, false);
}
public void flush() throws IOException {
drain();
out.flush();
}
public void close() throws IOException {
flush();
out.close();
// Android-added: Warning about writing to closed ObjectOutputStream
warnOnceWhenWriting = true;
}
/**
* Writes specified span of byte values from given array. If copy is
* true, copies the values to an intermediate buffer before writing
* them to underlying stream (to avoid exposing a reference to the
* original byte array).
*/
void write(byte[] b, int off, int len, boolean copy)
throws IOException
{
if (!(copy || blkmode)) { // write directly
drain();
out.write(b, off, len);
// Android-added: Warning about writing to closed ObjectOutputStream
warnIfClosed();
return;
}
while (len > 0) {
if (pos >= MAX_BLOCK_SIZE) {
drain();
}
if (len >= MAX_BLOCK_SIZE && !copy && pos == 0) {
// avoid unnecessary copy
writeBlockHeader(MAX_BLOCK_SIZE);
out.write(b, off, MAX_BLOCK_SIZE);
off += MAX_BLOCK_SIZE;
len -= MAX_BLOCK_SIZE;
} else {
int wlen = Math.min(len, MAX_BLOCK_SIZE - pos);
System.arraycopy(b, off, buf, pos, wlen);
pos += wlen;
off += wlen;
len -= wlen;
}
}
// Android-added: Warning about writing to closed ObjectOutputStream
warnIfClosed();
}
/**
* Writes all buffered data from this stream to the underlying stream,
* but does not flush underlying stream.
*/
void drain() throws IOException {
if (pos == 0) {
return;
}
if (blkmode) {
writeBlockHeader(pos);
}
out.write(buf, 0, pos);
pos = 0;
// Android-added: Warning about writing to closed ObjectOutputStream
warnIfClosed();
}
/**
* Writes block data header. Data blocks shorter than 256 bytes are
* prefixed with a 2-byte header; all others start with a 5-byte
* header.
*/
private void writeBlockHeader(int len) throws IOException {
if (len <= 0xFF) {
hbuf[0] = TC_BLOCKDATA;
hbuf[1] = (byte) len;
out.write(hbuf, 0, 2);
} else {
hbuf[0] = TC_BLOCKDATALONG;
Bits.putInt(hbuf, 1, len);
out.write(hbuf, 0, 5);
}
// Android-added: Warning about writing to closed ObjectOutputStream
warnIfClosed();
}
/* ----------------- primitive data output methods ----------------- */
/*
* The following methods are equivalent to their counterparts in
* DataOutputStream, except that they partition written data into data
* blocks when in block data mode.
*/
public void writeBoolean(boolean v) throws IOException {
if (pos >= MAX_BLOCK_SIZE) {
drain();
}
Bits.putBoolean(buf, pos++, v);
}
public void writeByte(int v) throws IOException {
if (pos >= MAX_BLOCK_SIZE) {
drain();
}
buf[pos++] = (byte) v;
}
public void writeChar(int v) throws IOException {
if (pos + 2 <= MAX_BLOCK_SIZE) {
Bits.putChar(buf, pos, (char) v);
pos += 2;
} else {
dout.writeChar(v);
}
}
public void writeShort(int v) throws IOException {
if (pos + 2 <= MAX_BLOCK_SIZE) {
Bits.putShort(buf, pos, (short) v);
pos += 2;
} else {
dout.writeShort(v);
}
}
public void writeInt(int v) throws IOException {
if (pos + 4 <= MAX_BLOCK_SIZE) {
Bits.putInt(buf, pos, v);
pos += 4;
} else {
dout.writeInt(v);
}
}
public void writeFloat(float v) throws IOException {
if (pos + 4 <= MAX_BLOCK_SIZE) {
Bits.putFloat(buf, pos, v);
pos += 4;
} else {
dout.writeFloat(v);
}
}
public void writeLong(long v) throws IOException {
if (pos + 8 <= MAX_BLOCK_SIZE) {
Bits.putLong(buf, pos, v);
pos += 8;
} else {
dout.writeLong(v);
}
}
public void writeDouble(double v) throws IOException {
if (pos + 8 <= MAX_BLOCK_SIZE) {
Bits.putDouble(buf, pos, v);
pos += 8;
} else {
dout.writeDouble(v);
}
}
public void writeBytes(String s) throws IOException {
int endoff = s.length();
int cpos = 0;
int csize = 0;
for (int off = 0; off < endoff; ) {
if (cpos >= csize) {
cpos = 0;
csize = Math.min(endoff - off, CHAR_BUF_SIZE);
s.getChars(off, off + csize, cbuf, 0);
}
if (pos >= MAX_BLOCK_SIZE) {
drain();
}
int n = Math.min(csize - cpos, MAX_BLOCK_SIZE - pos);
int stop = pos + n;
while (pos < stop) {
buf[pos++] = (byte) cbuf[cpos++];
}
off += n;
}
}
public void writeChars(String s) throws IOException {
int endoff = s.length();
for (int off = 0; off < endoff; ) {
int csize = Math.min(endoff - off, CHAR_BUF_SIZE);
s.getChars(off, off + csize, cbuf, 0);
writeChars(cbuf, 0, csize);
off += csize;
}
}
public void writeUTF(String s) throws IOException {
writeUTF(s, getUTFLength(s));
}
/* -------------- primitive data array output methods -------------- */
/*
* The following methods write out spans of primitive data values.
* Though equivalent to calling the corresponding primitive write
* methods repeatedly, these methods are optimized for writing groups
* of primitive data values more efficiently.
*/
void writeBooleans(boolean[] v, int off, int len) throws IOException {
int endoff = off + len;
while (off < endoff) {
if (pos >= MAX_BLOCK_SIZE) {
drain();
}
int stop = Math.min(endoff, off + (MAX_BLOCK_SIZE - pos));
while (off < stop) {
Bits.putBoolean(buf, pos++, v[off++]);
}
}
}
void writeChars(char[] v, int off, int len) throws IOException {
int limit = MAX_BLOCK_SIZE - 2;
int endoff = off + len;
while (off < endoff) {
if (pos <= limit) {
int avail = (MAX_BLOCK_SIZE - pos) >> 1;
int stop = Math.min(endoff, off + avail);
while (off < stop) {
Bits.putChar(buf, pos, v[off++]);
pos += 2;
}
} else {
dout.writeChar(v[off++]);
}
}
}
void writeShorts(short[] v, int off, int len) throws IOException {
int limit = MAX_BLOCK_SIZE - 2;
int endoff = off + len;
while (off < endoff) {
if (pos <= limit) {
int avail = (MAX_BLOCK_SIZE - pos) >> 1;
int stop = Math.min(endoff, off + avail);
while (off < stop) {
Bits.putShort(buf, pos, v[off++]);
pos += 2;
}
} else {
dout.writeShort(v[off++]);
}
}
}
void writeInts(int[] v, int off, int len) throws IOException {
int limit = MAX_BLOCK_SIZE - 4;
int endoff = off + len;
while (off < endoff) {
if (pos <= limit) {
int avail = (MAX_BLOCK_SIZE - pos) >> 2;
int stop = Math.min(endoff, off + avail);
while (off < stop) {
Bits.putInt(buf, pos, v[off++]);
pos += 4;
}
} else {
dout.writeInt(v[off++]);
}
}
}
void writeFloats(float[] v, int off, int len) throws IOException {
int limit = MAX_BLOCK_SIZE - 4;
int endoff = off + len;
while (off < endoff) {
if (pos <= limit) {
int avail = (MAX_BLOCK_SIZE - pos) >> 2;
int chunklen = Math.min(endoff - off, avail);
floatsToBytes(v, off, buf, pos, chunklen);
off += chunklen;
pos += chunklen << 2;
} else {
dout.writeFloat(v[off++]);
}
}
}
void writeLongs(long[] v, int off, int len) throws IOException {
int limit = MAX_BLOCK_SIZE - 8;
int endoff = off + len;
while (off < endoff) {
if (pos <= limit) {
int avail = (MAX_BLOCK_SIZE - pos) >> 3;
int stop = Math.min(endoff, off + avail);
while (off < stop) {
Bits.putLong(buf, pos, v[off++]);
pos += 8;
}
} else {
dout.writeLong(v[off++]);
}
}
}
void writeDoubles(double[] v, int off, int len) throws IOException {
int limit = MAX_BLOCK_SIZE - 8;
int endoff = off + len;
while (off < endoff) {
if (pos <= limit) {
int avail = (MAX_BLOCK_SIZE - pos) >> 3;
int chunklen = Math.min(endoff - off, avail);
doublesToBytes(v, off, buf, pos, chunklen);
off += chunklen;
pos += chunklen << 3;
} else {
dout.writeDouble(v[off++]);
}
}
}
/**
* Returns the length in bytes of the UTF encoding of the given string.
*/
long getUTFLength(String s) {
int len = s.length();
long utflen = 0;
for (int off = 0; off < len; ) {
int csize = Math.min(len - off, CHAR_BUF_SIZE);
s.getChars(off, off + csize, cbuf, 0);
for (int cpos = 0; cpos < csize; cpos++) {
char c = cbuf[cpos];
if (c >= 0x0001 && c <= 0x007F) {
utflen++;
} else if (c > 0x07FF) {
utflen += 3;
} else {
utflen += 2;
}
}
off += csize;
}
return utflen;
}
/**
* Writes the given string in UTF format. This method is used in
* situations where the UTF encoding length of the string is already
* known; specifying it explicitly avoids a prescan of the string to
* determine its UTF length.
*/
void writeUTF(String s, long utflen) throws IOException {
if (utflen > 0xFFFFL) {
throw new UTFDataFormatException();
}
writeShort((int) utflen);
if (utflen == (long) s.length()) {
writeBytes(s);
} else {
writeUTFBody(s);
}
}
/**
* Writes given string in "long" UTF format. "Long" UTF format is
* identical to standard UTF, except that it uses an 8 byte header
* (instead of the standard 2 bytes) to convey the UTF encoding length.
*/
void writeLongUTF(String s) throws IOException {
writeLongUTF(s, getUTFLength(s));
}
/**
* Writes given string in "long" UTF format, where the UTF encoding
* length of the string is already known.
*/
void writeLongUTF(String s, long utflen) throws IOException {
writeLong(utflen);
if (utflen == (long) s.length()) {
writeBytes(s);
} else {
writeUTFBody(s);
}
}
/**
* Writes the "body" (i.e., the UTF representation minus the 2-byte or
* 8-byte length header) of the UTF encoding for the given string.
*/
private void writeUTFBody(String s) throws IOException {
int limit = MAX_BLOCK_SIZE - 3;
int len = s.length();
for (int off = 0; off < len; ) {
int csize = Math.min(len - off, CHAR_BUF_SIZE);
s.getChars(off, off + csize, cbuf, 0);
for (int cpos = 0; cpos < csize; cpos++) {
char c = cbuf[cpos];
if (pos <= limit) {
if (c <= 0x007F && c != 0) {
buf[pos++] = (byte) c;
} else if (c > 0x07FF) {
buf[pos + 2] = (byte) (0x80 | ((c >> 0) & 0x3F));
buf[pos + 1] = (byte) (0x80 | ((c >> 6) & 0x3F));
buf[pos + 0] = (byte) (0xE0 | ((c >> 12) & 0x0F));
pos += 3;
} else {
buf[pos + 1] = (byte) (0x80 | ((c >> 0) & 0x3F));
buf[pos + 0] = (byte) (0xC0 | ((c >> 6) & 0x1F));
pos += 2;
}
} else { // write one byte at a time to normalize block
if (c <= 0x007F && c != 0) {
write(c);
} else if (c > 0x07FF) {
write(0xE0 | ((c >> 12) & 0x0F));
write(0x80 | ((c >> 6) & 0x3F));
write(0x80 | ((c >> 0) & 0x3F));
} else {
write(0xC0 | ((c >> 6) & 0x1F));
write(0x80 | ((c >> 0) & 0x3F));
}
}
}
off += csize;
}
}
}
各种书:龙书、虎书
网友评论