对于String的每次修改,其实都是创建新内存空间,将修改后的值存入新内存空间,然后再将引用变量指向新内存空间的首地址。
我们如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间。
为解决上述问题,Java语言引入了StringBuffer。
StringBuffer默认分配参数字符串长度 + 16个字符的存储空间
StringBuffer
- 线程安全(多线程环境下访问数据的正确性)的 可变 字符序列
- 一个类似于 String 的字符串缓冲区,但可以修改(内容可变)
- 虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的 长度 和 内容
讲解构造方法的区别的时候,可以把长度功能和容量功能先解释一下。
长度:实际值
容量:理论值
- 构造方法
public StringBuffer()
public StringBuffer(int capacity)
public StringBuffer(String str)
public class Demo01 {
public static void main(String[] args) {
//public StringBuffer()
// capacity: 字符缓冲去本身的大小() 创建16个字符大小的缓冲区
// length:字符缓冲区中真正包含的字符个数
StringBuffer stringBuffer = new StringBuffer();
System.out.println(stringBuffer.capacity()); //16
System.out.println(stringBuffer.length()); //0
// public StringBuffer(int capacity) 可以指定StringBuffer初始容量
stringBuffer = new StringBuffer(20);
System.out.println(stringBuffer.capacity());
System.out.println(stringBuffer.length());
//public StringBuffer(String str)
stringBuffer = new StringBuffer("helloworld"); //默认分配参数字符串长度 + 16个字符的存储空间
System.out.println(stringBuffer.capacity()); //26
System.out.println(stringBuffer.length());
}
}
···························································································································································································································
StringBuffer能够自动扩容
2. 添加功能
不管任何数据类型都可以被添加到字符缓冲区中:
- 如果该类型不是字符串类型,就将该类型转化为字符串类型
- 然后再放入字缓冲区中
public StringBuffer append(String str) //向字符缓冲区中添加字符序列
public StringBuffer insert(int offset,String str)
虽然说,StringBuffer可以自动扩容,但是通常在开发中建议
public StringBuffer(int capacity)
如果在开发中,能有效的预估字符缓冲区所需的长度,因为每一次扩容,其实都比较耗时
public class Demo02 {
public static void main(String[] args) {
//StringBuffer 可以自己根据需要扩容
StringBuffer stringBuffer = new StringBuffer(); //capacity 16
stringBuffer.append("12345678901234567890abc");
String s = stringBuffer.toString(); //注意StringBuffer也有toString()功能
System.out.println(s);
System.out.println(stringBuffer.capacity());
// 测试尾部追加 append
int i = 10;
stringBuffer.append(i);
System.out.println(stringBuffer.toString());
boolean b = true;
stringBuffer.append(b);
System.out.println(stringBuffer.toString());
// insert
// public StringBuffer insert(int offset,String str)
String s1 = "jiang";
stringBuffer.insert(3, s1);
System.out.println(stringBuffer.toString());
//链式调用 向stringbuffer插入 ‘a’ 1 true
stringBuffer.insert(1, 'a')
.append(1)
.append(false);
System.out.println(stringBuffer.toString());
//我们自己测试自己写的链式调用方法
ChainedCall chainedCall = new ChainedCall();
ChainedCall print = chainedCall.print("123").print(new Demo02());
System.out.println(print == chainedCall);
}
}
class ChainedCall {
public ChainedCall print(Object o) {
System.out.println(o.toString());
return this;
}
}
···························································································································································································································
- 删除功能
- public StringBuffer deleteCharAt(int index)
- public StringBuffer delete(int start,int end) [start, end)
- 替换功能
- public StringBuffer replace(int start,int end,String str)
// 用所给字符串,替换掉字符缓冲区中,指定范围的字符串虚列
- 反转功能
- public StringBuffer reverse()
//反转字符缓冲区中的字符序列
- String.split(String str)
//以str为分隔符,对s进行分割 - 截取功能
- public String substring(int start)
- public String substring(int start,int end) [start, end)
这个功能String类也有。
截取功能和前面几个功能的不同
返回值类型是String类型,本身没有发生改变
public class Demo03 {
public static void main(String[] args) {
String s = "zhang";
StringBuffer stringBuffer = new StringBuffer(s);
//测试删除字符
//StringBuffer stringBuffer1 = stringBuffer.deleteCharAt(s.length() - 1);
//System.out.println(stringBuffer1.toString());
//测试删除某个[start, end)
//StringBuffer delete = stringBuffer.delete(2, s.length());
//System.out.println(delete.toString());
//替换功能
StringBuffer abcd = stringBuffer.replace(0, 2, "abcd");
System.out.println(abcd.toString());
//测试字符串反转
String str = reverseStr("abcd");
System.out.println(str);
//测试一下
str = "hansh";
StringBuffer sb1 = new StringBuffer(str);
System.out.println(sb1.reverse().toString());
//String.split(String str)
//以str为分隔符,对s进行分割
String path = "/zs/web/file";
String[] split = path.split("/");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
}
public static String reverseStr(String s) {
char[] temp = new char[s.length()];
int len = s.length();
for (int i = 0; i < len; i++) {
temp[i] = s.charAt(len - 1 - i);
}
return new String(temp);
}
}
···························································································································································································································
练习题:
package StringBuffer;
/**
* @author shihao
* @create 2020-04-24 15:46
* String和StringBuffer的相互转换
* 1.把数组拼接成一个字符串
* 2.把字符串反转
* 3.判断一个字符串是否是对称字符串
* 例如"abc"不是对称字符串,"aba"、"abba"、"aaa"、"mnanm"是对称字符串
*/
public class Demo05Exercise {
public static void main(String[] args) {
int[] a = {1, 2, 3, 4};
exercise1(a);
System.out.println(reverse2("1234"));
exercise2("1234");
exercise3("abbac");
}
//1.把数组拼接成一个字符串 {1, 2, 3}
public static void exercise1(int[] a) {
StringBuffer buffer = new StringBuffer("{");
if(a.length>0)
buffer.append(a[0]);
//这样可以先拼接逗号再拼接value,不用再最后一步删最后一个逗号
for (int i = 1; i < a.length; i++) {
int value = a[i];
buffer.append(',').append(value);
}
buffer.append("}");
System.out.println(buffer.toString());
}
//2. 把字符串反转
public static void exercise2(String s) {
StringBuffer buffer = new StringBuffer();
// insert(0) "abc"
// "a" -> "ba" -> "cba"
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
buffer.insert(0, c);
}
System.out.println(buffer.toString());
}
//我的做法:
public static String reverse2(String s){
int len=s.length();
StringBuffer sb=new StringBuffer(len);
for (int i = 0; i < len; i++) {
sb.append(s.charAt(len-1-i));
}
return new String(sb);
}
//3.判断一个字符串是否是对称字符串
public static void exercise3(String s) {
StringBuffer stringBuffer = new StringBuffer(s);
StringBuffer reverse = stringBuffer.reverse();
System.out.println(reverse.toString().equals(s));
}
}
···························································································································································································································
从 JDK 5 开始,为该类(StringBuffer)补充了一个单个线程使用的 等价类(在API的角度看,使用方式一模一样) ,即StringBuilder
- StringBuilder针对 单线程 运行环境,它的api和StringBuffer几乎没有差别
- VS
- StringBuffer 针对多线程运行环境
1. StringBuffer和StringBuilder从效率上来说哪个更快?
StringBuilder效率更快,因为StringBuffer适用于多线程,它会控制设备对一片内存的读写,读完才能写,写完才能读,不予许同时访问。
而StringBuilder适用于单线程,没有这种限制。
所以,如果你确保你的程序运行环境是单线程,就用StringBuilder
2.String, StringBuffer和StringBuilder有啥区别
(一)三者速度比较
String:(遍历100万次)
String aa = "";
long startTime = System.currentTimeMillis();
for(int i=0;i<100*100*10;i++){
//字符串拼接
aa = aa + "aa";
}
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+String.valueOf(endTime - startTime));
运行结果:
耗时:7614
StringBuffer:(遍历一亿次)
StringBuffer aa = new StringBuffer();
String ss = "ss";
long startTime = System.currentTimeMillis();
for(int i=0;i<100*100*100*100;i++){
//字符串拼接
aa.append(ss);
}
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+String.valueOf(endTime - startTime));
运行结果:
耗时:3128
StringBuilder:(遍历一亿次)
StringBuilder aa = new StringBuilder();
String ss = "ss";
long startTime = System.currentTimeMillis();
for(int i=0;i<100*100*100*100;i++){
//字符串拼接
aa.append(ss);
}
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+String.valueOf(endTime - startTime));
运行结果:
耗时:1240
结论:
(1)速度比较:String < StringBuffer < StringBuilder
(2)String的处理速度比StringBuffer、StringBuilder要慢的多。
(二)String的处理速度为什么要比StringBuffer、StringBuilder慢的多?
String是不可变的对象
StringBuffer是可变对象
StringBuiler是可变对象
请结合上面的代码理解这个问题:
(1)String本身就是一个对象,因为String不可变对象,所以,每次遍历对字符串做拼接操作,都会重新创建一个对象,循环100万次就是创建100万个对象,非常的消耗内存空间,而且创建对象本身就是一个耗时操作,创建100万次对象就相当的耗时了。
(2)StringBuffer和StringBuilder只需要创建一个StringBuffer或StringBuilder对象,然后用append拼接字符串,就算拼接一亿次,仍然只有一个对象。
(三)是不是可以抛弃使用String,转而使用StringBuffer和StringBuilder呢?
答案是否定的。
上文的总结只是针对于数据量比较多的情况,但是数据量比较少的情况呢?
我们分析一下代码:
(1)String遍历代码:一开始定义一个String常量(创建一个String对象), 再开始遍历;
(2)StringBuffer代码:一开始定义一个String常量(创建一个String对象)和一个创建StringBuffer对象,再开始遍历;
(3)StringBuiler代码:一开始定义一个String常量(创建一个String对象)和一个创建StringBuiler对象,再开始遍历;
(2)和(3)比(1)多了一个创建对象流程,所以,如果数据量比较小的情况建议使用String。
(四)是StringBuffer和StringBuilder的区别?
StringBuffer是线程安全的
StringBuilder是非线程安全的, 这也是速度比StringBuffer快的原因
注:不知道线程安全的可以自行查找资料补脑
(五)使用场景
(1)如果要操作少量的数据用 String
(2)单线程操作字符串缓冲区 下操作大量数据 StringBuilder
(3)多线程操作字符串缓冲区 下操作大量数据 StringBuffer
···························································································································································································································
类 Date 表示特定的瞬间,精确到毫秒
构造方法
Date()
分配 Date 对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
Date(long date)
分配 Date 对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。
public long getTime()
返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
···························································································································································································································
DateFormat类概述
- 是日期/时间 格式化 子类的 抽象类
- 它以与语言无关的方式格式化并解析日期或时间
- 因为是抽象类,所以实际使用的是SimpleDateFormat这个实现DateFormat抽象类的子类
- y 年
- M 表示年中的月份
- d 表示月份中的天数
- H 表示一天中的小时数
- m 小时中的分钟
- s 分钟中的秒数
如:yyyy/MM/dd HH:mm:ss
构造方法:
public Date parse(String source) 把一个用字符串表示的时间转化成一个Date对象,该对象表示的时间点,就是你用字符串表示的那个时间点。
//"yyyy/MM/dd HH:mm:ss"告诉format它应当以什么样的格式解析你给的字符串
SimpleDateFormat format = new SimpleDateFormat("yyyy--MM--dd HH:mm:ss");
//如果"2020--09--01 17:00:00"的格式不对会抛出格式解析异常
Date parse = format.parse("2020--09--01 17:00:00");
public final String format(Date date) 把一个Date对象表示成一个指定格式的表示时间的字符串
···························································································································································································································
练习:制作一个工具类。DateUtil,算一下你来到这个世界多少天?
public class Demo03DateUtil {
public static void main(String[] args) throws ParseException {
Scanner in = new Scanner(System.in);
System.out.println("请以yyyy-MM-dd的格式输入你的出生日期:");
String src = in.nextLine();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date srcdate = format.parse(src);
Date destdate = new Date();
String dest2 = format.format(destdate);
int days = myCode(src, dest2);
System.out.println(days);
}
public static int myCode(String src, String dest) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date srcDate = format.parse(src);
Date destDate = format.parse(dest);
long seconds = destDate.getTime() - srcDate.getTime();
int days = (int) (seconds / (1000L * 60 * 60 * 24));
return days;
}
···························································································································································································································
Math工具类
成员方法
public static int abs(int a) //求绝对值
public static double ceil(double a) // 取整 向大的方向取整
public static double floor(double a)// 取整 向小的方向取
public static int max(int a,int b) //min自学
public static double pow(double a,double b) //a^b
public static double random() // 返回带正号的 double 值,该值大于等于 0.0 且小于 1.0 [0.0 1.0) 随机数。
public static int round(float a) //取整 4舍五入的取整
public static double sqrt(double a) //返回正确舍入的 double 值的正平方根