首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 依位元組長度截取字串:避免半個中文字元的完整實作

     編輯:Java 相關知識

前言

在中文系統中,字串截取常遇到「半個漢字」問題:UTF-8 編碼下一個中文字佔 3 位元組,Big5/GBK 佔 2 位元組。若直接按位元組截取,極易產生亂碼。本文提供嚴謹的按位元組長度截取實作,確保不會切斷多位元組字元。

核心演算法邏輯

  1. 逐字元遍歷字串(char[])
  2. 判斷字元是否為 ASCII(<= 255)或多位元組字元(中文、日文等)
  3. ASCII 計 1 位元組,非 ASCII 計 2 位元組(針對 Big5/GBK)或 3 位元組(UTF-8)
  4. 累加位元組數,精準停在字元邊界

完整實作(針對雙位元組編碼 Big5/GBK)

package com.haohui.common.utils;

/**
 * 字串工具類:提供按位元組長度安全截取
 */
public class StringTool {

    /**
     * 截取指定位元組長度的字串,不會產生半個漢字
     * @param str 原始字串
     * @param length 目標位元組長度(雙位元組編碼下:1 中文 = 2 位元組)
     * @return 截取後字串,若長度不足回傳空字串
     * @example 網頁最多顯示 17 個中文 → length = 34
     * StringTool.getSubString(str, 34);
     */
    public static String getSubString(String str, int length) {
        if (str == null || length <= 0) {
            return "";
        }
        
        int byteCount = 0;
        char[] chars = str.toCharArray();
        
        for (int i = 0; i < chars.length; i++) {
            // 簡易判斷:字元值 > 255 視為雙位元組字元(中日韓文字)
            if (chars[i] > 255) {
                byteCount += 2;
            } else {
                byteCount += 1;
            }
            
            // 精準匹配長度
            if (byteCount == length) {
                return str.substring(0, i + 1);
            }
            // 超出長度且最後一個是雙位元組字元 → 退回上一字元
            if (byteCount == length + 1 && chars[i] > 255) {
                return str.substring(0, i);
            }
        }
        // 原字串位元組長度小於目標長度
        return str;
    }
}

UTF-8 版本(一個中文 = 3 位元組)


/**
 * UTF-8 編碼環境下的位元組長度截取
 * @param str 原始字串
 * @param byteLength 目標位元組長度
 * @return 截取後字串
 */
public static String getSubStringUtf8(String str, int byteLength) {
    if (str == null || byteLength <= 0) return "";
    
    try {
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        if (bytes.length <= byteLength) return str;
        
        // 從目標位置向前尋找合法 UTF-8 字元邊界
        int end = byteLength;
        while (end > 0 && (bytes[end] & 0xC0) == 0x80) { // 10xxxxxx 為續位元組
            end--;
        }
        return new String(bytes, 0, end, StandardCharsets.UTF_8);
    } catch (Exception e) {
        return str.substring(0, Math.min(str.length(), byteLength)); // 兜底
    }
}

單元測試驗證


import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class StringToolTest {
    
    @Test void testGbkSubstring() {
        String str = "測試字串ABC"; // 4中文(8bytes) + 3英文(3bytes) = 11 bytes
        assertEquals("測試", StringTool.getSubString(str, 4));   // 2中文 = 4 bytes
        assertEquals("測試字", StringTool.getSubString(str, 6)); // 3中文 = 6 bytes
        assertEquals("測試字串", StringTool.getSubString(str, 8)); // 4中文 = 8 bytes
        assertEquals("測試字串A", StringTool.getSubString(str, 9)); // +1 ASCII
        assertEquals("測試字串ABC", StringTool.getSubString(str, 11)); // 全部
        assertEquals("測試字串ABC", StringTool.getSubString(str, 20)); // 超過長度回傳原字串
    }

    @Test void testUtf8Substring() {
        String str = "測試ABC"; // 2中文(6bytes) + 3英文(3bytes) = 9 bytes
        assertEquals("測", StringTool.getSubStringUtf8(str, 3));   // 1中文 = 3 bytes
        assertEquals("測試", StringTool.getSubStringUtf8(str, 6)); // 2中文 = 6 bytes
        assertEquals("測試A", StringTool.getSubStringUtf8(str, 7)); // +1 ASCII
    }
    
    @Test void testEdgeCases() {
        assertEquals("", StringTool.getSubString(null, 10));
        assertEquals("", StringTool.getSubString("ABC", 0));
        assertEquals("ABC", StringTool.getSubString("ABC", 10)); // 不足長度回傳原字串
    }
}

常見陷阱與替代方案

方法問題建議
str.substring(0, n) 按 char 計數,中英文混雜時顯示寬度不一 僅適用純英文或固定字元數需求
new String(str.getBytes("GBK"), 0, len) 若切在雙位元組中間會拋出異常或產生替換字元 需搭配邊界檢查,不如自行實作直觀
Apache Commons Lang StringUtils.leftBytes() 依賴第三方庫,行為與編碼相關 專案已引入 Commons Lang 可直接使用
Java 9+ StringBuilder.setLength() + 字元寬度計算 需自行實作字元寬度邏輯(半形/全形) 顯示層面截取建議用 ICU4J BreakIterator

總結

按位元組截取的核心在於字元邊界識別。雙位元組編碼(Big5/GBK)可用簡易 char > 255 判斷;UTF-8 必須檢查位元組標記位(10xxxxxx)。生產環境建議統一使用 UTF-8 並採用 getSubStringUtf8 版本,或引入 ICU4J 處理更複雜的書寫系統(如表情符號、複合字元)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved