1、String类特点
String类是final类,因此其没有子类,不能继承。之前提到的方法区,里面包含了方法的代码、静态数据,常量池也在方法区里面(JDK1.7后常量池移到堆中)。常量池的数据是固定不变的,字符串就在常量池中。
class StringDemo
{
public static void main(String[] args)
{
/*
//String有一个空参数的构造方法String(),下面2种创建String类对象的方法是一样的,都是创建一个空的String对象
String s = new String();
String s1 = "";
//s2是一个(String)类类型变量, "abc"是一个对象。字符串是一个特殊的对象!
//字符串最大特点:一旦被初始化就不可以被改变。字符串既是一个常量,也是一个对象。
String s2 = "abc";
s2 = "lll";
System.out.println(s2);//结果是“lll”
//这种情况下,字符串“abc”并没有变化,还是在内存中,而s2指向了内存中的另一块区域“lll”。s2指向的地址变了,而“abc”对象的内容没有变化。
*/
//这一段参考文章:https://blog.csdn.net/zp357252539/article/details/82758877
//视频是10分50秒处的解释
//这种形式的赋值存放在常量池中而不是存放在堆中,下一次创建内容相同的String对象s3,仍然会指向该常量池中的内存。s1与s3指向同一个对象。
String s1 = "abc";
String s2 = new String("abc");
String s3 = "abc";
System.out.println(s1==s2);//"=="比较的是内存地址值是否相同(false)
System.out.println(s1.equals(s2));//String类复写了Object类中equals方法,该方法用于判断字符串内容是否相同。(true)
//s1和s2有什么区别?(见视频13-1,7分钟开始处解释):s1在内存中有一个对象,s2在内存中有2个对象
System.out.println(s1==s3);//true
}
}
2、String常见功能——获取和
String类适用于描述字符串事物,那么它就提供了多个方法对字符串进行操作。常见的操作如下:
1、获取。
1.1 字符串中的包含的字符数,也就是字符串的长度。
int length():获取长度。
1.2 根据位置获取位置上某个字符。
char charAt(int index):
1.3 根据字符获取该字符在字符串中位置。
int indexOf(int ch):返回的是ch在字符串中第一次出现的位置。
(注意,这个方法接受的是我们想获取字符的ASCII码,所以使用int类型)
int indexOf(int ch, int fromIndex) :从fromIndex指定位置开始,获取ch在字符串中出现的位置。
int indexOf(String str):返回的是str在字符串中第一次出现的位置。
int indexOf(String str, int fromIndex) :从fromIndex指定位置开始,获取str在字符串中出现的位置。
int lastIndexOf(int ch) :反向索引字符出现的位置。
2、判断。
2.1 字符串中是否包含某一个子串。
boolean contains(str):indexOf(str):可以索引str第一次出现位置,如果返回-1.表示该str不在字符串中存在,所以,也可以用于对指定判断是否包含:if(str.indexOf("aa")!=-1)。 而且该方法即可以判断,又可以获取出现的位置。
2.2 字符中是否有内容。
boolean isEmpty(): 原理就是判断长度是否为0.
2.3 字符串是否是以指定内容开头。
boolean startsWith(str);
2.4 字符串是否是以指定内容结尾。
boolean endsWith(str);
2.5 判断字符串内容是否相同。复写了Object类中的equals方法。
boolean equals(str);
2.6 判断内容是否相同,并忽略大小写。
boolean equalsIgnoreCase();
3、转换
3.1 将字符数组转成字符串。
构造函数:String(char[])
String(char[],offset,count):将字符数组中的一部分转成字符串。
静态方法:
static String copyValueOf(char[]);
static String copyValueOf(char[] data, int offset, int count)
static String valueOf(char[]):
3.2 将字符串转成字符数组。
char[] toCharArray():
3.3 将字节数组转成字符串。
String(byte[])
String(byte[],offset,count):将字节数组中的一部分转成字符串。
3.4 将字符串转成字节数组。
byte[] getBytes():
3.5 将基本数据类型转成字符串。
static String valueOf(int)
static String valueOf(double)
//3+"";//String.valueOf(3);
这两种方法是一样的,都是将int数据类型转换为String数据类型
特殊:字符串和字节数组在转换过程中,是可以指定编码表的。
4、替换
String replace(oldchar,newchar);
5、切割
String[] split(regex);
6、子串。获取字符串中的一部分。
String substring(begin);
String substring(begin,end);//注意end不包含在获取的字段
7、转换,去除空格,比较。
7.1 将字符串转成大写或则小写。
String toUpperCase();
String toLowerCase();
7.2 将字符串两端的多个空格去除。
String trim();
7.3 对两个字符串进行自然顺序的比较。
int compareTo(string);
相应的测试代码如下:
package pack;
class StringDemo
{
public static void main(String[] args)
{
// StringDemo.method_get();
// StringDemo.method_is();
// StringDemo.string_trans();
// StringDemo.method_replace();
// StringDemo.method_split();
// StringDemo.method_sub();
StringDemo.method_7();
}
//获取
public static void method_get()
{
String str = "abcdefabcdef";
//获取字符串长度
//"字符串str的长度为"+str.length():这一部分相当于一个String类的对象,我们在sop方法里面直接打印obj对象即可打印出相应的内容
sop("字符串str的长度为:"+str.length());
//获取某个位置上的字符
sop("字符串str第3位置上的字符为:"+str.charAt(3));
//获取字符与字符串在字符中第一次的位置
sop("字符“c”在字符串中第一次出现的位置是:"+str.indexOf('c'));
sop("字符串“cd”在字符串中第一次出现的位置是:"+str.indexOf("cde"));
//从某个位置开始,获取字符与字符串在字符中的位置
sop("从位置3开始,字符“c”在字符串中第一次出现的位置是:"+str.indexOf('c',3));
//反向索引字符出现的位置。
sop("字符“c”,反向索引字符出现的位置是:"+str.lastIndexOf('c'));
}
//判断
public static void method_is()
{
String str = "abcdefabcdef";
String str1 = "ABCDEFABCDEF";
//字符串中是否包含某一个子串
sop("str中包含“dd”?"+str.contains("dd"));
if(str.indexOf("dd")!=-1)//如果没有查找到“dd”,就返回-1
System.out.println("str中包含“dd”");
else
System.out.println("str中不包含“dd”");
//字符中是否有内容
sop("str是空的?"+str.isEmpty());
//字符串以指定内容开始或者结尾
sop("str以ab开头?"+str.startsWith("ab"));
sop("str以ab结尾?"+str.endsWith("ab"));
//内容是否相等
sop(str.equals(str1));
sop(str.equalsIgnoreCase(str1));//忽略大小写
}
//转换
public static void string_trans()
{
char[] arr = {'a','b','c','d','f','g'};
//通过构造方法创建字符数组的String对象
String s1 = new String(arr);
String s2 = new String(arr,2,3);
//通过普通方法创建字符数组的String对象
String s3 = String.copyValueOf(arr);
String s4 = String.copyValueOf(arr, 2, 3);
sop(s1);
sop(s2);
sop(s3);
sop(s4);
// 将基本数据类型转成字符串
String s5 = String.valueOf(2);
String s6 = String.valueOf(2.456d);
sop(s5);
sop(s6);
sop(2.5+"");//同样是将int数据类型转换为字符串
//将字符串转成字符数组
char[] arr2 = s1.toCharArray();
//将字符串转成字节数组
byte[] arr3 = s1.getBytes();
for(int x=0;x<arr3.length;x++)
{
System.out.print(arr3[x]+" ");//97 98 99 100 102 103 :打印abcdef的ASCII码
}
}
//替换
public static void method_replace()
{
String s1 = "hello java";
String s2 = s1.replace('l', 'w');//如果要替换的字符不存在,返回的还是原串。
sop(s2);
//替换字符串
String s3 = s1.replace("java", "world");
sop(s3);
}
//切割
public static void method_split()
{
String s = "zhagnsa,lisi,wangwu";
String[] arr = s.split(",");//按照“,”将字符串切割为字符串数组,切割后“,”不会存在
for(int x = 0; x<arr.length; x++)
{
sop(arr[x]);
}
}
//子串
public static void method_sub()
{
String s1 = "abcdefg";
sop(s1.subSequence(2, 5));//从指定位置开始到结尾。如果角标不存在,会出现字符串角标越界异常。(注意这里5位置不包含在子串中)
sop(s1.substring(3));
sop(s1.substring(0,s1.length()));//获取整个字符串
//获取子串也可以实现上面切割的功能,但是要先获取“,”的位置,再一部分一部分获取子串,比较麻烦
}
//转换,去除空格,比较
public static void method_7()
{
String s1 = " HELLO java ";
//大小写转换
sop(s1.toLowerCase());
sop(s1.toUpperCase());
//去除两边空格
sop(s1.trim());
//对两个字符串进行自然顺序的比较
String s2 = "a1c";
String s3 = "aaa";
//该比较基于字符串中各个字符的 Unicode 值,既ASCII码值
sop(s2.compareTo(s3));//-48:第二个字符1的ASCII值是49,a的ASCII值是97,49-97=-48
}
//我们发现每一次打印都需要写那么多打印的代码,我们干脆将打印的功能封装成方法
//由于基本数据类型都有其包装类(除了String),他们都是继承Object类,
//因此我们可以使用Object类的对象来表示基本数据类型的对象,这是基本数据类型的向上类型转换
public static void sop(Object obj)
{
System.out.println(obj);
}
}
3、字符串练习
练习1:实现trim()方法
/*
1、模拟一个trim方法,去除字符串两端的空格。
思路:
1)判断字符串第一个位置是否是空格,如果是继续向下判断,直到不是空格为止。结尾处判断空格也是如此;
2)当开始和结尾都判断到不是空格时,就是要获取的字符串。
*/
package pack;
class StringDemo
{
public static void main(String[] args)
{
String s1 = " hello world ";
sop("("+s1+")");
sop("("+myTrim(s1)+")");
}
public static String myTrim(String str)
{
//答案与我的思路是一样的,不过这里使用while循环会比for循环好
int start = 0 , end = str.length()-1;//开头结尾用start与end表示更加好
//首先start不得小于end否则说明整个str都是空格,那么trim方法也就没有意义。其次判断每一个字符是否为空格,用charAt获取字符
while(start<=end && str.charAt(start) == ' ')//我们其实也可以写start++,但是这样while循环没有语句,因此将++设为while循环语句
start++;
//去除结尾空格也是一样的方法,从结尾判断过来。同样判断start<=end
while(start<=end && str.charAt(end) == ' ')
end--;
//我们已经获取到了字符不为空段开头与结尾的下标,用获取子串方法substring(begin,end)
return str.substring(start,end+1);//这里end记得+1,因为substring的最后一位不包含在获取的字段
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
练习2:实现字符串反转与字符串指定部分的反转.
先贴上自己的实现
/*
/*
2、将一个字符串进行反转;将字符串中指定部分进行反转,"abcdefg";abfedcg
思路:
1)曾经学习过对数组的元素进行反转。
2)将字符串变成数组,对数组反转。
3)将反转后的数组变成字符串。
4)只要将或反转的部分的开始和结束位置作为参数传递即可。
*/
package pack;
class StringDemo
{
public static void main(String[] args)
{
String s = "abcdefg";
sop(s+"部分反转后的结果是:"+reverseString(s,1,4));
sop(s+"全部反转后的结果是:"+reverseString(s));
}
//字符串指定内容反转方法
public static String reverseString(String str , int start , int end)
{
char[] arr = str.toCharArray();//将字符串转换为字符串数组
arr = arrayReverse(arr,start,end);
// return String.copyValueOf(arr);
//当然这里也可以利用构造方法将字符串数组转换为字符串
return new String(arr);
}
//整个字符串反转的方法。这个方法我们可以重载指定内容反转的方法,这样比较方便!注意这种技巧!
public static String reverseString(String str)
{
return reverseString(str,0,str.length()-1);//调用字符串指定内容反转方法来实现全部反转的方法。
}
//数组内容反转的方法(该方法不需要暴露给外面,直接私有)
private static char[] arrayReverse(char[] arr , int start , int end)
{
//(end-start)/2的结果必然是int类型(2个int类型的变量做运算),因此如果start与end的差是1,(end-start)/2结果就是0,因此要加“=”,否则不会转换
for(int x=0; x<=(end-start)/2 ; x++)
{
char temp = arr[start+x];
arr[start+x] = arr[end-x];
arr[end-x] = temp;
}
//for循环部分不再引入多余变量x,而是直接用start与end表示
for(; start<end ; start++,end--)
{
swap(arr,start,end);
}
return arr;
}
//交换的方法
private static void swap(char[] arr,int x,int y)
{
char temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
下面是答案的实现:参考StringTest.java文件,和我的答案差不多!只要注意一般end不算在反转范围内即可。
练习3:取两个字符串中最大相同子串(这一题比较有意思),先上自己的答案:
/*
4、取两个字符串中最大相同子串。第一个动作:将短的那个串进行长度一次递减的子串打印。
"abcwerthelloyuiodef"
"cvhellobnm"
思路:1,将短的那个子串按照长度递减的方式获取到。(我们操作长度较小的字符串,这样操作的次数少一点)
2,将每获取到的子串去长串中判断是否包含,如果包含,已经找到!。
*/
package pack;
class StringDemo
{
public static void main(String[] args)
{
String s1 = "cdlobn";
String s2 = "abcdlobnm";
sop("s1与s2的最大相同子串为:"+getMaxSubString(s1,s2));
}
public static String getMaxSubString(String str1 , String str2)
{
String strMax = null;//创建变量strMax用于保存获取的最长子串,初始值赋值为null
boolean breakSign = false;//设置跳出循环的标志
//先取得str1与str2中长度较小者
String shorterStr = str1.length()<=str2.length()? str1 : str2 ;
//同样的方式取得str1与str2中长度较大者
String longerStr = str1.length()>str2.length()? str1 : str2 ;
//首先,用外循环的变量x控制每一轮判断的最长子串的长度:x的最大值为shorterStr.length(),最小值应该大于0,x若小于0,判断的最长子串长度为零,便没有意义
for(int x = shorterStr.length() ; x>0 ; x--)
{
//接下来,用内存循环变量y控制子串最开始取的位置,y从0开始,而每一次比较的最长子串长度为y+x
//y+x的最大值必须小于等于shorterStr.length()
for(int y = 0; y+x <= shorterStr.length() ; y++)
{
strMax = shorterStr.substring(y,y+x);
if(longerStr.contains(strMax))
{
// break;注意!!!因为break只跳出一层循环,因此错误,我们设置一个跳出循环的标志来跳出外层循环
breakSign = true;
break;//先将跳出循环标记置true,再跳出这层循环
}
else
strMax = null;//如果不存在我们必须将strMax重新置null,否则最后比较到不能比较的时候,strMax会变成shorterStr的最后一个字符
}
if(breakSign == true)
break;//当breakSign为true的时候,跳出外层循环
}
return strMax;
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
答案的方法
/*
注意:2种方法的区别不大,除了一些小技巧,最大的区别就是在2个for循环的时候变量设置不一样,可以试用答案的方法
*/
class StringTest3
{
/*
练习四。
*/
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++)//这里同样用x来控制每一轮比较子串的长度
{//注意这里的技巧:设置一个y用来控制子串开头,而设置一个z用来控制子串的结尾。y与z是同增的,那么每一轮获得的子串长度都是一样的
//当z增加到max.length()+1的时候,说明这一轮比较完毕。这里max.length()可以取到,因为下面的substring方法不会取到max.length()的点。
for(int y=0,z=min.length()-x; z!=max.length()+1; y++,z++)
{
String temp = min.substring(y,z);
sop(temp);
if(max.contains(temp))//if(s1.indexOf(temp)!=-1)
return temp;//获得最长子串则自接返回。不用break,用break还比较麻烦!
}
}
return "";//没获得子串则返回空字符串,上面的连续break的方法跳出循环太麻烦了,找到直接return,最后没找到就return null就可以。
}
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);
}
}
4、StringBuffer类
StringBuffer的特点如下:
StringBuffer是字符串缓冲区,是一个容器,长度可变化。因此其可以C create U update R read D delete(增删改查)(CURD)
特点:
1、长度是可变化的,既StringBuffer类字符串可以修改(修改内存中对象的内容),通过append(),insert()等方法修改这块内存区域的内容,对比String,String的对象一当创建便不可以修改,因此String没有这些方法。
2、可以直接操作多个数据类型。(数组一次只能操作一个类型)
3、最终会通过toString方法变成字符串。
什么时候使用:当我们要操作的数据类型不确定,数据个数不确定,最后要变成字符串,那么StringBuffer缓冲区是最方便的!
1、存储。
StringBuffer append():将指定数据作为参数添加到已有数据结尾处。
StringBuffer insert(index,数据):可以将数据插入到指定index位置。
2、删除。
StringBuffer delete(start,end):删除缓冲区中的数据,包含start,不包含end。
StringBuffer deleteCharAt(index):删除指定位置的字符。
3、获取。
char charAt(int index)
int indexOf(String str)
int lastIndexOf(String str)
int length()
String substring(int start, int end)
4、修改。
StringBuffer replace(start,end,string);
void setCharAt(int index, char ch) ;
5、反转。
StringBuffer reverse();
6、 将缓冲区中指定数据存储到指定字符数组中。
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
JDK1.5 版本之后出现了StringBuilder.
StringBuffer是线程同步。
StringBuilder是线程不同步。
以后开发,建议使用StringBuilder
升级三个因素:
1,提高效率。
2,简化书写。
3,提高安全性。
添加功能
package pack;
class StringDemo
{
public static void main(String[] args)
{
StringBuffer sb1 = new StringBuffer();
StringBuffer sb2 = sb1.append("def");
sop(sb1.toString());//我们可以通过toString()方法将StringBuffer对象变为字符串String,打印的时候其实也可以直接输出
sop(sb2.toString());
sop(sb1==sb2);//true
//2个的结果都是:def。sb1与sb2指向的是同一个对象(同一块内存)
//也就是说,我们不需要再创建一个变量sb2来表示(因为只有一块内存),如下,如果我们想给sb3添加内容,直接调用append添加即可
StringBuffer sb3 = new StringBuffer();
sop(sb3.append("abc").append("acvdas").append("cdasv"));//调用链:对象调用一个方法还是返回哪个对象,就可以使用这种调用链(也叫做链式编程)
//insert()方法
StringBuffer sb4 = new StringBuffer("lkjlkj");
sop(sb4.insert(3,"hhh"));//insert将第三位置及其以后的内容向后推,将“hhh”从第三位开始插入
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
删除、获取、修改、反转功能
package pack;
class StringDemo
{
public static void main(String[] args)
{
//删除
StringBuffer sb1 = new StringBuffer("abcdefg");
// sb1.delete(1, 4);
// sb1.deleteCharAt(2);
//清空缓冲区
sb1.delete(0, sb1.length());
sop(sb1.toString());//一般都是用StringBuffer的方法先操作内存,再用toString()方法打印
//获取较为简单,与String内容类型不多说
//修改
StringBuffer sb2 = new StringBuffer("abcdefg");
sb2.replace(1, 4, "hhh").append('a');//返回StringBuffer类,可以使用调用链
sb2.setCharAt(3, 'k');//返回void,不可以使用调用链
sop(sb2.toString());
//对于使用完还返回StringBuffer类的方法,他们并不会新增一块内存区域,还是操作原来的对象,那么他们就可以使用调用链
//反转
StringBuffer sb3 = new StringBuffer("abcdefg");
sb3.reverse();
sop(sb3.toString());
//将缓冲区中指定数据存储到指定字符数组中
StringBuffer sb4 = new StringBuffer("abcdefg");
char[] arr = {'1','2','3','4','5','6','7','8','9'};
sb4.getChars(1, 4, arr, 6);//获取sb4的1,2,3位的字符,并将其存储到字符串数组arr的6角标开始的位置
for(int x=0; x<arr.length; x++)
{
sop(arr[x]);
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
5、StringBuilder类
一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。
一般单线程我们使用StringBuilder类,多线程使用StringBuffer,多线程也可以使用StringBuilder,我们自己加锁。
Java的升级围绕3个部分:提高效率、简化书写、提高安全性
6、字符串相关重要知识补充
关于String字符串2种创建方法所创建对象在内存中的分配方式,见如下文章:文章1
需要注意的一点是jdk7之前是常量池是在方法区(永久代)中,之后的版本常量池(包括字符串池)则移到了堆中。
另外,假如说的有String m = “abc”; ,这时的abc常量被保存在常量池中,如果我要在后面继续拼接一个d,也就是 m = m+“d”; 拼接成一个新的字符串常量,这时候就会在常量池中创建三个字符串常量,也就是说,这三个字符串常量就是adc、和我们拼接的d,和拼接之后adcd,所以说非常消耗内存的,我刚刚说的显示的字符常量存在于常量池里,而new 出来的String对象则存在于堆中。
关于字符串的拼接,还可以参考如下文章:
文章2
String常量池与运行时常量池
字符串常量池,Class常量池、运行时常量池
注意下面图片,“a”、“b”、“c”、“ab”、“abc”都是独立的数组,虽然他们存放在常量池中,但是最后会创建多个数组(字符串)。但是现在编译器会优化,直接将全部是字符串值的字符串自动视为一个字符串,既只有一个数组。
再下面是StringBuilder的图解
字符串常量池的分析——见就业班-day08-03视频。
从JDK1.7开始,字符串常量池在堆里面,而Class常量池与运行时常量池仍然是在方法区。而且字符串常量池中保存字符串对象也是底层字节数组的地址值。具体图解如下:
String类的相关方法说明
首先是String方法
public boolean equals(Object obj):参数可以是任何对象,只有参数是一个字符串并且内容相同的才会给true;否则返回false。
注意事项:
1. 任何对象都能用Object进行接收。
2. equals方法具有对称性,也就是a.equals(b)和b.equals(a)效果一样。
3. 如果比较双方一个常量一个变量,推荐把常量字符串写在前面。
推荐:"abc".equals(str) 不推荐:str.equals("abc"),原因如下代码:
String str5 = null;
System.out.println("abc".equals(str5)); // 推荐:false
//System.out.println(str5.equals("abc")); // 不推荐:报错,空指针异常NullPointerException
public boolean equalsIgnoreCase(String str):忽略大小写,进行内容比较。
其次是split()方法
分割字符串的方法:
public String[] split(String regex):按照参数的规则,将字符串切分成为若干部分。
注意事项:
split方法的参数其实是一个“正则表达式”,今后学习。
今天要注意:如果按照英文句点“.”进行切分,必须写"\\."(两个反斜杠)
这个"\\."在正则表达式中表示".",如果只加一个"\",就会被认为是转义字符,再加一个"\",2个"\"加一个"."就表示是一个"."
String str3 = "XXX.YYY.ZZZ";
String[] array3 = str3.split(".");//应该写"\\."
System.out.println(array3.length); // 0
for (int i = 0; i < array3.length; i++) {
System.out.println(array3[i]);
}