14

我有个问题。如果我在那里使用东方语言,我的应用程序界面会慢得多。尤其是在 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

4

4 回答 4

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

于 2010-12-07T17:42:55.753 回答
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 中,英文字符占 1 个字节,所有其他字符占 2 个字节。

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

似乎与我们无关。

于 2010-12-02T10:11:51.617 回答
0

你能尝试使用字体类的方法吗?公共 GlyphVector layoutGlyphVector(FontRenderContext frc, char[] text, int start, int limit, int flags)

并且使用 GlyphVector 来测量您的字符串?

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

于 2010-12-02T14:46:47.410 回答
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);
}

}

于 2017-04-13T07:24:14.523 回答