Java字符串类是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉又陌生。下面将对String、StringBuffer和StringBuilder三种字符串类详细介绍:
1、String类是final的,不可被继承。public final class String。
2、String类是的本质是字符数组char[], 并且其值不可改变。private final char value[];
打开String类的API文档,就可以发现。
3、String类对象有个特殊的创建的方式,就是直接指定比如String x = "abc","abc"就表示一个字符串对象。而x是"abc"对象的地址,也叫做"abc"对象的引用。
4、String对象可以通过“+”串联。串联后会生成新的字符串。也可以通过String对象的concat()函数来串联。
5、Java运行时会维护一个String Pool (String池),String池用来存放运行时中产生的各种字符串,并且池中的字符串的内容不重复。而一般对象不存在这个缓冲池,并且创建的对象仅仅存在于方法的堆栈区。
创建String类字符串的方式很多,归纳起来有三类:
一,使用new关键字创建字符串,比如String s= new String("abc");
二,直接指定。比如String s = "abc";
三,使用串联生成新的字符串。比如String s = "ab" + "c";
通过构造方法创建:
1. new String();初始化一个新创建的 String 对象,使其表示一个空字符序列等同于String s = "";
2. new String(String str);初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。
3. new String(byte[] bytes);通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
4. byte[] bytes = {98,99,100}; new String(bytes);
5. new String(char[] chs);分配一个新的 String,使其表示字符数组参数中当前包含的字符序列
6. char[] chs = {'a','c'}; new String(chs);
原理1: 当使用任何方式来创建一个字符串对象s时,Java运行时(运行中JVM)会拿着这个s在String池中找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串s,否则,不在池中添加。
原理2: Java中,只要使用new关键字来创建对象,则一定会(在堆区或栈区)创建一个新的对象。
原理3: 使用直接指定或者使用纯字符串串联来创建String对象,则仅仅会检查维护String池中的字符串,池中没有就在池中创建一个,但绝不会在堆栈区再去创建该String对象。
原理4: 使用包含变量的表达式来创建String对象,则不仅会检查维护String池,而且还会在堆栈区创建一个String对象。
另外,String的intern()方法是一个本地方法,定义为public native String intern(); intern()方法的价值在于让开发者能将注意力集中到String池上。当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
定义一个string类型的变量有两种方式:
string name= "tom ";
string name =new string( "tom ")
使用第一种方式的时候,就使用了串池,使用第二中方式的时候,就是一种普通的声明对象的方式 。
如果你使用了第一种方式,那么当你在声明一个内容也是 "tom "的string时,它将使用串池里原来的那个内存,而不会重新分配内存,也就是说,string saname= "tom ",将会指向同一块内存 。
另外关于string类型是不可改变的问题:
string类型是不可改变的,也就是说,当你想改变一个string对象的时候,比如name= "madding "
那么虚拟机不会改变原来的对象,而是生成一个新的string对象,然后让name去指向它,如果原来的那个 "tom "没有任何对象去引用它,虚拟机的垃圾回收机制将接收它。 这样可以提高效率!!!
对于java程序中的字符串直接常量,JVM会使用一个字符串池来保存它们。当第一次使用某个字符串直接常量时,JVM会将它放入字符串池中进行缓存。在一般情况下,字符串池中的字符串对象不会被垃圾回收。当程序再次需要使用该字符串时,无需重新创建一个新的字符串就可以直接让引用变量直接指向字符串中已有的字符串。而使用new操作创建的字符串对象不指向字符串池中的对象,但是可以使用intern()方法使其指向字符串池中的对象。
代码如下:
public class StringDemo1 {
public static void main(String[] args){
String str1 ="abc";
String str2 ="abc";
String str3 =new String("abc");
System.out.println(str1==str2); //true
System.out.println(str1==str3); //false
}
}
常见问题
代码如下:
String str3 =new String("abc");
创建了几个对象? 答:两个
代码如下:
String str ="ab"+"cd";
创建了几个对象? 答:一个
"ab"和"cd"都是常量被放在字符串池中。因此只创建了一个abcd字符串池中并将字符串abcd保存在字符串池中。
代码如下:
public class StringDemo1 {
public static void main(String[] args){
String str1 ="ab";
String str2 ="cd";
String str3 ="ab"+"cd"; //创建对象并加入字符串池
String str4 =str1+str2;
String str5 =str1+"cd";
System.out.println(str3==str4); //false
System.out.println(str3==str5); //false
}
}
由上面代码可知:只有引号包含文本的方式才创建的String对象才能被添加到字符串池中,对于包含new方法新建对象的”+“连接表达式他所产生的新对象不会被加到字符串池中。
但是有一种情况需要引起我们的注意:
代码如下:
public class StringDemo1 {
private final static String str1 ="ab";
private final static String str2 ="cd";
public static void main(String[] args){
String str3 ="ab"+"cd"; //创建对象并加入字符串池
String str4 =str1+str2;
String str5 =str1+"cd";
System.out.println(str3==str4); //true
System.out.println(str3==str5); //true
}
}
将上面的代码稍加改变看看会出现什么情况。
代码如下:
public class StringDemo1 {
private final static String str1 ;
private final static String str2;
static{
str1="ab";
str2="cd";
}
public static void main(String[] args){
String str3 ="ab"+"cd";//创建对象并加入字符串池
String str4 =str1+str2;
String str5 =str1+"cd";
System.out.println(str3==str4); //false
System.out.println(str3==str5); //false
}
}
str1和str2虽然被定义为常量,但是它们没有马上赋值,在运算出s的值前,它们何时被赋值,以及被赋什么值都是变数,因此性质和变量一样。只能在运行时被创建。
1、判断
1.1 boolean equals(Object)
判断两个字符串内容是否相同:覆盖了Object类中的方法。
1.2 boolean equalsIgnoreCase(string)
判断两个字符串内容是否相同,忽略大小写。
1.3 boolean contains(string)
判断一个字符串中是否包含另一个字符串,jdk1.5出现的功能。
1.4 boolean startsWith(string)
判断一个字符串是否以一个字符串开头
1.5 boolean endsWith(string)
判断一个字符串是否以一个字符串结尾
1.6 boolean isEmpty()
判断字符串是否有具体的字符数据。或者成为判断字符串是否有内容
原理就是判断字符串的长度是否为0.该方法是jdk1.6版本。
2、获取
2.1 int length() 获取字符串的长度。
2.2 char charAt(index) 根据指定的位置获取该位置对应的字符。
2.3 int indexOf(ch) 根据字符获取该字符第一次出现的位置
int idnexOf(ch,fromIndex) 从fromIndex位置开始获取ch第一次出现的位置。找到了就返回第一次出现角标位,没找到,就返回-1.所以,可以通过-1这种方式判断一个字符是否在字符串存在。
2.4 int indexOf(string) 根据字符串获取该字符串第一次出现的位置
int idnexOf(string,fromIndex) 从fromIndex位置开始获取string第一次出现的位置。找到了就返回第一次出现角标位,没找到,就返回-1.所以,可以通过-1这种方式判断一个字符串是否在字符串存在。这既是contains方法原 理。
2.5 同样是获取一个字符或者字符串第一次出现的位置,但是是通过反向索引完成。
功能和indexOf一致。
int lastIndexOf(ch);
int lastIdnexOf(ch,fromIndex):
int lastIndexOf(string);
int lastIdnexOf(string,fromIndex):
2.6 获取字符串中的一部分字符串。
String substring(start): 从start开始到结尾。
String substring(start,end); 获取到的字符串包含start位,不包含end位。
3、转换
3.1 将字符串转成字节数组。
byte[] getBytes(): 编码解码时较为常用。还有在io流操作字节数据也很常用。
3.2 将字符串转成字符数组。
char[] toCharArray():
3.3 将字符数组或者数组中的一部分转成字符串。
static String copyValueOf(char[])
3.4 将字符数组或者数组中的一部分转成字符串。
static String valueOf(char[]):
3.5 基本数据类型变成字符串
static String valueOf(int);
static String valueOf(double);
static String valueOf(boolean);
...
int x = 3;
String.valueOf(x);--> "3";
x+""--> "3";
3.6 将对象变成字符串
static String valueOf(object); 和object.toString():结果是一样的。
3.7 将字符串转成大写或者小写。
小写:toLowerCase():
大写:toUpperCase():
4、替换
4.1 替换字符。
String replace(oldChar,newChar):
4.2 替换字符串
String replace(string,string);
5、切割
String[] split(regex);
注意://如果是.的话是特殊字符,需要转义
6、去除两端的空格
String trim();
7、比较
compareTo(String anotherString)
Buffer就是的缓冲区(CRUD的操作)
字符串缓冲区,它是存储字符串数据的,可以称之为一个容器。
既然是一个容器,就应该具备比如增加,删除等操作,那么这个stringbuffer具备哪些 功能看API:
1:添加。
StringBuffer append():可以将基本数据类型数据和引用类型数据添加到缓冲区。将数据 添加到缓冲区的末尾。数据追加。这个方法返回来的还是原理的缓冲区对象。
sb.append的返回值还是原来的缓冲区对象。可以使用==号进行判断,发现值为true。
因为返回值是自己,所以可以链式调用
2:缓冲区的特点:
1,可以加基本数据和引用数据。添加数据完毕,一般都需要将其转成字符串进行使用。 通过toString方法转成字符串。
2,该容器和数组不一样,数组是固定长度的,而且只能添加同一类型。而StringBuffer长度是可变的,可以添加不同类型。
类型一致的数据,可以使用数组,也可以StringBuffer。但是数组添加元素,需要保证数据的类型不变。而StringBuffer添加完数据,全变成字符串。
3,StringBuffer可以对字符串进行修改。
StringBuffer功能-添加-删除
1:除了append还有insert,把数据插入到指定位置。指定偏移量
这个相当于对字符串进行修改
2:sb.delete(start,stop),删除指定区间的数据
特殊使用形式,清空缓冲区,sb.delete(0,sb.length());
3:删除指定位置的字符
sb.deleteCharAt(index); 功能-获取字符串出现的位置&替换&修改&反转
4,获取字符串的出现的位置。
int lastIndexOf(str);
int indexOf(str);
5,替换:
StringBuffer replace(start,end,string);
6,修改:
void setCharAt(index,char);
7,反转:
StringBuffer reverse();
8:保留前面多少个字符
sb.setLength(3);//保留指定长度数据
总结:其实stringbuffer之所以可以存储数据,就是因为底层封装是一个字符数组
Jdk1.5的时候出现了一个stringbuilder
这个里面的方法和stringbuffer里面的一样。
Stringbuffer是一个线程安全的类,一个线程在操作的时候,另一个线程不能操作。
区别:
StringBuffer:是线程安全的。
StringBuilder:是线程不安全的。
日后开发,常用的是StringBuilder.因为一般都是单线程。主线程在运行。
如果真的有了多线程,那么建议使用StringBuffer.
StringBuilder的出现,是为了提高了效率。
JDK的升级:不外乎三个因素:
1,简化书写。
2,提高效率。
3,提高安全性。
StringBuilder() 构造一个其中不带字符的字符串缓冲区,初始容量为16 个字符。
注意,这个初始容量,就算是默认16个字符,超过16个字符也能往里面放。里面还有一个带有构造参数的stringbuilder,这个里面可以指定初始化容量,如果指定30个,那么多于30个的字符也能存到里面。如果你能确定字符串的长度,就使用带有参数的创建。如果不能确定字符串的长度,就使用默认的,这主要是牵扯到性能的问题。
注意:
如果你能确定字符串的长度,就直接指定,假设是长度是50的话,你还使用默认的构造参数,这样只能存16个字符,当添加第17个字符的时候,底层是需要重新创建一个新数组,把之前的数据拷贝到新数组,这样频繁的创建数据是非常消耗性能和资源的。所以,建议,如果能确定字符串的长度,就直接指定长度即可。
对数据相连接变成新的字符串
stringbuilder比string效率高些
string一旦被初始化不可以被改变
stringbuilder,可以对字符串进行修改
// 业务: 1:反转字符串"woainimenmemeda"成为"adememneminiaow"
public static String reverse(char[] str) {
int k = (str.length) / 2;
for (int i = 0, j = str.length - 1; i < k; i++, j--) {
char tmp = str[i];
str[i] = str[j];
str[j] = tmp;
}
return new String(str);
}
// 业务:2:子串"nba"在字符串"nbaernbatynbauinbaopnba"出现的次数
public static int countNum(char[] str, char[] str1) {
int num = 0;
for (int i = 0; i < str.length; i++) {
for (int j = 0; j < str1.length; j++, i++) {
if (str[i] != str1[j])
break;
else if (j == 2)
num++;
}
}
return num;
}
//业务:3:获取两个字符中最大相同子串
public static String maxString(String str1, String str2) {
String max = str1.length() > str2.length() ? str1 : str2;
String min = str1.length() < str2.length() ? str1 : str2;
for (int i = 0; i < min.length(); i++) {
for (int start = 0, end = min.length() - i; end != min.length() + 1; start++, end++) {
String tmp = min.substring(start, end);
if (max.contains(tmp))
return tmp;
}
}
return null;
}
//业务:4:把字符串整型转化为整型
public static void str2Int() {
String str = "123456";
int sum = 0;
char[] chs = str.toCharArray();
for (int i = 0; i < chs.length; i++) {
int num = chs[i] - '0';
sum += num * Math.pow(10, chs.length - 1 - i);
}
System.out.println(sum);
}
//业务:5:把十进制转化为十六进制
public static void dec2Hex(int num) {
StringBuilder sb = new StringBuilder("");
while (num != 0) {
int tmp = num % 16;
if (tmp > 9) {
char ch = (char) (55 + tmp);
sb.append(ch);
} else {
sb.append(tmp);
}
num /= 16;
}
System.out.println(sb.reverse().toString());
}
//业务:6;把十进制转化为八进制
public static void dec2Oct(int num) {
StringBuilder sb = new StringBuilder("");
while (num != 0) {
int tmp = num % 8;
sb.append(tmp);
num /= 8;
}
System.out.println(sb.reverse().toString());
}
//业务:7:自己实现一个String类中toUpperCase()的方法
public static String toUpperCase(String str) {
char[] chs = str.toCharArray();
for (int i = 0; i < chs.length; i++) {
if (chs[i] >= 'a' && chs[i] <= 'z') {
chs[i] -= 32;
}
}
return new String(chs);
}
// 业务:8: 自己实现一个String中trim()方法
public static String trim(String str) {
char[] chs = str.toCharArray();
int i =0; int j = chs.length-1;
while(i< j && chs[i] == ' '){
i++;
}
while(i < j && chs[j] == ' '){
j--;
}
return str.substring(i,j);
}