如果文本中有阿拉伯语或波斯语字母,则通过字体测量计算的字符串宽度非常慢

2022-09-04 04:24:58

我有一个问题。如果我在那里使用东方语言,我的应用程序界面工作得要慢得多。特别是我在JList,JCombobox,JTable等组件中感受到了它。

我如何发现FontMetrics.stringWidth方法的性能非常慢(500 +次),如果在文本中至少有一个字母是阿拉伯语或波斯语。我怎么知道它是各种摆动组件中常用的方法。

有没有办法提高这种方法的性能?

下面是演示该问题的示例类:

import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

public class FontMetricsSpeedTest
{

 public static void main( String args[] ) {
  String persian="صصصصصصصصصصصصصصصصصصصصص";
  String english="abcde()agjklj;lkjelwk";
  FontMetrics fm=createFontMetrics(new Font("dialog",Font.PLAIN,12));
  int size=50000;
  long start=System.currentTimeMillis();
  for(int i=0;i<size;i++)
  {
   fm.stringWidth(persian);
  }
  System.out.println("Calculation time for persian: "+(System.currentTimeMillis()-start)+" ms");
  start=System.currentTimeMillis();
  for(int i=0;i<size;i++)
  {
   fm.stringWidth(english);
  }
  System.out.println("Calculation time for english: "+(System.currentTimeMillis()-start)+" ms");
 }
 private static FontMetrics createFontMetrics(Font font)
 {
  BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
  Graphics g = bi.getGraphics();
  FontMetrics fm = g.getFontMetrics(font);
  g.dispose();
  bi = null;
  return fm;
 }
}

对我来说,它给出了下一个输出:

波斯语的计算时间: 5482 ms

英语的计算时间: 11 ms


答案 1

我挖了一点,发现接下来:

从FontDesignMetrics的来源,我们可以看到主要的动作序列

public int stringWidth(String str) {
float width = 0;
if (font.hasLayoutAttributes()) {
    /* TextLayout throws IAE for null, so throw NPE explicitly */
    if (str == null) {
        throw new NullPointerException("str is null");
    }
    if (str.length() == 0) {
        return 0;
    }
    width = new TextLayout(str, font, frc).getAdvance();
} else {
    int length = str.length();
    for (int i = 0; i < length; i++) {
        char ch = str.charAt(i);
        if (ch < 0x100) {
            width += getLatinCharWidth(ch);
        } else if (FontManager.isNonSimpleChar(ch)) {
            width = new TextLayout(str, font, frc).getAdvance();
            break;
        } else {
            width += handleCharWidth(ch);
        }
    }
}
return (int) (0.5 + width);

}

对于拉丁字符方法,使用 getLatinCharWidth(ch)。它缓存所有字符宽度。但对于波斯语和阿拉伯语字符,使用TextLayout代替。主要目的是因为东方字符可能具有可变形状和宽度,具体取决于上下文。可以添加将缓存字符宽度的方法,但它不会给出确切的值,例如它会忽略不同字符宽度的细微差别。此外,它将忽略各种连字。

我已经分别测试了TextLayout,对于英语和波斯语来说,它的速度都很慢。因此,性能缓慢的真正原因是 sun.font.TextLayout 类的工作缓慢。它用于确定字符串宽度,以防字符串中的字符不简单。不幸的是,我现在不知道如何提高TextLayout的性能。

如果有人在这里感兴趣,那么关于各种字体和文本布局细微差别的文章 http://download.oracle.com/javase/1.4.2/docs/guide/2d/spec/j2d-fonts.html


答案 2

我使用您的代码用其他语言执行了一些测试。首先你是对的:波斯字符串的计算花费了大量时间。

我尝试了字体类型和大小,没有看到显着差异。但结果肯定取决于您使用的脚本。这是我在机器上得到的结果。

Calculation time for Persian: 2877 ms
Calculation time for English: 8 ms
Calculation time for Russian: 47 ms
Calculation time for Hebrew:  16815 ms

如您所见,俄语比英语慢6倍。我相信这是因为字符串的内部表示是unicode。在 UTF-8 中,英文字符占用一个字节,所有其他字符占 2 个字节。

我不确定它能满足你:)但希伯来语测试比波斯语慢4倍。两者都很慢,所以我猜从右到左的计算会杀死它。

看来我们与此无关。


推荐