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

时间:2010-12-02 09:48:19

标签: java performance swing fonts fontmetrics

我有问题。如果我在那里使用东方语言我的应用程序界面工作得慢得多特别是我觉得它在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毫秒

英语的计算时间:11毫秒

4 个答案:

答案 0 :(得分:4)

我挖了一点然后找到了:

从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

答案 1 :(得分: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倍。两者都很慢,所以我猜从右到左的计算会杀死它。

似乎我们与此无关。

答案 2 :(得分:0)

你能尝试使用Font类的方法吗? public GlyphVector layoutGlyphVector(FontRenderContext frc,                                          char []文字,                                          int start,                                          int limit,                                          int flags)

是否使用GlyphVector来测量你的字符串?

或TextLayout  public TextLayout(String string,Font font,FontRenderContext frc)

答案 3 :(得分:0)

我在计算字符串宽度时使用缓存。它不能解决javas自己的类所做的内部调用,但它用波斯语字母解决了我的性能问题(我使用了很多自己的渲染等等)。 Pair类只是两个对象的类型bean ...

public class GuiUtils {
private static final Map<Pair<Boolean, Pair<FontMetrics, String>>, Integer> stringWidthCache = new HashMap<Pair<Boolean, Pair<FontMetrics, String>>, Integer>();

public static int getStringWidth(FontMetrics fm, String text){
    return getStringWidth(null, fm, text);
}

public static int getStringWidth(Graphics g, FontMetrics fm, String text){
    if(text == null || text.equals("")) {
        return 0;
    }
    Pair<Boolean, Pair<FontMetrics, String>> cacheKey = 
            new Pair<Boolean, Pair<FontMetrics, String>>(g != null, new Pair<FontMetrics, String>(fm, text));
    if (!stringWidthCache.containsKey(cacheKey)) {
        stringWidthCache.put(
                cacheKey, 
                g != null ? 
                        (int)Math.ceil(fm.getStringBounds(text, g).getWidth()) :
                            fm.stringWidth(text));
    }
    return stringWidthCache.get(cacheKey);
}

}