10. 认识String类
- 10.1 String类的重要性
- 10.2 常用方法
- 10.2.1 字符串构造
- 10.2.2 String对象的比较
- 10.2.3 字符串查找
- 10.2.4 转化
- 10.2.5 字符串替换
- 10.2.6 字符串拆分
- 10.2.7 字符串截取
- 10.2.8 字符串的不可变性
- 10.2.9 字符串修改
 
- 10.3 StringBuilder和StringBuffer
- 10.3.1 StringBuilder的介绍
- 10.3.2 面试题:
 
- 10.4 String类oj
10.1 String类的重要性
在C语言中已经涉及到字符串了,但是在C语言中要表示字符串只能使用字符数组或者字符指针,可以使用标准库提供的字符串系列函数完成大部分操作,但是这种将数据和操作数据方法分离开的方式不符合面相对象的思想,而字符串应用又非常广泛,因此Java语言专门提供了String类。
 另外在开发和校招笔试中,字符串也是常客,比如:
- 字符串转整形数字
- 字符串相加
而在面试中经常被提问:
 String、StringBuff和StringBuilder之间的区别等。
10.2 常用方法
10.2.1 字符串构造
常用的三种构造方法:
    public static void main(String[] args) {
        //使用常量串构造
        String str = "hello";
        System.out.println(str); // 调用println中print里面再调用write方法来打印
        //直接newString对象
        String str2 = new String("world");
        System.out.println(str2);
        //使用字符数组进行构造
        char[] array = {'h','e','l','l','o'};
        String str3 = new String(array);
        System.out.println(str3);
    }
其他方法需要使用可以去查看Java在线文档:链接: String官方文档
注意:
- String是引用类型,内部并不存储字符串本身,比如在String类的是实现源码,String类实例变量如下:
  
 字符串实际保存在char类型的数组,即value去引用里面的数组内容,而s3这个引用 指向了s1这个引用 指向的对象如下:
  
    public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = new String("world");
        String s3 = s1;
        System.out.println(s3);
    }
- 在Java中 “” 引起来也是String类型对象,如:
    public static void main(String[] args) {
        String str4 = "";
        System.out.println("输出:"+str4);
        System.out.println(str4.length());
        System.out.println(str4.isEmpty());//为空 则为true,但这里的空不是null
        String str5 = null;
        System.out.println(str5.isEmpty());//报错:NullPointerException 所以null 和 ""不一样
    }
10.2.2 String对象的比较
字符串的比较是常见操作之一,比如:字符串排序。Java中总共提供了4中方式:
- == 比较是否引用同一个对象
 注意:对于内置类型,双等号比较的是变量中的值;对于引用类型,==比较的是引用中的地址。
    public static void main(String[] args) {
		int a = 10;
		int b = 20;
		int c = 10;
		// 对于基本类型变量,==比较两个变量中存储的值是否相同
		System.out.println(a == b); // false
		System.out.println(a == c); // true
		// 对于引用类型变量,==比较两个引用中的地址
		String s1 = new String("hello");
		String s2 = new String("hello");
		String s3 = new String("world");
		String s4 = s1;
		System.out.println(s1 == s2); // false
		System.out.println(s2 == s3); // false
		System.out.println(s1 == s4); // true
    }
- boolean equals(Object anObject) 方法:按照字典序比较
 字典序:字符大小的顺序
 String类重写了父类Object中equals方法,Object中equals默认按照==比较,String重写equals方法后,按照
 如下规则进行比较,比如: s1.equals(s2)
    public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = new String("world");
        System.out.println(s1.equals(s2)); //String类重写了父类Object中equals方法
    }
而父类Object中equals方法如下:
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof 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;
    }
另外:
    public static void main(String[] args) {
        String s3 = "hello";
        String s4 = "hello";
        System.out.println(s3==s4); // true
        System.out.println(s3.equals(s4)); // true
    }

