Java中的String

  Java.Lang.String是Java语言自带的字符串实现,它并不是java的基本类型,但却和几乎每个java程序都密切相关的一个基础java类。

  string类内部实际实现存储的字符数组在定义时用关键字final修饰,意味着这个属性是一个常量,在初始化之后就不能再被修改。这也同时表明所有对String对象的修改操作(包括append,substring,concat,replace,trim等),在具体实现中返回的都是一个全新的string对象副本。总结来说,Java中的String具有不变性、不可继承性

讨论String对象在内存中的存储

  这里会涉及到三个概念:虚拟机栈、Java堆和运行时常量池,在我的第一篇文章中都有描述。根据JDK源码中的规范,String类的使用方式有如下几种:

    String str1 = new String("abc");

    String str2 = "abc";

    String str3 = "ab" + new String("c");

  在具体应用中,这里的几种String对象的创建方式是基本没有区别的。但实质上这里有一些微小的差异:第一个字符串的创建方式str1指向的对象被分配到了Java堆中,且创建的时机是在程序运行时。str2指向的字符串对象在编译期就已经确定,存放在运行时常量池中。str3的创建是一个比较复杂的过程,java虚拟机会重新组织对应的字节码,具体的过程在下文会分析。

  在学习的过程中,我也参考了很多前辈的文章,其中包括这篇《Java内存分配和String类型的深度解析》,文中作者在String的定义方法中提出了针对性的几点疑问,结合我自身的思考,我来尝试解答一下。

  • 堆中new出来的实例和常量池中的是什么关系?

  两者都是一个String类型的实例化对象,即使通过方法equals比较返回结果为true,两者在本质上都不会是同一个对象。

  • 常量池中的字符串常量与堆中的String对象有什么区别?

  一个最主要的区别就是内存中的位置不同,当然大部分的常量池中的字符串常量是在编译器确定的,除非很明确的调用string对象的intern方法返回(或创建)一个运行时常量池中的string对象,所有通过new操作符创建的string对象都会被分配到Java堆中。

  • 为什么直接定义的字符串同样可以调用String对象的各种方法呢?

  虽然说字符串常量”abc”是在编译器被确定的字符串常量,被存放在运行时常量池,但是这个常量字符串还是一个String类型的对象(这一点确实有别于c/c++语言中的常量的概念,也看出在java的哲学中万物都是对象),如果是一个标准的java对象,它可以调用String的方法。

字节码分析

  在分析问题的过程中,通过查看上文中提到的三行java代码对应的字节码(方法javap -c {具体要查看的*.class文件}),来确认我的自己的猜想,在这个过程中也可以看出java编译器对源代码的处置,具体的字节码分析如下:

       //0 ~ 9 对应的是第一行的java语句:String str1 = "abc";
0: new #21 // class java/lang/String
3: dup
/*
**装载一个常量字符串 ,符号#23代表的字符串对象就是 “abc”,
**常量字符串在程序运行之前就已经被创建
*/
4: ldc #23 // String abc
/*str1不指向常量字符串“abc”,
**而是将这个常量字符串作为构造函数的实参传入
**在java堆中重新创建了一个全新的对象
*/
6: invokespecial #25 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1 //从这里开始到12 都是第二行的Java代码: String str2 = "abc";
/*
** 直接调用运行时常量池中的对象
*/
10: ldc #23 // String abc
12: astore_2 //从此处开始到最后对应第三行的Java代码:String str3 = "ab" + new String("c");
/*
**对于这种new 对象与 常量字符串相结合的方式,
**JAVA编译器在处理过程中创建了一个StringBuilder对象用于处理异构字符串的拼接工作
** "ab"对应另一个常量字符串 “c”则运行时动态创建的String对象
**/
13: new #28 // class java/lang/StringBuffer
16: dup
17: ldc #30 // String ab
19: invokespecial #32 // Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
22: new #21 // class java/lang/String
25: dup
26: ldc #33 // String c
28: invokespecial #25 // Method java/lang/String."<init>":(Ljava/lang/String;)V
31: invokevirtual #35 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
34: invokevirtual #39 // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
37: astore_3
38: return

  由此可以看出,在处理不同方式构建的字符串拼接时,java编译器为我们付出了额外的一些代价,在我们的代码中,尽可能少出现类似第三行那样的代码。如果是确定的字符串常量,也尽可能写成”ab” + “c”这样的形式,在编译器优化时会在常量池中找到现成的对象对象,会在性能上有很大的提升。

同时我们能够看到编译器默认用的字符串拼接器是StringBuffuer类,通常情况下我们如果需要对几个动态的string对象做拼接用的都是StringBuilder类。StringBuffer与StringBuilder最本质的区别是:前者是线程安全的。那么如果只是明确的单线程环境下,在效率上编译器自补足的代码又会有更多性能上的欠缺。

