java 细说String
String类内部用一个字符数组表示字符串,实例变量定义为:
private final char value[];
String有两个构造方法,可以根据char数组创建String
public String(char value[])
public String(char value[], int offset, int count)
需要说明的是,String会根据参数新创建一个数组,并拷贝内容,而不会直接用参数中的字符数组。
String中的大部分方法,内部也都是操作的这个字符数组。比如说:
length()方法返回的就是这个数组的长度
substring()方法就是根据参数,调用构造方法String(char value[], int offset, int count)新建了一个字符串
indexOf查找字符或子字符串时就是在这个数组中进行查
这些方法的实现大多比较直接,我们就不赘述了。
String中还有一些方法,与这个char数组有关:
返回指定索引位置的char
public char charAt(int index)
返回字符串对应的char数组
public char[] toCharArray()
注意,返回的是一个拷贝后的数组,而不是原数组。
将char数组中指定范围的字符拷贝入目标数组指定位置
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
按Code Point处理字符
与Character类似,String类也提供了一些方法,按Code Point对字符串进行处理。
public int codePointAt(int index)
public int codePointBefore(int index)
public int codePointCount(int beginIndex, int endIndex)public int offsetByCodePoints(int index, int codePointOffset)
编码转换
String内部是按UTF-16BE处理字符的,对BMP字符,使用一个char,两个字节,对于增补字符,使用两个char,四个字节。我们在第六节介绍过各种编码,不同编码可能用于不同的字符集,使用不同的字节数目,和不同的二进制表示。如何处理这些不同的编码呢?这些编码与Java内部表示之间如何相互转换呢?
Java使用Charset这个类表示各种编码,它有两个常用静态方法:
public static Charset defaultCharset()
public static Charset forName(String charsetName)
第一个方法返回系统的默认编码,比如,在我的电脑上,执行如下语句:
System.out.println(Charset.defaultCharset().name());
输出为UTF-8
第二方法返回给定编码名称的Charset对象,其charset名称可以是:US-ASCII, ISO-8859-1, windows-1252, GB2312, GBK, GB18030, Big5, UTF-8,比如:
Charset charset = Charset.forName("GB18030");
String类提供了如下方法,返回字符串按给定编码的字节表示:
public byte[] getBytes()
public byte[] getBytes(String charsetName)public byte[] getBytes(Charset charset)
第一个方法没有编码参数,使用系统默认编码,第二方法参数为编码名称,第三个为Charset。
String类有如下构造方法,可以根据字节和编码创建字符串,也就是说,根据给定编码的字节表示,创建Java的内部表示。
public String(byte bytes[])
public String(byte bytes[], int offset, int length)public String(byte bytes[], int offset, int length, String charsetName)
public String(byte bytes[], int offset, int length, Charset charset)
public String(byte bytes[], String charsetName)
public String(byte bytes[], Charset charset)
除了通过String中的方法进行编码转换,Charset类中也有一些方法进行编码/解码,本节就不介绍了。重要的是认识到,Java的内部表示与各种编码是不同的,但可以相互转换。
不可变性
与包装类类似,String类也是不可变类,即对象一旦创建,就没有办法修改了。String类也声明为了final,不能被继承,内部char数组value也是final的,初始化后就不能再变了。
String类中提供了很多看似修改的方法,其实是通过创建新的String对象来实现的,原来的String对象不会被修改。比如说,我们来看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);
}
通过Arrays.copyOf方法创建了一块新的字符数组,拷贝原内容,然后通过new创建了一个新的String。关于Arrays类,我们将在后续章节详细介绍。
与包装类类似,定义为不可变类,程序可以更为简单、安全、容易理解。但如果频繁修改字符串,而每次修改都新建一个字符串,性能太低,这时,应该考虑Java中的另两个类StringBuilder和StringBuffer,我们在下节介绍它们。
常量字符串
Java中的字符串常量是非常特殊的,除了可以直接赋值给String变量外,它自己就像一个String类型的对象一样,可以直接调用String的各种方法。我们来看代码:
System.out.println("老马说编程".length());
System.out.println("老马说编程".contains("老马"));
System.out.println("老马说编程".indexOf("编程"));
实际上,这些常量就是String类型的对象,在内存中,它们被放在一个共享的地方,这个地方称为字符串常量池,它保存所有的常量字符串,每个常量只会保存一份,被所有使用者共享。当通过常量的形式使用一个字符串的时候,使用的就是常量池中的那个对应的String类型的对象。
比如说,我们来看代码:
String name1 = "老马说编程";
String name2 = "老马说编程";
System.out.println(name1==name2);
输出为true,为什么呢?可以认为,"老马说编程"在常量池中有一个对应的String类型的对象,我们假定名称为laoma,上面代码实际上就类似于:
String laoma = new String(new char[]{'老','马','说','编','程'});
String name1 = laoma;
String name2 = laoma;System.out.println(name1==name2);
实际上只有一个String对象,三个变量都指向这个对象,name1==name2也就不言而喻了。
需要注意的是,如果不是通过常量直接赋值,而是通过new创建的,==就不会返回true了,看下面代码:
String name1 = new String("老马说编程");
String name2 = new String("老马说编程");
System.out.println(name1==name2);
输出为false,为什么呢?上面代码类似于:
String laoma = new String(new char[]{'老','马','说','编','程'});
String name1 = new String(laoma);
String name2 = new String(laoma);System.out.println(name1==name2);
String类中以String为参数的构造方法代码如下:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
hash是String类中另一个实例变量,表示缓存的hashCode值,我们待会介绍。
可以看出, name1和name2指向两个不同的String对象,只是这两个对象内部的value值指向相同的char数组。其内存布局大概如下所示:
所以,name1==name2是不成立的,但name1.equals(name2)是true。
hashCode
我们刚刚提到hash这个实例变量,它的定义如下:
private int hash; // Default to 0
它缓存了hashCode()方法的值,也就是说,第一次调用hashCode()的时候,会把结果保存在hash这个变量中,以后再调用就直接返回保存的值。
我们来看下String类的hashCode方法,代码如下:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
如果缓存的hash不为0,就直接返回了,否则根据字符数组中的内容计算hash,计算方法是:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
s表示字符串,s[0]表示第一个字符,n表示字符串长度,s[0]*31^(n-1)表示31的n-1次方再乘以第一个字符的值。
为什么要用这个计算方法呢?这个式子中,hash值与每个字符的值有关,每个位置乘以不同的值,hash值与每个字符的位置也有关。使用31大概是因为两个原因,一方面可以产生更分散的散列,即不同字符串hash值也一般不同,另一方面计算效率比较高,31*h与32*h-h即 (h<<5)-h等价,可以用更高效率的移位和减法操作代替乘法操作。
在Java中,普遍采用以上思路来实现hashCode。
正则表达式
String类中,有一些方法接受的不是普通的字符串参数,而是正则表达式,什么是正则表达式呢?它可以理解为一个字符串,但表达的是一个规则,一般用于文本的匹配、查找、替换等,正则表达式有着丰富和强大的功能,是一个比较庞大的话题,我们将在后续章节单独介绍。
Java中有专门的类如Pattern和Matcher用于正则表达式,但对于简单的情况,String类提供了更为简洁的操作,String中接受正则表达式的方法有:
分隔字符串
public String[] split(String regex)
检查是否匹配
public boolean matches(String regex)
字符串替换
public String replaceFirst(String regex, String replacement)
public String replaceAll(String regex, String replacement)
小结
我们介绍了String类,介绍了其基本用法,内部实现,编码转换,分析了其不可变性,常量字符串,以及hashCode的实现。
我们提到,在频繁的字符串修改操作中,String类效率比较低,我们提到了StringBuilder和StringBuffer类。我们也看到String可以直接使用+和+=进行操作,它们的背后也是StringBuilder类。
java 细说String的更多相关文章
- 【重走Android之路】【Java面向对象基础(二)】细说String、StringBuffer和StringBuilder
[重走Android之路][基础篇(二)][Java面向对象基础]细说String.StringBuffer和StringBuilder 1.String String是Java中的一个final ...
- java中String类型变量的赋值问题
第一节 String类型的方法参数 运行下面这段代码,其结果是什么? package com.test; public class Example { String str = new String( ...
- java.lang.String.getBytes(String charsetName)方法实例
java.lang.String.getBytes(String charsetName) 方法编码将此String使用指定的字符集的字节序列,并将结果存储到一个新的字节数组. 声明 以下是java. ...
- OC与c混编实现Java的String的hashcode()函数
首先,我不愿意大家需要用到这篇文章里的代码,因为基本上你就是被坑了. 起因:我被Java后台人员坑了一把,他们要对请求的参数增加一个额外的字段,字段的用途是来校验其余的参数是否再传递过程中被篡改或因为 ...
- java 创建string对象机制 字符串缓冲池 字符串拼接机制
对于创建String对象的机制,在这一过程中涉及的东西还是值得探究一番的. 首先看通过new String对象和直接赋值的方式有什么区别,看如下代码: public static void main( ...
- hibernate报错Unknown integral data type for ids : java.lang.String
package com.model; // Generated 2016-10-27 14:02:17 by Hibernate Tools 4.3.1.Final /** * CmDept gene ...
- 前台传参数时间类型不匹配:type 'java.lang.String' to required type 'java.util.Date' for property 'createDate'
springMVC action接收参数: org.springframework.validation.BindException: org.springframework.validation.B ...
- 记录maven java.lang.String cannot be cast to XX error
在项目开发中自定义了一个maven plugin,在本地能够很好的工作,但是在ci server上却无法正常工作报错为: --------------------------------------- ...
- java中string内存的相关知识点
(一):区别java内存中堆和栈: 1.栈:数据可以共享,存放基本数据类型和对象的引用,其中对象存放在堆中,对象的引用存放在栈中: 当在一段代码块定义一个变量时,就在栈中 为这个变量分配内存空间,当该 ...
随机推荐
- OAutho2 请求响应格式
1.OAuth2.PasswordGrant REQUEST: POST /token HTTP/1.1 Host: localhost: Content-Type: application/x-ww ...
- Jersey+mybatis实现web项目第一篇
---恢复内容开始--- Jesery第一篇:实现Jesery前后台页面交互,Form表单提交,后台控制页面跳转 该项目中有实现的功能: Mybatis实现后台数据持久化 Jersey页面数据提交 后 ...
- Navicat通过跳板机连接数据库
完成对应设置后,即可连接数据库,本人亲测!
- 笔记:MyBatis XML配置-typeAliases 内建别名表
别名 映射的类型 _byte byte _long long _short short _int int _integer int _double double _float float _boole ...
- from提交表单后 数据提交到后台 但不跳转页面 可用iframe
可以页面事先加载被隐藏的iframe标签,或者等到需要的时候通过js生成,再提交,提交之前,form的target指向iframe(我是要实现新页面生成的时候程半透明状态,所以用了后者的方法) 代码如 ...
- 记录一则ASM实例阻塞,rbal进程异常的案例
1.故障现象描述 2.确认故障现象 3.排查ASM层面 4.解决问题 1.故障现象描述 环境:AIX 7.1 + Standalone Oracle 11.2.0.4 现象:客户反映某11g版本的AD ...
- Algorithm --> 二进制中1的个数
行文脉络 解法一——除法 解法二——移位 解法三——高效移位 解法四——查表 扩展问题——异或后转化为该问题 对于一个字节(8bit)的变量,求其二进制“1”的个数.例如6(二进制0000 0110) ...
- PLSQL配置怎么连ORACLE
如果是windows的话,在服务里面就可以查服务要启动,监听也要启动!可以用lsnrctl命令查看监听! 首先你需要在我的电脑的属性里,找到环境变量,配置系统下面的path,看看里面指向的是哪里(我的 ...
- Beta阶段敏捷冲刺报告-DAY3
Beta阶段敏捷冲刺报告-DAY3 Scrum Meeting 敏捷开发日期 2017.11.4 会议时间 12:30 会议地点 软工所 参会人员 全体成员 会议内容 当天任务确认,进度调整, 讨论时 ...
- Ubuntu下安装gsoap
昨天在ubuntu下进行安装gSOAP,费了很多时间,没成功,今天又来找了大量教程资料,终于一次成功,这里写下自己的安装步骤和方法,供大家参考. 首先下载gsoap,我下载的是gsoap-2.8.1. ...