Java文字列をバイト単位で切り取る

13153 ワード

これはよくあるJavaの面接問題で、多くの人が出会ったことがあります.ここには知識点がたくさんあります.ネット上の論断と実現もたくさんあります.私は家族の言葉にすぎません.同じところがあります.違うところがあります.各家の言葉を全面的に抜粋して総括したいと思っていたが、各方面の時間を節約するために、本人が受け取った価値のある部分と価値のある部分だけを列述した.
主な知識点備考:ASCIIは共通の英語文字セットを代表しているため、インターネットの背景の下で、世界各国の各種文字セットを統一するためにUnicode文字セットが流行し、JavaもUnicodeを原生でサポートしているのは明らかだが、Javaにcharに保存されているのはASCII文字でもGBK文字やUTF-8文字でもないという概念が混同されやすい.「A」でも「中」でも2バイトを占める、Java仕様におけるcharタイプに関する規定でもある.これは、1つのcharがUCS-2汎用文字セットを表しているが、Javaでは直接UCS-4の拡張文字セットを表すことができないことを示している.例えば、チャット表情文字は、4バイトを占有しているため、1つのcharは入れられないが、UCS-2とUCS-4はUnicode文字セットに属しているため、codePointAtを提案する方法が発見され、多くの方法ではcharタイプパラメータを受け入れることができるほか、また、同名の方法で4バイトのintを受け入れる、または返すことで、UCS-4のコードポイント値がオーバーフローしないようにする.
次に、ASCII、ISO-8859-1(Latin-1とも呼ばれ、英語ASCIIに西欧文字が含まれている)、GB 2312、GBK、GB 18030を使用するべきではないことを知る必要があります.Unicodeを使用するべきであるが、伝送中、プログラム中、記憶中にこれらの文字セットを区別するために、UnicodeにはUTF-8、UTF-8 BOM、UTF-16 BOM、UTF-16 BE、UTF-16 LE、UTF-32 BE、UTF-32 LEを含む多くの記憶実装スキームがある.ここでBOMはbyte order mark、すなわちバイト順タグであり、UTF-8のBOMは3バイトであり、バイト順ではなくUTF-8符号化であることを示している.WindowsはASCIIと互換性を持ちたいのでUnicodeもサポートしたいが、Linuxでshellスクリプトを実行する際に最初の3バイトがBOMであることは望ましくない.言い換えれば、彼らはこのようなBOMではないので、互換性の問題を引き起こさないため、UTF-8 with BOMのフォーマットは使用しないでください.また、「a」のように.getBytes(「UTF-16」)は4バイト、「ab」を返す.getBytes(「UTF-16」)は、6バイトを返す、すなわち、バイト順を指定しない場合、JavaにおけるUTF-16は、実際にはUTF-16 BOMを使用し、最初の2バイトは現在のプロセッサで使用するバイト順を表す.これは、異なる機器でのインタラクションにおいても同様に互換性のない文字化けしが生じる可能性があるので、このように使用することはない.ほとんどのアプリケーションはUTF-32 xxレベルのフォーマットが使えないに違いない.もったいないからだ.すべてのマシンが同じバイト順で、中国語のテキスト転送記憶を主とすると判断すると、UTF-16 BEまたはUTF-16 LEを考慮することができる.なぜかというと、UTF-16 BEとUTF-16 LEでは英語と中国語が2バイトを占めているが、UTF-8では英語が1バイトを占め、中国語が3バイトを占めている.テキストの長さが大きいと、UTF-16 BEとUTF-16 LEの方が節約できるに違いない.ただし、UTF-8にはバイト順の問題はない、UTF-16 LEはLittle endian(ローシーケンスバイトをホームアドレス、intel、AMDおよびX 86、IAアーキテクチャに格納)プロセッサ、UTF-16 BEはBig endian(ハイシーケンスバイトをホームアドレス、Javaバイトシーケンス、ネットワークバイトシーケンス、ARMおよびRISCアーキテクチャに格納)プロセッサに適している.だからこんなに多く言って、プラットフォームにまたがって、インターネットの応用に関連して、一律にBOMのないUTF-8符号化フォーマットを採用すべきです;
また、なぜUTF-8にバイト順の問題がないのか、中国語で3バイトで表すのではないでしょうか.ここでは順番の問題はありませんか?次のコードと説明は私の疑問を解決しました.
char data_utf8[]={0xE6,0xB1,0x89,0xE5,0xAD,0x97};//UTF-8  
char16_t data_utf16[]={0x6C49,0x5B57};//UTF-16  
char32_t data_utf32[]={0x00006C49,0x00005B57};//UTF-32  

