1.字符串创建对象的方式:
(1) String str = "hello";
这种方式创建字符串的时候,Jvm首先会检查字符串常量池中是否存在该字符串的对象,如果已经存在,那么就不会在字符串常量池中再创建了,直接返回该字符串在字符串常量池中的内存地址;如果该字符串在字符串常量池中并不存在,那么就会在字符串常量池中先创建该字符串的对象,然后再返回。
(2) new String("hello");
这种方式创建字符串对象的时候,Jvm首先会检查字符串常量池中是否存在“hello”的字符串,如果已经存在,则不会在字符串常量池中创建了;如果还没有存在,那么就会在字符串常量池中创建“hello”字符串对象,然后还会去堆内存中再创建一份字符串的对象,把字符串常量池中的"hello字符串内容拷贝到堆内存中的字符串对象,然后返回堆内存中字符串对象的内存地址。
2.实例一
(1) 实例:
public class Demo1 { public static void main(String[] args) { String str1 = "hello"; String str2 = "hello"; String str3 = new String("hello"); String str4 = new String("hello"); System.out.println("str1==str2?"+(str1==str2)); // true System.out.println("str2==str3?"+(str2==str3)); //false System.out.println("str3==str4?"+(str3==str4)); // false System.out.println("str3.equals(str4)?"+(str3.equals(str4))); //true } }(2) 运行结果:
(3) 分析:
①首先会在栈内存中声明一个变量str1,然后用双引号引起创建字符串的对象,这时候Jvm就会检查字符串常量池中是否有hello,没有的话就会在字符串常量池中先创建这个对象,然后把这个对象的内存地址返回给str1,假设它的内存地址是0x97,这时候返回给str1的就是0x97,这时候str1是指向了内存地址为0x97所存储的字符串对象。
②然后又声明了一个变量str2,用双引号引起创建字符串的对象,这时候Jvm就会检查字符串常量池中是否有hello,发现字符串常量池中已经有了hello,那么就不会在字符串常量池中再创建了。直接把字符串常量池中hello的内存地址返回给这个变量就可以,也就是说str2记录的内存地址也是0x97.所以现在str1和str2指向的是同一个对象,所以运行结果为true。
③首先在栈内存中声明一个变量str3,当new String的时候,里面有一个双引号引起来的字符串,有双引号引起来的都会先到字符串常量池中先检查,Jvm在字符串常量池中检查的时候发现已经存在hello,就不会在字符串常量池中再创建了,接着有个new关键字,凡是遇到new关键字,Jvm都会在堆内存中开辟一块新的内存空间,这时候会在堆内存中创建一个堆内存的对象,接着会把字符串常量池中的hello的内容拷贝到堆内存创建的对象中去,接着会把堆内存的内存地址返回给str3,比如说它的内存地址是0x78,str3记录的则是0x78的内存地址,它指向的是堆内存中创建的对象的内存地址。
④在栈内存中声明一个变量str4,字符串常量池中有hello,那么就不会再创建,会到堆内存中创建一个字符串对象,把字符串常量池中的内容拷贝到堆内存中创建的对象中,比如说它的内存地址是0x76,这时候栈内存中str4记录的就是0x76内存地址所存储的对象。
(4) 疑问:str3和str4的内存地址并不一样,为什么运行结果返回的是true?
虽然equals方法默认情况下(注意是默认)比较的是两个对象的内存地址,而现在用到的是字符串的对象,那么我们就不允许字符串的类对equals方法进行重写吗?字符串也可以重写equals方法,重写后就可以不遵循默认的情况,因为看的是String中有没有重写equals方法,所以应该查看String的源代码,查看String的源代码后我们发现,重写后的equals方法比较的是两个字符串对应的字符是否一致。
3.为什么比较字符串的时候建议使用equals而不是==号?
因为==号比较的是内存地址,但是我们在现实开发中一般都是比较内容是否一致,所以就不建议使用==号,如果使用==号,str2和str3就会是false,使用equals,只要两个字符串的内容一致那么返回的结果都是一致的。
注意:"=="用于比较 引用数据类型数据的时候比较的是两个对象的内存地址,equals方法默认情况下比较的也是两个对象的内存地址。
4. new String("abc")创建了几个对象?
创建了两个对象,一个对象是位于字符串常量池中,一个对象是位于堆内存中。
5.实例二
(1) 实例:
public class Demo2 { public static void main(String[] args) { test(null); } public static void test(String str){ if(str.equals("中国")) { System.out.println("回答正确"); }else{ System.out.println("回答错误"); } } }(2) 运行结果:
(3) 分析:
首先str.equals("中国")运行后就会报空指针异常,如何避免这种错误?
那么就需要使用"中国".equals(str),这也是最简便的一种方式,调换位置后,"中国"是个常量,也就是这个常量在调用equals方法,这样就变成了常量是这个方法的调用者,变量作为参数这样就可以避免永远都不会出现空指针异常。因为如果函数的调用者是一个变量,这个变量有可能指向一个空的对象;如果函数的调用者是一个常量,常量不可能为空,永远都不可能为空也就不会出现空指针异常。这是一个编程习惯,使用equals方法时,我们一般都会让常量作为这个方法的调用者,这样就可以避免空指针异常的出现。
(4) 修改后的实例:
public class Demo3 { public static void main(String[] args) { test(null); } public static void test(String str){ if("中国".equals("str")) { System.out.println("回答正确"); }else{ System.out.println("回答错误"); } } }(5) 运行结果:
注意:使用字节数组或者字符数组都可以构建一个字符串对象。
1.String();
(1) 该方法用于创建一个空内容的字符串对象。
(2) 实例:
//String类的无参的构造方法。 public class Demo4 { public static void main(String[] args) { String str = new String();//调用String类的无参的构造方法,相当于String str = " "; System.out.println("字符串的内容是:"+str); } }(3) 运行结果:
2.String(byte[] bytes) ;
(1) 该方法用于使用一个字节数组构建一个字符串对象;
(2) 实例:
//String(byte[] bytes) 使用一个字节数组构建一个字符串对象 public class Demo5 { public static void main(String[] args) { byte[] buf = {97,98,99};//这些字节数组里存储的都是那些字符对应的码值 String str = new String(buf); //使用一个字节数组构建一个字符串对象 System.out.println("字符串的内容:"+str); } }(3) 运行结果:
3.String(byte[] bytes, int offset, int length)
(1) 参数详解:
bytes:要解码的数组(解码就是把数字转换成数字代表的字符)
offset:指定从数组中那个索引值开始解码。也就是说从字节数组中的哪一位开始查找对应的字符。length:要解码多少个元素。也就是要查找多少个字符。
(2) 实例:
public class Demo6 { public static void main(String[] args) { byte[] buf = {97,98,99}; String str = new String(buf,1,2);//使用一个字节数组构建一个字符串对象,指定开始解码的索引值和解码的个数 System.out.println("字符串的内容:"+str); } }(3) 运行结果:
4.String(char[] value);
(1) 使用一个字符数组构建一个字符串。
(2) 实例:
//String(char[] value) 使用一个字符数组构建一个字符串。 public class Demo7 { public static void main(String[] args) { char[] arr = {'马','上','毕','业','了'}; String str = new String(arr); //使用字符数组构建一个字符串 System.out.println("字符串的内容:"+str); } }(3) 运行结果:
5.String(char[] value, int offset, int count);
(1) 使用一个字符数组构建一个字符串, 指定开始的索引值,与使用字符个数。
(2) 实例:
public class Demo8 { public static void main(String[] args) { char[] arr = {'马','上','毕','业','了'}; String str = new String(arr,2,2); System.out.println("字符串的内容:"+str); } }(3) 运行结果:
6.String(int[] codePoints,int offset,int count);
(1) 实例:
public class Demo9 { public static void main(String[] args) { int[] buf = {65,66,67}; String str = new String(buf,0,3); System.out.println("字符串的内容:"+str); } }(2) 运行结果:
1.获取的方法
(1) 方法:
①int length():该方法用于获取字符串的长度。
②char charAt(int index):传入一个索引值,根据索引值获取特定位置的字符 (注意角标越界)。
③int indexOf(String str):查找子串第一次出现的索引值,如果子串没有出现在字符串中,那么则返回-1表示。
④int lastIndexOf(String str):查找子串最后一次出现的索引值 , 如果子串没有出现在字符串中,那么则返回-1表示。
(2) 实例:
public class Demo3 { public static void main(String[] args) { String str = "abc你好ab你好"; System.out.println("字符串的字符个数:" + str.length()); System.out.println("根据索引值获取对应的字符:"+ str.charAt(3)); System.out.println("查找子串第一次出现的索引值:" + str.indexOf("你好")); System.out.println("查找子串最后一次出现的索引值:" + str.lastIndexOf("你好")); } }(3) 运行结果:
2.判断的方法
(1) 方法:
①boolean endsWith(String str):判断是否以指定字符串结束。②boolean isEmpty():判断长度是否为0,也就是判断字符串是否为空内容。③boolean contains(CharSequences):判断是否包含指定序列,该方法可以应用于搜索。④boolean equals(Object anObject):判断是否相等,判断两个字符串的内容是否一致。是区分大小写的。
⑤boolean equalsIgnoreCase(String anotherString) 忽略大小写是否相等,验证码都是忽略大小写的。
(2) 实例:
public class Demo11 { public static void main(String[] args) { String str = "Demo.java"; System.out.println("是否以指定 的字符串结束:"+ str.endsWith("java")); System.out.println("是否以指定 的字符串结束:"+ str.endsWith("va")); System.out.println("是否以指定 的字符串结束:"+ str.endsWith("ja")); System.out.println("判断字符串是否为空内容:"+str.isEmpty()); System.out.println("判断字符串是否包含指定的内容:"+ str.contains("Demo")); System.out.println("判断两个 字符串的内容是否一致:"+ "DEMO.JAVA".equals(str)); System.out.println("判断两个字符串的内容是否一致(忽略大小写比较):"+ "DEMO.JAVA".equalsIgnoreCase(str)); } }(3) 运行结果:
3.转换的方法
注意:字节数组与字符数组、字符串他们三者之间是可以互相转换的。
(1) 方法:
①char[] toCharArray() :将字符串转换为字符数组。
②byte[] getBytes():将字符串转换为字节数组。
(2) 实例:
import java.util.Arrays; public class Demo12 { public static void main(String[] args) { String str = "Demo.java"; char[] buf = str.toCharArray(); //把字符串转换字符数组 System.out.println("字符数组:"+ Arrays.toString(buf)); byte[] buf2 = str.getBytes(); //把字符串转字节数组 System.out.println("字节数组:"+ Arrays.toString(buf2)); } }(3) 运行结果:
4.其他的方法
注意:CharSequence是一个接口,而String类是它的实现类。
(1) 方法:
①String replace(String oldChar, String newChar):替换。
②String[] split(String regex):切割。③String substring(int beginIndex):指定开始的索引值截取子串。④String substring(int beginIndex, int endIndex):指定开始与结束的索引值截取子串。⑤String toUpperCase():转大写。⑥String toLowerCase():转小写。
⑦String trim():去除字符串首尾的空格。
(2) 实例:
public class Demo5 { public static void main(String[] args) { String str = "我们不学习"; System.out.println("指定新内容替换旧 的内容:"+ str.replace("不", "要好好")); str = "我们要-好-好-学-习";//切成5份 String[] arr = str.split("-"); //根据指定的字符进行切割 。 System.out.println("字符串数组的内容:"+ Arrays.toString(arr)); str = "马上要毕业了"; System.out.println("指定开始的索引值截取子串:"+ str.substring(2)); System.out.println("指定开始与结束的索引值截取子串:"+ str.substring(2,6)); //方法重载,包头不包尾 注意:截取的内容是包括开始的索引值,不包括结束的索引值, 截取的位置是结束的索引值-1. //注意,凡是Java方法中出现开始和结束的索引值,都是遵循包头不包尾 str = "abC中国"; System.out.println("转大写:" + str.toUpperCase()); str = "AbdfSDD"; System.out.println("转小写:"+ str.toLowerCase()); str = " 我们要 努力学习 "; System.out.println("去除字符串首尾的空格:"+ str.trim()); } }(3) 运行结果:
1.实例一
(1) 需求:自己实现trim的方法。
(2) 分析:只要我们能确定开始StartIndex和结束endIndex这两个位置的索引值,那么这个题目我们就能做出来。最关键的是这两个索引值应该如何确定?把这两个变量定义成这个字符串开始与结束的位置,首先用startIndex确定是否是空字符,是的话就前进一步,结束的索引值也要判断是否是空字符。是的话就后退一步。
(3) 实例:
public class Demo1 { public static void main(String[] args) { String str =" 我们 快要毕业了 "; System.out.println(myTrim(str)); } public static String myTrim(String str){ //先转换成字符 数组 char[] arr = str.toCharArray(); //定义两个 变量记录开始与结束 的索引值 int startIndex = 0 ; int endIndex = arr.length -1; //确定开始 的索引值 while(true){ if(arr[startIndex]==' '){ startIndex++; }else{ break; } } //确定结束 的索引值: while(true){ if(arr[endIndex]==' '){ endIndex--; }else{ break; } } //截取子串返回 return str.substring(startIndex,endIndex+1); } }(4) 运行结果:
2.实例二
(1) 需求:
获取上传文件名 "D:\\20180225\\day01\\Demo1.java"。从字符串中获取文件名为Demo1.java。
(2) 分析:
可以用切割的方法,按照"\\"来进行切割,把它分割成好几部分,返回一个字符串的数组,只获取字符串数组的最后一个就可以了;也可以用截取的方法,只要能确定D的索引值就可以了。那么如何确定D的索引值?也就是最后"\\"的索引值+1就是D的索引值。
(3) 实例:
public class Demo2 { public static void main(String[] args) { String str = "D:\\20180225\\day01\\Demo1.java"; getFileName(str); } public static void getFileName(String path){ int index = path.lastIndexOf("\\");//转义 String fileName = path.substring(index+1); System.out.println("文件名:"+ fileName); } }(4) 运行结果:
3.实例三
(1) 需求:
将字符串对象中存储的字符反序。例如:新中国好 -----> 好国中新。
(2) 分析:
只需要把它转换成一个字符数组,转成字符数组后就可以定义两个索引值,一个是开始的索引值startIndex,另一个是结束的索引值endIndex,这两个交换后,startIndex向前++,endIndex向后--,再继续进行交换,(要定义两个变量记录要交换位置的索引值,只要startIndex<endIndex那么就进行交换),当这个字符数组被反转后,我们只需要再使用这个字符数组构建一个字符串即可。
(3) 实例:
public class Demo3 { public static void main(String[] args) { String str = "新中国好"; System.out.println("翻转后的字符串:"+ reverse(str)); } public static String reverse(String str){ char[] arr = str.toCharArray(); for(int startIndex = 0 , endIndex=arr.length-1 ; startIndex<endIndex; startIndex++,endIndex--){ char temp = arr[startIndex]; arr[startIndex] = arr[endIndex]; arr[endIndex] = temp; } //使用字符数组构建一个字符串。 return new String(arr); } }(4) 运行结果
4.实例四
(1) 需求:
求一个子串在整串中出现的次数 。
(2) 分析:
先定义一个变量记录出现的次数,count = 0;接着定义一个变量用于记录开始寻找的索引值的位置,int fromIndex = 0;首先从索引值为0的地方开始找,找到第一个Java后,count++,然后继续开始向后寻找,第一次找到Java的索引值是3,第二次是从java后的a开始继续往后找,而这个a的索引值是7,那么第二次的fromIndex应该怎么计算出来?首先确定第一次java的索引值为3,接着java的长度是4,所以第二次从a的索引值7开始找。
(3) 实例:
public class Demo4 { public static void main(String[] args) { String str = "abcjavaabcjavaphpjava"; //统计java在这个字符串中出现的次数 getCount(str, "java"); } //统计子串出现 的次数 public static void getCount(String str,String target){ int count = 0 ; //用于记录出现的次数 int fromIndex = 0; // 记录从那个索引值开始寻找目标子串 while((fromIndex = str.indexOf(target, fromIndex))!=-1){ //如果indexof方法返回 的不是-1,那么就是已经找到了目标 元素。 //如果这个方法返回的是-1意味着再往后找就没有了,所以循环的结束条件是只要不等于-1就继续往后找。 //indexOf会返回一个索引值,找到之后会有一个返回值,比如返回3,那么就应该用fromIndex来记录它。 //因为下次开始寻找的位置是 count++; fromIndex = fromIndex+target.length(); } System.out.println("出现的次数:"+ count); } }(4) 运行结果:
1.前言
(1) 字符串的特点:字符串是常量,它们的值在创建之后不能更改。
(2) 字符串的内容一旦发生了变化,那么马上会创建一个新的对象。
(3) 实例:
public class Demo1 { public static void main(String[] args) { String str1 = "hello"; String str2 = str1+" world"; System.out.println("str1与str2是同一个 对象吗?"+(str1==str2)); } }运行结果如下图所示:
2.StringBuffer类:
(1) 字符串的内容不适宜频繁修改,因为一旦修改马上就会创建一个新的对象。如果不断频繁修改,在内存中就会有很多的字符串对象存在。如果需要频繁修改字符串的内容,建议使用字符串缓冲类(StringBuffer)。
(2) StringBuffer 是字符串的缓冲区,其实就是一个存储字符的容器,用它对字符串进行增删查改会非常方便。
(3) 疑问:使用Stringbuffer无参的构造函数创建 一个对象时,默认的初始容量是多少?如果长度不够使用了,自动增长多少倍?
通过查看源代码我们发现,StringBuffer 底层是依赖了一个字符数组才能存储字符数据的,该字符串数组默认 的初始容量是16, 如果字符数组的长度不够使用时,自动增长1倍,也就是是原来的2倍。
(4) 实例:
public class Demo2 { public static void main(String[] args) { //先使用StringBuffer无参的构造函数创建一个字符串缓冲类。 StringBuffer sb = new StringBuffer(); sb.append("java"); sb.append("java"); sb.append("java"); sb.append("java"); sb.append("java"); System.out.println(sb);//其初始容量为16个字符,但是为什么可以输出20个字符。 } }运行结果如下图所示:
3.StringBuffer类具备的方法:
(1) 增加的方法:
①append(boolean b) :有很多重载的方法,可以添加任意类型的数据到容器中。
package stringbuffer; public class append { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); //添加的方法 sb.append("abc"); sb.append(true);//把true变成字符串输出 sb.append(3.14f); System.out.println("字符串缓冲类的内容:"+sb); } }运行结果如下图所示:
②insert(int offset, boolean b):指定插入的索引值,插入对应的内容。
public class insert { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); //插入的方法 sb.append("abc"); sb.insert(2,"小明");//在b和c之间插入小明,因为b和c之间的索引值是2. System.out.println("字符串缓冲类的内容:"+sb); } }运行结果如下图所示:
(2) 删除的方法
①delete(int start, int end):根据指定的开始与结束的索引值删除对应的内容。
public class delete { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abc"); sb.insert(2,"小明"); System.out.println("执行删除操作前:"+sb); //删除的方法 sb.delete(2, 4);//删除小明,删除的时候也是包头不包尾的。 System.out.println("执行删除操作后:"+sb); } }运行结果如下图所示:
②deleteCharAt(int index):根据指定 的索引值删除一个字符。
public class deleteCharAt { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abc"); sb.insert(2,"小明"); System.out.println("执行删除操作前:"+sb); //删除 sb.deleteCharAt(3);//删除"明" System.out.println("执行删除操作后:"+sb); } }运行结果如下图所示:
(3) 修改的方法
①replace(int start, int end, String str):根据指定 的开始与结束索引值替代成指定的内容。
public class replace { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abc"); sb.insert(2,"小明"); System.out.println("执行替换操作前:"+sb); //替换 sb.replace(2,4,"张三");//把小明替换成张三 System.out.println("执行替换操作后:"+sb); } }运行结果如下图所示:
②reverse():反转字符串缓冲类的内容。abc--->cba
public class reverse { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abc"); sb.insert(2,"小明"); System.out.println("执行反转操作前:"+sb); //反转字符串的内容 sb.reverse(); System.out.println("执行反转操作后:"+sb); } }运行结果如下图所示:
③setCharAt(int index, char ch):把指定索引值的字符替换成指定的字符。
public class setCharAt { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abc"); sb.insert(2,"小明"); System.out.println("执行替换操作前:"+sb); //替换单个字符 sb.setCharAt(3, '红');//把'明'替换成'红' System.out.println("执行替换操作后:"+sb); } }运行结果如下图所示:
④substring(int start, int end):根据指定的索引值截取子串。
public class substring { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abc"); sb.insert(2,"小明"); //截取小明 String subString = sb.substring(2, 4);//返回类型是String类型 System.out.println("子串的内容:"+subString); } }运行结果如下图所示:
⑤ensureCapacity(int minimumCapacity):指定StringBuffer内部的字符数组长度的。基本很少用到。
如果想指定字符数组的初始容量可以使用带参的构造方法来指定。
public class ensureCapacity { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abc"); sb.insert(2,"小明"); sb.ensureCapacity(20);//指定字符数组的长度为20,默认是16 } }(4) 查看的方法
①indexOf(String str, int fromIndex):查找指定的字符串第一次出现的索引值,并且指定开始查找的位置。
public class indexOf { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abcjavaabc"); int index = sb.indexOf("java", 0);//从索引值为0的位置开始找 System.out.println("返回的索引值为:"+index); } }运行结果如下图所示:
②capacity():查看当前字符数组的长度。
public class capacity { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abcjavaabc"); //查看当前字符数组的长度 System.out.println("当前字符数组的长度是:"+sb.capacity());//16 sb.append("javajava");//此时存储了18个字符,那么这时候当前字符数组的长度又是多少? System.out.println("当前字符数组的长度是:"+sb.capacity());//34,因为字符串的长度会增长一倍+2 } }运行结果如下图所示:
③length():查看存储字符的个数。
public class length { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abcjavaabc"); sb.append("javajava"); System.out.println("查看当前字符数组存储字符的个数:"+sb.length()); } }运行结果如下图所示:
④charAt(int index):根据指定的索引值查找字符
public class charAt { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abcjavaabc"); sb.append("javajava"); System.out.println("根据指定的索引值查找字符:"+sb.charAt(2)); } }运行结果如下图所示:
⑤toString():把字符串缓冲类的内容转成字符串返回。
现在stringBuffer里存储了很多的字符数据,假设有一个test方法,这个方法是要接收一个字符串的,而字符的内容是存储在StringBuffer里的。而现在调用这个test方法应该如何调用呢?首先肯定不能把sb传进去,因为类型不一样,如何把这个容器中存储的字符转换成字符串呢?可以用sb.toString()方法,这样就会把里面存储的内容,以字符串的形式返回。
public class toString { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("abcjavaabc"); sb.append("javajava"); String content = sb.toString(); test(content); } public static void test(String str){ System.out.println("字符串缓冲类的内容是:"+str); } }运行结果如下图所示:
1.StringBuffer 与 StringBuilder的相同处与不同处:
(1) 相同点:①两个类都是字符串缓冲类。②两个类的方法都是一致的。(只要是StringBuffer有的方法,StringBuilder一样有)(2) 不同点:①StringBuffer是线程安全的,操作效率低 ,StringBuilder是线程非安全的,操作效率高。(所谓的安全是指在一个时间段里只会允许一个线程来操作这份代码,而线程不安全是指在一个时间段里一份代码可以由多个线程来执行)②StringBuffer是jdk1.0出现 的,StringBuilder 是jdk1.5的时候出现的。
2.推荐使用:StringBuilder,因为操作效率高。
