BufferedReader,你用對了嗎?

案例分析

這篇文章,我們來看看使用 BufferedReader 在 Android 設備上讀取文件的一個問題~
測試代碼是這個樣子的:

  public class MyActivity extends Activity {

    private static final int MSG_CODE_LOOP = 1024;
    private static final int MSG_CODE_QUIT = 0;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MSG_CODE_LOOP) {
                readFile();
                sendEmptyMessageDelayed(MSG_CODE_LOOP, 5000);
            } else if (msg.what == MSG_CODE_QUIT) {
                removeMessages(MSG_CODE_LOOP);
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        handler.sendEmptyMessage(MSG_CODE_LOOP);
    }

    private void readFile() {
        File file = Environment.getExternalStoragePublicDirectory("tmp.txt");
        FileReader fileReader = null;
        BufferedReader bufferedReader = null;
        try {
            fileReader = new FileReader(file);
            bufferedReader = new BufferedReader(fileReader);
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                Log.d("io-demo", line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

代碼很簡單,就是每 5 秒讀取一次 SDCard 根目錄中的 tmp.txt 文件,并在日志中打印出文件的內(nèi)容。
(我們這個 tmp.txt 文件很小,只有一行 "hello world")

初看,好像沒什么問題,F(xiàn)ileReader 和 BufferedReader 都在 finally 里關(guān)閉了。
我們把程序跑起來,看看內(nèi)存占用怎么樣?
這里寫了一個腳本,每 120 秒取一下進程的內(nèi)存占用:

                 Pss      Privite
                 Total    Dirty
------------------------------------
 Dalvik Heap     1249     1036
       TOTAL     6451     4880  
 Dalvik Heap     2042     1832 // 2 分鐘后,heap 增加 807K
       TOTAL     7243     5668 
 Dalvik Heap     2818     2608 // 4 分鐘后,heap 增加 1569K
       TOTAL     8043     6468
 Dalvik Heap     3598     3392 // 6 分鐘后,heap 增加 2349K
       TOTAL     8847     7276    
 Dalvik Heap     4374     4168 // ...
       TOTAL     9651     8080   
 Dalvik Heap     5150     4944  
       TOTAL    10447     8876  
 Dalvik Heap     5926     5720     
       TOTAL    11247     9676    
 Dalvik Heap     6675     6496     
       TOTAL    12003    10476  
 Dalvik Heap     7440     7272    
       TOTAL    12789    11272 
 Dalvik Heap     8217     8048
       TOTAL    13586    12068
 Dalvik Heap     8826     8660
       TOTAL    14222    12728

結(jié)果真是讓我們大吃一驚啊,Heap 平均每分鐘漲 400 K,怎么會這樣?
我們打開 Android Device Monitor,查看 Allocation Tracker:
截圖一

screen_shot_a.png

打開 BufferedReader.java 這個文件:

public BufferedReader(Reader in) {
    this(in, 8192); // line 9
}
public BufferedReader(Reader in, int size) {
    super(in);
    if (size <= 0) {
        throw new IllegalArgumentException("size <= 0");
    }
    this.in = in;
    buf = new char[size]; // line 112
}

這段代碼中,每 new 一個BufferedReader, 會分配 8192 個 char (112 行),因為 java 中 char 占 2 個 Byte(16位),8192 個 char 占用 16384 個 Byte,約等于 16400 Byte (約16K),與Device Monitor 中數(shù)據(jù)相符。

截圖二

screen_shot_b.png

按照截圖一中的分析方法,我們來看看 InputStreamReader.java

// InputStreamReader.java
private final ByteBuffer bytes = ByteBuffer.allocate(8192); // line 47
// ByteBuffer.java
public static ByteBuffer allocate(int capacity) {
    if (capacity < 0) {
        throw new IllegalArgumentException("capacity < 0: " + capacity);
    }
    return new ByteArrayBuffer(new byte[capacity]);// line 56
}

由代碼可知,ByteBuffer.java 分配了一個 size 為 8192 的 byte 數(shù)組,Java 中每 byte 類型為 8 位,即每一個 byte 占用一個字節(jié)(Byte),8192 個 byte 占用內(nèi)存為 8208 Byte(約8K),符合預期。

我們終于知道,這些增長的內(nèi)存是哪里來的了。

建議

  • 不要在 Java 層,做頻繁地 文件 操作,可以在 native 層,用 C 語言來處理;
  • 推而廣之,我們的分析也應證了 “不要在 Java 中頻繁地分配對象” 這句話。

備注

  1. 測試數(shù)據(jù)來自為 Smartisan T1,另在 Note3 上,heap 也會持續(xù)增加;
  • 文中截圖,只選取了 BufferedReader 和 ByteBuffer 相關(guān)的內(nèi)存分配,其它因為讀取文件而分配的對象,讀者可以自行在工具中查看;
  • 文中 heap 持續(xù)增加的情況,并不是每臺手機上都出現(xiàn),在Nexus 7 平板上,heap 會在 6 分鐘左右趨于平穩(wěn),數(shù)據(jù)如下:
 Dalvik Heap     1075      716    
       TOTAL     6428     4960      
 Dalvik Heap     1684     1060  
       TOTAL     7370     5416     
 Dalvik Heap     1804     1180 
       TOTAL     7538     5584    
 Dalvik Heap     1820     1196 // 從 6 分鐘開始, heap 保持在 1820K
       TOTAL     7566     5612   
 Dalvik Heap     1820     1196
       TOTAL     7574     5620   
 Dalvik Heap     1820     1196
       TOTAL     7578     5624   
 Dalvik Heap     1820     1196
       TOTAL     7578     5624   
 Dalvik Heap     1820     1196
       TOTAL     7609     5652   
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,823評論 18 399
  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,872評論 11 349
  • tags:io categories:總結(jié) date: 2017-03-28 22:49:50 不僅僅在JAVA領(lǐng)...
    行徑行閱讀 2,304評論 0 3
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,275評論 0 62
  • 朋友和家人對我的評價大多是溫柔一類的詞,大概是因為我從來不斥聲厲責我的孩子,對朋友至親至善,對待家人更是如...
    阿盟果果閱讀 597評論 0 0

友情鏈接更多精彩內(nèi)容