實例:android java 利用map來減少重覆物件記憶體的浪費
最近在研究如何將pdfbox轉到android中使用, 其中有一個類別PDFont
它是一個記錄pdf格式檔字型的物件, 它的成員如下:
public abstract class PDFont implements COSObjectable, PDFontLike
{
protected static final Matrix DEFAULT_FONT_MATRIX = new Matrix(0.001f, 0, 0, 0.001f, 0, 0);
// TODO: 2016/7/17 hwjane
private static HashMap
protected final COSDictionary dict;
private final CMap toUnicodeCMap;
private final FontMetrics afmStandard14; // AFM for standard 14 fonts
private PDFontDescriptor fontDescriptor;
private List
private float avgFontWidth;
private float fontWidthOfSpace = -1f;
其中toUnicodeCMap 是一個CMap物件, 其結構如下:
public class CMap
{
private int wmode = 0;
private String cmapName = null;
private String cmapVersion = null;
private int cmapType = -1;
private String registry = null;
private String ordering = null;
private int supplement = 0;
private int minCodeLength = 4;
private int maxCodeLength;
// code lengths
private final List
// Unicode mappings
private final Map
CMap中有一個物件charToUnicode , 是一個HashMap的型態, 用來存放將pdf字型轉換成
unicode的轉換表, 所以toUnicodeCMap(或charToUnicode)是一個佔用ram很大的物件.
而偏偏某些pdf檔toUnicodeCMap會有很多個, 但並不代表charToUnicode 也有很多個
, 因為可能200或300個或1000個toUnicodeCMap只指向三或五個charToUnicode,
但依照原來pdfbox的原始碼
toUnicodeCMap = readCMap(toUnicode);
會由 readCMap傳回CMap的物件型態給toUnicodeCMapreadCMap函數:protected final CMap readCMap(COSBase base) throws IOException
{
if (base instanceof COSName)
{
// predefined CMap
String name = ((COSName)base).getName();
return CMapManager.getPredefinedCMap(name);
}
else if (base instanceof COSStream)
{
// embedded CMap
InputStream input = null;
try
{
input = ((COSStream)base).createInputStream();
//傳回CMap的物件型態(每次都會建立一個CMap物件變數, 會佔用200個以上的空間)
return CMapManager.parseCMap(input);
}
finally
{
IOUtils.closeQuietly(input);
}
}
else
{
throw new IOException("Expected Name or Stream");
}
}
以上程式碼, 因為toUnicodeCMap數量太大, 佔空間太多, 沒多久就出現
out of memory的錯誤, 但因為charToUnicode都是一直重覆的, 不必要每
次都建立一個新的物件,所以想法子, 將charToUnicode放到map內, 只需
要佔三到五個空間, 要使用時, 只要指向map的那個要用到的charToUnicode
即可, 如此out of memory的情況就不再出現了.
所以修改以下兩行
input = ((COSStream)base).createInputStream();
return CMapManager.parseCMap(input);
變成
//如果cacheCMap中有以base為key存放的cmap物件,則直接取出來用
CMap cmap = cacheCMap.get(base);
if (cmap==null) {
//如果沒有則建立新的,建立完後再放到cacheCMap中, 供下次使用
input = ((COSStream) base).createInputStream();
cmap = CMapManager.parseCMap(input);
//base 是readCMap(toUnicode)傳入的參數,當作key放入map中
cacheCMap.put(base,cmap);
}
return cmap;