- int compareTo(String s) 方法
public static void main(String[] args) {
	String s1 = new String("abc");
	String s2 = new String("ac");
	String s3 = new String("abc");
	String s4 = new String("abcdef");
	System.out.println(s1.compareTo(s2)); // 不同输出字符差值-1
	System.out.println(s1.compareTo(s3)); // 相同输出 0
	System.out.println(s1.compareTo(s4)); // 前k个字符完全相同,输出长度差值 -3
}
与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。具体比较方式:
- 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
- 如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
- int compareToIgnoreCase(String str) 方法:与compareTo方式相同,但是忽略大小写比较:
public static void main(String[] args) {
	String s1 = new String("abc");
	String s2 = new String("ac");
	String s3 = new String("ABc");
	String s4 = new String("abcdef");
	System.out.println(s1.compareToIgnoreCase(s2)); // 不同输出字符差值-1
	System.out.println(s1.compareToIgnoreCase(s3)); // 相同输出 0
	System.out.println(s1.compareToIgnoreCase(s4)); // 前k个字符完全相同,输出长度差值 -3
}
10.2.3 字符串查找
字符串查找也是字符串中非常常见的操作,String类提供的常用查找的方法:
    public static void main(String[] args) {
        String s1 = new String("hello");
        //返回字符串对应下标的字符
        char ch = s1.charAt(1);
        System.out.println(ch); //e
        //返回对应字符出现的下标位置 从头开始一个一个找 没有返回-1
        int index = s1.indexOf('l');
        System.out.println(index); //2
        // 从指定位置(3)开始找某个字符出现的位置   没有返回-1
        int index2 = s1.indexOf('l',3);
        System.out.println(index2); //3
        // 字符串查找 从一个字符串找另一个字符串   没有返回-1
        int index3 = s1.indexOf("llo");
        System.out.println(index3); //2
        int index4 = s1.indexOf("llo",3);
        System.out.println(index4); //-1
        //从后往前找,返回ch第一次出现的位置,没有返回-1
        int index5 = s1.lastIndexOf('l');
        System.out.println(index5); //3
        
        // 从指定位置开始找,从后往前找ch第一次出现的位置,没有返回-1
        int index6 = s1.lastIndexOf('l',2);
        System.out.println(index6); // 2
        // 字符串也可以从后往前同理
    }
10.2.4 转化
结论:字符串的改变 都是产生了新的对象
 1.数值和字符串转化:String.valueOf()方法,里面可以是整数,double,boolean等:
 
    public static void main(String[] args) {
        //把整数  转化为  字符串
        String str = String.valueOf(123);
        System.out.println(str); 
        //这个过程  可以理解为序列化
        String str1 = String.valueOf(new Student("张三",12));
        System.out.println(str1); //上面重写一下toString方法来变成想要的形式  姓名:张三  年龄:12
        //包装类:首先是一个类 其次 类中拥有更多的方法 使用起来非常方便
        //把字符串变成整型
        int a = Integer.parseInt("123");
        System.out.println(a);  //123
        //变成double  变成什么类型就是 该包装类.parse类型
        double d = Double.parseDouble("99.99");
        System.out.println(d);  //99.99
    }
2.大小写转换:toUpperCase(),变小:toLowerCase()
    public static void main(String[] args) {
        //结论:字符串的改变 都是产生了新的对象
        //转化成大写的时候,产生了一个新的对象
        String str = "hello";
        String ret = str.toUpperCase();
        System.out.println(ret);  //HELLO
        System.out.println(str);
        String str2 = "HELLO";
        String ret2 = str2.toLowerCase();
        System.out.println(ret2); // hello
    }
