ก่อนอื่น ให้ฉันอธิบายว่าสิ่งนี้หมายถึง String ใน Java แม้ว่าฉันจะตัดสินใจเปลี่ยนไปใช้ C/C++ แล้ว เนื่องจากฉันพบปัญหาในวันนี้ แต่ฉันก็ยังอยากจะดู คำจำกัดความของ String มีดังนี้:
คัดลอกรหัสรหัส ดังต่อไปนี้:
สตริงคลาสสุดท้ายสาธารณะ
-
ค่าถ่านสุดท้ายส่วนตัว []; // สตริงที่บันทึกไว้
ออฟเซ็ต int สุดท้ายส่วนตัว // ตำแหน่งเริ่มต้น
จำนวน int ส่วนตัว // จำนวนอักขระ
แฮช int ส่วนตัว // ค่าแฮชที่แคชไว้
-
-
เมื่อทำการดีบักคุณสามารถดูค่าที่บันทึกไว้ได้ดังนี้:
ควรสังเกตว่าหากไม่ได้เรียก hashCode() ค่าแฮชจะเป็น 0 เป็นเรื่องง่ายที่จะทราบว่าค่าที่นี่คืออาร์เรย์ถ่านของค่าสตริงที่บันทึกไว้จริง (นั่นคือ "การทดสอบสตริง") และค่าของอักขระแต่ละตัวคือเท่าใด ง่ายต่อการตรวจสอบ: Unicode
ณ จุดนี้ ทุกคนสามารถเดาได้ว่า subString ที่ใช้กันทั่วไปของเราถูกนำไปใช้อย่างไร: หากเราจะนำไปใช้ ให้สตริงใหม่ใช้ค่าเดียวกัน (char array) และแก้ไขเฉพาะออฟเซ็ตและนับเท่านั้น ซึ่งจะช่วยประหยัดพื้นที่และรวดเร็ว (ไม่จำเป็นต้องคัดลอก) และจริงๆ แล้วจะเป็นดังนี้:
คัดลอกรหัสรหัส ดังต่อไปนี้:
สตริงย่อยสตริงสาธารณะ (int beginningIndex) {
กลับสตริงย่อย (beginIndex, นับ);
-
สตริงย่อยสตริงสาธารณะ (int beginningIndex, int endIndex) {
-
return ((beginIndex == 0) && (endIndex == count)) ? นี้ :
สตริงใหม่ (ออฟเซ็ต + beginningIndex, endIndex - beginningIndex, ค่า);
-
สตริง (int offset, int count, ค่าถ่าน []) {
this.value = ค่า;
this.offset = ชดเชย;
this.count = นับ;
-
เนื่องจากเรากำลังพูดถึงสตริง JVM จะใช้การเข้ารหัสใดเป็นค่าเริ่มต้น ผ่านการดีบัก คุณจะพบ:
คัดลอกรหัสรหัส ดังต่อไปนี้:
สาธารณะ Charset คงที่ defaultCharset () {
ถ้า (defaultCharset == null) {
ซิงโครไนซ์ (Charset.class) {
java.security.PrivilegedAction pa = GetPropertyAction ใหม่ ("file.encoding");
สตริง csn = (สตริง) AccessController.doPrivileged (pa);
ชุดอักขระ cs = ค้นหา (csn);
ถ้า (cs != null)
defaultCharset = cs;
อื่น
defaultCharset = forName("UTF-8");
-
-
ค่าของ defaultCharset สามารถส่งผ่านได้:
-Dfile.encoding=utf-8
ทำการตั้งค่า แน่นอน หากคุณต้องการตั้งค่าเป็น "abc" คุณสามารถทำได้ แต่จะตั้งค่าเป็น UTF-8 ตามค่าเริ่มต้น คุณสามารถดูค่าเฉพาะได้ผ่าน System.getProperty("file.encoding") ทำไมคุณถึงเห็น defaultCharset? เนื่องจากกระบวนการส่งผ่านเครือข่ายควรเป็นไบต์อาร์เรย์ ไบต์อาร์เรย์ที่ได้รับโดยวิธีการเข้ารหัสที่แตกต่างกันอาจแตกต่างกัน ดังนั้นเราจำเป็นต้องรู้ว่าวิธีการเข้ารหัสได้มาอย่างไรใช่ไหม? วิธีการเฉพาะในการรับอาร์เรย์ไบต์คือ getBytes ซึ่งเราจะเน้นไปที่ด้านล่าง สิ่งที่ท้ายที่สุดเรียกว่าวิธีเข้ารหัสของ CharsetEncoder ดังต่อไปนี้:
คัดลอกรหัสรหัส ดังต่อไปนี้:
การเข้ารหัส CoderResult สุดท้ายสาธารณะ (CharBuffer ใน, ByteBuffer ออก, endOfInput บูลีน) {
int newState = endOfInput ? ST_END : ST_CODING;
ถ้า ((สถานะ != ST_RESET) && (สถานะ != ST_CODING) && !(endOfInput && (สถานะ == ST_END)))
ThrowIllegalStateException (สถานะ, newState);
สถานะ = สถานะใหม่;
สำหรับ (;;) {
CoderResult cr;
พยายาม {
cr = encodeLoop(เข้า, ออก);
} จับ (BufferUnderflowException x) {
โยน CoderMalfunctionError ใหม่ (x);
} จับ (BufferOverflowException x) {
โยน CoderMalfunctionError ใหม่ (x);
-
ถ้า (cr.isOverflow())
กลับ cr;
ถ้า (cr.isUnderflow()) {
ถ้า (endOfInput && in.hasRemaining()) {
cr = CoderResult.malformedForLength(in.remaining());
} อื่น {
กลับ cr;
-
-
การกระทำ CodingErrorAction = null;
ถ้า (cr.isMalformed())
การกระทำ = InputAction ที่มีรูปแบบไม่ถูกต้อง;
อย่างอื่นถ้า (cr.isUnmappable())
การกระทำ = unmappableCharacterAction;
อื่น
ยืนยันเท็จ : cr.toString();
ถ้า (การกระทำ == CodingErrorAction.REPORT)
กลับ cr;
ถ้า (การกระทำ == CodingErrorAction.REPLACE) {
ถ้า (out.remaining() < replacement.length)
กลับ CoderResult.OVERFLOW;
out.put (ทดแทน);
-
ถ้า ((การกระทำ == CodingErrorAction.IGNORE) || (การกระทำ == CodingErrorAction.REPLACE)) {
in.position(in.position() + cr.length());
ดำเนินการต่อ;
-
ยืนยันเท็จ;
-
-
แน่นอนว่า CharsetEncoder ที่เกี่ยวข้องจะถูกเลือกก่อนตามรูปแบบการเข้ารหัสที่ต้องการ และสิ่งที่สำคัญที่สุดคือ CharsetEncoder ที่แตกต่างกันจะใช้วิธี encodeLoop ที่แตกต่างกัน คุณอาจไม่เข้าใจว่าทำไมถึงมี for(;;) ที่นี่? ในความเป็นจริง คุณสามารถเข้าใจได้โดยดูที่แพ็คเกจ (nio) ซึ่งเป็นที่ตั้งของ CharsetEncoder และพารามิเตอร์: ฟังก์ชันนี้สามารถจัดการสตรีมได้ (แม้ว่าเราจะไม่วนซ้ำเมื่อเราใช้งานที่นี่)
ในเมธอด encodeLoop ตัวอักษรมากที่สุดเท่าที่เป็นไปได้จะถูกแปลงเป็นไบต์ และสตริงใหม่เกือบจะเป็นกระบวนการย้อนกลับข้างต้น
ในกระบวนการพัฒนาจริง มักพบอักขระที่อ่านไม่ออก:
รับชื่อไฟล์เมื่ออัพโหลดไฟล์
สตริงที่ส่งผ่านโดย JS ไปยังแบ็กเอนด์
ขั้นแรกให้ลองใช้ผลลัพธ์การทำงานของโค้ดต่อไปนี้:
คัดลอกรหัสรหัส ดังต่อไปนี้:
โมฆะคงที่สาธารณะ main (String [] args) พ่นข้อยกเว้น {
สตริง str = "สตริง";
// -41 -42 -73 -5 -76 -82
printArray(str.getBytes());
// -27 -83 -105 -25 -84 -90 -28 -72 -78
printArray(str.getBytes("utf-8"));
-
System.out.println(สตริงใหม่(str.getBytes(), "utf-8"));
// ยิ่งจวน?
System.out.println(สตริงใหม่(str.getBytes("utf-8"), "gbk"));
//อักขระ??
System.out.println(new String("瀛涓?".getBytes("gbk"), "utf-8"));
// -41 -42 -73 -5 63 63
printArray(สตริงใหม่("Yingjuan?".getBytes("gbk"), "utf-8").getBytes());
-
โมฆะคงสาธารณะ printArray (ไบต์ [] bs) {
สำหรับ(int i = 0; i < bs.length; i++){
System.out.print(bs[i] + " ");
-
System.out.println();
-
ผลลัพธ์อธิบายไว้ในความคิดเห็นในโปรแกรม:
เนื่องจาก 2 ไบต์ใน GBK แทนอักขระจีน จึงมี 6 ไบต์
เนื่องจาก 3 ไบต์ใน UTF-8 แทนอักขระจีน จึงมี 9 ไบต์
เนื่องจากอาร์เรย์ไบต์ที่ไม่สามารถสร้างโดย GBK ได้ถูกนำมาใช้เพื่อสร้างสตริงตามกฎ UTF-8 ??? จะปรากฏขึ้น
นี่คือสาเหตุที่มักพบอักขระที่อ่านไม่ออก GBK ใช้ไบต์ที่สร้างโดย UTF-8 เพื่อสร้างสตริง
แม้ว่าโค้ดที่สร้างขึ้นข้างต้นจะอ่านไม่ออก แต่คอมพิวเตอร์กลับไม่คิดเช่นนั้น ดังนั้นจึงยังคงสามารถรับอาร์เรย์ไบต์ผ่าน getBytes และสามารถจดจำ UTF-8 ในอาร์เรย์นี้ได้
ควรเติม 63 (?) สองอันสุดท้ายด้วยการเข้ารหัส (หรือมีไบต์ไม่เพียงพอที่จะเติมโดยตรงฉันไม่ได้ดูสถานที่นี้อย่างระมัดระวัง)
เนื่องจากการเข้ารหัสตัวอักษรและตัวเลขจะเหมือนกันระหว่าง GBK และ UTF-8 จะไม่มีอักขระที่อ่านไม่ออกในการประมวลผลอักขระเหล่านี้ อย่างไรก็ตาม การเข้ารหัสอักขระภาษาจีนแตกต่างกันอย่างแน่นอน นี่คือที่มาของปัญหามากมาย ที่รหัสด้านล่าง:
สตริงใหม่(สตริงใหม่("เรา".getBytes("UTF-8"), "GBK").getBytes("GBK"), "UTF-8);
แน่นอนว่าผลลัพธ์ของโค้ดนี้คือ "เรา" แต่โค้ดนี้มีผลอะไรกับเราบ้าง? ก่อนอื่นเราสังเกตเห็น:
สตริงใหม่("เรา".getBytes("UTF-8"), "GBK");
ผลลัพธ์ของโค้ดนี้คือโค้ดที่อ่านไม่ออก และโค้ดที่อ่านไม่ออกหลายโค้ดก็ "สับสนแบบนี้" แต่จำไว้ว่า: ความสับสนวุ่นวายที่นี่มีไว้สำหรับเรา และสำหรับคอมพิวเตอร์ ไม่สำคัญว่ามันจะ "ยุ่ง" หรือ "ไม่ยุ่ง" เมื่อเราเกือบจะยอมแพ้ มันก็ยังสามารถรับมันได้จากโค้ดที่อ่านไม่ออกผ่าน "getBytes( "GBK")” มันคือ "backbone" จากนั้นเราสามารถใช้ "backbone" เพื่อกู้คืนสตริงต้นฉบับได้
ดูเหมือนว่าโค้ดข้างต้นสามารถแก้ปัญหาที่อ่านไม่ออกระหว่าง "GBK" และ "UTF-8" ได้ แต่วิธีแก้ปัญหานี้จำกัดอยู่เพียงกรณีพิเศษเท่านั้น: จำนวนอักขระภาษาจีนที่ต่อเนื่องกันทั้งหมดเป็นเลขคู่! เหตุผลที่ได้กล่าวไปแล้วข้างต้นและจะไม่ทำซ้ำที่นี่
แล้วจะแก้ไขปัญหานี้อย่างไร?
วิธีแก้ปัญหาแรก: encodeURI เหตุใดจึงใช้วิธีนี้? เหตุผลนั้นง่ายมาก: GBK และ UTF-8 มีการเข้ารหัส % ตัวเลข และตัวอักษรเหมือนกัน ดังนั้นสตริงหลังการเข้ารหัสจึงสามารถรับประกันได้ 100% ว่าเป็นสิ่งเดียวกันภายใต้การเข้ารหัสทั้งสองนี้ จากนั้นจึงถอดรหัสเพื่อให้ได้อักขระ . แค่เสียบไม้. ตามรูปแบบของ String เราสามารถเดาได้ว่าประสิทธิภาพของการเข้ารหัสและถอดรหัสนั้นสูงมาก ดังนั้นนี่จึงเป็นวิธีแก้ปัญหาที่ดีเช่นกัน
วิธีที่สอง: รูปแบบการเข้ารหัสแบบรวม <BR>เรากำลังใช้การขุด Webx ที่นี่ คุณจะต้องตั้งค่า defaultCharset="UTF-8" ใน webx.xml เท่านั้น