Java杂谈4——Java中的字符串存储的更多相关文章

  1. Java去除ArrayList集合中重复字符串的案例

    ArrayList去除集合中的字符串重复值 分析: A:创建集合对象 B:添加多个字符串元素 C:创建新集合 D:遍历旧集合,获取得到每一个元素 E:拿着个元素到新集合去找,看有没有 有:不进去 没有 ...

  2. 【Java】运行时Java对象在内存中是如何存储的?

    翻译自这一篇文章 我们知道函数在内存中实现为一个活动记录的栈.我们也知道Java方法在JVM栈区中实现为一个帧栈而Java对象是在堆区进行分配的. Java对象在堆内存中是怎样的呢?一旦对象保存在内存 ...

  3. Java杂谈6——Java安全模型

    Java语言安全模型是其有别于传统的编程语言的一个很重要的特点,采用一种沙箱模型隔离了Java的运行环境与具体的操作系统,使得Java在网络环境下能够更为安全的运行.理解Java的安全模型,能够帮助我 ...

  4. C语言中字符串存储方法

    众所周知,C语言中没有数据类型能够存储字符串, char数据类型仅仅能够存储一个字符的数据,那么在C语言中关于存储字符串这一难题我们改何去何从呢? 下面将详述相关的字符串存储方法; 1,使用字符数组存 ...

  5. List中存放字符串进行排序

    package com.bjpowernode.t03sort; import java.util.ArrayList;import java.util.Collections; /* * List中 ...

  6. 从源代码的角度聊聊java中StringBuffer、StringBuilder、String中的字符串拼接

    长久以来,我们被教导字符串的连接最好用StringBuffer.StringBuilder,但是我们却不知道这两者之间的区别.跟字符串相关的一些方法中总是有CharSequence.StringBuf ...

  7. Java基础知识强化之IO流笔记52:IO流练习之 把一个文件中的字符串排序后再写入另一个文件案例

    1. 把一个文件中的字符串排序后再写入另一个文件 已知s.txt文件中有这样的一个字符串:"hcexfgijkamdnoqrzstuvwybpl" 请编写程序读取数据内容,把数据排 ...

  8. Java基础知识强化之IO流笔记45:IO流练习之 把集合中的数据存储到文本文件案例

    1. 把集合中的数据存储到文本文件案例:    需求:把ArrayList集合中的字符串数据存储到文本文件 ? (1)分析:通过题目的意思我们可以知道如下的一些内容,ArrayList集合里存储的是字 ...

  9. 理解Java中的字符串类型

    1.Java内置对字符串的支持: 所谓的内置支持,即不用像C语言通过char指针实现字符串类型,并且Java的字符串编码是符合Unicode编码标准,这也意味着不用像C++那样通过使用string和w ...

随机推荐

  1. CSS垂直居中小结

    1.设置子元素: { ... position :absolute; margin:auto; top:; right:; bottom:; left:; } 2.设置子元素:(height必须是固定 ...

  2. Java类的声明和访问介绍

    1.类的声明 类本身的声明:对类的声明来说,主要包括类的访问权限声明和非访问修饰符的使用.对于一个普通的Java类(POJO)来说,主要的访问权限修饰符只有两个public和默认权限,内部类可以有pr ...

  3. Java并发(11)- 有关线程池的10个问题

    引言 在日常开发中,线程池是使用非常频繁的一种技术,无论是服务端多线程接收用户请求,还是客户端多线程处理数据,都会用到线程池技术,那么全面的了解线程池的使用.背后的实现原理以及合理的优化线程池的大小等 ...

  4. 转:Mybatis系列之集合映射

    转:Mybatis系列之集合映射 上篇文章我们讲了关联映射,实现了销售与登录用户之间的关联.本文我们接着来讲一讲集合映射,实现销售与客户的多对多关系. 实现销售与客户多对多关系 本文中仍延用<M ...

  5. HDU5748---(记录每个元素的 最长上升子序列 nlogn)

    分析: 给一个序列,求出每个位置结尾的最长上升子序列 O(n^2) 超时 #include "cstdio" #include "algorithm" #def ...

  6. 【Atcoder】ARC083 D - Restoring Road Network

    [算法]图论,最短路? [题意]原图为无向连通图,现给定原图的最短路矩阵,求原图最小边权和,n<=300. [题解]要求最小边权和下,原图的所有边一定是所连两端点的最短路. 那么现在将所有最短路 ...

  7. python脚本运行的几种方式

    1.脚本式编程 将如下代码拷贝至 hello.py文件中: print ("Hello, Python!"); 通过以下命令执行该脚本: $ python ./hello.py h ...

  8. python3 线程_threading模块

    '''并发:同一个时间段内运行多个程序的能力 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成 程序:食谱数据集:鸡蛋.牛奶.糖等进程控制块:记下食谱做到 ...

  9. 【bzoj4272】筐子放球

    看题解会的系列…… 详细解释先坑着,以后补…… #include<bits/stdc++.h> #define N 200005 using namespace std; ,tot=,cn ...

  10. Mysql 查看连接数,状态的相关命令

    命令: show processlist; 如果是root帐号,你能看到所有用户的当前连接.如果是其它普通帐号,只能看到自己占用的连接. show processlist;只列出前100条,如果想全列 ...