3.字符串转数组
    public static void main(String[] args) {
        String str = "hello";
        char[] chars = str.toCharArray();
        for (char ch:chars) {
            System.out.println(ch);
        }
4.格式化
    public static void main(String[] args) {
        String s = String.format("%d-%d-%d",2019,9,14);
        System.out.println(s); //2019-9-14
    }
10.2.5 字符串替换
    public static void main(String[] args) {
        String str = "abcdabcddef";
        // 字符替换 把a替换成A
        String ret = str.replace('a','A');
        System.out.println(ret);
        // 字符串替换  把ab替换成AB 
        String ret2 = str.replace("ab","AB");
        System.out.println(ret2);
    }
10.2.6 字符串拆分
.split(“切割符”,限制数) 拆分为几部分的数组接收:
    public static void main(String[] args) {
        String str = "name=zhagnsan&age=10";
        String[] ret = str.split("&");
        for (int i = 0; i < ret.length; i++) {
            System.out.println(ret[i]);
            //name=zhagnsan
            //age=10
        }
        //可以加限制分割为几部分
        String str2 = "hello world hello bit";
        String[] ret2 = str2.split(" ",2);
        for (int i = 0; i < ret2.length; i++) {
            System.out.println(ret2[i]);    
            //hello
            //world hello bit
        }
        String str3 = "192.168.1.1";
        String[] ret3 = str3.split("\\.");
        for (int i = 0; i < ret3.length; i++) {
            System.out.println(ret3[i]);
            //192
            //168
            //1
            //1
        }
注意:
- 字符"|“,”*“,”+“都得加上转义字符,前面加上”\" .
- 而如果是"" ,那么就得写成"\\" .
- 如果一个字符串中有多个分隔符,可以用"|"作为连字符.
    public static void main(String[] args) {
    	//正则表达式的分割
        String str = "192\\168\\1\\1";
        String[] ret = str.split("\\\\");
        for (int i = 0; i < ret.length; i++) {
            System.out.println(ret[i]);
            //192
            //168
            //1
            //1
        }
				
				// 俩个分割符
        String str2 = "name=zhagnsan&age=10";
        String[] ret2 = str2.split("=|&");
        for (int i = 0; i < ret2.length; i++) {
            System.out.println(ret2[i]);
            //name
            //zhagnsan
            //age
            //10
        }
- 多次拆分:
    public static void main(String[] args) {
        //实现多次分割
        String str = "name=zhagnsan&age=10";
        String[] ret = str.split("&");
        // [0] name=zhagnsan  [1]  age=10
        for (String x:ret) {
            String[] s = x.split("=");
            for (String ss:s) {
                System.out.println(ss);
                //name
                //zhagnsan
                //age
                //10
            }
        }
    }
10.2.7 字符串截取
从一个完整的字符串之中截取出部分内容。可用方法如下:
 String substring(int beginIndex) 从指定索引截取到结尾
 String substring(int beginIndex, int endIndex) 截取部分内容
 trim() 截取字符串两边的空格 保留中间空格
    public static void main(String[] args) {
        String str = "abcdef";
        //从指定截取到结尾
        String ret = str.substring(3);
        System.out.println(ret);   //def
        //截取部分内容  [a,b) 在Java中一般是左闭有开
        String ret2 = str.substring(2,5);   //cde
        System.out.println(ret2);
        //trim() 截取字符串两边的空格  保留中间空格
        String str3 = "    abcde fefef  f     ";
        String ret3 = str3.trim();
        System.out.println(ret3);   //abcde fefef  f
    }
10.2.8 字符串的不可变性
String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,对字符串的改变操作都会产生新的对象。
在被private修饰封装起来的value[] 拿不到value里面的内容,而且也没有提供get 和set方法。
 
    public static void main15(String[] args) {
        final char[] value = {'a','b','c','d'};
        value[0] = 'g';//修改对象 0 下标内容
        //value = new char[10];//指向新的对象
    }
final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内
 容是可以修改的。
public static void main(String[] args) {
	final int array[] = {1,2,3,4,5};
	array[0] = 100;
	System.out.println(Arrays.toString(array));
	// array = new int[]{4,5,6}; // 编译报错: 无法为最终变量array分配值
	//可以修改值但是不能修改数组的指向
}
为什么 String 要设计成不可变的?(不可变对象的好处是什么?)
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
10.2.9 字符串修改
注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率
 非常低下。
    public static void main15(String[] args) {
        //String s = "hello";
        //s += " world";  // s指向了新的指向
        //System.out.println(s); // 输出:hello world
			//相当于下面:
			
        String s = "hello";
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(s);
        s = stringBuilder.toString();
        System.out.println(s);
        System.out.println(stringBuilder);
    }

但是这种方式不推荐使用,因为其效率非常低,中间创建了好多临时对象。
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder("abcdef");
        stringBuilder.reverse();
        System.out.println(stringBuilder);
    }
    public static void main17(String[] args) {
        long start = System.currentTimeMillis();
        StringBuffer sbf = new StringBuffer("");
        for(int i = 0; i < 100000; ++i){
            sbf.append(i);
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        start = System.currentTimeMillis();
        StringBuilder sbd = new StringBuilder();
        for(int i = 0; i < 100000; ++i){
            sbd.append(i);
        }
        end = System.currentTimeMillis();
        System.out.println(end - start);
         start = System.currentTimeMillis();//获取当前系统的时间戳
        String s = "";
        for(int i = 0; i < 100000; ++i){
            //以后不能在循环当中 这样去拼接字符串
            s += i;
        }
        end = System.currentTimeMillis();
        System.out.println(end - start);
    }
10.3 StringBuilder和StringBuffer
10.3.1 StringBuilder的介绍
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。这两个类大
 部分功能是相同的,这里介绍 StringBuilder常用的一些方法,其他可以点击链接: stringBuilder在线文档
    public static void main(String[] args) {
        //StringBuilder StringBuffer 是一个可变的对象 返回的是this 也就是当前对象
        // 使用多线程情况下 synchronized 执行一个append方法时得先执行完才能执行其他方法
        // 但是频繁得上锁 和 解锁是需要耗费系统的资源
        StringBuffer stringBuffer = new StringBuffer("aaaa");
        stringBuffer.append("111");
        System.out.println(stringBuffer); //aaaa111
        StringBuilder stringBuilder = new StringBuilder("abcdef");
        stringBuilder.append("123");
        stringBuilder.append("456").append("789");
        stringBuilder.reverse(); // 逆置字符串
        System.out.println(stringBuilder);  //987654321fedcba
   }
- StringBuff append(String str) ----- 在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、double、float、int、long、Object、String、StringBuff的变量
- char charAt(int index) ----- 获取index位置的字符
- int length() ----- 获取字符串的长度
- int capacity() ----- 获取底层保存字符串空间总的大小
- void ensureCapacity(int mininmumCapacity) ----- 扩容
- void setCharAt(int index,char ch) ----- 将index位置的字符设置为ch
- int indexOf(String str) 返回str第一次出现的位置
- int indexOf(String str, int fromIndex) ----- 从fromIndex位置开始查找str第一次出现的位置
- int lastIndexOf(String str) ----- 返回最后一次出现str的位置
- int lastIndexOf(String str, int fromIndex) ----- 从fromIndex位置开始找str最后一次出现的位置
- StringBuff insert(int offset, String str) ----- 在offset位置插入:八种基类类型 & String类型 & Object类型数据
- StringBuffer deleteCharAt(int index) ----- 删除index位置字符
- StringBuffer delete(int start, int end) ----- 删除[start, end)区间内的字符
- StringBuffer replace(int start, int end, String str) ----- 将[start, end)位置的字符替换为str
- String substring(int start) ----- 从start开始一直到末尾的字符以String的方式返回
- String substring(int start,int end) ----- 将[start, end)范围内的字符以String的方式返回
- StringBuffer reverse() ----- 反转字符串
- String toString() ----- 将所有字符按照String的方式返回
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder("hello");
        StringBuilder sb2 = sb1;
        // 追加:即尾插-->字符、字符串、整形数字
        sb1.append(' '); // hello
        sb1.append("world"); // hello world
        sb1.append(123); // hello world123
        System.out.println(sb1); // hello world123
        System.out.println(sb1 == sb2); // true
        System.out.println(sb1.charAt(0)); // 获取0号位上的字符 h
        System.out.println(sb1.length()); // 获取字符串的有效长度14
        System.out.println(sb1.capacity()); // 获取底层数组的总大小
        sb1.setCharAt(0, 'H'); // 设置任意位置的字符 Hello world123
        sb1.insert(0, "Hello world!!!"); // Hello world!!!Hello world123
        System.out.println(sb1);
        System.out.println(sb1.indexOf("Hello")); // 获取Hello第一次出现的位置
        System.out.println(sb1.lastIndexOf("hello")); // 获取hello最后一次出现的位置
        sb1.deleteCharAt(0); // 删除首字符
        sb1.delete(0,5); // 删除[0, 5)范围内的字符
        String str = sb1.substring(0, 5); // 截取[0, 5)区间中的字符以String的方式返回
        System.out.println(str);
        sb1.reverse(); // 字符串逆转
        str = sb1.toString(); // 将StringBuffer以String的方式返回
        System.out.println(str);
    }
总结:String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可
 以修改。频繁修改字符串的情况考虑使用StringBuilder。
 注意:String和StringBuilder类不能直接转换。如果要想互相转换,可以采用如下原则:
- String变为StringBuilder: 利用StringBuilder的构造方法或append()方法
- StringBuilder变为String: 调用toString()方法。
10.3.2 面试题:
- String、StringBuffer、StringBuilder的区别
- String的内容不可修改,StringBuffer和StringBuilder的内容可以修改
- StringBuffer和StringBuilder大部分功能是相似的
- StringBuffer采用同步处理,属于多线程安全操作;而StringBuilder未采用同步操作,属于线程不安全操作
- 以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】
10.4 String类oj
- 链接: 第一个只出现一次的字符
    public int firstUniqChar(String s) {
        //1.定义一个计数数组
        int[] count = new int[26];
        //2.遍历字符串 记录次数
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            count[ch-'a']++;
        }
        //3.再次遍历原字符串
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (count[ch -'a'] == 1) {
                return i;
            }
        }
        return -1;
    }
