2008-09-23 7 views
26

मैं जावा String कैसे छोटा कर सकता हूं ताकि मुझे पता चले कि यह यूटीएफ -8 एन्कोडेड होने के बाद बाइट्स स्टोरेज की एक निश्चित संख्या में फिट होगा?यूटीएफ -8 एन्कोड किए जाने के बाद, किसी दिए गए बाइट्स में फ़िट होने के लिए मैं एक जावा स्ट्रिंग को कैसे छोटा कर सकता हूं?

उत्तर

21

यहाँ एक सरल पाश है कि मायने रखता है कितना बड़ा UTF-8 प्रतिनिधित्व होने जा रहा है है, और जब यह पार हो गई है ट्रंकेटस:

public static String truncateWhenUTF8(String s, int maxBytes) { 
    int b = 0; 
    for (int i = 0; i < s.length(); i++) { 
     char c = s.charAt(i); 

     // ranges from http://en.wikipedia.org/wiki/UTF-8 
     int skip = 0; 
     int more; 
     if (c <= 0x007f) { 
      more = 1; 
     } 
     else if (c <= 0x07FF) { 
      more = 2; 
     } else if (c <= 0xd7ff) { 
      more = 3; 
     } else if (c <= 0xDFFF) { 
      // surrogate area, consume next char as well 
      more = 4; 
      skip = 1; 
     } else { 
      more = 3; 
     } 

     if (b + more > maxBytes) { 
      return s.substring(0, i); 
     } 
     b += more; 
     i += skip; 
    } 
    return s; 
} 

यह इनपुट स्ट्रिंग में दिखाई देने वाले surrogate pairs को संभालता है। जावा के यूटीएफ -8 एन्कोडर (सही ढंग से) सरोगेट जोड़े को दो 3-बाइट अनुक्रमों के बजाय एक 4-बाइट अनुक्रम के रूप में आउटपुट करता है, इसलिए truncateWhenUTF8() यह सबसे लंबी कटाई वाली स्ट्रिंग को वापस कर देगा। यदि आप कार्यान्वयन में सरोगेट जोड़े को अनदेखा करते हैं तो छिद्रित तारों को उनकी आवश्यकता से कम किया जा सकता है।

मुझे लगता है कि कोड पर परीक्षण का एक बहुत नहीं किया है, लेकिन यहां कुछ प्रारंभिक परीक्षण:

private static void test(String s, int maxBytes, int expectedBytes) { 
    String result = truncateWhenUTF8(s, maxBytes); 
    byte[] utf8 = result.getBytes(Charset.forName("UTF-8")); 
    if (utf8.length > maxBytes) { 
     System.out.println("BAD: our truncation of " + s + " was too big"); 
    } 
    if (utf8.length != expectedBytes) { 
     System.out.println("BAD: expected " + expectedBytes + " got " + utf8.length); 
    } 
    System.out.println(s + " truncated to " + result); 
} 

public static void main(String[] args) { 
    test("abcd", 0, 0); 
    test("abcd", 1, 1); 
    test("abcd", 2, 2); 
    test("abcd", 3, 3); 
    test("abcd", 4, 4); 
    test("abcd", 5, 4); 

    test("a\u0080b", 0, 0); 
    test("a\u0080b", 1, 1); 
    test("a\u0080b", 2, 1); 
    test("a\u0080b", 3, 3); 
    test("a\u0080b", 4, 4); 
    test("a\u0080b", 5, 4); 

    test("a\u0800b", 0, 0); 
    test("a\u0800b", 1, 1); 
    test("a\u0800b", 2, 1); 
    test("a\u0800b", 3, 1); 
    test("a\u0800b", 4, 4); 
    test("a\u0800b", 5, 5); 
    test("a\u0800b", 6, 5); 

    // surrogate pairs 
    test("\uD834\uDD1E", 0, 0); 
    test("\uD834\uDD1E", 1, 0); 
    test("\uD834\uDD1E", 2, 0); 
    test("\uD834\uDD1E", 3, 0); 
    test("\uD834\uDD1E", 4, 4); 
    test("\uD834\uDD1E", 5, 4); 

} 

अपडेट किया गया संशोधित कोड उदाहरण, यह अब सरोगेट जोड़े संभालती है।

+0

