利用Java语言举办Unicode署理编程
当前位置:以往代写 > JAVA 教程 >利用Java语言举办Unicode署理编程
2019-06-14

利用Java语言举办Unicode署理编程

利用Java语言举办Unicode署理编程

副标题#e#

早期 Java 版本利用 16 位 char 数据范例暗示 Unicode 字符。这种设计方 法有时较量公道,因为所有 Unicode 字符拥有的值都小于 65,535 (0xFFFF), 可以通过 16 位暗示。可是,Unicode 厥后将最大值增加到 1,114,111 (0x10FFFF)。由于 16 位太小,不能暗示 Unicode version 3.1 中的所有 Unicode 字符,32 位值 — 称为码位(code point) — 被用于 UTF-32 编码模式。

但与 32 位值对比,16 位值的内存利用效率更高, 因此 Unicode 引入了一个种新设计要领来答允继承利用 16 位值。UTF-16 中采 用的这种设计要领分派 1,024 值给 16 位高署理(high surrogate),将别的 的 1,024 值分派给 16 位低署理(low surrogate)。它利用一个高署理加上一 个低署理 — 一个署理对(surrogate pair) — 来暗示 65,536 (0x10000) 和 1,114,111 (0x10FFFF) 之间的 1,048,576 (0x100000) 值 (1,024 和 1,024 的乘积)。

Java 1.5 保存了 char 范例的行为来表 示 UTF-16 值(以便兼容现有措施),它实现了码位的观念来暗示 UTF-32 值。这个扩展(按照 JSR 204:Unicode Supplementary Character Support 实现) 不需要记着 Unicode 码位或转换算法的精确值 — 但领略署理 API 的正 确用法很重要。

东亚国度和地域连年来增加了它们的字符会合的字符数 量,以满意用户需求。这些尺度包罗来自中国的国度尺度组织的 GB 18030 和来 自日本的 JIS X 0213。因此,寻求遵守这些尺度的措施更有须要支持 Unicode 署理对。本文表明相关 Java API 和编码选项,面向打算从头设计他们的软件, 从只能利用 char 范例的字符转换为可以或许处理惩罚署理对的新版本的读者。

顺序会见

顺序会见是在 Java 语言中处理惩罚字符串的一个根基操纵。在 这种要领下,输入字符串中的每个字符从新至尾按顺序会见,可能有时从尾至头 会见。本小节接头利用顺序会见要领从一个字符串建设一个 32 位码位数组的 7 个技能示例,并预计它们的处理惩罚时间。

示例 1-1:基准测试(不支持代 理对)

清单 1 将 16 位 char 范例值直接分派给 32 位码位值,完全没 有思量署理对:

清单 1. 不支持署理对

int[]  toCodePointArray(String str) { // Example 1-1
  int len  = str.length();     // the length of str
  int[] acp  = new int[len];    // an array of code points

  for (int i = 0, j = 0; i < len; i++) {
     acp[j++] = str.charAt(i);
  }
  return acp;
}

尽量这个示例不支持署理对,但它提供了一个处理惩罚时间基准来比 较后续顺序会见示例。

示例 1-2:利用 isSurrogatePair()

清单 2 利用 isSurrogatePair() 来计较署理对总数。计数之后,它分派足够的内存 以便一个码位数组存储这个值。然后,它进入一个顺序会见轮回,利用 isHighSurrogate() 和 isLowSurrogate() 确定每个署理对字符是高署理照旧低 署理。当它发明一个高署理后头带一个低署理时,它利用 toCodePoint() 将该 署理对转换为一个码位值并将当前索引值增加 2。不然,它将这个 char 范例值 直接分派给一个码位值并将当前索引值增加 1。这个示例的处理惩罚时间比 示例 1 -1 长 1.38 倍。

清单 2. 有限支持

