一、String类
1、 String的特性
- String类代表字符串。Java层序中的所有字符串字面值(如“abc”)都作为此类的实例实现;
- String是一个final类,代表不可变的字符序列;
- 字符串是常量,用双引号引起来表示。它们的值在创建之后不可更改(不可变性);
- String对象的字符内容是存储在一个**字符数组value[]**中的。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** Thre value is used for character storage*/
private final char value[];
/** Cache the hash code for the string*/
private int hash; //Default to 0
}
2、 String对象的创建
String对象的创建有多种方式,大致分为一下两种:
- 通过字面量定义的方式
- 通过new + 构造器的方式
//通过字面量定义的方式:此时的str1和str2的数据Hello声明在方法区中的字符串常量池中。
String str1 = "Hello";
String str2 = "Hello";
//通过new + 构造器的方式:此时的s1、s2、s3和s4保存的地址值是数据在对空间中开辟空间以后对应的地址值。
//本质上为this.value = new char[0];
String s1 = new String();
//本质上为this.value = new original.value;
String s2 = new String(String orginal);
//本质上为this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
String s4 = new String(char[] a, int startIndex, int count);
3、 String使用总结
- String字符串使用一对“”引起来表示,其声明为final的,不可被继承;
- String实现了Serializable接口和Comparable接口,分别表示字符串可支持序列化和比较大小;
- String内部定义了
final char[] value
用于存储字符串数据; - String代表不可变的字符序列,即不可变性,其主要表现在以下几点:
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值;
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值;
- 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值;
- 通过字面量的方式给一个字符串赋值,此时的字符串声明在字符串常量池中,目的是为了共享;
- 字符串非常量对象存储在堆中;
- 字符串常量池中不会存储相同内容的字符串,因此当两个字符串的字面量值相同时,它们一定指向常量池中的同一块地址。
@Test
public void test1(){
String s1 = "abc"; //字面量的定义方式
String s2 = "abc";
s1 = "hello";
System.out.println(s1 == s2); //比较s1和s2的地址值
System.out.println(s1); //hello
System.out.println(s2); //abc
System.out.println("*****************");
String s3 = "abc";
s3 += "def";
System.out.println(s3); //abcdef
System.out.println(s2);
System.out.println("*****************");
String s4 = "abc";
String s5 = s4.replace('a', 'm');
System.out.println(s4); //abc
System.out.println(s5); //mbc
}
@Test
public void test2(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
}
结论:
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量;
- 只要其中有一个是变量,结果就在堆中;
- 如果拼接的结果调用intern()方法,返回值就在常量池中。
4、 常用方法
int length()
:返回字符串的长度: return value.lengthchar charAt(int index)
: 返回某索引处的字符return value[index]boolean isEmpty()
:判断是否是空字符串:return value.length == 0String toLowerCase()
:使用默认语言环境,将 String 中的所有字符转换为小写String toUpperCase()
:使用默认语言环境,将 String 中的所有字符转换为大写String trim()
:返回字符串的副本,忽略前导空白和尾部空白boolean equals(Object obj)
:比较字符串的内容是否相同boolean equalsIgnoreCase(String anotherString)
:与equals方法类似,忽略大小写String concat(String str)
:将指定字符串连接到此字符串的结尾。 等价于用“+”int compareTo(String anotherString)
:比较两个字符串的大小String substring(int beginIndex)
:返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。String substring(int beginIndex, int endIndex)
:返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。boolean contains(CharSequence s)
:当且仅当此字符串包含指定的 char 值序列时,返回 trueint indexOf(String str)
:返回指定子字符串在此字符串中第一次出现处的索引int indexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始int lastIndexOf(String str)
:返回指定子字符串在此字符串中最右边出现处的索引int lastIndexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
boolean endsWith(String suffix)
:测试此字符串是否以指定的后缀结束boolean startsWith(String prefix)
:测试此字符串是否以指定的前缀开始boolean startsWith(String prefix, int toffset)
:测试此字符串从指定索引开始的子字符串是否以指定前缀开始
下面几种方法分别代表替换、匹配和切片
String replace(char oldChar, char newChar)
:返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。String replace(CharSequence target, CharSequence replacement)
:使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。String replaceAll(String regex, String replacement)
: 使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。String replaceFirst(String regex, String replacement)
: 使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。boolean matches(String regex)
:告知此字符串是否匹配给定的正则表达式。String[] split(String regex)
:根据给定正则表达式的匹配拆分此字符串。String[] split(String regex, int limit)
:根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
5、 补充
5.1、StringBuffer
StringBuffer类不同于String,其对象必须使用构造器生成,且其是 JDK 1.0 就推出来的线程安全的一个可变字符序列。有三个构造器:
StringBuffrt()
:初始化容量为16的字符串缓冲区;StringBuffer(int size)
:构造指定容量的字符串缓冲区;StirngBuffer(String str)
:将内容初始化为指定字符串内容。
StringBuffer sb1 = new StringBuffer(); //无参构造器
StringBuffer sb2 = new StringBuffer(5); //创建一个长度为5的数组char[] value = new char[5];
StringBuffer sb3 = new StringBuffer("Hello"); //创建一个长度为"Hello".length+16的数组char[] value = new char["Hello".length+16],其内容为"Hello";
5.2、StringBuilder
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样,不同的就是 StringBuilder 是 JDK 5.0 新推出的线程不安全的字符序列。
StringBuilder sb4 = new StringBuilder(); //无参构造器
StringBuilder sb5 = new StringBuilder(5); //创建一个长度为5的数组char[] value = new char[5];
StringBuilder sb6 = new StringBuilder("Hello"); //创建一个长度为"Hello".length+16的数组char[] value = new char["Hello".length+16],其内容为"Hello";
5.3、 三者对比
-
String:不可变的字符序列;底层使用**char[]**存储;
-
StringBuffer:可变的字符序列;线程安全但效率低;底层使用**char[]**存储;
-
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全但效率高;底层使用**char[]**存储;
-
作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder 会改变其值;
-
开发中建议使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
-
通过下面代码对比三者的效率
@Test public void test3(){ //初始设置 long startTime = 0L; long endTime = 0L; String text = ""; StringBuffer buffer = new StringBuffer(""); StringBuilder builder = new StringBuilder(""); //开始对比 startTime = System.currentTimeMillis(); for (int i = 0; i < 20000; i++) { buffer.append(String.valueOf(i)); } endTime = System.currentTimeMillis(); System.out.println("StringBuffer的执行时间:" + (endTime - startTime)); startTime = System.currentTimeMillis(); for (int i = 0; i < 20000; i++) { builder.append(String.valueOf(i)); } endTime = System.currentTimeMillis(); System.out.println("StringBuilder的执行时间:" + (endTime - startTime)); startTime = System.currentTimeMillis(); for (int i = 0; i < 20000; i++) { text = text + i; } endTime = System.currentTimeMillis(); System.out.println("String的执行时间:" + (endTime - startTime)); }
很明显可以看到,StringBuilder的效率远远大于其它两个,其中属String效率最低
5.4、常用方法
StringBuffer与StringBuilder的方法基本一致,差距只有线程上的安全与否,因此下方只介绍StringBuffer类的常用方法,StringBuilder类使用一致:
- StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
- StringBuffer delete(int start,int end):删除指定位置的内容
- StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
- StringBuffer insert(int offset, xxx):在指定位置插入xxx
- StringBuffer reverse() :把当前字符序列逆转
- public int indexOf(String str):查找子串在目标字符串中第一次出现的索引,用法与String一致
- public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
- public int length():用法与String一致
- public char charAt(int n ):用法与String一致
- public void setCharAt(int n ,char ch):用法与String一致
总结:
- 增:append(xxx)
- 删:delete(int start,int end)
- 改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
- 查:charAt(int n )
- 插:insert(int offset, xxx)
- 长度:length();
- 遍历:for() + charAt() / toString()
值得注意的是,以上方法都支持方法链操作,即某个该类型的对象可以持续调用方法
//源码解析
public synchronized StringBuffer append(char c) {
toStringCache = null;
super.append(c);
return this;
}
//演示代码
@Test
public void Test3() {
StringBuffer sb1 = new StringBuffer(6);
sb1.append('H').append('e').append('l').append('l').append('o'); //方法链操作
System.out.println(sb1); //Hello
}
**扩容问题:**如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。
二、日期类
1、System类
System类提供的public static long currentTimeMillis()
用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差,这称为时间戳。
@Test
public void Test1() {
//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
long time1 = System.currentTimeMillis();
System.out.println(time1); //1636611312478
}
2、Date类
Java中有两个不同包下的Data类,分别为java.util.Date类
和java.sql.Date类
,他们之间为父类与子类的关系
java,util.Date
可通过以下两种构造器进行实例化对象:
- Date():创建一个对应当前时间的Date对象
- Date(long date):创建指定毫秒数的Date对象
@Test
public void Test2() {
//创建一个对应当前时间的java.util.Date对象
Date date1 = new Date();
System.out.println(date1.toString()); //显示当前的年、月、日、时、分、秒
System.out.println(date1.getTime()); //等同于currentTimeMillis()
//创建指定毫秒数的java.util.Date对象
Date date2 = new Date(1636611312478L);
System.out.println(date2.toString());
//创建指定毫秒数的java.sql.Date对象
Date date4 = new java.sql.Date(1636611312478L);
System.out.println(date4.toString());
}
java.sql.Date()
一般只有一种构造器进行实例化(另一种已过时),但有两种方法进行实例化,当我们想要存放某类日期数据到数据库中时推荐使用第二种方法
//创建指定毫秒数的java.sql.Date对象
Date date4 = new java.sql.Date(1636611312478L);
System.out.println(date4.toString());
//通过java.util.Date对象进行创建java.sql.Date对象
Date date5 = new java.sql.Date(date2.getTime());
System.out.println(date4.toString());
3、SimpleDateFormat类
Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat
类是一个不与语言环境有关的方式来格式化和解析日期的具体类。它允许进行格式化(日期->文本)和解析(文本->日期)
3.1、格式化
- SimpleDateFormat():默认的模式和语言环境创建对象
- public SimpleDateFormat(String pattern):该构造方法可以用参数pattern指定的格式创建一个对象
- public String format(Date date):方法格式化时间对象date
3.2、解析
- public Date parse(String sourse):从给定字符串的开始解析文本,以生成一个日期
3.3、使用无参构造器实例化
@Test
//SimpleDateFormat的实例化方式一:使用默认的构造器
public void Test1() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化
Date date = new Date();
System.out.println(date);
String format = sdf.format(date);
System.out.println(format);
//解析:格式必须与实例化时的格式严格一致,无参构造器需和默认格式一致,否则抛异常
Date date1 = sdf.parse("21-11-15 上午9:52");
System.out.println(date1);
}
3.4、使用有参构造器实例化
@Test
//SimpleDateFormat的实例化方式一:使用带参的构造器
ublic void Test2() throws ParseException {
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
Date date = new Date();
String format = sdf1.format(date);
System.out.println(format);
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),否则抛异常
Date date1 = sdf1.parse("2021-11-15 10:30:10");
System.out.println(date1);
}
3.5、简单demo练习
@Test
//练习一:字符串"2020-09-08"转换为java.sql.Date
//思路:先根据给出字符串的格式转化成Date的对象,再使用getTime()作为参数实例化java.sql.Date对象
public void Test1() throws ParseException {
String str = "2020-09-08";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(str);
java.sql.Date date1 = new java.sql.Date(date.getTime());
System.out.println(date1);
}
@Test
//练习二:"三天打渔两天晒网" 从1990-01-01开始至今是在打渔or晒网?
//总天数 % 5 == 1,2,3 : 打渔
//总天数 % 5 == 4,0 : 晒网
public void Test2() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date startDate = sdf.parse("1990-01-01");
Date currentDate = new Date();
int days = (int)((currentDate.getTime()-startDate.getTime())/(1000 * 60 * 60 * 24));
System.out.println(days%5);
}
4、Calendar类
4.1、实例化
观看源码我们可以知道Calendar为抽象类,因此我们不能直接对其进行实例化,Java给我们提供了另外两种方式进行实例化:
-
创建其子类GregorianCalendar的对象
-
调用其静态方法getInstance()
//Calendar日历类实例化方式一:创建其子类GregorianCalendar的对象 GregorianCalendar calendar1 = new GregorianCalendar(); //Calendar日历类实例化方式二:调用其静态方法getInstance(); Calendar calendar2 = Calendar.getInstance(); System.out.println(calendar2.getClass()); //可发现其本质还是实例化了Calendar的子类
4.2、常用方法
@Test
public void Text3() {
//Calendar日历类实例化方式一:创建其子类GregorianCalendar的对象
GregorianCalendar calendar1 = new GregorianCalendar();
//Calendar日历类实例化方式二:调用其静态方法getInstance();
Calendar calendar2 = Calendar.getInstance();
System.out.println(calendar2.getClass()); //可发现其本质还是实例化了Calendar的子类
//常用方法
//get()
int days = calendar2.get(Calendar.DAY_OF_MONTH); //本月中的第几天
System.out.println(days); //15
System.out.println(calendar2.get(Calendar.DAY_OF_YEAR)); //319, 本年中的第几年
//set():体现了Calendar的可变性
calendar2.set(Calendar.DAY_OF_MONTH, 1);
System.out.println(calendar2.get(Calendar.DAY_OF_MONTH)); //1,重新设置
//add()
calendar2.add(Calendar.DAY_OF_MONTH, 3);
System.out.println(calendar2.get(Calendar.DAY_OF_MONTH)); //4,在原本的基础上添加amount天
//getTime():日历类 -- > Date
Date date = calendar2.getTime();
System.out.println(date); //Thu Nov 04 11:36:23 CST 2021,输出当前日期
//setTime(): Date -- > 日历类
Date date1 = new Date();
calendar2.setTime(date1);
System.out.println(calendar2.get(Calendar.DAY_OF_MONTH)); //15
}
5、JDK 8日期类
在日常开发过程中使用JDK 8之前的日期类就发现存在以下问题,因此在JDK 8之后,Java 引入了新的时间API java.time
- 可变性:像日期和时间这样的类应该是不可变的。
- 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
- 格式化:格式化只对Date有用,Calendar则不行。
- 此外,它们也不是线程安全的;
- 不能处理闰秒等。
LocalDate、LocalTime、LocalDateTime
类是其中较重要的几个类,它们的实例 是不可变的对象。
- LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期。
- LocalTime表示一个时间,而不是日期。
- LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。
@Test
public void test1(){
//now():获取当前的日期、时间、日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate); //2021-11-15
System.out.println(localTime); //11:55:01.436
System.out.println(localDateTime); //2021-11-15T11:55:01.436
//of():设置指定的年、月、日、时、分、秒。没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
System.out.println(localDateTime1); //2020-10-06T13:23:43
//getXxx():获取相关的属性
System.out.println(localDateTime.getDayOfMonth()); //15
System.out.println(localDateTime.getDayOfWeek()); //MONDAY
System.out.println(localDateTime.getMonth()); //NOVEMBER
System.out.println(localDateTime.getMonthValue()); //11
System.out.println(localDateTime.getMinute()); //55
//体现不可变性--withXxx():设置相关的属性
LocalDate localDate1 = localDate.withDayOfMonth(22);
System.out.println(localDate); //2021-11-15
System.out.println(localDate1); //2021-11-22
LocalDateTime localDateTime2 = localDateTime.withHour(4);
System.out.println(localDateTime); //2021-11-15T11:55:01.436
System.out.println(localDateTime2); //2021-11-15T04:55:01.436
//体现不可变性--plusXxx():在某属性基础上加上数值并返回对应值
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
System.out.println(localDateTime); //2021-11-15T11:55:01.436
System.out.println(localDateTime3); //2022-02-15T11:55:01.436
//体现不可变性--minusXxx():在某属性基础上减去数值并返回对应值
LocalDateTime localDateTime4 = localDateTime.minusDays(6);
System.out.println(localDateTime); //2021-11-15T11:55:01.436
System.out.println(localDateTime4); //2021-11-09T11:55:01.436
}
6、Java比较器
众所周知,对于Java中的对象在正常情况下,只能使用==或!=
进行比较,而大部分比较运算符不允许被使用。但在开发场景中,我们需要对多个对象进行排序,言外之意,就是需要比较对象的大小,这就需要用到Comparable 或 Comparaor接口
。
Comparable接口的方式可以保证Comparable接口的实现类的对象在任何位置都可以比较大小;
Comparator接口属于临时性的比较。
6.1、Comparable接口
对于Comparable接口,自然排序是一个很典型的例子;
- 像String、包装类实现了Comparable接口,重写了**compara To(obj)**方法,给出了比较两个对象大小的方式;
- 像String、包装类重写了**comparaTo()**方法以后,进行了从小到大的排序
- 重写**comparaTo(obj)**的规则:
- 如果当前对象this大于形参对象obj,则返回正整数;
- 如果当前对象this小于形参对象obj,则返回负整数;
- 如果当前对象this等于形参对象obj,则返回零。
- 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写comparaTo(obj)方法。
@Test
public void test1(){
String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
@Test
public void test2(){
Goods[] arr = new Goods[5];
arr[0] = new Goods("lenovoMouse",34);
arr[1] = new Goods("dellMouse",43);
arr[2] = new Goods("xiaomiMouse",12);
arr[3] = new Goods("huaweiMouse",65);
arr[4] = new Goods("microsoftMouse",43);
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
class Goods implements Comparable{
private String name;
private double price;
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
//指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从高到低排序
@Override
public int compareTo(Object o) {
// System.out.println("**************");
if(o instanceof Goods){
Goods goods = (Goods)o;
//方式一:
if(this.price > goods.price){
return 1;
}else if(this.price < goods.price){
return -1;
}else{
// return 0;
return -this.name.compareTo(goods.name);
}
//方式二:
// return Double.compare(this.price,goods.price);
}
// return 0;
throw new RuntimeException("传入的数据类型不一致!");
}
}
6.2、Comparator接口
对于Comparator接口,定制排序是一个很典型的例子。当元素的类型没有实现java.lang.Comparable接口而不方便修改代码,或实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来排序。
通过重写compare(Object o1, Object o2)方法来比较o1和o2的大小:
- 返回正整数时,表示o1大于o2;
- 如果返回0时,表示相等;
- 返回负整数时,表示o1小于o2。
@Test
public void test3(){
String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
Arrays.sort(arr,new Comparator(){
//按照字符串从大到小的顺序排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
return 0;
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
@Test
public void test4(){
Goods[] arr = new Goods[6];
arr[0] = new Goods("lenovoMouse",34);
arr[1] = new Goods("dellMouse",43);
arr[2] = new Goods("xiaomiMouse",12);
arr[3] = new Goods("huaweiMouse",65);
arr[4] = new Goods("huaweiMouse",224);
arr[5] = new Goods("microsoftMouse",43);
Arrays.sort(arr, new Comparator() {
//指明商品比较大小的方式:按照产品名称从低到高排序,再按照价格从高到低排序
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Goods && o2 instanceof Goods){
Goods g1 = (Goods)o1;
Goods g2 = (Goods)o2;
if(g1.getName().equals(g2.getName())){
return -Double.compare(g1.getPrice(),g2.getPrice());
}else{
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}