ここではchar、char 16_t、char32_tは、符号なし8ビット整数、符号なし16ビット整数、符号なし32ビット整数をそれぞれ表す.UTF-8、UTF-16、UTF-32はそれぞれchar、char 16_t、char32_tを符号化単位とする.(注:char 16_tおよびchar 32_tはC++11規格の新規キーワードです.コンパイラがC++11規格をサポートしていない場合は、unsigned shortおよびunsigned longに変更してください.)「漢字」のUTF-8符号化には6バイトが必要です.「漢字」のUTF-16符号化には2つのchar 16_tが必要です.サイズは4バイトです.「漢字」のUTF-32符号化には2つのchar 32_が必要ですt、サイズは8バイトです.
最後に、すべてのUTF-8を決定し、UTF-8の符号化規則に従って、「Java文字列をバイト単位で切り取る」という問題を処理することができる(ここでいうUTF-8とは、BOMのないものである):0000,000-0000 007 F|0 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2行目は中国語の簡体字と繁体字、日本語、韓国語の常用文字セット、すなわち3バイトの表現を含む.2バイトの、3バイト以上の簡略化と需要は、考慮する.
したがって、ネットワークから送信されたとしても、ファイルが開かれたとしても、メモリ内の文字列としても、そのバイト配列形式が得られると仮定し、現在、前のnバイトのコードを切り取り、後の文字をバイトカットによって文字化けしないようにし、バイトトップを判断することでこの問題を解決することができる.例は以下の通りである.
import java.io.UnsupportedEncodingException;

public class StrTruncByByteCountTest {
    public static void main(String[] args) throws Exception{
        final int len = 15;
        for(byte b : "E".getBytes("UTF-8")) {
            System.out.println(byteToBinary(b));
        }
        System.out.println("=================");
        for(byte b : " ".getBytes("UTF-8")) {
            System.out.println(byteToBinary(b));
        }
        System.out.println("=================");
        final String string = "ab  c 12  3";
        final byte[] bytes = string.getBytes("UTF-8"); //byte array also can from IO stream
        final TruncateTips tips = new TruncateTips();
        System.out.println(substrUTF8(bytes, len, tips));
        System.out.println("=================");
        System.out.println("                   : " + tips.getOver());
        System.out.println("                     : " + tips.getNeed());
    }

    private static String substrUTF8(byte[] bytes, int len, TruncateTips tips)
            throws UnsupportedEncodingException {
        if (bytes == null || bytes.length == 0 || len <= 0) {
            return "";
        }
        if (len >= bytes.length) {
            return new String(bytes, "UTF-8");
        }
        int index = 0;
        while (index < len) {
            final byte b = bytes[index];
            if ((b & 0x80/*0b10000000*/) == 0) {// is ascii
                ++index;
            } else {
                int count = 1;
                byte t = 0x40/*0b01000000*/;
                for (int i = 1; i < 8; ++i) {
                    if ((b & t) != 0) {
                        ++count;
                        t >>>= 1;
                    } else {
                        break;
                    }
                }
                final int sum = index + count;
                if (sum <= len) {
                    index = sum;
                } else {
                    if (tips != null) {
                       tips.setNeed(sum - len);
                       tips.setOver(len - index);
                    }
                    break;
                }
            }
        }
        return new String(bytes, 0, index, "UTF-8");
    }

    public static final class TruncateTips {
        private int over;
        private int need;
        public void setNeed(int need) {
            this.need = need;
        }
        public void setOver(int over) {
            this.over = over;
        }
        public int getNeed() {
            return need;
        }
        public int getOver() {
            return over;
        }
    }

    private static String byteToBinary(byte b) {
        final char[] chars = new char[8];
        byte t = 0x1/*0b10000000*/;
        for (int i = 7; i >= 0; --i) {
            if ((b & t) == 0) {
                chars[i] = '0';
            } else {
                chars[i] = '1';
            }
            t <<= 1;
        }
        return String.valueOf(chars);
    }
}

上記の私は実戦で使ったことがあります.特に、ネットワークがデータを受信し、つづった文字列が大きくて、メモリを全部読み込むべきではありません.しかし、理想主義の面接問題であれば、文字セットを仮定することはできないと述べ、クラスライブラリメソッドを使用することができ、メソッド署名は以下の通りでなければならない:static String substr(String originString,String charsetName,int byteLen);ネット上の実装によって、次のことが考えられます.
private static String substr(String originString, String charsetName, int byteLen)
            throws UnsupportedEncodingException {
        if (originString == null || originString.isEmpty() || byteLen <= 0) {
            return "";
        }
        char[] chars = originString.toCharArray();
        int length = 0, index = chars.length;
        for (int i = 0; i < chars.length; ++i) {
            final int len = String.valueOf(chars[i]).getBytes(charsetName).length + length;
            if (len <= byteLen) {
                length = len;
            } else {
                index = i;
                break;
            }
        }
        return String.valueOf(chars, 0, index);
    }