Java中substring内存泄露问题

Java中substring内存泄露问题_itmyhome的专栏-CSDN博客

在Java中,String是最常用的数据类型,String有一个substring方法用来截取字符串,或许我们没注意到该方法可能会引起内存泄露问题(出现于Java6中)。

方法介绍:

在Java中提供了两个截取子字符串的方法:

substring(int beginIndex)
substring(int beginIndex, int endIndex)12

问题重现:

public class Test {
    private String largeString = new String(new byte[100000]);
​
    String getString() {
        return this.largeString.substring(0, 2);
    }
​
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
​
        for (int i = 0; i < 1000000; i++) {
            Test t = new Test();
            list.add(t.getString());
        }
    }
}12345678910111213141516

运行上面代码,如果使用Java6(Java7以上不会抛异常)运行一下就会报如下异常,说明没有足够的堆内存供我们创建对象

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:133)
    at java.lang.StringCoding.decode(StringCoding.java:173)
    at java.lang.StringCoding.decode(StringCoding.java:185)1234

于是有人会说,我们每个循环都创建一个Test对象,100万条数据存储到ArrayList中,这样必然会造成OOM,其实不然,看下面这段代码,只修改getString()方法

public class Test {
    private String largeString = new String(new byte[100000]);
​
    String getString() {
        //return this.largeString.substring(0, 2);
        return new String("ab");
    }
​
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
​
        for (int i = 0; i < 1000000; i++) {
            Test t = new Test();
            list.add(t.getString());
        }
    }
}1234567891011121314151617

执行上面的方法,并不会导致OOM异常,因为我们持有的时1000000个ab字符串对象,而Test对象(包括其中的largeString)会在java的垃圾回收中释放掉。所以这里不会存在内存溢出。

那么究竟是什么导致的内存泄露呢?

在 JDK 1.6 中 String.substring(int, int)的源码为:

public String substring(int beginIndex, int endIndex) { 
     if (beginIndex < 0) { 
          throw new StringIndexOutOfBoundsException(beginIndex); 
     } 
     if (endIndex > count) { 
          throw new StringIndexOutOfBoundsException(endIndex); 
     } 
     if (beginIndex > endIndex) { 
          throw new StringIndexOutOfBoundsException(endIndex - beginIndex); 
     } 
     return ((beginIndex == 0) && (endIndex == count)) ? this : 
          new String(offset + beginIndex, endIndex - beginIndex, value); 
}12345678910111213

调用的 String 构造函数源码为:

String(int offset, int count, char value[]) { 
  this.value = value; 
  this.offset = offset; 
  this.count = count; 
}12345

我们发现 String.substring()所返回的 String 仍然会保存原始 String,其实substring中生成的字符串与原字符串共享内容数组是一个很棒的设计,这样避免了每次进行substring重新进行字符数组复制。这种设计在很多时候可以很大程度的节省内存,因为这些 String 都复用了原始 String,只是通过 int 类型的 start, end 等值来标识每一个 String。而对于上面的案例,从一个巨大的 String 截取少数 String 为以后所用,这样的设计则造成大量冗余数据。

既然导致大量内存占用的根源是 String.substring()返回结果中包含大量原始 String,那么一个显而易见的减少内存浪费的的途径就是去除这些原始 String。办法有很多种,在此我们采取比较直观的一种,即再次调用 new String构造一个的仅包含截取出的字符串的 String

String newString = new String(largeString.substring(0,2));
1

Java 7 实现

在Java 7 中substring的实现抛弃了之前的内容字符数组共享的机制,对于子字符串(自身除外)采用了数组复制实现单个字符串持有自己的应该拥有的内容。

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
        : new String(value, beginIndex, subLen);
}1234567891011121314

substring方法中调用的构造方法,进行内容字符数组复制。

public String(char value[], int offset, int count) {
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count < 0) {
        throw new StringIndexOutOfBoundsException(count);
    }
    // Note: offset or count might be near -1>>>1.
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}12345678910111213
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Javasubstring是一个常用的字符串处理方法。它用于从一个字符串提取子字符串。substring方法有多个重载形式,可以根据需要提取不同位置的子字符串。 在示例代码\[1\],使用了substring方法来提取字符串s的子字符串。s.substring(2, 4)表示提取s索引位置2到4之间的字符(不包括索引4)。s.substring(2)表示提取s索引位置2到末尾的字符。s.substring(s.length()-3)表示提取s倒数第3个字符到末尾的字符。 在示例代码\[2\],使用了substring方法来提取字符串sb@符号之前的子字符串。sb.substring(0, sb.indexOf("@"))表示提取sb从索引位置0到@符号之前的字符。 总结来说,substring方法可以根据索引位置来提取字符串的子字符串,可以用于字符串的截取和处理。它在Java是一个常用的字符串操作方法。 #### 引用[.reference_title] - *1* [Java截取字符串(substring)](https://blog.csdn.net/weixin_40052298/article/details/121868965)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [java字符串截取的几种方式](https://blog.csdn.net/qq_43173523/article/details/103029720)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [java基础之substring()方法](https://blog.csdn.net/qq_41720578/article/details/124170035)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值