यूटीएफ -8 किसी भी यूसीएस 2 को एन्कोड कर सकता है 3 बाइट्स या उससे कम में चरित्र। उस पृष्ठ को देखें जो आप संदर्भित करते हैं। हालांकि, अगर आप यूसीएस 4 या यूटीएफ 16 (जो दोनों संपूर्ण वर्णसेट का संदर्भ दे सकते हैं) का पालन करना चाहते हैं, तो आपको यूटीएफ 8 में 6-बाइट वर्णों की अनुमति देनी होगी। – billjamesdev

+0

विधेयक: विकिपीडिया पृष्ठ पर सीईएसयू -8 चर्चा देखें। मेरी समझ है कि यूटीएफ -8 को सरोगेट जोड़े को एक 4-बाइट अनुक्रम के रूप में एन्कोड करना है, दो 3-बाइट अनुक्रम नहीं। –

+0

यह 2 तीन-बाइट नहीं है, यह यूसीएस 4 स्टोर करने के लिए 1 6-बाइट अनुक्रम तक है, जो एक पूर्ण 31-बिट चरित्र है, 2 16-बिट "जोड़े" (वह यूटीएफ 16) नहीं है। एक 6-बाइट seq = 1111110C 10CCCCCC 10CCCCCC 10CCCCCC 10CCCCCC 10CCCCCC जहां सी डेटा डेटा बिट्स हैं। अभी, 4 बाइट्स की आवश्यकता के लिए केवल पर्याप्त वर्ण उपयोग में हैं। – billjamesdev

9

यूटीएफ -8 एन्कोडिंग में एक साफ विशेषता है जो आपको यह देखने की अनुमति देती है कि आप बाइट-सेट में कहां हैं।

अपनी इच्छित सीमा सीमा पर स्ट्रीम की जांच करें।

  • यदि इसकी उच्च बिट 0 है, तो यह एकल-बाइट चार है, बस इसे 0 के साथ बदलें और आप ठीक हैं।
  • यदि इसकी उच्च बिट 1 है और अगली बिट है, तो आप एक बहु-बाइट चार की शुरुआत में हैं, तो बस उस बाइट को 0 पर सेट करें और आप अच्छे हैं।
  • यदि उच्च बिट 1 है लेकिन अगली बिट 0 है, तो आप एक चरित्र के बीच में हैं, बफर के साथ वापस यात्रा करें जब तक कि आप एक बाइट हिट न करें जिसमें उच्च बिट्स में 2 या अधिक 1s हो और प्रतिस्थापित करें 0.

उदाहरण: यदि आपकी स्ट्रीम है: 31 33 31 सी 1 ए 3 32 33 00, तो आप अपनी स्ट्रिंग 1, 2, 3, 5, 6, या 7 बाइट्स लंबी बना सकते हैं, लेकिन 4 नहीं , क्योंकि यह सी 1 के बाद 0 डाल देगा, जो एक बहु-बाइट चार की शुरुआत है।

+0

http://java.sun.com/j2se/1.5.0/docs/api/java/io/DataInput.html#modified-utf-8 संशोधित UTF-8 जावा और द्वारा प्रयुक्त एन्कोडिंग बताते हैं दर्शाता है कि यह जवाब सही क्यों है। – Alexander

+1

बीटीडब्ल्यू, यह समाधान (एक बिल @ बिल जेम्स) @Matt Quail द्वारा वर्तमान में स्वीकृत उत्तर की तुलना में अधिक कुशल है, क्योंकि पूर्व में आपको सबसे अधिक 3 बाइट्स की जांच करने की आवश्यकता होती है, जबकि बाद में आपको सभी पात्रों का परीक्षण करने की आवश्यकता होती है ये पाठ। – Alexander

+1

अलेक्जेंडर: पूर्व के लिए आपको पहले स्ट्रिंग को यूटीएफ 8 * में परिवर्तित करने की आवश्यकता होती है, जिसके लिए पाठ में सभी पात्रों पर पुनरावृत्ति की आवश्यकता होती है। –

19

आपको CharsetEncoder का उपयोग करना चाहिए, सरल getBytes() + कॉपी करें जितना आप आधे में यूटीएफ -8 चार्टर्स को काट सकते हैं।

कुछ इस तरह:

