一、Math类
1.源码分析
1)java.lang下的包可以直接使用,无需导包
2)final修饰,不能被继承
3)构造器是私有的,说明不能创建Math类的对象(不能这么使用:Math math = new Math();)
4)Math类里面的属性、方法都由static修饰,说明可以通过Math.属性/方法 调用,不需要创建对象
2.常用属性和方法
public class Demo01 {
public static void main(String[] args) {
//常量
System.out.println(Math.PI);
//常用方法
System.out.println("随机数:"+Math.random()); // 取值范围是【0.0,1.0)
System.out.println("绝对值:"+Math.abs(-6));
System.out.println("向上取值(往大取)"+Math.ceil(9.1));
System.out.println("向下取值(往小取)"+Math.floor(9.9));
System.out.println("四舍五入"+Math.round(3.5));
System.out.println("最大值"+Math.max(3,6));
System.out.println("最小值"+Math.min(3,6));
}
}
6)分析源码可以发现,Math.random其实就是在调用Random下的nextDouble方法
二、Random类
1.源码分析
1)无final修饰,说明是可以创建对象的
无final修饰,说明是可以创建对象的
2)由无参构造器和有参构造器
3.调用构造器
import java.util.Random;
//Random类
public class Demo02 {
public static void main(String[] args) {
//Math类产生随机数
System.out.println(Math.random());
//Random类
//使用带参数的构造器创建对象,参数是long类型
Random r1 = new Random(100000L);
int i= r1.nextInt();//生成一个随机数
System.out.println(i);
//多次运行发现输出的i都是同一个数,这是因为seed,也就是种子的意思,seed不变,那么产生的随机数也不会变
//所以我们用System.currentTimeMillis(当前时间距1970-1-1 00:00:00的时间差,long类型)来作为seed
Random r2 = new Random(System.currentTimeMillis());
System.out.println(r2.nextInt());
//使用空参构造器创建对象:表面是在调用无参构造器,其实底层还是调用了有参构造
//无参构造源码里面 this(seedUniquifier() ^ System.nanoTime());
//这两者进行异或,由于nanoTime是可变的,所以每次异或出来的值是不同的,所以可以生成不同的随机数
Random r3 = new Random();
System.out.println(r3.nextInt());
//上面是无参的nextInt(),还有一个人带参数的nextInt(int bound),bound为范围
//这里是10,也就是会生成[0,10)之间的随机数
System.out.println(r3.nextInt(10));
//返回[0.0,1.0)之间的double值
System.out.println(r3.nextDouble());
}
}
三、String类
1.源码分析
1)String str="abc"; "abc"是String类的一个实例,也就是类的一个具体的对象
2)字符串是不可变的(可变字符串后面再学)
3)final修饰,不能被继承
4)字符串表面看起来是一个字符串,实际上是存放在一个value[]数组里面(JDK1.8之前是char类型数组,1.8之后源码中是byte类型数组)
debug验证:
5)String str="abc";是类似于自动装箱方式创建对象,还可以通过构造器创建对象
构造器的底层源码功能就是给对象底层的value数组进行赋值
public static void main(String[] args) {
String str="abc";
System.out.println(str);
//空构造器创建对象
String s1 = new String();
String k="";
/*public String() {
this.value = "".value;
}
源码中可以看到this.value就是s1.value,而空.value,可以通过debug上面的k看到,空.value里面没有内容,也就是说
只是构建了s1这个对象,但是指向的堆是s1:null
*/
//有参构造
String s2 = new String("abc");
/*
public String(String original) {
this.value = original.value;
}
可以看到,源码中是将传入的original的value作为this.value也就是s2.value
*/
//有参构造,参数为数组
String s3 = new String(new char[]{'a','b','c'});
System.out.println(s3);
/*
public String(char value[]) {
this(value, 0, value.length, null);
}
*/
}
2.String类常用方法
//常用方法
//equals():比较两个字符串的值
String abc1 = new String("abc");
String abc2 = new String("abc");
System.out.println(abc1.equals(abc2)); //返回的是true,这里是比较两个字符串的值
/*
compareTo()
会对字符串的每一个字符遍历进行比较。比较的次数是两个字符串里面长度较短的次数。
例如"abc","abcdef"进行比较,a=a,b=b,c=c,那么比较输出的结果就是"abc".length减去"abcdef".length,也就是输出3-6=-3
例如"abc","abc"进行比较,都相等,那就是"abc".length减去"abc".length,也就是输出3-3=0
例如"abc","acc"进行比较,a=a,b≠c,那么就会输出b和c的ASCII的差值,也就是-1。
*/
System.out.println(abc1.compareTo(abc2));
//substring():字符串的截取,substring(3):从下标为3(下标从0开始)的开始截取,substring(3,6)截取下标3-5位
String s6 = new String("abcdefghijk");
System.out.println(s6.substring(3)); //defghijk
System.out.println(s6.substring(3,6)); //def
//concat():字符串拼接
String s7 = new String("aaaaaaa");
System.out.println(s6.concat(s7));
//replace(a,b):将字符串中的a全部替换为b
String s8 = new String("abcccba");
System.out.println(s8.replace("a","k"));
//split("-"):以()内传入的指定的字符串为分隔符,进行分割后组成一个数组
String s9 = new String("a-b-c-d-f");
String[] strs = s9.split("-");//Alt+回车,Introduce local variable自动加载,可以看到这里生成的是一个数组
//对数组进行输出
System.out.println(Arrays.toString(strs));
//大小写转换toUpperCase():转大写,toLowerCase转小写
String abc = new String("ABC");
System.out.println(abc.toLowerCase());
System.out.println(abc.toLowerCase().toUpperCase());
//去除字符串首尾的空格
String s10 = new String(" a b c ");
System.out.println(s10.trim());
//将boolean类型转换为String类型
System.out.println(String.valueOf(false));
/*
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
查看源码可以看到,将传入的参数b和true比较,如果和true相等,则返回true,如果不等于true,则返回false。
因为boolean值只有true和false。
*/
3.String类内存分析
编写一段代码如下
public static void main(String[] args) {
String s1="a"+"b"+"c";
String s2="ab"+"c";
String s3="a"+"bc";
String s4="abc";
String s5="abc"+"";
}
重新编译这段代码,点击IDEA菜单Build-->Recompile 'xxx.java'
反编译这个class文件,这里我使用了一个在线的反编译网站http://www.javadecompilers.com/,结果如下,可以看到,字符串进行了编译器优化,能直接合并成为完整的字符串。
这段代码内存分析如下
如果用new String来创建一个新的对象
String s6 = new String("abc");
内存分析如下
如果在创建对象时加入变量
String s7 = "abc";
String s8= s7+"def";
System.out.println(s8);
由于在编译String s8= s7+"def"; 的时候,编译器不会知道s7是“abc”,所以不会像上面的代码一样直接优化成“abcdef”。
四、StringBuilder和StringBuffer类
字符串可以分为两类:不可变字符串(String)、可变字符串(StringBuilder、StringBuffer)
1.StringBuilder
1)属性
StringBuilder继承了它的父类的两个属性(JDK1.8之前是char类型数组,1.8之后源码中是byte类型数组)
value[]:StringBuilder底层的存储
count:value数组中被使用的长度
2.调用构造器
StringBuilder中有三个构造器
1)空构造器
可以看到空构造器中是调用了父类的有参构造器,并且传了一个值16
于是跳转到父类构造器
可以看到源码中的COMPACT_STRINGS初始定义为true,于是走if为真代码,value数组的长度为16。
所以空构造器就是给当前对象的value[]数组的长度赋初始值16。
2)int有参构造器
可以看到有参构造器也是调用父类的构造器,也就是将我们调用有参构造时传入的数值赋值给value[]数组的长度。
3)string有参构造器
分析后,可以得到:str参数类型的构造器的功能是将value[]数组的长度赋值为str.length+16,
并且将str字符串从下标为0开始放到数组中,将count赋值为数组使用了的长度也就是str的长度
例如,传了一个字符串abc,那么数组为 :
调用构造器代码
package com.rzd.commonusedclass;
public class Demo05 {
public static void main(String[] args) {
//调用StringBuilder空构造器创建StringBuilder对象
StringBuilder sb1 = new StringBuilder();
//调用StringBuilder有参构造器创建StringBuilder对象
StringBuilder sb2 = new StringBuilder(10);
//这时value[]的长度为16+str.length=19,count=3
StringBuilder sb3 = new StringBuilder("abc");
/*如果此时向数组中继续添加字符串,可以使用append方法来添加。
可以进行多次添加,这里添加了10个a,又添加了10个b
添加10个a后count为13,数组长度为19,可以放得下,
再添加10个b后,count数组放不下了,这时底层源码会对数组的长度进行扩容,
扩容后数组长度为大于19的数(具体查看源码逻辑),扩容后10个b可以添加到数组中,
count为13+10=23。
*/
StringBuilder sb=sb3.append("aaaaaaaaaa").append("bbbbbbbbbb");
//输出sb时底层完成了将StringBuilder类型转换为String类型后输出
System.out.println(sb);
}
}
3.可变与不可变
1)String是不可变的,如果给str赋值为“abc”,它在内存中会有固定的地址0x99,当把str修改为“abcdef”时,在内存中会重新开辟新的空间,那么str指向的地址也会改变
2)StringBuilder是可变的,利用append方法追加字符串,地址不会改变,可以利用代码测试
StringBuilder sb4 = new StringBuilder();
System.out.println(sb4.append("abc")==sb4.append("def"));
运行结果为true。
4.StringBuilder和StringBuffer的常用方法(它们的常用方法是一样的)
public class Demo06 {
public static void main(String[] args) {
//StringBuilder和StringBuffe用法
//增
StringBuilder sb = new StringBuilder("abcdef");
sb.append("你好世界");
System.out.println(sb); //abcdef你好世界
//删
sb.delete(6,8);//删除数组下标为[6-8)位置上的字符,一个汉字占1个字符
System.out.println(sb); //abcdef世界
sb.deleteCharAt(5); //删除位置3的字符
System.out.println(sb); //abcde世界
//改-->插入
sb.insert(2,"梦想。。");
System.out.println(sb); //ab梦想。。cde世界
//改-->替换
sb.replace(2,3,"理想"); //[2,3) 将“梦”替换为“理想”
System.out.println(sb); //ab理想想。。cde世界
//查-->查询单个字符
System.out.println(sb.charAt(3));
//查-->查询多个字符
for (int i = 0; i < sb.length(); i++) {
System.out.print(sb.charAt(i)+"\t");
}
System.out.println();
//查-->截取
String str=sb.substring(2,4);
System.out.println(str);//返回的是一个新的字符串,对StringBuilder没有影响
}
}
5.StringBuilder和StringBuffer的区别
String和它们的区别是不可变与可变的。
StringBuilder:JDK1.5开始使用,效率高,线程不安全
StringBuffer:JDK1.0开始使用,效率低,线程安全