美文网首页
BufferedReader,你用对了吗?

BufferedReader,你用对了吗?

作者: 二胡 | 来源:发表于2014-12-03 18:53 被阅读5813次

案例分析

这篇文章,我们来看看使用 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 文件,并在日志中打印出文件的内容。
(我们这个 tmp.txt 文件很小,只有一行 "hello world")

初看,好像没什么问题,FileReader 和 BufferedReader 都在 finally 里关闭了。
我们把程序跑起来,看看内存占用怎么样?
这里写了一个脚本,每 120 秒取一下进程的内存占用:

                 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

结果真是让我们大吃一惊啊,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 中数据相符。

截图二

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 数组,Java 中每 byte 类型为 8 位,即每一个 byte 占用一个字节(Byte),8192 个 byte 占用内存为 8208 Byte(约8K),符合预期。

我们终于知道,这些增长的内存是哪里来的了。

建议

  • 不要在 Java 层,做频繁地 文件 操作,可以在 native 层,用 C 语言来处理;
  • 推而广之,我们的分析也应证了 “不要在 Java 中频繁地分配对象” 这句话。

备注

  1. 测试数据来自为 Smartisan T1,另在 Note3 上,heap 也会持续增加;
  • 文中截图,只选取了 BufferedReader 和 ByteBuffer 相关的内存分配,其它因为读取文件而分配的对象,读者可以自行在工具中查看;
  • 文中 heap 持续增加的情况,并不是每台手机上都出现,在Nexus 7 平板上,heap 会在 6 分钟左右趋于平稳,数据如下:
 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   

相关文章

  • BufferedReader,你用对了吗?

    案例分析 这篇文章,我们来看看使用 BufferedReader 在 Android 设备上读取文件的一个问题~测...

  • 你用对牙膏了吗?

    牙膏作为日常生活常用的清洁用品,有着很悠久的历史。随着科学技术的不断发展,工艺装备的不断改进和完善,各种类型的牙膏...

  • 你用对思维了吗?

    我们知道大学学的是思维、方法,而不是知识。或许毕业后很对知识都忘光了,可是思考问题的维度以及采取解决问题的方法依旧...

  • 你用对synchronized了吗

    最近遇到一个crash问题,是关于线程同步锁的,检查代码的时候发现方法已经使用synchronized同步了,为什...

  • 你用对逻辑了吗?

    人类究竟是怎么了,竟会把胡言乱语也当成妙笔生花?——塔勒布 先说一个看起来不太相关,实际非常值得深思的话题。 19...

  • BufferedReader

    java.io.*中有一个类叫BufferedReader; 从字符输入流中读取文本,缓冲各个字符,从而实现字符、...

  • BufferedReader

    BufferedReader是缓存流的一个类。 缓存流是计算机领域中输入输出流的一种常见形式。包括Buffered...

  • BufferedReader 读取 txt 文件中文乱码

    读取 txt 文件乱码: BufferedReader read = new BufferedReader(new...

  • 文件读取编码问题

    读取txt文件乱码:BufferedReader read = new BufferedReader(new Fi...

  • JAVA IO之缓冲

    1. BufferedWriter和BufferedReader 1.1 BufferedReader类 从JDK...

网友评论

      本文标题:BufferedReader,你用对了吗?

      本文链接:https://www.haomeiwen.com/subject/iybgtttx.html