- 链接: 字符串最后一个单词的长度
- 使用split方法分割空格后的字符串
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextLine()) { // 注意 while 处理多个 case
            String s = in.nextLine();
            String[] ret = s.split(" ");
            System.out.println(ret[ret.length-1].length());
        }
    }
- 使用lastIndexOf方法找到空格的位置,然后substring截取空格+1之后的字符串
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextLine()) { // 注意 while 处理多个 case
            String s = in.nextLine();
            int index = s.lastIndexOf(" ");
            String str = s.substring(index+1);
            int len = str.length();
            System.out.println(len);
        }
    }
- 链接: 检测字符串是否为回文
    public boolean isPalindrome(String s) {
        s = s.toLowerCase();
        int left = 0;
        int right = s.length()-1;
        while(left < right) {
            //1.left走到合法字符底下
            while(left<right && !isCharacterNum(s.charAt(left))) {
                left++;
            }
            //2.right走到合法字符底下
            while(left<right && !isCharacterNum(s.charAt(right))) {
                right--;
            }
            if(s.charAt(left) == s.charAt(right)) {
                left++;
                right--;
            } else {
                return false;
            }
        }
        return true;
    }
    private boolean isCharacterNum(char ch) {
        // if(ch >= 'a' && ch <= 'z' || ch >='0' && ch <= '9') {
        //     return true;
        // }
        // return false;
        if(Character.isDigit(ch) || Character.isLetter(ch)) {
            return true;
        }
        return false;
    }



















