每天都在用String,你真的了解吗?
1.String概述
java.lang.String
类代表字符串。Java程序中所有的字符串文字(例如"abc")都可以被看作是实现此类的实例String 中包括用于检查各个字符串的方法,比如用于比较字符串,搜索字符串,提取子字符串以及创建具有翻译为大写或小写的所有字符的字符串的副本。
2.String源码分析
2.1.String成员变量
// String的属性值,String的内容本质上是使用不可变的char类型的数组来存储的。
private final char value[];
/*String类型的hash值,hash是String实例化对象的hashcode的一个缓存值,这是因为String对象经常被用来进行比较,如果每次比较都重新计算hashcode值的话,是比较麻烦的,保存一个缓存值能够进行优化 */
private int hash; // Default to 0
//serialVersionUID为序列化ID
private static final long serialVersionUID = -6849794470754667710L;
//serialPersistentFields属性用于指定哪些字段需要被默认序列化
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
serialPersistentFields具体用法为:
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("name", String.class),
new ObjectStreamField("age", Integer.Type)
}
transient用于指定哪些字段不会被默认序列化,两者同时使用时,transient会被忽略。
在 Java 9 及之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder
来标识使用了哪种字符集编码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
2.2.String构造方法
1、空参构造
/**
* final声明的 value数组不能修改它的引用,所以在构造函数中一定要初始化value属性
*/
public String() {
this.value = "".value;
}
2、用一个String来构造
// 初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
3、使用char数组构造
// 分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
// 分配一个新的 String,它包含取自字符数组参数一个子数组的字符。
public String(char value[], int offset, int count)
4、使用int数组构造
// 分配一个新的 String,它包含 Unicode 代码点数组参数一个子数组的字符。
public String(int[] codePoints, int offset, int count)
5、使用byte数组构造
// 通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
public String(byte bytes[])
// 通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
public String(byte[] bytes)
// 通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String。
public String(byte[] bytes, Charset charset)
// 通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。
public String(byte[] bytes, int offset, int length)
// 通过使用指定的 charset 解码指定的 byte 子数组,构造一个新的 String。
public String(byte[] bytes, int offset, int length, Charset charset)
// 通过使用指定的字符集解码指定的 byte 子数组,构造一个新的 String。
public String(byte[] bytes, int offset, int length, String charsetName)
//通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String。
public String(byte[] bytes, String charsetName)
6、使用StringBuffer或者StringBuilder构造
//分配一个新的字符串,它包含字符串缓冲区参数中当前包含的字符序列。
public String(StringBuffer buffer)
// 分配一个新的字符串,它包含字符串生成器参数中当前包含的字符序列。
public String(StringBuilder builder)
3.字符串常量池
作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么?
3.1常量池的实现思想
- 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能
- JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
- 为字符串开辟一个字符串常量池,类似于缓存区
- 创建字符串常量时,首先查看字符串常量池是否存在该字符串
- 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
- 实现的基础
- 实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享
- 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收
3.2常量池的内存位置
- 堆
- 存储的是对象,每个对象都包含一个与之对应的class
- JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
- 对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定
- 栈
- 每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象)
- 每个栈中的数据(原始类型和对象引用)都是私有的
- 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)
- 数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会自动消失
- 方法区
- 静态区,跟堆一样,被所有的线程共享
- 方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量
字符串常量池则存在于方法区
3.3案例分析
String str1 = "abc";
String str2 = "abc";
String str3 = "abc";
String str4 = new String("abc");
String str5 = new String("abc");
String str6 = new String("abc");
变量str1到str6的内存分布如图所示;str1 = "abc"会先去常量池中看有没有abc,如果有则引用这个字符串,没有则创建一个;str2和str3都是直接引用常量池中的abc;
String str4 = new String("abc") 这段代码会做两步操作,第一步在常量池中查找是否有"abc"对象,有则返回对应的引用实例,没有则创建对应的实例对象;在堆中new一个String("abc")对象,将对象地址赋值给Str4,创建一个引用。
4.String内存分析
我们先来看一段代码
public class TestString {
public static void main(String[] args) {
String str1 = "wugongzi";
String str2 = new String("wugongzi");
String str3 = str2; //引用传递,str3直接指向st2的堆内存地址
String str4 = "wugongzi";
/**
* ==:
* 基本数据类型:比较的是基本数据类型的值是否相同
* 引用数据类型:比较的是引用数据类型的地址值是否相同
* 所以在这里的话:String类对象==比较,比较的是地址,而不是内容
*/
System.out.println(str1==str2);//false
System.out.println(str1==str3);//false
System.out.println(str3==str2);//true
System.out.println(str1==str4);//true
}
}
下面我们来分析一下这段代码的内存分布
第一步:String str1 = "wugongzi" ,首先会去常量池中看有没有wugongzi,发现没有,则在常量池中创建了一个wugongzi,然后将wugongzi的内存地址赋值给str1;
第二步:String str2 = new String("wugongzi"),这段代码因为new了一个String对象,它首先常量池中查找是否有wugongzi,发现已经有了,则返回对应的引用实例;然后再去堆中new一个String("wugongzi")对象,将对象地址赋值给Str2,创建一个引用。
第三步:String str3 = str2,// 引用传递,str3直接指向st2的堆内存地址;
第四步:String str4 = "wugongzi",同第一步
5.String常用方法
5.1.equals方法
这里重写了Object中的equals方法,用来判断两个对象实际意义上是否相等,也就是值是否相等
public boolean equals(Object anObject) {
//如果引用的是同一个对象,则返回真
if (this == anObject) {
return true;
}
//如果不是String类型的数据,返回假
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
//如果char数组长度不相等,返回假
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
//从后往前单个字符逐步判断,如果有不相等,则返回假
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
//每个字符都相等,则返回真
return true;
}
}
return false;
}
5.2.compareTo方法
用于比较两个字符串的大小,如果两个字符串长度相等则返回0,如果长度不相等,则返回当前字符串的长度减去被比较的字符串的长度。
public int compareTo(String anotherString) {
//自身对象字符串长度len1
int len1 = value.length;
//被比较对象字符串长度len2
int len2 = anotherString.value.length;
//取两个字符串长度的最小值lim
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
//从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符)
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//如果前面都相等,则返回(自身长度-被比较对象长度)
return len1 - len2;
}
5.3.hashCode方法
这里重写了hashCode方法,采用多项式进行计算,可以通过不同的字符串得到相同的hash,所以两个String对象的hashCode相同,并不代表两个String是相同的。
算法:假设n = 3
i=0 -> h = 31 * 0 + val[0]
i=1 -> h = 31 * (31 * 0 + val[0]) + val[1]
i=2 -> h = 31 * (31 * (31 * 0 + val[0]) + val[1]) + val[2]
h = 3131310 + 3131val[0] + 31val[1] + val[2]
h = 31^(n-1)val[0] + 31^(n-2)val[1] + val[2]
public int hashCode() {
int h = hash;
//如果hash没有被计算过,并且字符串不为空,则进行hashCode计算
if (h == 0 && value.length > 0) {
char val[] = value;
//计算过程
//s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
//hash赋值
hash = h;
}
return h;
}
5.4.startWith方法
startsWith和endWith方法也是比较常用的方法,常用来判断字符串以特定的字符开始或结尾。
public boolean startsWith(String prefix, int toffset) {
char ta[] = value;
int to = toffset;
char pa[] = prefix.value;
int po = 0;
int pc = prefix.value.length;
// Note: toffset might be near -1>>>1.
//如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
//从所比较对象的末尾开始比较
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}
5.5.concat方法
concat方法用于将指定的字符串参数连接到字符串上。
public String concat(String str) {
int otherLen = str.length();
//如果被添加的字符串为空,则返回对象本身
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
5.6.replace方法
replace的参数是char和charSequence,即可以支持字符的替换,也支持字符串的替换(charSequence即字符串序列的意思)
replaceAll的参数是regex,即基于规则表达式的替换,比如可以通过replaceAll("\d","*")把一个字符串所有的数字字符都替换成星号;
相同点:都是全部替换,即把源字符串中的某一字符或者字符串全部替换成指定的字符或者字符串。
不同点:replaceAll支持正则表达式,因此会对参数进行解析(两个参数均是),如replaceAll("\d",""),而replace则不会,replace("\d","")就是替换"\d"的字符串,而不会解析为正则。
public String replace(char oldChar, char newChar) {
//新旧值先对比
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value;
//找到旧值最开始出现的位置
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
//从那个位置开始,直到末尾,用新值代替出现的旧值
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
5.7.trim方法
trim用于删除字符串的头尾的空格。
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
//找到字符串前段没有空格的位置
while ((st < len) && (val[st] <= ' ')) {
st++;
}
//找到字符串末尾没有空格的位置
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
//如果前后都没有出现空格,返回字符串本身
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
5.8.其他方法
//字符串是否包含另一个字符串
public boolean contains(CharSequence s)
//返回字符串长度
public int length()
//返回在指定index位置的字符,index从0开始
public char charAt(int index)
//返回str字符串在当前字符串首次出现的位置,若没有返回-1
public int indexOf(String str)
//返回str字符串最后一次在当前字符串中出现的位置,若无返回-1
public int lastIndexOf(String str)
//返回s字符串从当前字符串startpoint位置开始的,首次出现的位置
public int indexOf(String s ,int startpoint)
//返回s字符串从当前字符串startpoint位置开始的,最后一次出现的位置
public int lastIndexOf(String s ,int startpoint)
//返回从start开始的子串
public String substring(int startpoint)
//返回从start开始到end结束的一个左闭右开的子串。start可以从0开始的
public String substring(int start,int end)
//按照regex将当前字符串拆分,拆分为多个字符串,整体返回值为String[]
public String[] split(String regex)
6.String常用转化
6.1字符串 --->基本数据类型、包装类
调用相应的包装类的parseXxx(String str);
String str1 = "wugongzi";
int i = Integer.parseInt(str1);
System.out.println(i);
6.2字符串---->字节数组
调用字符串的getBytes()
String str = "wugongzi520";
byte[] b = str.getBytes();
for(int j = 0;j < b.length;j++){
System.out.println((char)b[j]);
}
6.3字节数组---->字符串
调用字符串的构造器
String str = "wugongzi520";
byte[] b = str.getBytes();
String str3 = new String(b);
System.out.println(str3);
6.4字符串---->字符数组
调用字符串的toCharArray();
String str4 = "abc123";
char[] c = str4.toCharArray();
for(int j = 0;j < c.length;j++){
System.out.println(c[j]);
}
6.5字符数组---->字符串
调用字符串的构造器
参考:
https://segmentfault.com/a/1190000009888357
https://www.cnblogs.com/liudblog/p/11196293.html
每天都在用String,你真的了解吗?的更多相关文章
- Java基础知识强化101:Java 中的 String对象真的不可变吗 ?
1. 什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对 ...
- 为什么String被设计为不可变?是否真的不可变?
1 对象不可变定义 不可变对象是指对象的状态在被初始化以后,在整个对象的生命周期内,不可改变. 2 如何不可变 通常情况下,在java中通过以下步骤实现不可变 对于属性不提供设值方法 所有的属性定义为 ...
- Java String 对象,你真的了解了吗?
String 对象的实现 String对象是 Java 中使用最频繁的对象之一,所以 Java 公司也在不断的对String对象的实现进行优化,以便提升String对象的性能,看下面这张图,一起了解一 ...
- object都有string
object都有tostringString item=spinner.getSelectedItem().toString();String item01=String.valueOf(spinne ...
- 不要听吹牛逼什么前端MVVM框架就是好,其实都是一帮没学好分层设计的搞出来的,让你彻底看清前端MVVM的本质
最近前端圈子里面,发现大家都在热炒概念,什么knockout,angularJs,都被捧成神了,鄙人不才,最近心情也不好,特地写这篇文章来找骂 写代码的码农都知道,Java社区虽然不是一个提出分层思想 ...
- 你写的Try...Catch真的有必要么?
很多人喜欢用Try...Catch把每一个方法都包裹起来,可是真的有必要么? 为什么要这样做?我估计是大家被BUG吓怕了,生怕生产环境出现各种莫名其妙的错误,比如最经典的NullReferenceEx ...
- 从为什么String=String谈到StringBuilder和StringBuffer
前言 有这么一段代码: public class TestMain { public static void main(String[] args) { String str0 = "123 ...
- 新手容易混乱的String+和StringBuffer,以及Java的方法参数传递方式。
之前在交流群里和猿友们讨论string+和stringbuffer哪个速度快以及Java的方法参数传递的问题,引起了群里猿友的小讨论.最终LZ得出的结果是string+没有stringbuffer快, ...
- Java中的String为什么是不可变的?
转载:http://blog.csdn.net/zhangjg_blog/article/details/18319521 什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那 ...
随机推荐
- 大型Java进阶专题(九) 设计模式之总结
前言 关于设计模式的文章就到这里了,学习这门多设计模式,你是不是有这样的疑惑,发现很多设计模式很类似,经常会混淆某些设计模式.这章节我们将对设计模式做一个总结,看看各类设计模式有什么区别.需要注意 ...
- C语言学习笔记二---数据类型运算符与表达式
一.C的基本语法单位 1.标识符:有效长度:31(DOS环境下) 2.关键字:main不是 3.分隔符:空格符,制表符,换行符,换页符 4.注释符:a./*.....*/ b.// 二.C的常用输 ...
- MacOS下Git安装及使用
微信搜索"艺术行者",关注并回复关键词"git"获取Github安装包 上传的在线学习视频(黑马和传智双元,感谢) 微信搜索"艺术行者",关 ...
- 谁来教我渗透测试——黑客应该掌握的Windows基础
今天我们看看作为一个黑客对于Windows应该掌握哪些基础知识,主要内容包含以下四个方面: 系统目录.服务.端口和注册表: 黑客常用的DOS命令及批处理文件的编写: 黑客常用的快捷键,以及如何优化系统 ...
- asp.net mvc 模拟百度搜索
页面代码: <td><span>*</span>车牌号码:</td> <td> <div id="search"& ...
- Codeforces Round #649 (Div. 2) E. X-OR 交互 二进制 随机 期望
LINK:X-OR 本来是应该昨天晚上发的 可是昨天晚上 做这道题 写了一个分治做法 一直wa 然后查错 查不出来 心态崩了 想写对拍 发现交互库自己写不出来. 一系列sb操作 == 我都醉了. 今天 ...
- Android JNI之静态注册
这篇说静态注册,所谓静态注册,就是native的方法是直接通过方法名的规定格式和Java端的声明处代码对应起来的,其对应规则如下: JNIEXPORT <返回值> JNICALL Java ...
- 承诺会计/预留款会计(commitment accounting/Encumbrance Accounting) in AX 2012
作者:umfish 博文 http://blog.csdn.net/umfish/article/details/7751397 如果要使用Encmubrance Accounting, 需要先在G ...
- ios签名app稳定不掉签技术详细教程详解
iOS签名是专门针对ios的APP内测的数字签名,是苹果面向开发者提出的一箱机制. 因为现在苹果APP下载渠道只有App Store,还可以加上一个内测用的testflight,也就是说,除了这两个官 ...
- 自身写Android组合多个布局的经历
今天不总结课程了,留着有时间补上. 今天的是ExpandListView,就是可以扩展的列表视图. 今天我做了个总结,然后模仿了扣扣的聊天界面,仅仅写了三个页面而已,用到的xml和活动就不下于10个, ...