字符串类 String
String是引用类型,默认值是null,不是""
声明字符串
String构造方法
//声明字符串
String str = "abc你好";
//构造方法
str = new String();//无参构造方法
str = new String("你好");//传入字符串
char[] arr = {'a','b','c',65};//其中的65表示的是转换后的字符'A'
str = new String(arr);//传入字符数组
字符串拼接(用加号 + )
1.字符串和所有类型相加(+拼接)后得到的都是字符串
str = "123"+"abc";// "123abc"
str = "123"+23;// "12323"
str = "123"+new Object();// "123"+这个对象的toString方法的结果
//数组是引用类型,有length属性,有属性就是引用类型
str = "123"+new int[]{1,2,3};//得到的是"123"+数组首个元素的地址
System.out.println(str);
数组是引用类型,有length属性
2.加号在拼接字符串和数学运算时优先级是一样的
System.out.println("123"+123+123);//"123123123"
System.out.println(123+123+"123");//"246123"
两种不同顺序的输出结果不一样
空串 "" 也是字符串
equals() --字符串比较
使用equals方法比较,String类重写了Object的equals方法
public boolean equals(Object anObject) {
if (this == anObject) {//是否是同一个对象
return true;
}
if (anObject instanceof String) {//是否是String类型
String anotherString = (String)anObject;//强转成String类型
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;
}
比较分三步:
1.检查是否是同一个对象 ==
2.检查对象类型是否一样 instanceof
3.检查对象属性的内容是否一样
boolean bool="123".equals("123");//true
String strA = new String("123");
String strB = new String("123");
bool = strA.equals(strB);//true
System.out.println(bool);
System.out.println(strA==strB);//false
new出来的对象有着不同的内存地址,直接用==比较,不是同一个对象,返回false
String类型的常用方法
valueOf() --将传入的任意类型的参数转换成字符串
public class EasyString {
public static void main(String[] args) {
String.valueOf('1');
String.valueOf(new Object());
//String.valueOf(null);
test(null);//调用String的参数
}
//也是一种就近原则,传入null值倾向于优先调用更精确的类型
public static void test(String str){
System.out.println("String");
}
public static void test(Object obj){
System.out.println("Object");
}
}
indexOf() --查找子串出现的位置
从前往后找用indexOf,从后往前找用lastIndexOf,找不到返回-1
int index = "123456".indexOf("3457");
System.out.println(index);
index = "123123123".indexOf("1");//从前往后找 0
index = "123123123".lastIndexOf("1");//从后往前找 下标位置6
System.out.println(index);
charAt() --获取指定位置的字符
下标从零开始,输入下标超限则抛出字符串下标越界异常StringIndexOutOfBoundException
char item = "123456".charAt(4);//5
System.out.println(item);
substring() --截取字符串
输入一个参数表示从哪个下标开始截取到最后
输入两个参数表示从哪里截取到哪里,包含开始下标,不包含结束下标,越界也会报异常
//截取字符串
str="123456".substring(1);//从下标为1开始截取到最后
System.out.println(str);
//从下标1截取到4,包含开始下标不包含结束下标
str="123456".substring(1,4);
System.out.println(str);
replace() --替换
把一个指定的字符串全部替换成另一个字符串
replaceAll() 正则表达式替换,元字符 点. 表示任意字符
str ="12345634".replace("34","AAA");
System.out.println(str);
//replaceAll正则表达式替换,元字符 点. 表示任意字符
str ="12.31.23".replaceAll(".","A");
System.out.println(str);
split() --分隔字符串
当指定的分隔符在最前面时,toString输出会有一个逗号,在最后则没有
//分割字符串
String[] strArr ="1231231231".split("1");//[, 23, 23, 23]
//用1分割字符串,在最前面会有个空串,在最后没有
System.out.println(Arrays.toString(strArr));
length() --字符串长度
注:数组的length是一个属性,而字符串类中是一个方法
// 字符串长度 length()方法
int l="123123".length();
for(int i=0;i<str.length();i++){
//自定义操作
}
trim() --去除前后空白位
空白位包括:空格 换行 \n 回车 \r 制表符 \t
前后的空白位能去除,中间的空白位不会去除
//trim 去除前后空白位 空格 \n \r \t
String strC = "\n\r \t 1 2 3 \n\r ";
System.out.println(strC);
System.out.println("-----------");
System.out.println(strC.trim());//中间的空白位不会去除
大小写(针对字母)
toUpperCase() --将小写字母转换成大写,其他不变
toLowerCase() --将大写字母转换成小写,其他不变
// 大写 小写(针对字母)
str = "123ABCabc".toUpperCase();
System.out.println(str);
str = "123ABCabc".toLowerCase();
System.out.println(str);
isEmpty() --判断是否为空串
//判断是否为空串
bool = "123".isEmpty();//是空串返回true
System.out.println(bool);
if(!str.isEmpty()) {//非空,取反
//str有内容就要执行此代码
}
startsWith() --是否以某个字符串开始
endsWith() --是否以某个字符串结束
//是否以某个字符串开始或结束
"123456".startsWith("123");//true
"123456".endsWith("56");//true
字符串常量池
字符串常量池是为了节省内存空间、提高字符串比较效率和作为字符串缓存的一种机制。它通过复用相同值的字符串对象来优化程序的性能。
String 对象,定义后就不可以改变,是常量,存储在:
private final char value[];//外部访问不到,不可修改
也就是说一旦创建就不能修改。为了节省内存空间,只需要在常量池中存储一个字符串的实例,多个引用可以指向同一个实例,而不需要每个引用都创建一个新的实例。
字符串怎样加入到常量池中:使用量的方式声明的字符串就会加入到常量池中
String str="abc";
Integer.valueOf("23");
char[] arr={'a','b','c'};
str=new String(arr);//这种不会加入到常量池中
程序中第一次使用量的形式定义"123",会将这个字符串对象存入<字符串常量池中>
之后再使用量的形式使用该对象,就执行使用常量池中的对象。
String strA = "123";
String strB = "123";
System.out.println(strA==strB);//true,指向常量池中的同一个对象
使用 new 创建的对象开辟了新的内存地址,不在常量池之中,不指向同一个对象。
String strC = new String("123");
String strD = new String("123");
System.out.println(strA==strC);//false
System.out.println(strC==strD);//false
常量编译优化
常量的编译优化是指编译器在编译过程中对常量表达式进行优化的过程。常量是指在程序执行过程中其值不会发生改变的变量。常量的编译优化可以提高程序的执行效率和性能。
常量的编译优化包括以下几个方面:
-
常量折叠(Constant Folding):编译器在编译过程中发现可以在编译时就计算出常量表达式的结果时,会直接将计算结果代替原表达式,避免了在运行时再进行计算的开销。
-
常量传播(Constant Propagation):编译器会将常量的值传播到使用该常量的地方,避免了重复计算常量的开销。
-
常量合并(Constant merging):如果多个常量具有相同的值,编译器会将它们合并为一个常量,减少程序的存储空间。
-
常量预计算(Constant Pre-computation):编译器会将常量表达式的计算结果预先计算出来,并将结果存储在程序的常量池中,减少运行时的计算开销。
常量的编译优化可以提高程序的执行效率和性能,减少不必要的计算和存储开销,从而提升程序的运行速度和资源利用率。
String strE="12"+"3";//常量优化,在编译时已经解析成"123"
String strF="1"+"2"+"3";//"123"
String strH="12"+3;
System.out.println(strA==strE);//true
System.out.println(strA==strF);//true
System.out.println(strE==strF);//true
System.out.println(strA==strH);//true
当表达式中有变量参与、需要方法的返回值、或者new了新的对象等,这些情况不符合常量的编译优化,不会直接使用常量池中的对象,而是另外开辟内存空间来存储字符串
String item="12";//变量,没有常量计算优化
//str()返回"12" + "3"
String strG=item+"3";
String strGG=item+3;
System.out.println((strG==strGG)+"--------");//false
String strH="12"+3;
final String aa="12";
String strI = aa+"3";//只要是常量就是明确的,就有常量计算优化
System.out.println(strA==strI);//true
final String bb=new String("12");
String strJ = bb+"3";
System.out.println(strA==strJ);//false
str.intern() --返回str对象在字符串常量池中的副本对象
过程:检查str是否在字符串常量池中存在副本,如果已经存在,直接返回副本对象;
如果不存在就复制一份存入常量池中,然后返回常量池中的副本对象。
如果两个字符串对象equals结果为true,那么这两个的intern方法一定 ==
strA = new String("123123");
strB = new String("123123");
System.out.println(strA.equals(strB));//true
System.out.println(strA==strB);//false
System.out.println(strA.intern()==strB.intern());//true
面试题:new String("abc") 创建了几个对象? 一个或两个
如果常量池已经存在副本对象就一个;如果没有副本对象,需要先在常量池中创建一个副本对象,然后在new一个新的对象,就是两个。
StringBuilder 和 StringBuffer类
//String 字符串定义后就不可改变,存在常量池中
String str="";
for(int i=0;i<10;i++){//将字符串0、1、2...拼接
str=str+i;
}
//0 01 012 0123 01234... 0123456789会产生大量的中间串
为了在字符串拼接时不产生中间串,减少不必要的内存浪费,使用StringBuilder类
StringBuilder strB=new StringBuilder();//可以传入参数设置容量,默认16
strB.append("123");//可以追加任意类型
strB.append("abc");
strB.append("456");
System.out.println(strB.toString());
StringBuilder中继承父类的value数组不是用final修饰的,是动态可变的,允许扩容
调用append方法,往StringBuilder数组中追加字符,中间没有产生字符串对象。
StringBuilder 默认容量16
追加字符容量不够时需要扩容,默认扩容: 原来的容量 *2+2
StringBuffer与StringBuilder基本相同,但StringBuffer的方法用 synchronized 修饰:线程同步
StringBuffer线程安全,但效率不如StringBuilder。
arraycopy() 数组拷贝(五个参数)
System.arraycopy(src,0,dest,0,23);
五个参数分别表示:从源数组的,哪个位置开始,复制到目标的,哪一个位置,复制多少个
时间类型 Date
使用long类型来记录时间,从1970-1-1 00:00:00 000 开始,每过一毫米就+1
获取代码执行时的当前时间
Date date=new Date();//传入一个值可以当做一个时间戳
System.out.println(date);
long time = date.getTime();//获取时间戳
System.out.println(time);
time = time+2*24*60*60*1000;//加两天
date = new Date(time);
System.out.println(date);
当使用new Date(0); 发现得出的结果是1970-1-1 8:00:00 是由于时区的缘故(东八区)
月份取值用0~11表示,日期从1号开始,时间从0点到23点59分59秒
System.out.println(date.getMonth());//用0~11表示月份
System.out.println(date.getDate());//日期从1号开始
System.out.println(date.getHours());//时间从0点到23点59分59秒999
SimpleDateFormat时间格式化:
yyyy表示年份2024,yy表示24,HH表示24小时制,hh表示12小时制,SSS表示毫秒
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String sdfStr=sdf.format(date);
System.out.println(sdfStr);
LocalDateTime 本地时间
ZoneDateTime 时区时间
LocalDateTime ldt = LocalDateTime.now();//本地时间
System.out.println(ldt);
ZonedDateTime zdt = ZonedDateTime.now();
Object obj = zdt.getZone();//记录时区
System.out.println(obj);
Math类
取整
1.四舍五入取整 round()
传入double返回long类型,传入float返回int类型
往数轴正方向四舍五入取整:-0.5取0,-0.4取0,-0.6取-1
2.向上取整 ceil() 返回double类型
3.向下取整 floor() 返回double类型
//取整
ranNum = 12.5;
//四舍五入取整,传入double返回long类型,传入float返回int类型
//往上取,-0.5取0 -0.4取0 -0.6取-1 往数轴正方向上取
long num = Math.round(ranNum);//13
int intNum = Math.round(12.33f);
//向上取整
double ceilNum = Math.ceil(ranNum);//13.0
//向下取整
double floorNum = Math.floor(ranNum);//12.0
某些具体应用中,向下取整比向上取整好:
-
计算结果更接近实际值:向下取整会将小数部分舍去,得到一个整数值,该值更接近数轴上的原始值。而向上取整则会将小数部分进一位,得到一个比原始值更大的整数值。对于需要精确计算的情况,如金融领域的利息计算,向下取整可以得到更接近实际值的结果。
-
消除误差累积:在一系列计算中,如果每次都向上取整,会导致误差逐渐累积,最终得到的结果可能会与实际值相差较大。而向下取整则可以减少误差的累积,得到更稳定的结果。
-
避免数据溢出:在计算机中,整数类型的取值范围是有限的,如果计算结果超过了这个范围,就会发生数据溢出。向下取整可以减少这种溢出的可能性,使得计算结果更可靠。
总之,向下取整可以得到更接近实际值、更稳定的结果,因此在很多情况下更合适和更优选。但具体应用时,还需根据实际需求和场景来选择合适的取整方式。
随机数
使用 Math.random() 获取 [0,1) 之间的随机数,真随机
//随机数
double ran=Math.random();// [0,1)之间的随机数,真随机
//ran*50 表示[0,50)
double ranNum = ran*82+8; //表示[8,90)
Random类的随机数对象,假随机
生成随机数需要传入一个种子,没有则默认传入当前时间戳作为种子
相同种子在获取相同次数的随机数时,产生的结果相同
Random ranObj=new Random(12);//需要一个种子获取随机数
Random ranObjN=new Random(12);
int a = ranObj.nextInt();
int b = ranObjN.nextInt();
System.out.println(a+"===="+b);//种子一样每次获取的随机数就一样,是假随机
a = ranObj.nextInt(200);
b = ranObjN.nextInt(200);//可以传入一个界限,也是一样的
System.out.println(a+"===="+b);
思考:两个对象的哈希值一样怎么解决?
对象的哈希值是一个整数值,由对象的内容计算而来。哈希值主要用于在数据结构中进行查找、插入和删除操作。例如,哈希表是一种通过哈希值快速定位元素的数据结构。
哈希值的作用是通过将对象映射到一个较小的范围内的整数值,从而加快查找操作的速度。哈希值可以将对象分配到不同的存储桶或槽中,在查找时只需要比较对象的哈希值,而不需要对整个对象进行比较。
如果两个对象的哈希值相同,这种情况称为哈希冲突。为了解决哈希冲突,常见的方法有两种:
-
链接法:将哈希冲突的对象放在同一个存储桶中,使用链表或其他数据结构来存储冲突的对象。
-
开放寻址法:当发生哈希冲突时,通过一定的算法找到另一个位置存储对象,直到找到一个空的位置。
这样,即使两个对象的哈希值相同,它们仍然可以被正确地插入和检索。