String简介
今天闲着没事,重新温故了下string源码,下面主要介绍下String主要方法和实现,以及衍生出的StringBuffer与StringBuilder之间区别与联系。
言归正传了,我们都知道String类在java.lang包中,String类创建一个字符串变量,字符串变量属于对象。String类被声明为final,所以对象创建后不能修改,由0或多个字符组成。
创建:String str = new String(""); / String str = "";
组成:字符数组 char value[] / 哈希值:int hash
String本身提供了16个构造方法,可以传入不同的参数进行创建对象,本身数据结构为一个字符数组。
String主要方法
length() 获取字符串长度
直接返回字符数组长度 : value.length
isEmpty() 判断是否为null
主要判断字符数组长度是否为0 : value.length == 0
charAt(int) 截取一个字符
直接返回字符数组某索引处值: value[i]
getChars(char,int) 截取多个字符
使用了带有native声明的方法: System.arraycopy()
该方法是使用C写的,所以性能上比使用循环高效;
如果对JNI感兴趣可以看看这篇文章:equal 与 == 比较字符串是否相等
比较两个字符串:equal主要就是比较两个字符数组每个字符是否相等,
== 判断两个字符串是否是同一引用了;compareTo 返回比较值
这个主要是比较两个字符串的大小了,附上代码:public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); //获取更短字符串长度 char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { //遍历 char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { //判断k处索引字符是否相等 return c1 - c2; //不等,返回两个字符hash差值 } k++; } //如果前面的字符都相等 return len1 - len2; //返回两个字符串的长度差值 }
主要看返回值的正负,和0比较 ,当等于0的时候和equal的功能一样了。
startsWith(string) 与 endsWith(string) 判断开始或结尾是否为某字符串
这个主要就是判断下 字符数组前几个或后几个是否和 传入的字符串一致了
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; // 判断下传入的字符串是否过长 if ((toffset < 0) || (toffset > value.length - pc)) { return false; } while (--pc >= 0) { //循环遍历,判读字符数组值了 if (ta[to++] != pa[po++]) { return false; } } return true; }
hashCode() 获取hash值
这里主要是看下hash值的计算方法:
public int hashCode() { int h = hash; //字符串hash默认初始为0 if (h == 0 && value.length > 0) { char val[] = value; //遍历累加 31*h + 字符hash值 for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
indexOf 与 lastIndexOf 判断字符或字串索引位置
主要逻辑就是 遍历从0或者length-1 索引开始找到和传入的string相同的,返回该位子索引就行了;两个for嵌套就行,如果入参为 char ,一个for就行;
static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) { //indexOf 通用方法 //判断开始索引位置 是否大于 字符串长度 if (fromIndex >= sourceCount) { return (targetCount == 0 ? sourceCount : -1); } //判断索引位置是否小于0,赋初始值 0,即索引位置从0开始 if (fromIndex < 0) { fromIndex = 0; } //入参长度为0 if (targetCount == 0) { return fromIndex; } //判断的入参初始字符 char first = target[targetOffset]; //最大遍历次数 字符串起始位置 + (字符串长度-入参长度) int max = sourceOffset + (sourceCount - targetCount); //循环遍历开始 for (int i = sourceOffset + fromIndex; i <= max; i++) { //入参第一个字符 与 字符串数组进行匹配,直到第一个相等出现 if (source[i] != first) { while (++i <= max && source[i] != first); } if (i <= max) { int j = i + 1; int end = j + targetCount - 1; //如果入参字符串长度大于1,判断后续值是否相等 for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++); if (j == end) { return i - sourceOffset; } } } return -1; }
subString() 截取字符串
这个实现比较简单,就是截取 开始索引至结束索引这段长度的字符数组,返回一个新的string对象;
concat() 连接字符串
这里主要用了Arrays.copyOf方法进行数据拷贝,返回的也是新创建的string对象;
replace() 替换子串
将字符串中和入参一样的子串进行替换,和上面indexOf 实现差不多,只是发现相同的时候进行数据替换,并且一直遍历下去,直到结束;而不是发现相同就返回索引位置;
trim() 前后去空
两个 while 循环,判断“”之后记录前后索引位置,有变动在调用subString方法进行截取;
valueOf() 转换为string
这里就有点复杂了,根据传入的入参类型不同实现方法也不同,这里就讲一下object的吧,
本身代码就一句:return (obj == null) ? “null” : obj.toString();
object的toString方法计算为:
getClass().getName() + “@” + Integer.toHexString(hashCode());
hashCode()也是一个native修饰的方法,感兴趣的可以去找找C实现代码看看;toLowerCase() 与 toUpperCase() 转换大小写
字符串的字符数组大小写转换,其实就是 字符的hash进行+32 或者 -32 操作了;
比较String、StringBuffer与StringBuilder
执行速度 StringBuilder > StringBuffer > String
理由:
String: 不可改变对象
StringBuilder:线程非安全的
StringBuffer:线程安全的
字符串缓冲区被多个线程使用时,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。
总结:
少量拼接或常量 String
单线程 拼接字符串 StringBuilder
多线程 拼接字符串 StringBuffer
具体比较代码或者方法解释就不多说了,可以看看这篇博文: