pdfbox,提取文本时 cpu 100%
pdfbox, cpu 100% while extracting text
我正在使用 pdfbox 2.0.1 像这样解析 pdf 文档。
for (int i = 0; i < 5; i ++) {
new Thread(new Runnable() {
@Override
public void run() {
InputStream in = new ByteArrayInputStream(fileContent);
PDDocument document = null;
PDFTextStripper stripper;
String content;
try {
document = PDDocument.load(in);
stripper = new PDFTextStripper();
content = stripper.getText(document).trim();
} finally {
if (document != null) {
document.close();
}
if (in != null) {
in.close();
}
}
System.out.println(content);
}
}).start();
}
有时 cpu 在并发解析 pdf 时运行 100%。堆栈如下:
java.lang.Thread.State: RUNNABLE
at java.util.HashMap.get(HashMap.java:303)
at org.apache.pdfbox.pdmodel.font.encoding.GlyphList.toUnicode(GlyphList.java:231)
at org.apache.pdfbox.pdmodel.font.PDSimpleFont.toUnicode(PDSimpleFont.java:308)
at org.apache.pdfbox.pdmodel.font.PDSimpleFont.toUnicode(PDSimpleFont.java:273)
at org.apache.pdfbox.contentstream.PDFStreamEngine.showText(PDFStreamEngine.java:668)
at org.apache.pdfbox.contentstream.PDFStreamEngine.showTextStrings(PDFStreamEngine.java:609)
at org.apache.pdfbox.contentstream.operator.text.ShowTextAdjusted.process(ShowTextAdjusted.java:52)
at org.apache.pdfbox.contentstream.PDFStreamEngine.processOperator(PDFStreamEngine.java:815)
at org.apache.pdfbox.contentstream.PDFStreamEngine.processStreamOperators(PDFStreamEngine.java:472)
at org.apache.pdfbox.contentstream.PDFStreamEngine.processStream(PDFStreamEngine.java:446)
at org.apache.pdfbox.contentstream.PDFStreamEngine.processPage(PDFStreamEngine.java:149)
at org.apache.pdfbox.text.PDFTextStreamEngine.processPage(PDFTextStreamEngine.java:136)
at org.apache.pdfbox.text.PDFTextStripper.processPage(PDFTextStripper.java:391)
at org.apache.pdfbox.text.PDFTextStripper.processPages(PDFTextStripper.java:319)
at org.apache.pdfbox.text.PDFTextStripper.writeText(PDFTextStripper.java:266)
at org.apache.pdfbox.text.PDFTextStripper.getText(PDFTextStripper.java:227)
GlyphList.java代码为:
// Adobe Glyph List (AGL)
private static final GlyphList DEFAULT = load("glyphlist.txt", 4281);
/**
* Returns the Unicode character sequence for the given glyph name, or null if there isn't any.
*
* @param name PostScript glyph name
* @return Unicode character(s), or null.
*/
public String toUnicode(String name)
{
if (name == null)
{
return null;
}
String unicode = nameToUnicode.get(name);
if (unicode != null)
{
return unicode;
}
// separate read/write cache for thread safety
unicode = uniNameToUnicodeCache.get(name);
if (unicode == null)
{
// test if we have a suffix and if so remove it
if (name.indexOf('.') > 0)
{
unicode = toUnicode(name.substring(0, name.indexOf('.')));
}
else if (name.startsWith("uni") && name.length() == 7)
{
// test for Unicode name in the format uniXXXX where X is hex
int nameLength = name.length();
StringBuilder uniStr = new StringBuilder();
try
{
for (int chPos = 3; chPos + 4 <= nameLength; chPos += 4)
{
int codePoint = Integer.parseInt(name.substring(chPos, chPos + 4), 16);
if (codePoint > 0xD7FF && codePoint < 0xE000)
{
LOG.warn("Unicode character name with disallowed code area: " + name);
}
else
{
uniStr.append((char) codePoint);
}
}
unicode = uniStr.toString();
}
catch (NumberFormatException nfe)
{
LOG.warn("Not a number in Unicode character name: " + name);
}
}
else if (name.startsWith("u") && name.length() == 5)
{
// test for an alternate Unicode name representation uXXXX
try
{
int codePoint = Integer.parseInt(name.substring(1), 16);
if (codePoint > 0xD7FF && codePoint < 0xE000)
{
LOG.warn("Unicode character name with disallowed code area: " + name);
}
else
{
unicode = String.valueOf((char) codePoint);
}
}
catch (NumberFormatException nfe)
{
LOG.warn("Not a number in Unicode character name: " + name);
}
}
uniNameToUnicodeCache.put(name, unicode);
}
return unicode;
}
所以,当我们这样调用时
GlyphList.DEFAULT.toUnicode(code)
出现并发错误(注意var uniNameToUnicodeCache),PDSimpleFont.toUnicode就是这样。
不过好像没有其他人遇到过同样的问题。不知道我上面说的对不对。如果它真的是一个错误,它是否已修复?
查看 GlyphList
class 代码,很明显它还没有为 multi-threaded 使用做好准备。另一方面,文本提取代码同时通过 getAdobeGlyphList
将其 DEFAULT
实例用作单例。
如果有问题的文档使用非官方方案 uniXXXX
或 uXXXX
的字形名称,这可能会成为其 toUnicode(String)
方法中的一个问题,因为在这种情况下,该方法不仅尝试从 HashMap uniNameToUnicodeCache
读取但也写入它(添加找到的非官方字形名称以供以后快速查找)。
如果这样的写入与其他线程从映射中读取同时发生,确实可能会发生 ConcurrentModificationException
。
我建议将 GlyphList
更改为
- 不再写给
uniNameToUnicodeCache
,或者
- 同步
toUnicode(String)
或更准确地说 uniNameToUnicodeCache
读取和写入其中,或
- 使
uniNameToUnicodeCache
成为 ConcurrentHashMap
而不是 HashMap
。
我希望第三个选项比第二个选项执行得更好。
我正在使用 pdfbox 2.0.1 像这样解析 pdf 文档。
for (int i = 0; i < 5; i ++) {
new Thread(new Runnable() {
@Override
public void run() {
InputStream in = new ByteArrayInputStream(fileContent);
PDDocument document = null;
PDFTextStripper stripper;
String content;
try {
document = PDDocument.load(in);
stripper = new PDFTextStripper();
content = stripper.getText(document).trim();
} finally {
if (document != null) {
document.close();
}
if (in != null) {
in.close();
}
}
System.out.println(content);
}
}).start();
}
有时 cpu 在并发解析 pdf 时运行 100%。堆栈如下:
java.lang.Thread.State: RUNNABLE
at java.util.HashMap.get(HashMap.java:303)
at org.apache.pdfbox.pdmodel.font.encoding.GlyphList.toUnicode(GlyphList.java:231)
at org.apache.pdfbox.pdmodel.font.PDSimpleFont.toUnicode(PDSimpleFont.java:308)
at org.apache.pdfbox.pdmodel.font.PDSimpleFont.toUnicode(PDSimpleFont.java:273)
at org.apache.pdfbox.contentstream.PDFStreamEngine.showText(PDFStreamEngine.java:668)
at org.apache.pdfbox.contentstream.PDFStreamEngine.showTextStrings(PDFStreamEngine.java:609)
at org.apache.pdfbox.contentstream.operator.text.ShowTextAdjusted.process(ShowTextAdjusted.java:52)
at org.apache.pdfbox.contentstream.PDFStreamEngine.processOperator(PDFStreamEngine.java:815)
at org.apache.pdfbox.contentstream.PDFStreamEngine.processStreamOperators(PDFStreamEngine.java:472)
at org.apache.pdfbox.contentstream.PDFStreamEngine.processStream(PDFStreamEngine.java:446)
at org.apache.pdfbox.contentstream.PDFStreamEngine.processPage(PDFStreamEngine.java:149)
at org.apache.pdfbox.text.PDFTextStreamEngine.processPage(PDFTextStreamEngine.java:136)
at org.apache.pdfbox.text.PDFTextStripper.processPage(PDFTextStripper.java:391)
at org.apache.pdfbox.text.PDFTextStripper.processPages(PDFTextStripper.java:319)
at org.apache.pdfbox.text.PDFTextStripper.writeText(PDFTextStripper.java:266)
at org.apache.pdfbox.text.PDFTextStripper.getText(PDFTextStripper.java:227)
GlyphList.java代码为:
// Adobe Glyph List (AGL)
private static final GlyphList DEFAULT = load("glyphlist.txt", 4281);
/**
* Returns the Unicode character sequence for the given glyph name, or null if there isn't any.
*
* @param name PostScript glyph name
* @return Unicode character(s), or null.
*/
public String toUnicode(String name)
{
if (name == null)
{
return null;
}
String unicode = nameToUnicode.get(name);
if (unicode != null)
{
return unicode;
}
// separate read/write cache for thread safety
unicode = uniNameToUnicodeCache.get(name);
if (unicode == null)
{
// test if we have a suffix and if so remove it
if (name.indexOf('.') > 0)
{
unicode = toUnicode(name.substring(0, name.indexOf('.')));
}
else if (name.startsWith("uni") && name.length() == 7)
{
// test for Unicode name in the format uniXXXX where X is hex
int nameLength = name.length();
StringBuilder uniStr = new StringBuilder();
try
{
for (int chPos = 3; chPos + 4 <= nameLength; chPos += 4)
{
int codePoint = Integer.parseInt(name.substring(chPos, chPos + 4), 16);
if (codePoint > 0xD7FF && codePoint < 0xE000)
{
LOG.warn("Unicode character name with disallowed code area: " + name);
}
else
{
uniStr.append((char) codePoint);
}
}
unicode = uniStr.toString();
}
catch (NumberFormatException nfe)
{
LOG.warn("Not a number in Unicode character name: " + name);
}
}
else if (name.startsWith("u") && name.length() == 5)
{
// test for an alternate Unicode name representation uXXXX
try
{
int codePoint = Integer.parseInt(name.substring(1), 16);
if (codePoint > 0xD7FF && codePoint < 0xE000)
{
LOG.warn("Unicode character name with disallowed code area: " + name);
}
else
{
unicode = String.valueOf((char) codePoint);
}
}
catch (NumberFormatException nfe)
{
LOG.warn("Not a number in Unicode character name: " + name);
}
}
uniNameToUnicodeCache.put(name, unicode);
}
return unicode;
}
所以,当我们这样调用时
GlyphList.DEFAULT.toUnicode(code)
出现并发错误(注意var uniNameToUnicodeCache),PDSimpleFont.toUnicode就是这样。
不过好像没有其他人遇到过同样的问题。不知道我上面说的对不对。如果它真的是一个错误,它是否已修复?
查看 GlyphList
class 代码,很明显它还没有为 multi-threaded 使用做好准备。另一方面,文本提取代码同时通过 getAdobeGlyphList
将其 DEFAULT
实例用作单例。
如果有问题的文档使用非官方方案 uniXXXX
或 uXXXX
的字形名称,这可能会成为其 toUnicode(String)
方法中的一个问题,因为在这种情况下,该方法不仅尝试从 HashMap uniNameToUnicodeCache
读取但也写入它(添加找到的非官方字形名称以供以后快速查找)。
如果这样的写入与其他线程从映射中读取同时发生,确实可能会发生 ConcurrentModificationException
。
我建议将 GlyphList
更改为
- 不再写给
uniNameToUnicodeCache
,或者 - 同步
toUnicode(String)
或更准确地说uniNameToUnicodeCache
读取和写入其中,或 - 使
uniNameToUnicodeCache
成为ConcurrentHashMap
而不是HashMap
。
我希望第三个选项比第二个选项执行得更好。