字符串和编码
String
String是引用类型,它也是一个class,"…"表示一个字符串
String s = "Hello!";
String内部是通过一个char[ ]数组表示的
String s = new String(new char[ ]{‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘!’} );`
字符串比较
比较字符串内容是否相同,必须用equal()方法而不能用==。
Java字符串具有不可变性,字符串生成后就存放在常量池中,引用的指向可以改变,但是字符串内容不可变
String s1 = "hello";//s1指向“hello”
String s2 = "hello";//s2指向“hello”
String s3 = "HELLO".toLowerCase();//创建新对象“HELLO”
System.out.println(s1==s2);//true,因为s1、s2都指向hello这同一个对象
System.out.println(s1==s3);//false,s1、s3的引用不同
System.out.println(s1.equals(s3));//true,字符串内容相同
String类常用方法
搜索子串
"Hello".contains("ll");//true,注意contains()方法的参数是CharSequence,是String的父类
"Hello".indexOf("l");//2,返回子串第一次出现的位置
"Hello".lastIndexOf("l");//3,返回子串最后一次出现的位置
"Hello".startsWith("He");//true,判断字符串是否以"..."开头
"Hello".endsWith("lo");//true,判断字符串是否以"..."结尾
提取子串
"Hello".substring(2);//"llo"
"Hello".substring(2,4);//"ll",提取到4-1=3那个字符
去除首尾空白字符
*下面这些方法返回的是新字符串,旧字符串还在常量池里,字符串内容具有不变性 *
trim()方法可以移除字符串首位空白字符,空白字符包括空格,\t, \r, \n
strip()方法类似中文的空格字符\u3000也会被移除
String提供isEmpty()和isBlank()来判断字符串是否为空和空白字符串
" \tHello\r\n".trim();//"Hello"
"\u3000Hello\n".strip();//"Hello"
" Hello ".stripLeading();//"Hello " 去除开头空白字符
" Hello ".stripTrailing();//" Hello" 去除尾部空白字符
"".isEmpty();//true,因为字符串长度为0,这里注意一点和C语言不同,Java字符串没有以'\0'结尾
" \n".isBlank();//true,只包含空白字符
替换子串
根据字符或字符串替换,通过正则表达式替换
String s = "hello";
s.replace('l','w');//"hewwo"
s.replace("ll","~~");//"he~~o"
String s = "A,,B;C ,D";
s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"
上面的代码通过正则表达式,把匹配的子串同意替换为","
分割字符串
使用split()方法,传入的也是正则表达式
String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}
拼接字符串
静态方法join(),用指定的字符串连接字符串数组
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"
格式化字符串
字符串提供了formatted()方法和format()静态方法,可以传入其他参数,替换占位符,然后生成新的字符串:
public class Main(){
public static void main(String[] args){
String s = "Hi %s, your score is %d!";
System.out.println(s.formatted(Alice, 80));
System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
}
}
占位符和参数一致(类型,数量)。常用的占位符:
%s:显示字符串
%d:显示整型
%x:显示十六进制整数
%f:显示浮点数
类型转换
使用静态方法valueOf(),把任意基本类型或者引用类型转换为字符串
String.valueOf(123);//"123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 类似java.lang.Object@636be97
把字符串转换为其他类型
int n1 = Integer.parseInt("123");//123
int n2 = Integer.parseInt("ff",16);//按十六进制转换,255
//字符串转换为boolean类型
boolean b1 = Boolean.parseBoolean("ture");//true
boolean b2 = Boolean.parseBoolean("FALSE");//false
要特别注意,Integer有个getInteger(String)方法,它不是将字符串转换为int,而是把该字符串对应的系统变量转换为Integer:
String和char[ ]类型可以互相转换
char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String
字符编码
Java的String和char在内存中总是以Unicode编码表示。
关于字符编码的详细解释可以参考廖雪峰老师的Java教程
字符编码
小结
Java字符串String是不可变对象;
字符串操作不改变原字符串内容,而是返回新字符串;
常用的字符串操作:提取子串、查找、替换、大小写转换等;
Java使用Unicode编码表示String和char;
转换编码就是将String和byte[]转换,需要指定编码;
转换为byte[]时,始终优先考虑UTF-8编码。
StringBuilder
直接用+拼接字符串,每次拼接都会创建新的String对象,浪费内存
高效拼接内存->StringBuilder(Java标准库提供)
StringBuilder sb = new StringBuilder(1024);//参数是缓冲区大小
for(int i = 0; i < 1000; i++){
sb.append(',');
sb.append(i);
}
String s = sb.toString();
StringBuilder还可以进行链式操作,每次append返回this对象
public class Main {
public static void main(String[] args) {
var sb = new StringBuilder(1024);
sb.append("Mr ")
.append("Bob")
.append("!")
.insert(0, "Hello, ");
System.out.println(sb.toString());
}
}
//Hello, Mr Bob!
小结
StringBuilder是可变对象,用来高效拼接字符串;
StringBuilder可以支持链式操作,实现链式操作的关键是返回实例本身;
StringBuffer是StringBuilder的线程安全版本,现在很少使用
StringJoiner
用分隔符拼接字符串数组的需求很常见,Java标准库提供了StringJoiner来实现,还可以指定“开头”和“结尾”
import java.util.StringJoiner;
public class Main {
public static void main(String[] args) {
String[] names = {"Bob", "Alice", "Grace"};
var sj = new StringJoiner(", ", "Hello ", "!");
for (String name : names) {
sj.add(name);
}
System.out.println(sj.toString());
}
}
包装类型
Java数据类型
- 基本类型:byte,short,int,long,boolean,float,double,char
- 引用类型:所有class和interface类型,引用类型可以复制为null,
将基本类型变成引用类型->包装类型
public class Main {
public static void main(String[] args) {
int i = 100;
// 通过new操作符创建Integer实例(不推荐使用,会有编译警告):
Integer n1 = new Integer(i);
// 通过静态方法valueOf(int)创建Integer实例:
Integer n2 = Integer.valueOf(i);
// 通过静态方法valueOf(String)创建Integer实例:
Integer n3 = Integer.valueOf("100");
System.out.println(n3.intValue());
}
}
自动装箱和拆箱
自动装箱和拆箱发生在编译阶段,目的是少写代码
Integer n = 100; // 编译器自动使用Integer.valueOf(int)
int x = n; // 编译器自动使用Integer.intValue()
引用类型的比较一定要用equals()
** 创建新对象时,优先选用静态工厂方法而不是new操作符。**
例如 优先方法2
方法1:Integer n = new Integer(100);
方法2:Integer n = Integer.valueOf(100);
进制转换
Interger类中的静态方法parsenInt()可以把字符串解析成整数
int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析
所有的整数和浮点数都继承自Number
处理无符号数
Java并没有无符号整型(Unsigned)的基本数据类型。无符号整型和有符号整型的转换需要借助包装类型的静态方法完成
例如byte是有符号整形,范围是-128+127,但是如果把byte看成无符号整型,范围就是0255。
public class Main {
public static void main(String[] args) {
byte x = -1;
byte y = 127;
System.out.println(Byte.toUnsignedInt(x)); // 255
System.out.println(Byte.toUnsignedInt(y)); // 127
}
}
小结
Java核心库提供的包装类型可以把基本类型包装为class;
自动装箱和自动拆箱都是在编译期完成的(JDK>=1.5);
装箱和拆箱会影响执行效率,且拆箱时可能发生NullPointerException;
包装类型的比较必须使用equals();
整数和浮点数的包装类型都继承自Number;
包装类型提供了大量实用方法。
JavaBean
JavaBean是一种符合命名规范的class,它通过getter和setter来定义属性;
属性是一种通用的叫法,并非Java语法规定;
可以利用IDE快速生成getter和setter;
使用Introspector.getBeanInfo()可以获取属性列表。
枚举类
使用enum来定义枚举类
enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
使用enum定义枚举的好处:num常量带有类型信息,赋值时如果类型不匹配,编译器会报错
enum的比较
==比较的是两个引用类型的变量指向是否一致(是否指向同一个对象)
equals()比较的是变量值
引用类型的比较一定要用equals()方法,但是enum类型可以例外,因enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==比较
enum类型的方法
name()
返回常量名
String s = Weekday.SUN.name(); // "SUN"
ordinal()
返回定义的常量的顺序,从0开始计数
int n = weekday.MON.ordinal();//1
enum类型用于switch语句
public class Main {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
switch(day) {
case MON:
case TUE:
case WED:
case THU:
case FRI:
System.out.println("Today is " + day + ". Work at office!");
break;
case SAT:
case SUN:
System.out.println("Today is " + day + ". Work at home!");
break;
default:
throw new RuntimeException("cannot process " + day);
}
}
}
enum Weekday {
MON, TUE, WED, THU, FRI, SAT, SUN;
}
加上default语句,可以在漏写某个枚举常量时自动报错,从而及时发现错误。
小结
Java使用enum定义枚举类型,它被编译器编译为final class Xxx extends Enum { … };
通过name()获取常量定义的字符串,注意不要使用toString();
通过ordinal()返回常量定义的顺序(无实质意义);
可以为enum编写构造方法、字段和方法
enum的构造方法要声明为private,字段强烈建议声明为final;
enum适合用在switch语句中。
记录类
String,Integer都是不变类
Java 14开始,引入了新的Record类,使用关键字record定义Record类
例如定义一个Point类,他是Record类
public class Main(){
public static void main(String[ ] args) {
Point p = new point(123,456);
System.out.println(p.x());
System.out.println(p.y());
System.out.println(p);
}
}
public record Point(int x, int y){}/
自动用final修饰class以及每个地段,自动创建构造方法,重写toString()、equals()、hashCode()方法名
小结
小结
从Java 14开始,提供新的record关键字,可以非常方便地定义Data Class:
使用record定义的是不变类;
可以编写Compact Constructor对参数进行验证;
可以定义静态方法。
BigInteger
BigInteger内部用一个int[ ]数组来模拟超大整数
import java.math.BigInteger
BigInteger bi = new BigInteger("1234567890");
System.out.println(bi.pow(5)); // 2867971860299718107233761438093672048294900000
只能用实例方法对BigInteger做运算(不能用±这些)
BigInteger i1 = new BigInteger("1234567890");
BigInteger i2 = new BigInteger("12345678901234567890");
BigInteger sum = i1.add(i2); // 1234567890246913578
BigInteger继承自Number类,Number定义了转换为基本类型的几个方法
转换为byte:byteValue()
转换为short:shortValue()
转换为int:intValue()
转换为long:longValue()
转换为float:floatValue()
转换为double:doubleValue()
BigInteger转换成基本类型会有高位丢失的风险(溢出),可以使用intValueExact()、longValueExact()等方法,在转换时如果超出范围,将直接抛出ArithmeticException异常。
BigInteger i = new BigInteger("123456789000");
System.out.println(i.longValue()); // 123456789000
System.out.println(i.multiply(i).longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range
小结
BigInteger用于表示任意大小的整数;
BigInteger是不变类,并且继承自Number;
将BigInteger转换成基本类型时可使用longValueExact()等方法保证结果准确。
BigDecimal
BigDecimal可以表示一个任意大小且精度完全准确的浮点数。
同样有各种方法,详细参考廖老师的Java教程
BigDecimal
常用工具类
Math
求绝对值:
Math.abs(-100);//100
Math.abs(-7.8); // 7.8
取最大或最小值:
Math.max(100, 99); // 100
Math.min(1.2, 2.3); // 1.2
计算x的y次方:
Math.pow(2, 10); // 2的10次方=1024
计算√x
Math.sqrt(2); // 1.414…
计算e的x次方:
Math.exp(2); // 7.389…
计算以e为底的对数:
Math.log(4); // 1.386…
计算以10为底的对数:
Math.log10(100); // 2
三角函数:
Math.sin(3.14); // 0.00159…
Math.cos(3.14); // -0.9999…
Math.tan(3.14); // -0.0015…
Math.asin(1.0); // 1.57079…
Math.acos(1.0); // 0.0
Math还提供了几个数学常量:
double pi = Math.PI; // 3.14159…
double e = Math.E; // 2.7182818…
Math.sin(Math.PI / 6); // sin(π/6) = 0.5
生成一个随机数x,x的范围是0 <= x < 1:
Math.random(); // 0.53907… 每次都不一样
如果我们要生成一个区间在[MIN, MAX)的随机数,可以借助Math.random()实现,计算如下:
// 区间在[MIN, MAX)的随机数
public class Main {
public static void main(String[] args) {
double x = Math.random(); // x的范围是[0,1)
double min = 10;
double max = 50;
double y = x * (max - min) + min; // y的范围是[10,50)
long n = (long) y; // n的范围是[10,50)的整数
System.out.println(y);
System.out.println(n);
}
}
Random
Random用来创建伪随机数,伪随机数是指只要种子一致,产生的随机数序列是完全一样的
生成随机数。默认使用系统当前时间戳作为种子
Random r = new Random();
r.nextInt();//15798153,每次都不一样
r.nextInt(10);//5,生成一个[0,10]之间的int
r.nextLong();
r.nextFloat();//生成一个[0,1]之间的float
r.nextDouble();//生成一个[0,1]之间的double
Math.random()实际上内部调用了Random类,只是我们无法指定种子
SecureRandom
真正的真随机数只能通过量子力学原理来获取
SecureRandom用来创建安全的不可预测的随机数
SecureRandom无法指定种子
SecureRandom的安全性是通过操作系统提供的安全的随机种子来生成随机数。这个种子是通过CPU的热噪声、读写磁盘的字节、网络流量等各种随机事件产生的“熵”。
在密码学中,安全的随机数非常重要。如果使用不安全的伪随机数,所有加密体系都将被攻破。因此,时刻牢记必须使用SecureRandom来产生安全的随机数。
SecureRandom sr = new SecureRandom();
System.out.println(sr.nextInt(100));
实际使用,有限获得高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器:
import java.util.Arrays;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public class Main {
public static void main(String[] args) {
SecureRandom sr = null;
try {
sr = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器
} catch (NoSuchAlgorithmException e) {
sr = new SecureRandom(); // 获取普通的安全随机数生成器
}
byte[] buffer = new byte[16];
sr.nextBytes(buffer); // 用安全随机数填充buffer
System.out.println(Arrays.toString(buffer));
}
}