2016年7月26日 星期二

實例: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 HashMapcacheCMap = new HashMap();

    protected final COSDictionary dict;
    private final CMap toUnicodeCMap;
    private final FontMetrics afmStandard14; // AFM for standard 14 fonts

    private PDFontDescriptor fontDescriptor;
    private List widths;
    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 codespaceRanges = new ArrayList();

    // Unicode mappings
    private final Map charToUnicode = new HashMap();



CMap中有一個物件charToUnicode , 是一個HashMap的型態, 用來存放將pdf字型轉換成
unicode的轉換表, 所以toUnicodeCMap(或charToUnicode)是一個佔用ram很大的物件.

而偏偏某些pdf檔toUnicodeCMap會有很多個, 但並不代表charToUnicode 也有很多個
, 因為可能200或300個或1000個toUnicodeCMap只指向三或五個charToUnicode,


但依照原來pdfbox的原始碼

toUnicodeCMap = readCMap(toUnicode);
會由 readCMap傳回CMap的物件型態給toUnicodeCMap

readCMap函數:
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;

  © Blogger templates Psi by Ourblogtemplates.com 2008

Back to TOP