public static int truncateUtf8(String input, byte[] output) { 

    ByteBuffer outBuf = ByteBuffer.wrap(output); 
    CharBuffer inBuf = CharBuffer.wrap(input.toCharArray()); 

    Charset utf8 = Charset.forName("UTF-8"); 
    utf8.newEncoder().encode(inBuf, outBuf, true); 
    System.out.println("encoded " + inBuf.position() + " chars of " + input.length() + ", result: " + outBuf.position() + " bytes"); 
    return outBuf.position(); 
} 
+2

यह मेरे लिए बहुत अच्छा काम करता है - शायद कम कुशल, लेकिन गलत होने के लिए बहुत कठिन है, और यह किसी भी चरित्र सेट के लिए काम करता है। एक त्वरित 'नई स्ट्रिंग (आउटपुट, 0, आउटपुट। लम्बाई - रिटर्न वैल्यू, चार्जसेट)' – ojrac

+0

@ सिग्जेट का समाधान समान है और इसके अलावा वास्तविक लंबाई छिद्रित स्ट्रिंग देता है, केवल लंबाई –

3

आप बिना किसी रूपांतरण किए बाइट्स की संख्या की गणना कर सकते हैं।

foreach character in the Java string 
    if 0 <= character <= 0x7f 
    count += 1 
    else if 0x80 <= character <= 0x7ff 
    count += 2 
    else if 0x800 <= character <= 0xd7ff // excluding the surrogate area 
    count += 3 
    else if 0xdc00 <= character <= 0xffff 
    count += 3 
    else { // surrogate, a bit more complicated 
    count += 4 
    skip one extra character in the input stream 
    } 

आप 4 बाइट प्रत्येक वैध किराए की जोड़ी के लिए किराए की जोड़े (D800-DBFF और U + DC00-U + DFFF) का पता लगाने और गिनती करने के लिए होगा। यदि आपको पहली श्रेणी में पहला मान मिलता है और दूसरी श्रेणी में दूसरा, यह ठीक है, उन्हें छोड़ दें और 4. जोड़ें लेकिन यदि नहीं, तो यह एक अवैध सरोगेट जोड़ी है। मुझे यकीन नहीं है कि जावा इसके साथ कैसे व्यवहार करता है, लेकिन आपके एल्गोरिदम को उस (असंभव) मामले में सही गिनती करना होगा।

9

यहां मैं जो आया हूं, वह मानक जावा एपीआई का उपयोग करता है, इसलिए सभी यूनिकोड अजीबता और सरोगेट जोड़े आदि के साथ सुरक्षित और संगत होना चाहिए।समाधान को http://www.jroller.com/holy/entry/truncating_utf_string_to_the से लिया गया है जिसमें नल के लिए जोड़े गए चेक और डिकोडिंग से बचने के लिए जब स्ट्रिंग maxBytes से कम बाइट्स है।

/** 
* Truncates a string to the number of characters that fit in X bytes avoiding multi byte characters being cut in 
* half at the cut off point. Also handles surrogate pairs where 2 characters in the string is actually one literal 
* character. 
* 
* Based on: http://www.jroller.com/holy/entry/truncating_utf_string_to_the 
*/ 
public static String truncateToFitUtf8ByteLength(String s, int maxBytes) { 
    if (s == null) { 
     return null; 
    } 
    Charset charset = Charset.forName("UTF-8"); 
    CharsetDecoder decoder = charset.newDecoder(); 
    byte[] sba = s.getBytes(charset); 
    if (sba.length <= maxBytes) { 
     return s; 
    } 
    // Ensure truncation by having byte buffer = maxBytes 
    ByteBuffer bb = ByteBuffer.wrap(sba, 0, maxBytes); 
    CharBuffer cb = CharBuffer.allocate(maxBytes); 
    // Ignore an incomplete character 
    decoder.onMalformedInput(CodingErrorAction.IGNORE) 
    decoder.decode(bb, cb, true); 
    decoder.flush(cb); 
    return new String(cb.array(), 0, cb.position()); 
} 
+0

'CharBuffer.allocate (maxBytes) 'बहुत आवंटित करता है। क्या यह 'CharBuffer.allocate (s.length()) 'हो सकता है? –

संबंधित मुद्दे

 संबंधित मुद्दे