int[]  toCodePointArray(String str) { // Example 1-2 
  int len  = str.length();     // the length of str
  int[]  acp;            // an array of code points
   int surrogatePairCount = 0;   // the count of surrogate  pairs

  for (int i = 1; i < len; i++) {
    if (Character.isSurrogatePair(str.charAt(i - 1),  str.charAt(i))) {
      surrogatePairCount++;
       i++;
    }
  }
  acp = new int[len -  surrogatePairCount];
  for (int i = 0, j = 0; i <  len; i++) {
    char ch0 = str.charAt(i);     //  the current char
    if (Character.isHighSurrogate(ch0)  && i + 1 < len) {
      char ch1 =  str.charAt(i + 1); // the next char
      if  (Character.isLowSurrogate(ch1)) {
        acp[j++] =  Character.toCodePoint(ch0, ch1);
        i++;
         continue;
      }
    }
     acp[j++] = ch0;
  }
  return acp;
}


#p#副标题#e#

清单 2 中更新软件的要领很幼稚。它较量贫苦,需要大量修改 ,使得生成的软件很懦弱且此后难以变动。详细而言,这些问题是:

需 要计较码位的数量以分派足够的内存

很可贵到字符串中的指定索引的正 确码位值

很难为下一个处理惩罚步调正确移动当前索引

一个改造后的算法呈此刻下一个示例中。

示例:根基支持

Java 1.5 提供了 codePointCount()、codePointAt() 和 offsetByCodePoints() 要领来别离处理惩罚 示例 1-2 的 3 个问题。清单 3 利用 这些要领来改进这个算法的可读性:

清单 3. 根基支持

#p#分页标题#e#

int[] toCodePointArray(String str) { // Example 1- 3
  int len = str.length();     // the length of  str
  int[] acp = new int[str.codePointCount(0, len)];

  for (int i = 0, j = 0; i < len; i =  str.offsetByCodePoints(i, 1)) {
    acp[j++] =  str.codePointAt(i);
  }
  return acp;
}

可是,清单 3 的处理惩罚时间比 清单 1 长 2.8 倍。

示例 1-4:利用 codePointBefore()

当 offsetByCodePoints() 吸收一个负数 作为第二个参数时,它就能计较一个间隔字符串头的绝对偏移值。接下来, codePointBefore() 可以或许返回一个指定索引前面的码位值。这些要领用于清单 4 中从尾至头遍历字符串:

清单 4. 利用 codePointBefore() 的根基支持

int[] toCodePointArray(String str) { // Example 1-4  
  int len = str.length();     // the length of  str
  int[] acp = new int[str.codePointCount(0, len)];
  int j = acp.length;       // an index for acp

  for (int i = len; i > 0; i =  str.offsetByCodePoints(i, -1)) {
    acp[--j] =  str.codePointBefore(i);
  }
  return acp;
}

#p#副标题#e#

这个示例的处理惩罚时间 — 比 示例 1-1 长 2.72 倍 — 比 示例 1-3 快一些。凡是,当您较量零而不长短零值时,JVM 中的代 码巨细要小一些,这有时会提高机能。可是,微小的改造大概不值得牺牲可读性 。

示例 1-5:利用 charCount()

示例 1-3 和 1-4 提供根基的代 理对支持。他们不需要任何姑且变量,是结实的编码要领。要获取更短的处理惩罚时 间,利用 charCount() 而不是 offsetByCodePoints() 是有效的,但需要一个 姑且变量来存放码位值,如清单 5 所示:

清单 5. 利用 charCount() 的优化支持

int[] toCodePointArray(String str) { //  Example 1-5 
  int len = str.length();     // the  length of str
  int[] acp = new int[str.codePointCount(0,  len)];
  int j = 0;            // an index  for acp

  for (int i = 0, cp; i < len; i +=  Character.charCount(cp)) {
    cp = str.codePointAt (i);
    acp[j++] = cp;
  }
  return acp;
}

清单 5 的处理惩罚时间低落到比 示例 1-1 长 1.68 倍。

示例 1-6:会见一个 char 数组

清单 6 在利用 示例 1-5 中展 示的优化的同时直接会见一个 char 范例数组:

