1.intern()定义
public native String intern(); 这是一个本地方法。在调用这个方法时,JAVA虚拟机首先检查字符串池中是否已经存在与该对象值相等对象存在,如果有则返回字符串池中对象的引用;如果没有,则先在字符串池中创建一个相同值的String对象,然后再将它的引用返回。
换一种解释:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法:当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。换即当调用intern方法时,如果池已经包含一个等于此String对象的字符串(该对象由equals(Object)方法确定),则返回池中的字符串,否则,将此String对象添加到池中,并且返回此String。
2.intern()应用
我们说String str1 = "abc" 和 String str2 = new String("def")都分别在pool中创建了"abc"和"def"对象,String.intern()还能有什么用(pool已经存在与该对象值相等对象,还判断什么)。但我们错了,定义String对象就上面两种方法,但获得String对象的方式却很多:rs.getString(1)、new String("abc")+def、10+"abc"等等这些获得String的方法都没有在loop中创建String对象(而只是在heap中创建了对象)。这样intern()就有了用武之地。
举个例子:
sql脚本为
- DROP TABLE IF EXISTS temp;
- CREATE TABLE temp (
- id INT(32) AUTO_INCREMENT NOT NULL,
- content VARCHAR(200) NOT NULL,
- PRIMARY KEY (id)
- );
- INSERT INTO temp (content) VALUES
- ('forrest test the performance of intern() method of String!! --> 1'),
- ('forrest test the performance of intern() method of String!! --> 1'),
- ... #插入10000个相同的数据
- ('forrest test the performance of intern() method of String!! --> 1');
测试代码为
- public static void main(String[] args) {
- Connection conn = null;
- PreparedStatement pstmt = null;
- ResultSet rs = null;
- List<String> cList = new ArrayList<String>();
- String sql = "SELECT content FROM temp";
- try {
- Class.forName("com.mysql.jdbc.Driver");
- conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testcasetrack", "root", "");
- pstmt = conn.prepareStatement(sql);
- rs = pstmt.executeQuery();
- while (rs.next()) {
- String contentBean = new String();
- contentBean = rs.getString(1);
- cList.add(contentBean);
- }
- } catch (SQLException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } finally {
- try {
- rs.close();
- pstmt.close();
- conn.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- System.out.println(Runtime.getRuntime().totalMemory());
- System.out.println(Runtime.getRuntime().freeMemory());
- }
- }
- /*
- * 测试结果
- * Runtime.getRuntime().totalMemory() :5984256
- * Runtime.getRuntime().freeMemory() :2441160
- *
- * */
- public static void main(String[] args) {
- Connection conn = null;
- PreparedStatement pstmt = null;
- ResultSet rs = null;
- List<String> cList = new ArrayList<String>();
- String sql = "SELECT content FROM temp";
- try {
- Class.forName("com.mysql.jdbc.Driver");
- conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testcasetrack", "root", "");
- pstmt = conn.prepareStatement(sql);
- rs = pstmt.executeQuery();
- while (rs.next()) {
- String contentBean = new String();
- contentBean = rs.getString(1).intern();
- cList.add(contentBean);
- }
- } catch (SQLException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } finally {
- try {
- rs.close();
- pstmt.close();
- conn.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- System.out.println(Runtime.getRuntime().totalMemory());
- System.out.println(Runtime.getRuntime().freeMemory());
- }
- }
- /*
- * 测试结果
- * Runtime.getRuntime().totalMemory() :3411968
- * Runtime.getRuntime().freeMemory() :1585368
- *
- * */
- static String get() {
- return "forrest test the performance of intern() method of String!! --> 1";
- }
- public static void main(String[] args) {
- Connection conn = null;
- PreparedStatement pstmt = null;
- ResultSet rs = null;
- List<String> cList = new ArrayList<String>();
- String sql = "SELECT content FROM temp";
- try {
- Class.forName("com.mysql.jdbc.Driver");
- conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testcasetrack", "root", "");
- pstmt = conn.prepareStatement(sql);
- rs = pstmt.executeQuery();
- while (rs.next()) {
- String contentBean = new String();
- contentBean = get();
- cList.add(contentBean);
- }
- } catch (SQLException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } finally {
- try {
- rs.close();
- pstmt.close();
- conn.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- System.out.println(Runtime.getRuntime().totalMemory());
- System.out.println(Runtime.getRuntime().freeMemory());
- }
- }
- /*
- * 测试结果
- * Runtime.getRuntime().totalMemory() :3428352
- * Runtime.getRuntime().freeMemory() :1609760
- *
- * */
从以上代码的测试结果可以看出:如果db中有10000条相同数据的时候,使用intern()的话,系统会占用更少内存(使用intern()占用的内存/没有使用intern()占用的内存 = 57%);而如果直接利用get()方法将pool中的String对象赋值,系统占用的内存和使用intern()从db中取数据时占用的内存几乎是相同的。
当然,如果我们sql脚本是创建的10000个不同的数据,则上面三段代码测试的系统使用内存情况几乎是相同的(也做过测试。1000个测试数据是用一个循环产生的or利用excel的数字拖拽自增长的方式来产生),这也就印证了我们在前两篇博文中得出的结论。
最后我们再来说说String对象在JAVA虚拟机(JVM)中的存储,以及字符串池与堆(heap)和栈(stack)的关系。
栈(stack):主要保存基本类型(或者叫内置类型)(char、byte、short、int、long、float、double、boolean)和对象的引用,数据可以共享,速度仅次于寄存器(register),快于堆。
堆(heap):用于存储对象。
我们查看String类的源码就会发现,它有一个value属性,保存着String对象的值,类型是char[],这也正说明了字符串就是字符的序列。 当执行String a="abc";时,JAVA虚拟机会在栈中创建三个char型的值'a'、'b'和'c',然后在堆中创建一个String对象,它的值(value)是刚才在栈中创建的三个char型值组成的数组{'a','b','c'},最后这个新创建的String对象会被添加到字符串池中。如果我们接着执行 String b=new String("abc");代码,由于"abc"已经被创建并保存于字符串池中,因此JAVA虚拟机只会在堆中新创建一个String对象,但是它的值(value)是共享前一行代码执行时在栈中创建的三个char型值值'a'、'b'和'c'。