Ширина строки с помощью расчета шрифтов очень медленная, если в тексте есть арабские или персидские буквы - PullRequest
14 голосов
/ 02 декабря 2010

У меня проблема.Мой интерфейс приложения работает намного медленнее, если я использую там восточные языки.Особенно я чувствовал это в таких компонентах, как JList, JCombobox, JTable.

Как я обнаружил, что производительность метода FontMetrics.stringWidth очень медленная (более 500 раз), если в тексте хотя бы одна буква арабская или персидская.Насколько я знаю, это часто используемый метод в различных компонентах Swing.

Есть ли способ повысить производительность этого метода?

Вот пример класса, который демонстрирует проблему:

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 ]

4 голосов
/ 07 декабря 2010

Я немного покопался и нашел следующее:

Из источника 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 голосов
/ 02 декабря 2010

Я провел несколько тестов на других языках, используя ваш код.Сначала вы правы: расчеты по персидской струне заняли много времени.

Я играл с типом и размером шрифта и не видел существенных различий.Но результат определенно зависит от сценария, который вы используете.Вот результаты, которые я получил на своей машине.

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 раза медленнее, чем на персидском.И то, и другое медленно, поэтому я предполагаю, что вычисления справа налево убивают его.

Кажется, мы не имеем к этому никакого отношения

0 голосов
/ 13 апреля 2017

Я использую кеш при расчете ширины строки.Он не решает внутренние вызовы, которые делают собственные классы javas, но он решил мои проблемы с производительностью с персидскими буквами (я использую много собственных рендеров и так далее).Класс Pair - это просто типизированный бин из двух объектов ...

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);
}

}

0 голосов
/ 02 декабря 2010

Не могли бы вы попробовать использовать метод Font class '.public GlyphVector layoutGlyphVector (FontRenderContext frc, char [] text, int start, int limit, int flags)

И можете ли использовать GlyphVector для измерения вашей строки?

Или TextLayout public TextLayout (String string,Шрифт Font, FontRenderContext frc)

...