清单 6. 利用一个 char 数组的优化支持

int[] toCodePointArray(String str) { //  Example 1-6
  char[] ach = str.toCharArray(); // a  char array copied from str
  int len = ach.length;       // the length of ach 
  int[] acp = new int [Character.codePointCount(ach, 0, len)];
  int j = 0;             // an index for acp

  for (int  i = 0, cp; i < len; i += Character.charCount(cp)) {
    cp = Character.codePointAt(ach, i);
    acp[j++]  = cp;
  }
  return acp;
}

char 数组 是利用 toCharArray() 从字符串复制而来的。机能获得改进,因为对数组的直 接会见比通过一个要领的间接会见要快。处理惩罚时间比 示例 1-1 长 1.51 倍。但 是,当挪用时,toCharArray() 需要一些开销来建设一个新数组并将数据复制到 数组中。String 类提供的那些利便的要领也不能被利用。可是,这个算法在处 理大量数据时有用。

示例 1-7:一个面向工具的算法

这个示例的 面向工具算法利用 CharBuffer 类,如清单 7 所示:

清单 7. 利用 CharSequence 的面向工具算法

int[] toCodePointArray(String  str) {    // Example 1-7
  CharBuffer cBuf =  CharBuffer.wrap(str); // Buffer to wrap str
  IntBuffer  iBuf = IntBuffer.allocate(  // Buffer to store code points
      Character.codePointCount(cBuf, 0, cBuf.capacity ()));

  while (cBuf.remaining() > 0) {
     int cp = Character.codePointAt(cBuf, 0); // the current code  point
    iBuf.put(cp);
    cBuf.position (cBuf.position() + Character.charCount(cp));
  }
   return iBuf.array();
}

#p#分页标题#e#

与前面的示例差异,清单 7 不 需要一个索引来持有当前位置以便举办顺序会见。相反,CharBuffer 在内部跟 踪当前位置。Character 类提供静态要领 codePointCount() 和 codePointAt() ,它们能通过 CharSequence 接口处理惩罚 CharBuffer。CharBuffer 老是将当前位 置配置为 CharSequence 的头。因此,当 codePointAt() 被挪用时,第二个参 数老是配置为 0。处理惩罚时间比 示例 1-1 长 2.15 倍。

#p#副标题#e#

处理惩罚时间较量

这些顺序会见示例的计时测试利用了一个包括 10,000 个署理对和 10,000 个非署理对的样例字符串。码位数组从这个字符串建设 10,000 次。测 试情况包罗:

OS:Microsoft Windows® XP Professional SP2

Java:IBM Java 1.5 SR7

CPU:Intel® Core 2 Duo CPU T8300 @ 2.40GHz

Memory:2.97GB RAM

表 1 展示了示例 1-1 到 1-7 的绝 对和相对处理惩罚时间以及关联的 API:

表 1. 顺序会见示例的处理惩罚时间和 API

示例 说明 处理惩罚
时间
(毫秒)

示例 1-1 的比率
API
1-1 不支持署理对 2031 1.00  
1-2 有限支持 2797 1.38 Character 类:

static boolean isHighSurrogate(char ch)

static boolean isLowSurrogate(char ch)

static boolean isSurrogatePair(char high, char low)

static int toCodePoint(char high, char low)

1-3 根基支持 5687 2.80 String 类:

int codePointAt(int index)

int codePointCount(int begin, int end)

int offsetByCodePoints(int index, int cpOffset)

1-4 利用 codePointBefore() 的根基支持 5516 2.72 String 类:

int codePointBefore(int index)

1-5 使 用 charCount() 的优化支持 3406 1.68 Character 类:

static int charCount(int cp)

1-6 利用 一个 char 数组的优化支持 3062 1.51 Character 类:

static int codePointAt(char[] ach, int index)

static int codePointCount(char[] ach, int offset, int count)

1-7 利用 CharSequence 的面向工具要领 4360 2.15 Character 类:

static int codePointAt(CharSequence seq, int index)

static int codePointCount(CharSequence seq, int begin, int end)

