客户端这里自己模拟json数据时,一时没忍住搞得json字符串太长了(idea上大概有600多行吧),这个问题就蹦出来了。老方法直接google、百度然后看到一堆有关String 字符串常量池字符最大限制的文章,这些前辈都是从jvm的运行时常量池的常量表占的内存数,从原理上讲解了常量池所能容纳的最大字符数。对于不了解jvm的同学来说,啥无符号数、有符号数、u1、u2之类的看着看着就懵逼了。俗话说学而不思则罔,这里我就从常量池内存区域的角度来简单分析下自己的观点。

一、情景再现

JAVA 长字符串 常量 java报错常量字符串过长_JAVA 长字符串 常量

/**
 * Created by sunnyDay on 2019/11/19 17:31
 * <p>
 * double check
 */
public class Jsons {
    private volatile static Jsons instance;

    private Jsons() {
    }
    public static Jsons getInstance() {
        if (instance == null) {
            synchronized ("lock") {
                if (instance == null) {
                    instance = new Jsons();
                }
            }
        }
        return instance;
    }
    
    public String getConstellationTestJson(){
        return "很长的json,粘贴的下图json文件的字符串"
    }
}

public class Test {
    public static void main(String[] args) {
        // 编译时直接报错
        System.out.println(Jsons.getInstance().getConstellationTestJson());
    }
}

JAVA 长字符串 常量 java报错常量字符串过长_java对常量池utf8过长_02


如上,就是一个工具类,提供json字符串,为了演示我单独抽出来放idea中跑了下。这样bug就mock出来了。

二、我的理解及其解决方案

看到过一篇文章,感觉颇有收获。理论性的东西前辈们都总结的差不多了,这里就添加下个人理解。及其解决方案。

1、首先理解下jvm方法区的运行时池

常量池好处:
常量池是为了避免频繁的创建销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
注意点:
1、java中基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean。这里就就只说字符传常量池相关。
2、java中以双引号引住的内容就是字符创常量,编译时就会吧双引号引的内容放入字符创常量池。

2、字符创常量池的实战参考(一定要看)
String stra = "abcd"; // 对象存储在常量池中
        String strb = new String("abcd");// new 就是在堆中分配了新的地址
        System.out.println(stra==strb);//false
       
        String str1 = "str";
        String str2 = "ing";
        String str3 = "str" + "ing";
        String str4 = str1 + str2; //对于字符串变量的“+”连接表达式,它所产生的新对象都不会被加入字符串池中,其属于在运行时创建的字符串,具有独立的内存地址,所以不引用自同一String对象。
        System.out.println("string" == "str" + "ing");// true 只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入常量池中
        System.out.println(str3 == str4);//false
        String str5 = "string";
        System.out.println(str3 == str5);//true

注意点:
1、+号连接的对象引用
2、+号链接的字符串常量

3、解决方案

不让放那么多,这里放不了我们不放这里或者这里放少点。不ojbk了。太聪明了嘿嘿嘿!!!

(1)错误解决方式

public String getConstellationTestJson(){
     return "很长的json一半"+"很长的json一半";
}

这里+号拼接的结果还是会放进常量池的。

(2)正确解决方式

```java
 public String getConstellationTestJson(){
       String s1 = "很长的json一半"; //内容放常量池
       String s2 = "很长的json一半"; // 内容放常量池
      return s1+s2;  // 结果具有了新的内存地址(堆中)
}

解决,s1+s2的结果会放入新的内存地址中,参考上文实战。

(3)正确解决 使用new String 拆分拼接

(4)使用StringBuild#StringBuffer 拆分拼接

(5)通过文件读取(这里使用安卓的assets为例子)

private String getAssetsData() {
        String result = "";
        try {
            InputStream mAssets = getAssets().open("dream.json");
            int lenght = mAssets.available();
            byte[] buffer = new byte[lenght];
            mAssets.read(buffer);
            mAssets.close();
            result = new String(buffer, StandardCharsets.UTF_8); // 关键之处
            return result;
        } catch (IOException e) {
            e.printStackTrace();
            return result;
        }
    }
4、感悟

只要堆的内存不满,或者拆分的子字符串满足常量池大小,就基本没啥问题了。