最近还是在做多文件的项目,到最后的阶段发现使用DataOutputStream时,writeUTF总是出错,所以就去看了writeUTF的源码,来揭晓原因。
1.进入writeUTF()
// 在调用writeUTF方法时,
//实际调用了static int writeUTF(String str, DataOutput out)方法
public final void writeUTF(String str) throws IOException {
writeUTF(str, this);
}
// 进入static int writeUTF(String str, DataOutput out)
/*
首先,通过writeShort方法将两个字节写入出去,给出后面的字节数。
这个值是实际输出的字节数,而不是字符串的长度。
在长度之后,使用修改后的UTF-8编码顺序输出字符串的每个字符。
如果没有抛出异常,则写入的计数器将按写入输出流的总字节数递增。
这至少是str的长度加2,最多是str的长度加2倍
*/
static int writeUTF(String str, DataOutput out) throws IOException {
int strlen = str.length(); //得到字符串的长度
int utflen = 0;
int c, count = 0;
/* use charAt instead of copying String to char array */
/* 这个utflen是每个字符的长度,比如一个汉字就是三个字符长度,这里需要注意*/
for (int i = 0; i < strlen; i++) {
c = str.charAt(i); //判断字符是多少位的,
if ((c >= 0x0001) && (c <= 0x007F)) {
utflen++;
} else if (c > 0x07FF) {
utflen += 3; //utf-8
} else {
utflen += 2;
}
}
// 如果字符长度超过65535,那么之间抛出异常,这就是我犯错的地方
if (utflen > 65535)
throw new UTFDataFormatException(
"encoded string too long: " + utflen + " bytes");
byte[] bytearr = null;
if (out instanceof DataOutputStream) {
DataOutputStream dos = (DataOutputStream)out;
if(dos.bytearr == null || (dos.bytearr.length < (utflen+2)))
dos.bytearr = new byte[(utflen*2) + 2];
bytearr = dos.bytearr;
} else {
bytearr = new byte[utflen+2];
}
bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);
int i=0;
for (i=0; i<strlen; i++) {
c = str.charAt(i);
if (!((c >= 0x0001) && (c <= 0x007F))) break;
bytearr[count++] = (byte) c;
}
for (;i < strlen; i++){
c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
bytearr[count++] = (byte) c;
} else if (c > 0x07FF) {
bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F));
bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
} else {
bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
}
}
out.write(bytearr, 0, utflen+2);
return utflen + 2;
}
所以我们就要对我们字符的长度进行判断了。
2. 解决长字符串的wirte问题
public static final int WRITE_READ_UTF_MAX_LENGTH = 65535;
// 传入输出流和要发送的字符串
public static void sendStr(DataOutputStream dos, String sendStr) throws IOException {
//sendStr.length()在底层获取的是char[]的长度,所以不用担心字符串长度的不匹配
dos.writeUTF(String.valueOf(sendStr.length())); // 先将字符串的长度发过去
//如果字符串的大于65535,那么就需要进行切片发送了
if (sendStr.length() > WRITE_READ_UTF_MAX_LENGTH) {
for (int i = 1; i < sendStr.length() / WRITE_READ_UTF_MAX_LENGTH + 2; i++) {
dos.writeUTF(sendStr.substring(WRITE_READ_UTF_MAX_LENGTH*(i-1) ,
WRITE_READ_UTF_MAX_LENGTH * i < sendStr.length()
? WRITE_READ_UTF_MAX_LENGTH*i : sendStr.length()));
}
} else {
dos.writeUTF(sendStr);
}
}
总体来说,就是一个切分思想,先发头部,再发真正的数据。
我们既然把消息已经发过去了,那么也就需要完整的将数据接收过来
3. readUTF()
分片发送,自然就要接收片段,将其整合到一起。
// 传入输入流,此方法要和sendStr一起使用。
public static String getStr(DataInputStream dis) throws IOException {
String length = dis.readUTF(); //先读取字符串的长度
int len = Integer.valueOf(length); //用字符串的长度来控制循环
StringBuilder sBulider = new StringBuilder(); //字符串的拼接
//如果长度大于等于65535,继续读取
while(len >= WRITE_READ_UTF_MAX_LENGTH) {
String tempStr = dis.readUTF();
sBulider.append(tempStr);
len -= tempStr.length();
}
//最后接收小于WRITE_READ_UTF_MAX_LENGTH的字符串
String tempStr = dis.readUTF();
sBulider.append(tempStr);
return sBulider.toString();//返回结果
}
网络编程的水真的是太深了,如果不对发送的数据和接收的数据的长度进行控制,那真的是一场灾难,并且debug真的很难发现。
敬畏Java,敬畏网络编程