#p#副标题#e#

随时机见

随时机见是直接会见一个字符串中 的任意位置。当字符串被会见时,索引值基于 16 位 char 范例的单元。可是, 假如一个字符串利用 32 位码位,那么它不能利用一个基于 32 位码位的单元的 索引会见。必需利用 offsetByCodePoints() 来将码位的索引转换为 char 范例 的索引。假如算法设计很糟糕,这会导致很差的机能,因为 offsetByCodePoints() 老是通过利用第二个参数从第一个参数计较字符串的内 部。在这个小节中,我将较量三个示例,它们通过利用一个短单元来支解一个长 字符串。

示例 2-1:基准测试(不支持署理对)

清单 8 展示如 何利用一个宽度单元来支解一个字符串。这个基准测试留作后用,不支持署理对 。

清单 8. 不支持署理对

#p#分页标题#e#

String[] sliceString(String  str, int width) { // Example 2-1
  // It must be  that "str != null && width > 0".
   List<String> slices = new ArrayList<String>();
   int len = str.length();    // (1) the length of str
  int sliceLimit = len - width; // (2) Do not slice  beyond here.
  int pos = 0;         // the  current position per char type

  while (pos <  sliceLimit) {
    int begin = pos;             // (3)
    int end  = pos + width;        //  (4)
    slices.add(str.substring(begin, end));
     pos += width;             // (5)
  }
   slices.add(str.substring(pos));      // (6)
  return  slices.toArray(new String[slices.size()]); }

sliceLimit 变量对支解位置有所限制,以制止在剩余的字符串不敷以支解当前宽度单元时抛 出一个 IndexOutOfBoundsException 实例。这种算法在当前位置超出 sliceLimit 时从 while 轮回中跳出后再处理惩罚最后的支解。

示例 2-2: 利用一个码位索引

清单 9 展示了如何利用一个码位索引来随时机见一个 字符串:

清单 9. 糟糕的机能

String[] sliceString (String str, int width) { // Example 2-2 
  // It  must be that "str != null && width > 0".
   List<String> slices = new ArrayList<String>();
   int len = str.codePointCount(0, str.length()); // (1) code  point count [Modified]
  int sliceLimit = len - width;  // (2) Do not slice beyond here.
  int pos = 0;          // the current position per code point

   while (pos < sliceLimit) {
    int begin =  str.offsetByCodePoints(0, pos);      // (3) [Modified]
    int end  = str.offsetByCodePoints(0, pos + width);   // (4) [Modified]
    slices.add(str.substring(begin,  end));
    pos += width;                      // (5)
  }
  slices.add(str.substring (str.offsetByCodePoints(0, pos))); // (6) [Modified]
   return slices.toArray(new String[slices.size()]); }

#p#副标题#e#

清 单 9 修改了 清单 8 中的几行。首先,在 Line (1) 中,length() 被 codePointCount() 替代。其次,在 Lines (3)、(4) 和 (6) 中,char 范例的 索引通过 offsetByCodePoints() 用码位索引替代。

根基的算法流与 示 例 2-1 中的看起来险些一样。但处理惩罚时间按照字符串长度与示例 2-1 的比率同 比增加,因为 offsetByCodePoints() 老是从字符串头到指定索引计较字符串内 部。

示例 2-3:淘汰的处理惩罚时间

可以利用清单 10 中展示的要领 来制止 示例 2-2 的机能问题:

清单 10. 改造的机能

String[] sliceString(String str, int width) { //  Example 2-3
  // It must be that "str != null  && width > 0".
  List<String> slices =  new ArrayList<String>();
  int len = str.length();  // (1) the length of str
  int sliceLimit     //  (2) Do not slice beyond here. [Modified]
      =  (len >= width * 2 || str.codePointCount(0, len) >  width)
      ? str.offsetByCodePoints(len, -width) :  0;
  int pos = 0;      // the current position per  char type

  while (pos < sliceLimit) {
     int begin = pos;                // (3)
    int end  = str.offsetByCodePoints(pos, width); // (4)  [Modified]
    slices.add(str.substring(begin, end));
    pos = end;                   // (5)  [Modified]
  }
  slices.add(str.substring(pos));            // (6)
  return slices.toArray(new String [slices.size()]); }

