1、长度
public int length() {
return value.length;
}
如上的方法是返回字符串的长度,但是需要注意的是:
String类中的length()方法也是对字符串进行char统计,也就是计算代码单元数量(代码单元有可能大于正真的字符数量,如果字符串中有增补字符的话)。如果要计算代码点数量,必须用str.codePointCount(0,str.length())方法。
public int codePointCount(int beginIndex, int endIndex) {
if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
throw new IndexOutOfBoundsException();
}
return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
}
static int codePointCountImpl(char[] a, int offset, int count) {
int endIndex = offset + count;
int n = count;
for (int i = offset; i < endIndex; ) {
if (isHighSurrogate(a[i++]) && i < endIndex && isLowSurrogate(a[i])) {
n--;
i++;
}
}
return n;
}
由此可见,用char类型来处理字符串在有些情况下是不准确的,我们最好使用字符串类型中处理代码点的方法,不要使用处理char类型(一个代码单元)的方法。
2、截取字符串字串
//最常用,用户自己给开始位置和结束位置
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
//用户只给开始位置,结束位置默认为最后
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
如果没变就返回本来的String,否则返回了一个新的字符串,串中的内容与原串中要截取的子字符串相同。不会出现JDK1.6版本的char[] value 数组被共享了。而截取字串的操作是通过偏移量offset和长度count来实现的。这样就会造成内存泄漏问题,例如需要多次截取大字符串中的很小一部分时,导致无法释放内存并且造成了内存的巨大浪费,最终程序运行会抛出异常:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space。
3、分割字符串
(1)用传统的split()
//第一个会调第二个
public String[] split(String regex) {
return split(regex, 0);
}
//第二个,limit为你期望的数组的长度,如果为0,则全部分割
public String[] split(String regex, int limit) {
char ch = 0;
//判断分隔符是否正确
if (((regex.value.length == 1 && //不去要转义的
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' && //需要用\\转义的".$|()[{^?*+\\"这几个符号都要转义
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{ //例子:aa,bb,cc,dd自己想想下怎么分
int off = 0; //当前分隔符的位置+1
int next = 0; //下一个分隔符的位置:indexOf(分隔符, 从指定位置开始)
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) { //循环分割,加入到list列表中
if (!limited || list.size() < limit - 1) { //
list.add(substring(off, next)); //off开始位置和next结束位置
off = next + 1;
} else { //assert (list.size() == limit - 1);
list.add(substring(off, value.length)); //最后那个达不到期望的limit值,后面所有的为一个加载到list中,并打断
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
if (limit == 0)
while (resultSize > 0 && list.get(resultSize - 1).length() == 0)
resultSize--;
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result); //将list转化为String[] result并返回
}
//除上面的外就返回正则表达式的分割
return Pattern.compile(regex).split(this, limit);
}
//上面函数调用的indexOf函数:从指定索引开始,向后搜索与分隔符ch一样的字符,并返回该位置
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) { //索引小于0置为0,大于长度置为-1
fromIndex = 0;
} else if (fromIndex >= max) {
return -1;
}
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
final char[] value = this.value;
for (int i = fromIndex; i < max; i++) { //从指定索引开始,向后搜索与分隔符ch一样的字符,并返回该位置
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return indexOfSupplementary(ch, fromIndex); //超过范围,就是增补的,就按增补的算,这个只需要知道即可
}
}
(2)StringTokenizer类实现分割,效率优于splite()方法
StringTokenizer strtk=new StringTokenizer(str,";");
while(strtk.hasMoreTokens()){
strtk.nextToken();
}
StringTokenizer类在java.util中,有兴趣的可以阅读一下。
(3)虽然subString()会造成内存泄漏,但是由于使用了空间换时间的方法,所以查找的速度还是很快的。
//循环下面代码
int j=str.indexOf(';');
str.substring(0,j);
str=str.substring(j+1);
4、字符串的hashcode()
public int hashCode() {
int h = hash;
//该类在定义hash变量是值为0,所以空字符串的value.length=0的,就不会进入if中,则h=hash值为0。
if (h == 0 && value.length > 0) {
char val[] = value;
//这个就是String计算他的hashCode方法
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
字符串的hashCode()计算的方法为:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
所以说,当字符串一样的时候,他们的hashCode方法是一样的。
5、字符串的equals()
public boolean equals(Object anObject) {
if (this == anObject) {//比较是不是同一个
return true;
}
if (anObject instanceof String) {//看是不是String类型
String anotherString = (String) anObject;
int n = value.length;
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;
}
比较两个字符串中字符的序列组成是否相等,也就是比较字符串中的内容。如果不覆写这个方法,默认继承的是Object中的equals()方法,代码如下:
public boolean equals(Object obj) {
return (this == obj);
}
使用“==”符号来比较两个引用类型的值,则比较的是引用地址,对于String类型只比较字符串时不准确,所以一定要进行覆写。
6、缓存字符串
public native String intern();
这是一个本地的方法,当调用 intern 方法时,如果缓存池已经包含一个等于此 String 对象的字符串,则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
String param = "abc";
String newStr = new String("abc");
String param2 = new String("abc");
newStr.intern();
param2 = param2.intern(); // param2指向intern返回的常量池中的引用
System.out.println(param == newStr); // false
System.out.println(param == param2); // true
可以看到,使用intern()方法后,字符串变量的值如果不存在缓冲区中将会被缓存。
7、字符串比较大小
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) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
正数为大于,负数为小于,0为一样
8、搜索是否以什么开始,和以什么结尾
//第一种调用第二种
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
//prefix为以它开始,toffset为字符串从那个地方开始算起,在以prefix开始进行比较
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.
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {//pa为prefix串从po=0开始,ta为字符串,从to=toffset开始
return false;
}
}
return true;
}
//以suffix结尾也是用第二个方法,只不过,是从总长度刚好减去suffix长度的地方开始比较
public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}
9、从后往前查找,但是以Unicode编码进行查找的
//第一个调用第二个
public int lastIndexOf(int ch) {
return lastIndexOf(ch, value.length - 1);
}
//注意ch为Unicode编码不是char类型,而fromIndex为索引,表示意思是从那个地方开始倒着查找
public int lastIndexOf(int ch, int fromIndex) {
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
int i = Math.min(fromIndex, value.length - 1);
for (; i >= 0; i--) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return lastIndexOfSupplementary(ch, fromIndex);//增补字符
}
}
后面还有函数,后续补充