day19
字符串相关的类
参考常用类.doc–见文件资料
分类:
1.String
2.StringBuffer
3.StringBuilder
String
1.概念
String是不可变类, 即一旦一个String对象被创建, 包含在这个对象中的字符序列是不可改变的, 直至该对象被销毁。
String类是final类,不能有子类。
2.使用
String str = “123abc”;//(下标)字符串底层:new char[]{‘1’,‘2’,‘3’,‘a’,‘b’,‘c’};
拼接: str = str.concat(“DEf123”);//拼接字符串,并返回新的字符串
截取:substring(2)/substring(1, 7);从开始下标截取到末尾,并返回新的/从开始下标除(包含)截取到结束下标处(排他),并返回新的字符串
转大/小写:toUpperCase()/toLowerCase();//转大写,并返回新的字符串
去除首尾空格:trim();//去除首尾空格,并返回新的字符串
replaceAll(" ", “”);//去除空格(将空格字符串替换成空内容的字符串)
替换字符/字符串:replace(‘a’, ‘A’);//替换字符,并返回新的字符串
replaceAll(“ABC”, “abc”);//(支持正则表达式)替换字符串,并返回新的字符串
replaceFirst(“23”, “66”);//替换第一次出现的字符串,(replaceAll()替换所有出现的字符串),并返回新的字符串
获取字符串的字符个数:length()
判断两个字符串的内容是否相同(区分大小写):equals
判断两个字符串的内容是否相同(不区分大小写):equalsIgnoreCase(做验证码适用)
判断字符串的内容是否以某个字符串开头/结尾:startsWith/endsWith
查找子字符串在此字符串第一次/最后一次出现的下标indexOf(“1”)/lastIndexOf(“1”)
获取字符串中指定下标的字符:charAt(6)
把其他类型转换为String类型:valueOf()/用拼接也可以10 + “”
* String练习题: 练习:完成一个邮箱格式的校验 hhy@qq.com (1),“@”不能在第一位 (2),“.”不能在最后一位 (3),“@”和“.”中间应该有字符 (4),***@***.*** 当然还有更严格的校验规则,我们此处考虑到这即可 * */ String email = "hhy@qq.com"; int indexOf01 = email.indexOf("@"); int indexOf02 = email.indexOf("."); if (indexOf01==0 || indexOf02==email.length()-1 || indexOf02-indexOf01<=1) { System.out.println("邮箱格式错误"); }else{ System.out.println("邮箱格式正确"); }
StringBuffer/StringBuilder
1.概念
StringBuffer代表可变的字符序列。
StringBuffer称为字符串缓冲区,它的工作原理是:预先申请一块内存,存放字符序列,如果字符序列满了,会重新改变缓存区的大小,以容纳更多的字符序列。StringBuffer是可变对象,这个是String最大的不同
StringBuilder与StringBuffer的用法完全一致,唯一的区别是StringBuffer是线程安全的,而StringBuilder不是线程安全的。所以StringBuilder的性能要比StringBuffer要好。单线程推荐使用StringBuilder,多线程使用StringBuffer。
2.两者和String比较
StringBuilder/StringBuffer是可变对象:因为char[] value = new char[];
String是不可变的对象,因为final char[] value = new char[];
3.使用
StringBuffer sb = new StringBuffer();//new出来的对象没有内容的 sb.append("123abcDEF123");//在末尾追加字符串 sb.insert(6, "xyz");//将字符串插入到此字符串指定下标的位置 sb.setCharAt(6, 'X');//替换指定下标上的字符 sb.replace(3, 9, "ABCDEFG");//从开始下标(包含)替换到结束下标(排他)的字符串 sb.deleteCharAt(6);//删除指定下标上的字符 sb.delete(3, 11);//从开始下标(包含)删除到结束下标(排他)的字符串 sb.reverse();//反转字符串 int len = sb.length(); System.out.println("字符串长度为:" + len); System.out.println(sb.toString());//底层重写了toString()
注意:在调用方法层面上StringBuilder和StringBuffer是一样的
3.功能
StringBuffer sb = new StringBuffer();
sb.append(“xxx”);
4.构造方法
构造字符串缓冲区
StringBuilder/StringBuffer代表可变的字符序列,
StringBuilder/StringBuffer称为字符串缓冲区,
它的工作原理是:预先申请一块内存(char[] value = new char[]),存放字符序列,如果字符序列满了,会重新改变缓存区的大小,以容纳更多的字符序列(扩容机制:原来数组长度的两倍+2)。
//缓冲区默认长度:16个字符 StringBuffer sb = new StringBuffer(); //自定义缓冲长度:99个字符 StringBuffer sb = new StringBuffer(99); //自定义缓冲长度:"abc".length() + 16 StringBuffer sb = new StringBuffer("abc");
5.应用场景
频繁拼接字符串,使用StringBuilder/StringBuffer
(1)应用原因
字符串的不变性:一个String对象的长度是固定的,不能改变它的内容,或者是附加新的字符到String对象中。
如果程序对这种附加字符串的需求很频繁,系统会频繁在内存中创建String对象,造成性能下降。所以并不建议使用+来进行频繁的字符串串联。应该使用java.lang.StringBuffer类。
(2)案例
//获取自1970年1月1日0:0:0:到现在的毫秒数(1000毫秒 = 1秒)
long currentTimeMillis = System.currentTimeMillis();
// long startTime = System.currentTimeMillis(); // String str = "奇男子"; // for (int i = 1; i < 50000; i++) { // str += "欲戴其冠必乘其重"; ///* 底层实现: // str = str + "欲戴其冠必乘其重"; // str = new StringBuilder(str).append("欲戴其冠必乘其重");//变量参与多次new StringBuilder对象*/ // } // long endTime = System.currentTimeMillis(); // System.out.println("消耗时长" + (endTime - startTime));//消耗时长5303
System.out.println(“-----------------------------------”);
//优化// StringBuilder sb = new StringBuilder("奇男子");//只new一个StringBuilder对象 // for (int i = 1; i < 500000; i++) { // sb.append("欲戴其冠必乘其重"); // }
System.out.println(“-----------------------------------”);
//再优化StringBuilder sb = new StringBuilder(560000);//优化方案:减少底层扩容次数 sb.append("奇男子"); for (int i = 1; i < 500000; i++) { sb.append("欲戴其冠必乘其重"); }
字符串类的深入
常量池概念:
Java运行时会维护一个常量池, 常量池用来存放运行时中产生的各种字符串,并且池中的字符串的内容不重复。
1.面试题
面试题1:
描述下列代码会创建几个String对象
String str1 = "abc"; String str2 = "abc";
答案:1个(常量池中的数据不允许重复)
面试题2:
描述下列代码会创建几个String对象
String str1 = new String("abc"); String str2 = new String("abc");
答案:3个(new了两个String对象 + “abc”)
2.深入String创建对象
String str1 = "abc"; String str2 = "abc"; System.out.println(str1 == str2);//true //注意两个常量在编译时直接拼接 String str3 = "ab" + "c";//String str3 = "abc"; System.out.println(str1==str3);//true //注意两个常量在编译时直接拼接 final String s1 = "ab"; final String s2 = "c"; String str4 = s1 + s2;//String str4 = "abc"; System.out.println(str1 == str4);//true //注意:有变量参与字符串拼接的情况,底层会创建StingBuilder进行拼接 String s3 = "ab"; String s4 = "c"; String str5 = s3 + s4;//newStringBuilder(String.value(s3)).append(s4).toString(); System.out.println(str1 == str5);//false
注意:可以用将代码的class文件反编译查看底层拼接情况
3.StringBuilder和StringBuffer的底层原理
注意:
1.StringBuffer和StringBuilder都是继承的AbstractStringBuilder
2.StringBuffer和StringBuilder的核心功能都是由父类实现
3.StringBuffer在方法中上锁、解锁 – 在多线程下使用
4.StringBuffer和StringBuilder相比,因为有了上锁和解锁的步骤,所以效率没有StringBuilder高
public class MyAbstractStringBuilder { //缓存数组 char[] value; //储存字符个数 int count; public MyAbstractStringBuilder() { // TODO Auto-generated constructor stub } MyAbstractStringBuilder(int capacity){ value = new char[capacity]; } //获取字符个数 public int length() { return count; } public MyAbstractStringBuilder append(String str) { //如果添加的字符串为null if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } //添加字符串为null的情况 private MyAbstractStringBuilder appendNull(){ int c = count; //判断是否扩容的方法 ensureCapacityInternal(c + 4);//4 - "null".length() final char[] value = this.value; value[c++] = 'n'; value[c++] = 'u'; value[c++] = 'l'; value[c++] = 'l'; count = c; return this; } //判断是否扩容 private void ensureCapacityInternal(int minimumCapacity) { //有溢出的代码(容量必须大于缓存数组的长度才扩容) if (minimumCapacity - value.length >0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } } //计算新的长度 private int newCapacity(int minimumCapacity) { //扩容机制:原来的长度*2 + 2 int newCapacity = (value.length << 1) + 2;//+2的原因:怕用户设置初始化数组长度为0,0<<1还是0,就得不到扩容 return newCapacity; } }
ps:MyStringBuffer(StringBuilder不同之处,没有synchronized)
public class MyStringBuffer extends MyAbstractStringBuilder{ public MyStringBuffer(){ super(16);//默认缓冲数组的长度 } public MyStringBuffer(int capacity){ super(capacity);//自定义缓冲数组的 } public MyStringBuffer(String str){ super(str.length() + 16);//自定义缓存数组的长度:字符串长度+16 append(str);//追加字符串 } //synchronized -- 线程安全的方法 @Override public synchronized MyAbstractStringBuilder append(String str) {//上锁 super.append(str);//依赖于父类的append return this; }//解锁 }
正则表达式
参考正则表达式常用文档
见文件资料
概念:
含义:用来描述或者匹配一系列符合某个语句规则的字符串
Pattern类 及 Matcher
Pattern:代表正则表达式的匹配模式
Matcher:提供了对正则表达式的分组支持,以及对正则表达式的多次匹配支持
总结:Pattern与Matcher一起合作.Matcher类提供了对正则表达式的分组支持,以及对正则表达式的多次匹配支持. 单独用Pattern只能使用Pattern.matches(String regex,CharSequence input)一种最基础最简单的匹配。
正则表达式作用
1.做替换
案例:把一个字符串中带电话号码替换成138****1234
String str = "小陈12345678912小红12343565646小黄12122142154"; String regex = "(1\\d{2})(\\d{4})(\\d{4})";//两个\\表示一个\,d表示数字D表示非数字 str = str.replaceAll(regex, "$1****$3");//$n保留第几组正则表达式里的数据
2.做验证
案例:验证QQ邮箱
String email = "183666918@qq.com"; String regex = "\\d{4,11}@qq.com";//数字在什么区间的限定,后缀固定 boolean matches = email.matches(regex);//获取验证结果
3.做字符串的分割
案例:分割路径
String str = "C:\\资源\\中国\\春秋"; // String regex = "\\\\";//分割:\\ //:?表示冒号出现1次或0次 String regex = ":?\\\\";//分割::\\ 或 \\ String[] split = str.split(regex); for (String string : split) { System.out.println(string); }
4.利用正则表达式做字符串的爬数据的工作
案例:Pattern和Matcher找到前端代码中的图片路径
String str = "<img src='hhy/aaa.jpg'/><div><div/> <input type='image' src='submit.gif' /><img src='bbb.jpg'/>"; String regex = "<img\\b[^>]*\\bsrc\\b\\s*=\\s*('|\")?([^'\"\n\r\f>]+(\\.jpg)\\b)[^>]*>"; //获取正则表达式的对象 Pattern pattern = Pattern.compile(regex); //获取匹配结果 Matcher matcher = pattern.matcher(str); //循环遍历匹配结果 while(matcher.find()){ // System.out.println("x");//验证匹配几个 //获取匹配结果 String group = matcher.group(3);//3表示第3组 System.out.println(group);
日期时间类
分类:
- Date类
- SimpleDateFormat类
- Calendar类
Date(java.util)
日期类
Date date = new Date();
System.out.println(date);
//Sun Jan 28 21:11:46 CST 2024
//星期 月份 日期 时:分:秒 区 年份
SimpleDateFormat
格式化日期类
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
//Date转String
String datetime = sdf.format(new Date());
System.out.println(datetime);
//String转Date
Date date = sdf.parse("2024年01月28日21时20分09秒");
System.out.println(date);
Calendar
日历类
//获取当前系统的日历对象
Calendar c = Calendar.getInstance();
//简单工厂模式
int year = c.get(Calendar.YEAR);
int moth = c.get(Calendar.MONTH)+1;//注意:月份0~11,0表示1月,所以加1
int day = c.get(Calendar.DAY_OF_MONTH);
int hour = c.get(Calendar.HOUR);
int minute = c.get(Calendar.MINUTE);
int second = c.get(Calendar.SECOND);
System.out.println(year);
System.out.println(moth);
System.out.println(day);
System.out.println(hour);
System.out.println(minute);
System.out.println(second);
小结:
Date + SimpleDateFormat一起使用,获取我们想要的日期格式
获取当日历信息,考虑使用Calenda
Math类
理解:
Math类提供了一系列基本数学运算和几何函数的方法
Math类是final类,并且它的所有成员变量和成员方法都是静态的
方法:
System.out.println("求次方:" + Math.pow(2, 3));//求次方:8.0
System.out.println("求平方根:" + Math.sqrt(9));//求平方根:3.0
System.out.println("求绝对值:" + Math.abs(-100));//求绝对值:100
System.out.println("求最小值:" + Math.min(10, 20));//求最小值:10
System.out.println("求最大值:" + Math.max(10, 20));//求最大值:20
System.out.println("向上取整:" + Math.ceil(1.1));//向上取整:2.0
System.out.println("向下取整:" + Math.floor(1.9));//向下取整:1.0
System.out.println("四舍五入:" + Math.round(1.53));//四舍五入:2(取决于小数点后一位)
System.out.println("取随机值:" + Math.random());//取随机值:0.8459309625873379(注意:随机出0~1的数字,包含0,1排他)
//需求:随机出1~100的数字
int num = (int)(Math.random()*100)+1;
System.out.println("获取随机值:" + num);
//获取int类型的最大值 -> 2147483647 -- 2的31次方-1
System.out.println(Integer.MAX_VALUE);
//获取int类型的最小值 -> -2147483648 -- 负的2的31次方
System.out.println(Integer.MIN_VALUE);
//面试题:Math.abs会出现负数吗?
//答案:会出现负数 --> -2147483648
System.out.println(Math.abs(-2147483648));//超过int类型的最大取值范围
静态导入:
将类中所有的静态属性和方法都导入到本类来,作为本类自己的静态属性和方法
import static java.lang.Math.*;缺点:如果类中的方法和静态导入类中的方法重名了,会调用本类自己的静态方法,所有可读性差,不建议使用
public static void main(String[] args) {
System.out.println("求次方:" + pow(2, 3));//求次方:8.0
System.out.println("求平方根:" + sqrt(9));//求平方根:3.0
}
public static int sqrt(int i){
return 123456;
}
Random类
理解
随机类,此类用于生成随机数
方法
Random ran = new Random(); System.out.println("随机出int取值范围里的数值" + ran.nextInt()); System.out.println("随机出0~9的int数值" + ran.nextInt(10)); System.out.println("随机出double取值范围里的数值" + ran.nextDouble()); System.out.println("随机出boolean取值范围里的数值" + ran.nextBoolean());
种子数
是初始化时产生的一个long类型的变量,Random会根据改变了生成伪随机数流,种子数固定,随机出的数据就是固定的
MyRandom
注意:seed叫做种子数,种子数一旦固定随机出的数字就是固定的!
测试类:验证
Random ran = new Random(1000); System.out.println("随机出int取值范围里的数值" + ran.nextInt()); System.out.println("随机出0~9的int数值" + ran.nextInt(10)); System.out.println("-----------------------"); MyRandom myRandom = new MyRandom(4); System.out.println("随机出int取值范围里的数值" + myRandom.nextInt()); System.out.println("随机出0~9的int数值" + myRandom.nextInt(10));
Random底层原理
public class MyRandom { //种子数 private long seed; public MyRandom(){ this(seedUniquifier() ^ System.nanoTime());//伪随机数流 } //获取相对随机的值 private static long seedUniquifier() { for (;;) {//死循环 long cunrrent = System.currentTimeMillis();//获取一个long值 long next = cunrrent * 181783497276652981L; if (next%3==0 || next%12==0 || next%17==0) { return next; } } } public MyRandom(long seed) { super(); this.seed = seed;//随机种子数 } public int nextInt(){ return (int)seed;//返回种子数 } public int nextInt(int bound){ return Math.abs((int)seed)%bound;//随机出0~9的int数值 } }
System类
理解:
Java程序的运行平台
1.System类提供了一些静态属性和方法,允许通过类名直接调用
2.System类提供了代表标准输入、标注输出、错误输出的类属性
3.System类提供了一些静态方法用于访问环境变量、系统属性的方法
属性:
static PrintStream | err “标准”错误输出流。 |
---|---|
static InputStream | in “标准”输入流。 |
static PrintStream | out “标准”输出流。 |
//获取系统的标准输入流 - 方向:控制台 -> 程序
InputStream in = System.in;
Scanner scan = new Scanner(in);
System.out.println("请输入int值:");
int num = scan.nextInt();
//获取系统的标准输出流 - 方向:程序 -> 控制台
PrintStream out = System.out;
out.println(num);//黑色
//获取系统的标准错误输出流 - 方向:程序 -> 控制台
PrintStream err = System.err;
err.println(num);//红色
scan.close();
注意:理解System.out和System.err多线程抢资源
out和err先抢到资源先输出一个字符串结束一个,之后换行也会争抢,不一定在字符串输出就立即执行;再重复操作;直到所有都输出完结束。
由于上述条件就会出现多种情况
小陈
小明
小红小红
小陈
小明小陈
小红
小明小陈
小明
小红小陈
小红小明小陈小红
小明小红小陈
小明… System.out.println("小陈"); System.err.println("小红"); System.out.println("小明");
方法
long currentTimeMillis = System.currentTimeMillis();
System.out.println("获取自1970年1月1日到现在的毫秒数:" + currentTimeMillis);
//获取系统参数
Properties properties = System.getProperties();
System.out.println(properties);
System.out.println("---------------------");
//通过具体Key获取相应的Value
String property = System.getProperty("os.name");
System.out.println(property);
//拷贝数组
int[] arr = {1,2,3,4,5,6,7,8,9,10};
int[] newArr = new int[4];
System.arraycopy(arr, 3, newArr, 0, 4);//(原数组,开始下标,目标数组,开始下标,拷贝长度)
for (int num : newArr) {
System.out.println(num);
}
System.out.println("----------------");
//利用System.arraycopy做数组的删除功能
String[] names = {"aaa","bbb","ccc","ddd","eee","fff",null,null,null};
System.arraycopy(names, 2, names, 1, 4);
names[5] = null;
for (String str : names) {
System.out.println(str);
}
//退出当前虚拟机,0表示正常退出
System.exit(0);
Runtime类
理解
Runtime表示运行时系统(JVM)
Runtime代表Java程序的运行时环境,可以通过 getRuntime 方法获取当前运行时。
应用程序不能自己创建Runtime对象,可以通过Runtime的静态方法getRuntime()获得Runtime对象。
Runtime类可以访问jvm的相关信息,如处理器数量,内存信息等
方法
``` //获取运行时系统对象 Runtime run = Runtime.getRuntime(); System.out.println("获取最大操作数" + run.availableProcessors());//获取最大操作数12 System.out.println("获取最大内存数(byte)" + run.maxMemory());//获取最大内存数 System.out.println("获取空闲内存数(byte)" + run.freeMemory());//获取空闲内存数 ```
程序的效率(时间、内存)消耗相对越少越好
频繁拼接字符串,StringBuilder/StringBuffer应用场景案例,加上Runtime的方法测试其消耗内存
//再优化 Runtime run = Runtime.getRuntime(); long startTime = System.currentTimeMillis(); long startmemory = run.freeMemory(); StringBuilder sb = new StringBuilder(560000);//优化方案:减少底层扩容次数 sb.append("奇男子"); for (int i = 1; i < 500000; i++) { sb.append("欲戴其冠必乘其重"); } long endTime = System.currentTimeMillis(); long endmemory = run.freeMemory(); System.out.println("消耗时长" + (endTime - startTime));//消耗时长9 System.out.println("消耗内存" + (endmemory - startmemory));//消耗内存-16800120
大数值运算类
分类:
BigInteger
BigDecimal
BigInteger
整数大数值运算类
方法(API查找)ps:
BigInteger big1 = new BigInteger("1700000000000000");
BigInteger big2 = new BigInteger("1700000000000000");
BigInteger add = big1.add(big2);
System.out.println("加法" + add);
BigInteger subtract = big1.subtract(big2);
System.out.println("减法" + subtract);
BigInteger multiply = big1.multiply(big2);
System.out.println("乘法" + multiply);
BigInteger divide = big1.divide(big2);
System.out.println("除法" + divide);
BigDecimal
小数大数值运算类
注意: 1.小数做运算会失去精度,所以小数做运算都要生成大数值的运算类(用这个解决)
2.构造方法里的值使用字符串,否则会失去精度
方法(API查找)ps:
System.out.println(0.5 - 0.4);//0.09999999999999998
BigDecimal big1 = new BigDecimal("0.5");
BigDecimal big2 = new BigDecimal("0.4");
BigDecimal add = big1.add(big2);
System.out.println("加法" + add);
BigDecimal subtract = big1.subtract(big2);
System.out.println("减法" + subtract);
BigDecimal multiply = big1.multiply(big2);
System.out.println("乘法" + multiply);
BigDecimal divide = big1.divide(big2);
System.out.println("除法" + divide);
整数相除有小数情况:
BigDecimal big1 = new BigDecimal("10");
BigDecimal big2 = new BigDecimal("3");
BigDecimal divide = big1.divide(big2, 3, BigDecimal.ROUND_HALF_UP);//(执行数,保留小数位数,保留小数形成限定情况)
System.out.println("除法:" + divide);