#p#分页标题#e#

首先,在 Line (2) 中,(清单 9 中的 )表达式 len-width 被 offsetByCodePoints(len,-width) 替代。可是,当 width 的值大于码位的数量时,这会抛出一个 IndexOutOfBoundsException 实 例。必需思量界线条件以制止异常,利用一个带有 try/catch 异常处理惩罚措施的 子句将是另一个办理方案。假如表达式 len>width*2 为 true,则可以安详 地挪用 offsetByCodePoints(),因为纵然所有码位都被转换为署理对,码位的 数量仍会高出 width 的值。可能,假如 codePointCount(0,len)>width 为 true,也可以安详地挪用 offsetByCodePoints()。假如是其他环境, sliceLimit 必需配置为 0。

在 Line (4) 中,清单 9 中的表达式 pos + width 必需在 while 轮回中利用 offsetByCodePoints(pos,width) 替换。需 要计较的量位于 width 的值中,因为第一个参数指定当 width 的值。接下来, 在 Line (5) 中,表达式 pos+=width 必需利用表达式 pos=end 替换。这制止 两次挪用 offsetByCodePoints() 来计较沟通的索引。源代码可以被进一步修改 以最小化处理惩罚时间。

处理惩罚时间较量

图 1 和图 2 展示了示例 2-1 、2-2 和 2-3 的处理惩罚时间。样例字符串包括沟通数量的署理对和非署理对。当 字符串的长度和 width 的值被变动时,样例字符串被切割 10,000 次。

图 1. 一个分段的常量宽度

操作Java语言举行Unicode代理编程

图 2. 分段的常量计数

操作Java语言举行Unicode代理编程

#p#副标题#e#

示例 2-1 和 2-3 凭据长度比例增加了它们的处理惩罚时间,但 示例 2-2 凭据长度的平方比例增加了处理惩罚时间。当字符串长度和 width 的值增加而分段 的数量固按时,示例 2-1 拥有一个常量处理惩罚时间,而示例 2-2 和 2-3 以 width 的值为比例增加了它们的处理惩罚时间。

信息 API

大大都处理惩罚 署理的信息 API 拥有两种名称沟通的要领。一种吸收 16 位 char 范例参数, 另一种吸收 32 为码位参数。表 2 展示了每个 API 的返回值。第三列针对 U+53F1,第 4 列针对 U+20B9F,最后一列针对 U+D842(即高署理),而 U+20B9F 被转换为 U+D842 加上 U+DF9F 的署理对。假如措施不能处理惩罚署理对, 则值 U+D842 而不是 U+20B9F 将导致意想不到的功效(在表 2 中以粗斜体暗示 )。

表 2. 用于署理的信息 API

要领/结构函数 针对 U+53F1 的值 针对 U+20B9F 的值 针对 U+D842 的值
    操作Java语言举行Unicode代理编程 操作Java语言举行Unicode代理编程  
Character static byte getDirectionality(int cp) 0 0 0
  static int getNumericValue(int cp) -1 -1 – 1
  static int getType(int cp) 5 5 19
  static boolean isDefined(int cp) true true true
  static boolean isDigit(int cp) false false false
  static boolean isISOControl(int cp) false false false
  static boolean isIdentifierIgnorable(int cp) false false false
  static boolean isJavaIdentifierPart(int cp) true true false
  static boolean isJavaIdentifierStart(int cp) true true false
  static boolean isLetter(int cp) true true false
  static boolean isLetterOrDigit(int cp) true true false
  static boolean isLowerCase(int cp) false false false
  static boolean isMirrored(int cp) false false false
  static boolean isSpaceChar(int cp) false false false
  static boolean isSupplementaryCodePoint(int cp) false true false
  static boolean isTitleCase(int cp) false false false
  static boolean isUnicodeIdentifierPart(int cp) true true false
  static boolean isUnicodeIdentifierStart(int cp) true true false
  static boolean isUpperCase(int cp) false false false
  static boolean isValidCodePoint(int cp) true true true
  static boolean isWhitespace(int cp) false false false
  static int toLowerCase(int cp) (不行变动 )
  static int toTitleCase(int cp) (不行变动)
  static int toUpperCase(int cp) (不行变动)
