-----------android培训、java培训、java学习型技术博客、期待与您交流! ------------
一、定义
多个字符组成的一个序列,叫字符串。
由来:生活中很多数据的描述都采用的是字符串的。而且我们还会对其进行操作。所以,java就提供了这样的一个类供我们使用。
二、特点
1、字符串是一个特殊的对象;
2、字符串一旦初始化就不可以被改变了;
3、字符串对象都存储在常量池中。 字符串常量池。
4、字符串初始化有2种方式:
(1)String s1 = “abcd”; //这时在常量池中创建了一个字符串对象。
(2)String s2 = new String(“abcd”); //这时在堆内存创建一个String类的对象。并在常量池创建了"abcd"对象。
区别:
创建s1并初始化,内存中只有一个对象,就是字符串对象。创建s2并初始化,内存有两个对象,一个"abcd"字符串对象,在方法区的常量池(当创建新对象时,系统会到常量池中寻找原来已经存在的相同元素,组成新的对象,这是为了节约内存);
选取:
这种两种定义"abc"字符串的方式都可以。 s1相对简单,一般都按照s1这样定义。
代码示例:
class Demo {
public static void main(String[] arg) {
String s1 = "abcd";//s1是一个类类型变量, "abcd"是一个字符串对象。
String s2 = "abcd";// 这时在常量池中创建了一个字符串对象。
System.out.println(s1 == s2);// “==”比较两个类类型变量本身的值,即两个对象在内存中的首地址。
System.out.println(s1.equals(s2));//String类覆盖了Object中的equals方法,比较的是字符串对象内容是否相同。
String s3 = new String("abcd");//这时在堆内存创建一个String类的对象,并在常量池创建了"abcd"对象。
System.out.println(s2 == s3);// false (s2指向了常量池中的字符串对象,s3指向了堆內存中的字符串对象)地址不同
System.out.println(s2.equals(s3));
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());//hashCode值相同,可见其与对象的内存地址无关
String s4 = new String("abcd");
System.out.println(s3 == s4);//在堆内存创建另外一个String类的对象。
System.out.println(s3.equals(s4));
String s5 = "ab"+"cd";//自动去常量池中寻找是否已经有这些字符存在;若有,则不再重复建立字符串对象,引用指向原来的对象
System.out.println(s1==s5);//true.
}
}
/*output:
true
true
false
true
2987074
2987074
false
true
true
*/
5、String s = new String( "xyz "); 创建了几个对象?
要理解这个,就要知道string类的工作原理。
你知道在java中除了8种基本类型外,其他的都是类对象以及其引用。所以 "xyz "在java中它是一个String对象。对于string类对象来说他的对象值是不能修改的,也就是具有不变性。
看例子:
String s= "Hello ";
s= "Java ";
String s1= "Hello ";
String s2=new String( "Hello ");
啊,s所引用的string对象不是被修改了吗?之前所说的不变性,去哪里了啊?
你别着急,让我告诉你说发生了什么事情:
在jvm的工作过程中,会创建一片的内存空间专门存入string对象。我们把这片内存空间叫做string池。
String s= "Hello "; 当jvm看到 "Hello ",在string池创建string对象存储它,并将他的引用返回给s。
s= "Java ",当jvm看到 "Java ",在string池创建新的string对象存储它,再把新建的string对象的引用返回给s。而原先的 "Hello "仍然在string池内,没有消失,他是不能被修改的。
所以我们仅仅是改变了s的引用,而没有改变他所引用的对象,因为string对象的值是不能被修改的。
String s1= "Hello ";jvm首先在string池内里面看找不找到字符串 "Hello ",找到,返回他的引用给s1,否则,创建新的string对象,放到string池里。这里由于s= "Hello "了,对象已经被引用,所以依据规则s和s1都是引用同一个对象。所以 s==s1将返回true。(==,对于非基本类型,是比较两引用是否引用内存中的同一个对象)
String s2=String( "Hello ");jvm首先在string池内里面看找不找到字符串 "Hello ",找到,不做任何事情,否则,创建新的string对象,放到string池里面。由于遇到了new,还会在内存上(不是string池里面)创建string对象存储 "Hello ",并将内存上的(不是string池内的)string对象返回给s2。所以s==s2将返回false,不是引用同一个对象。
好,现在我们看题目:
String s = new String( "xyz ");
首先在string池内找,如果找到了,不创建string对象;否则创建,这样就一个string对象 。
遇到new运算符号了,在内存上创建string对象,并将其返回给s,又一个对象。
所以总共是2个对象
另外,常量池是在编译期生成的,而new一个对象是在运行时进行的,有一个先后顺序。
再解:
"xyz "本身作为字符常量,在汇编语言中应该作为常量放在数据段,Java有一个类似数据段的constant pool保存这个常量,在classloader加载这个类的时候就把 "xyz "和这个类的其他一些信息放在constant pool;
new String( "xyz ")是根据常量 "xyz "在heap上创建String对象;
s只不过是stack上的一个引用,指向heap上的String对象。
所以,一共两个对象 。
6、String类中每一个看起来会修改String的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动。
代码示例:
public class Immutable {
public static String upcase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
String q = "howdy";
System.out.println(q); // howdy
String qq = upcase(q);
System.out.println(qq); // HOWDY
System.out.println(q); // howdy
}
} /*
* Output: howdy HOWDY howdy
*/// :~
解释:当把q传给upcase( )方法时,实际传递的是q引用(q变量)的一个副本。其实,每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未修改过(类似于指针?)。
再看upcase( )的定义,引用传入其中后就有了名字s,只有upcase( )中的代码运行的时候,局部引用s才存在。一旦upcase( )运行结束,s就消失了。当然,upcase( )的返回值,其实是最终结果的引用,并且这个最终结果是一个新的对象,因此这个引用就指向了新的对象,而原来的q被原封不动的留下了。
三、String类方法查找的基本思路
一旦操作字符串,先找String对象中的方法。没有时在进行自定义,而自定义过程中,往往是多个String方法的组合完成
1、明确所需功能的有无参数,返回值类型。另外,若明确了返回值类型,可以这样写来缩小查找范围。
字符串中多少个字符串啊?字符串的长度
int len = str.length();
2、其中一个字符在字符串中的哪个位置上?
indexOf(int ch):ch是char的简写。char根据ACII码转换成数字。
int index = str.indexOf('a');//'a'在字符串中第一次出现的位置。
而lastIndexOf('a');反向索引第一次出现的角标位置。
int index = str.indexOf('a',2);是从字符串2角标位置向后索引'a'第一次出现的位置。
3、具体一个子串在该字符串的哪个位置出现?
字符串是"abcnbacd"
int index2 = str.indexOf("cba");
System.out.println("index2="+index2); 象这样的索引方法
好处一:获取具体的位置。好处二:还可以判断被索引的内容是否存在。通过-1来判断即可。
4、指定位置上的字符是什么?
char ch = str.charAt(8);//StringIndexOutOfBoundsException 访问到字符串中不存在的角标就会发生该异常。
5、能不能获取到字符串中的指定的子串。
结果:string,参数:两个int。索引index。
String sub_str = str.substring(2, 5);包含头角标,不包含尾角标。这是规律。
str.substring(0,str.length());截取整个字符串
6、如何将这个字符串变成大写的字符串呢?
结果:String,参数,无。
String upper_str = str.toUpperCase();都变成了大写
toLowerCase();都变成了小写。
String类中的toString()方法:返回此对象本身(它已经是个字符串)
四、分类练习及综合练习
练习1、将字符串中的指定字符串替换成给定字符串。
replaceAll(String regex ,String replacement):regex:正则表达式
replace(CharSequence target,CharSequence replacement)
String s = str.replace("nba", "haha");
如果要被替换的字符串不存在,结果又是什么?返回原串。
练习2、将字符串变成多个字符(char[]字符数组).
char[] chs = str.toCharArray();//将字符串转成字符数组。
练习3、字符串是否包含指定的子串。以及是否是以指定字符串开头或者结尾。
结果:boolean 参数:string.
boolean b = str.contains("Demo");是否包含
boolean b1 = str.endsWith(".java");是否以指定的字符串结尾
startsWith:是否以指定字符串开头
练习4、将给定的字符串"zhangsan,lisi,wangwu"获取其中每一个人的姓名。
分析:意味着获取多个字符串。
结果:String[] 参数:指定的方式。
str = "zhangsan,lisi,wangwu";
String[] names = str.split(",");
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
综合练习1:将一个字符串进行反转。将字符串中指定部分进行反转,"abcdefg";abfedcg
思路:
(1)曾经学习过对数组的元素进行反转。
(2)将字符串变成数组,对数组反转。
(3)将反转后的数组变成字符串。
(4)只要将或反转的部分的开始和结束位置作为参数传递即可。
代码:
class StringTest {
public static void sop(String str) {
System.out.println(str);
}
public static void main(String[] args) {
String s = " ab cd ";
sop("(" + s + ")");
sop("(" + reverseString(s) + ")");
}
public static String reverseString(String s, int start, int end) {
// 字符串变数组。
char[] chs = s.toCharArray();
// 反转数组。
reverse(chs, start, end);
// 将数组变成字符串。
return new String(chs);
}
public static String reverseString(String s) {
return reverseString(s, 0, s.length());
}
private static void reverse(char[] arr, int x, int y) {
for (int start = x, end = y - 1; start < end; start++, end--) {
swap(arr, start, end);
}
}
private static void swap(char[] arr, int x, int y) {
char temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
}
/*output:
( ab cd )
( dc ba )
*/
综合练习2:模拟一个trim方法,去除字符串两端的空格。
思路:
(1)判断字符串第一个位置是否是空格,如果是继续向下判断,直到不是空格为止。结尾处判断空格也是如此。
(2)当开始和结尾都判断到不是空格时,就是要获取的字符串。
代码:
class StringTest {
public static void sop(String str) {
System.out.println(str);
}
public static void main(String[] args) {
String s = " ab cd ";
sop("(" + s + ")");
s = myTrim(s);
sop("(" + s + ")");
}
public static String myTrim(String str) {
int start = 0, end = str.length() - 1;
while (start <= end && str.charAt(start) == ' ')
start++;
while (start <= end && str.charAt(end) == ' ')
end--;
return str.substring(start, end + 1);
}
}
/*output:
( ab cd )
(ab cd)
*/
综合练习3:获取一个字符串在另一个字符串中出现的次数。"abkkcdkkefkkskk"
思路:
1,定义个计数器。
2,获取kk第一次出现的位置。
3,从第一次出现位置后剩余的字符串中继续获取kk出现的位置。
每获取一次就计数一次。
4,当获取不到时,计数完成。
代码:
class StringTest {
/*
* 练习三。
*/
public static int getSubCount(String str, String key) {
int count = 0;
int index = 0;
while ((index = str.indexOf(key)) != -1) {
sop("str=" + str);
str = str.substring(index + key.length());
count++;
}
return count;
}
/*
* 练习三,方式二。
*/
public static int getSubCount_2(String str, String key) {
int count = 0;
int index = 0;
while ((index = str.indexOf(key, index)) != -1) {
sop("index=" + index);
index = index + key.length();
count++;
}
return count;
}
public static void main(String[] args) {
String str = "kkabkkcdkkefkks";
// /sop("count====="+str.split("kk").length);不建议使用。
sop("count=" + getSubCount_2(str, "kk"));
}
public static void sop(String str) {
System.out.println(str);
}
}
/*output:
index=0
index=4
index=8
index=12
count=4
*/
综合练习4:获取两个字符串中最大相同子串。
第一个动作:将短的那个串进行长度依次递减的子串打印。
"abcwerthelloyuiodef"
"cvhellobnm"
思路:
1,将短的那个子串按照长度递减的方式获取到。
2,将每获取到的子串去长串中判断是否包含,如果包含,已经找到!。
代码:
class StringTest {
/*
* 练习四。
*/
public static String getMaxSubString(String s1, String s2) {
String max = "", min = "";
max = (s1.length() > s2.length()) ? s1 : s2;
min = (max == s1) ? s2 : s1;
// sop("max="+max+"...min="+min);
for (int x = 0; x < min.length(); x++) {
for (int y = 0, z = min.length() - x; z != min.length() + 1; y++, z++) {
String temp = min.substring(y, z);
sop(temp);
if (max.contains(temp))// if(s1.indexOf(temp)!=-1)
return temp;
}
}
return "";
}
public static void main(String[] args) {
String s1 = "ab";
String s2 = "cvhellobnm";
sop(getMaxSubString(s2, s1));
}
public static void sop(String str) {
System.out.println(str);
}
}
/*output:
ab
a
b
b
*/
五、StringBuffer
1、定义:
字符串的缓冲区,是一个容器。
2、特点:
(1)该容器的长度是可变的(和String的区别)。
(2)可以直接操作多个数据类型,基本类型和引用类型。(数组一次只能操作一种类型)
(3)提供了对容器中内容的操作的方法(最多无外乎四种:增删改查。)
(4)无论怎么样的改变容器中的数据,最终要使用结果,还是必须要将其转成字符串,使用toString方法
(5)适用场合:数据类型不确定,数据个数不确定,而且最终要变成字符串时
3、特有方法:
A:增加(存储)数据
**StringBuffer append():将指定数据作为参数添加到已有数据结尾处。
**StringBuffer insert(index,数据):可以将数据插入到指定index位置。
B:删除数据
**StringBuffer delete(start,end):删除缓冲区中的数据,包含start,不包含end。
**StringBuffer deleteCharAt(index):删除指定位置的字符
**delete 还可以用于清空StringBuffer的缓冲区
C:替换(修改)
**StringBuffer replace(start,end,string);
**void setCharAt(int index, char ch) ;
D:获取
**char charAt(int index)
**int indexOf(String str)
**int lastIndexOf(String str)
**int length()
**String substring(int start, int end)(注意:是String,不是StringBuffer)
E:长度和容量
**length() 元素的个数
**capacity 元素的理论值
F:获取元素的位置
**indexOf
**lastIndexOf
G:截取
**substring(int start)
**substring(int start,int end)
H:反转
** StringBuffer reverse();
I:将缓冲区中指定数据存储到指定字符数组中。
**void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
4、细节:
(1)String str = "abc"+4+'c';这句话的实际底层操作其实是:
str = new StringBuffer().append("abc").append(4).append('c').toString();
(2)创建一个字符串缓冲区。StringBuffer sb = new StringBuffer("abcd");
StringBuffer s1 = sb.append(45);
System.out.println(sb==s1);//true,append返回的还是原来的缓冲区。
sb.append(45).append(true).append("abc");//连续添加,调用动作:方法调用链
append():追加:在结尾处添加。
(3)在任意位置添加 insert方法。sb.insert(1, true);
删除内容不是delete就是remove
sb.delete(0,sb.length());//清空缓冲区。
(4) 修改内容:sb.replace(1, 3, "ak47");修改(替换)一段内容
(5)字符串反转System.out.println(sb.reverse());
5、字符串和StringBuffer的转换
String-->StringBuffer通过构造:
如:StringBuffer sb = new StringBuffer(String str)
StringBuffer--String通过toString方法
如:StringBuffer sb = new StringBuffer();
sb.toString();
六、StringBuilder
1、和StringBuffer的区别
和StringBuffer的功能是一样的,但是有区别:
StringBuffer(JDK1.0)是线程安全的。
StringBuilder(JDK1.5)不保证线程安全。
一般来说,我们写的程序都是单线程的,所以,用StringBuilder,效率高。
2、StringBuffer和数组容器的区别?
(1)数组容器是固定长度的。
StringBuffer是可变长度的。
(2)数组容器一旦初始化就明确了元素的类型。
StringBuffer可以存储任意类型。包括基本和引用。
(3)组存储完元素可以对元素进行操作(通过角标)。
StringBuffer存储完元素后,都会变成字符串,只能用字符串的方法来操作。
3、什么时候用StringBuffer或者StringBuilder?
数据个数可以是固定的,可是是不固定的,数据类型也可以是固定的,或者不固定的。 只要最终这些数据都需要变成字符串来操作时,就可以使用字符串缓冲区。