Character.
UnicodeBlock
Character.UnicodeBlock of(int cp) CJK_UNIFIED_IDEOGRAPHS CJK_UNIFIED_IDEOGRAPHS_EXTENSI ON_B HIGH_SURROGATES
Font boolean canDisplay(int cp) (取决于 Font 实例)
FontMetrics int charWidth(int cp) (取决于 FontMetrics 实例)
String int indexOf(int cp) (取决于 String 实例)
  int lastIndexOf(int cp) (取决于 String 实例)

#p#副标题#e#

其他 API

本小节先容前面的小节中没有接头 的署理对相关 API。表 3 展示所有这些剩余的 API。所有署理对 API 都包括在 表 1、2 和 3 中。

表 3. 其他署理 API

#p#分页标题#e#

要领/结构函数
Character static int codePointAt(char[] ach, int index, int limit)
  static int codePointBefore(char[] ach, int index)
  static int codePointBefore(char[] ach, int index, int start)
  static int codePointBefore(CharSequence seq, int index)
  static int digit(int cp, int radix)
  static int offsetByCodePoints (char[] ach, int start, int count, int index, int cpOffset)
  static int offsetByCodePoints (CharSequence seq, int index, int cpOffset)
  static char[] toChars(int cp)
  static int toChars(int cp, char[] dst, int dstIndex)
String String(int[] acp, int offset, int count)
  int indexOf(int cp, int fromIndex)
  int lastIndexOf(int cp, int fromIndex)
StringBuffer StringBuffer appendCodePoint (int cp)
  int codePointAt(int index)
  int codePointBefore(int index)
  int codePointCount(int beginIndex, int endIndex)
  int offsetByCodePoints(int index, int cpOffset)
StringBuilder StringBuilder appendCodePoint (int cp)
  int codePointAt(int index)
  int codePointBefore(int index)
  int codePointCount(int beginIndex, int endIndex)
  int offsetByCodePoints(int index, int cpOffset)
IllegalFormat
CodePointException
IllegalFormatCodePointException (int cp)
  int getCodePoint()

#p#分页标题#e#

清单 11 展示了从一个码位建设一个字符串的 5 种 要领。用于测试的码位是 U+53F1 和 U+20B9F,它们在一个字符串中反复了 100 亿次。清单 11 中的注释部门显示了处理惩罚时间:

清单 11. 从一个码位 建设一个字符串的 5 种要领

int cp = 0x20b9f; // CJK  Ideograph Extension B 
String str1 = new String(new int []{cp}, 0, 1);  // processing time: 206ms
String str2 =  new String(Character.toChars(cp));         // 187ms
String str3 = String.valueOf(Character.toChars(cp));        // 195ms
String str4 = new StringBuilder ().appendCodePoint(cp).toString(); // 269ms
String str5 =  String.format("%c", cp);              // 3781ms

str1、str2、str3 和 str4 的处理惩罚时间没有明明差异。相反,建设 str5 耗费的时间要长得多,因为它利用 String.format(),该要领支持基 于当地和名目化信息的机动输出。str5 要领应该只用于措施的末端来输出文本 。

竣事语

Unicode 的每个新版本都包括了通过署理对暗示的新定 义的字符。东亚字符集尺度并不是这样的字符的惟一来历。譬喻,移动电话中还 需要支持 Emoji 字符(心情图释),尚有各类古字符需要支持。您从本文收获 的技能和机能阐明将有助于您在您的 Java 应用措施中支持所有这些字符。

    关键字:

在线提交作业