0. Intellij Idea
快捷键
ALT+SHIFT+0:生成构造器或setter、getter
IDEA的Debug
IDEA导入已有的模块
1 字符串相关的类
1.1 String类
String源码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
private final char value[];
private int hash;
...
}
String的实例化
String s1="hello";//字面量实例化
String s2=new String();//本质是this.value=new char[0];
String s3=new String(String original);//本质是this.value=original.value;
String s4=new String(char[] a);//this.value=Arrays.copyOf(value, value.length);
String s5=new String(char[] a, int startIndex, int count);
总体来说:
String的实例化方式:
* 方式1:通过字面量定义的方式:存放到方法区中的字符串常量池中
* 方式2:通过new + 构造器的方式:存放到堆中
String:字符串,使用一对""引起来表示
- 1.String声明为final的,不可被继承
- 2.String实现Serializable接口:表示字符串是支持序列化的
实现Comparable接口:表示字符串可以比较大小 - 3.String内部定义了final char[] value用于存储字符串数据
- 4.String代表不可变的字符序列。简称:不可变性
体现:1.当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。//见内存解析
2.当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。s1+="def";则内存地址已经变了
3.当调用String的replace()方法修改指定的字符或字符串时,也需要重新指定内存区域赋值。String s3="abc"; String s4=s3.replace('a', 'm');
- 5.通过字面量的方式(如String s1=“abc”;)(区别于new的方式)给一个字符串赋值,此时的字符串值声明在方法区的字符串常量池中
- 6.字符串常量池中是不会存储相同内容的字符串的
面试题
面试题:String s=new String(“abc”);方式创建对象,在内存中创建了几个对象?
2个:1个是堆空间中new的结构,另一个是char[]对应的字符串常量池中的数据,即"abc"
小结论
1.常量与常量的拼接结果在常量池中,且常量池中不会存在相同内容的常量
2.只要其中有一个是变量,结果就在堆中
3.intern()方法可以返回字符串常量池中的地址值
内存解析1
内存解析2
内存解析3
内存解析4
String s1="hello";
String s2="world";
String S3="helloworld";
String s4="hello"+"world";
String s5=s1+"world";
String s6=s1+s2;
String s7=s5.intern();//此时返回的s7是常量池中已经存在的"helloworld"的地址值
解析:
1.2 String的一些方法
- 1.int length():返回字符串的长度
- 2.char charat(int index):返回某索引处的字符
- 3.boolean isEmpty():判断是否是空字符串
- 4.String toLowerCase():使用默认语言环境,将String中的所有字符转换为小写
- 5.String toupperCase():使用默认语言环境,将String中的所有字符转换为大写
- 6.String trim():返回字符串的副本,忽略前导空白和尾部空白
如" h ello "返回的是"h ello"
- 7.boolean equals(Object obj):比较字符串的内容是否相等
- 8.boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
- 9.String concat(String str):将字符串str连接到此字符串的结尾。等价于”+“
- 10.int compareto(String anotherString):比较两个字符串的大小
若返回是负数,则当前对象小;返回0,相等。对比是把字符串从前往后的每一个字符的ASCII码做对比 - 11.String substring(int beginIndex):返回一个新的字符串,它是此字符串从beginIndex开始截取到最后一个的子字符串
- 12.String substring(int beginIndex, int endindex):返回一个新字符串,它是此字符串从beginIndex开始截取到endindex(不包含)的一个子字符串
一般java中都是左闭右开的 - 13.boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
- 14.boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
- 15.boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始//意思就是字符串中的第toffset个开始的字符串是否是prefix
- 16.boolean contains(CharSequence s):当且仅当此字符串包含字符序列(字符串)s时,返回true
- 17.int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的开头索引,若没有找到,则返回-1
- 18.int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
- 19.int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
- 20.int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
- 注:indexOf和lastIndexOf方法如果未找到都是返回-1。调用indexOf(str)和lastIndexOf(str)返回值相同的情况:存在唯一的str或不存在str
- 21.String replace(char oldChar, char newChar):返回一个新的字符串,它是通过newChar替换此字符串中出现的所有oldChar得到的
- 22.String replace(CharSequence target, CharSequence replacement):使用指定的字符序列替换所有的目标字符序列
- 23.String replaceAll(String regex, String replacement):使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串
- 24.String replaceFirst(String regex, String replacement):使用给定的replacement替换此字符串匹配给定的正则表达式的第一个字符串
- 25.boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
- 26.String[] split(String regex):(切片)根据正则表达式的匹配拆分此字符串
- 27.String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
String与char[]之间的转换
String—>char[]:调用String的一个方法tochararray():char[] arr=str.tochararray();
char[]—>String:调用String的构造器:String str = new String(arr);
String与byte[]之间的转换
String—>byte[]:调用String的一个方法getBytes():byte[] b = str.getBytes();
byte[]—>String:调用String的构造器:String str = new String(b);
- 编码:字符集—>字节(看得懂—>看不懂的二进制数据)(String—>byte[])
- 解码:编码的逆过程,字节—>字符串(看不懂的二进制数据—>看得懂)(byte[]—>String)
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码
String s1="abc123中国";
byte[] b = s1.getBytes();//使用默认的字符集进行转换编码,因为我们之前给idea设置的是utf-8,所以默认的是utf-8
System.out.println(Arrays.toString(b));
byte[] b2 = s1.getBytes("gbk");//使用gbk字符集来进行编码
System.out.println(Arrays.toString(b2));
//utf-8和gbk都可以把汉字编码成数字,前者用三个数字来表述一个汉字,后者用两个数字表述一个汉字;二者存放英文字符的编码都是一样的
String s2 = new String(b);//使用默认的字符集进行解码,即使用utf-8来解码
System.out.println(s2);
String s3 = new String(b2, "gbk");//需要使用gbk来解码,若使用utf-8解码,则会出现乱码
System.out.println(s3);
1.3 StringBuffer和StringBuilder
String,StringBuffer,StringBuilder三者的异同?
- String:不可变的字符序列
- StringBuffer:可变的字符序列:线程安全的(里面的方法都是同步方法),效率低
- StringBuilder:可变的字符序列:线程不安全的,效率高
三者底层都是使用char[]存储的,只不过String加了final,后面两个没加
正是因为String是不可变的字符序列,所以它的对字符串操作的一些方法都有返回值类型,如String toupperCase()
而StringBuffer和StringBuilder是可变的字符序列,所以对字符串操作的一些方法虽然有返回值类型的,如StringBuffer append(xxx),但是一般不接受,如str.append(),因为s已经变了,很少用两一个字符串去接收
String与StringBuffer、StringBuilder之间的转换
- String—>StringBuffer,StringBuilder:调用StringBuffer,StringBuilder的构造器
- StringBuffer,StringBuilder—>String:调用String的构造器或者调用StringBuffer,StringBuilder的toString()方法
对比String,StringBuffer,StringBuilder的效率:
从高到低排列:StringBuilder > StringBuffer > String
String源码分析:
String s=new String();/底层是char[] value=new char[0];
String s=new String(“abc”);底层是char[] value=new char[]{'a', 'b', 'c'};
StringBuffer源码分析(StringBuilder类似):
StringBuffer s=new StringBuffer();char[] value=new char[16];底层创建了一个长度是16的char数组
s.append(‘a’);value[0]='a';
StringBuffer s=new StringBuffer(“abc”);char[] value=new char["abc".length()+16];底层创建了一个长度为3+16=19的char数组
StringBuffer常见问题和错误(StringBuilder类似)
- 问题1:System.out.println(s);//输出是3
- 问题2:扩容问题:如果要添加的数据底层的数组存不下了,那就需要扩容底层的数组。默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。特殊情况见源码
- 指导意义:开发中如果需要一个字符串经常变,如经常调用append(),则尽量用StringBuffer(当可能出现线程安全问题时)和StringBuilder(当没有线程安全问题时),而少使用String。开发中建议使用StringBuffer(int capacity)和StringBuilder(int capacity)构造器,尽量避免去扩容
1.4 StringBuffer和StringBuilder的方法
- 1.StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串的拼接
s1.append(1);s1.append('1');
- 2.StringBuffer delete(int start, int end):删除指定位置的内容
s1.delete(2,4);
- 3.StringBuffer replace(int start, int end, String str):把[start, end)位置替换为str
s2.replace(2,4,"hello");
- 4.StringBuffer insert(int offset, xxx):在指定位置插入xxx
s3.insert(2,false);
- 5.StringBuffer reverse():把当前字符序列反转
s3.reverse();
- 6.public int indexOf(String str):返回str在字符串中首次出现的位置
- 7.public String subString(int start, int end):返回[start, end)的子字符串,此时此StringBuffer或StringBuilder的字符串没有改变,只是返回String类型的子串
- 8.public int length()
- 9.public char charat(int n):返回第n个的字符
- 10.public void setCharat(int n, char ch):将第n个的字符换成ch
由于append()等方法返回的是StringBuffer或StringBuilder字符串本身,因此可以使用方法链原理,即多次调用:s.append().append().append().append();
总结:
- 增: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()
- 遍历:直接sout输出,或sout输出toString()方法,或for循环+charat(n)输出
1.5 练习题:滑动窗口
import org.junit.Test;
import java.util.Arrays;
/**
* @author JiaMing
* @create 08-22 18:05
* @description 获取两个字符串中最大相同字串。比如:str1="abcwerthelloyuiodef12345",str="cvghellobnm12345"
* 提示:将短的那个串进行长度依次递减的子串与较长串比较
*/
public class Exer5 {
public String maxSubString(String s1, String s2){
//由于可能存在多个长度相同的最大相同子串,因此创建builder等会方便存进去,不用数组存是因为不知道有几个最大相同子串,所以不知道数组的长度该创建多大
StringBuilder builder = new StringBuilder();
//先选出哪个长哪个短
String maxStr = s1.length() >= s2.length() ? s1 : s2;
String minStr = s1.length() < s2.length() ? s1 : s2;
//外层循环逐次加一
for (int i = 0; i < maxStr.length(); i++) {
//内层循环的意思是:第一次比较的是subStr即"cvghellobnm12345",maxStr不包含subStr
//第二次比较的是:subStr="cvghellobnm1234","vghellobnm12345",maxStr不包含subStr
//第三次比较的是:subStr="cvghellobnm123","vghellobnm1234","ghellobnm12345",maxStr不包含subStr
//...这样依次循环
for (int x=0,y=minStr.length()-i; y<=minStr.length(); x++,y++) {
String subStr=minStr.substring(x,y);
if(maxStr.contains(subStr)){
//如果maxStr包含subStr,则存到builder中,并且把此次循环继续执行,看看是否有多个长度相同的最大相同子串并存到builder中
builder.append(subStr+",");
}
}
//当这一次的内层循环结束并且builder中已经有最大子串时,则结束外层循环,不需要再找了
if(builder.length()!=0){
break;
}
}
//先把builder转换成String类型,再去掉最后的"," 然后并切片成多份,命名到数组中
String[] split = builder.toString().replaceAll(",$", "").split("\\,");
return Arrays.toString(split);//将数组转换成String类型并返回
}
@Test
public void test() {
String str1="abcwerthelloyuiodef12345";
String str2="cvghellobnm12345";
String maxSubString = maxSubString(str1, str2);
System.out.println(maxSubString);
}
}
2 JDK8 之前日期和时间的API测试
2.1 java.lang.System类
System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差
2.2 java.util.Date类
①两个构造器的使用
->空参构造器:Date():创建一个对应当前时间的Date对象。Date d1 = new Date();
->参数为long型整数的构造器:Date(long date):创建指定毫秒数的Date对象,参数中的date是与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。Date d2 = new Date(1661156872226L); System.out.println(d2.toString());//输出是Mon Aug 22 16:27:52 CST 2022
②两个方法的使用
->toString():显式当前的年、月、日、星期几、时、分、秒。
->getTime():获取当前Date对象对应的毫秒数(时间戳),和java.lang.System.currentTimeMillis()功能一致。System.out.println(d1.getTime());
2.3 java.sql.Date类
java.sql.Date类是对应数据库中的日期类型的变量,只有在和数据库交互中才会使用这个(注:java.sql.Date是java.util.Date的子类)
->创建java.sql.Date对象。java.sql.Date d3 = new java.sql.Date(135646872313L);
java.sql.Date也可以调用toString()方法
->如何将java.util.Date对象转换为java.sql.Date对象。
java.util.Date d1 = new java.util.Date();
java.sql.Date d4 = new java.sql.Date(d1.getTime());
2.4 java.text.SimpleDateFormat类
SimpleDateFormat的使用:SimpleDateFormat是对日期Date类的格式化和解析的类
①当使用默认构造器进行SimpleDateFormat的实例化,按照默认的方式格式化和解析:SimpleDateFormat sdf = new SimpleDateFormat();
两个操作:
->格式化:日期—>字符串
Date d1 = new Date();
String s2 = sdf.format(d1);
System.out.println(s2);//输出的是中文:2022/8/22 下午7:42
->解析:格式化的逆过程,字符串—>日期
String s3="2022/9/27 上午11:20";//字符串必须是这种格式,如果不是这种格式,会抛"ParseException"异常
System.out.println(d2);//此时输出的就又是Tue Sep 27 11:20:00 CST 2022
②当调用带参构造器进行SimpleDateFormat的实例化,按照指定的方式格式化和解析:SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
其中,yyyy代表4位数是年份,MM代表2位数的月份,dd代表2位数的日期,hh代表2位数的小时,mm代表2位数的分钟,ss代表2位数的秒
当然构造器的参数还有其他格式,如"yyyyy.MMMMM.dd GGG hh:mm:aaa"输出的格式是"02001.July.04 AD 12:08 PM"等等,具体可见java API
两个操作:
->格式化:日期—>字符串
String s4 = sdf2.format(d1);//输出2022-08-22 07:55:07,
//这个格式和构造器中的参数类型是相同的
->解析:格式化的逆过程,字符串—>日期
Date d3 = sdf2.parse("2021-05-05 2:12:12");//要求字符串必须符合
//SimpleDateFormat的构造器的参数格式,否则会抛异常,
//输出为Wed May 05 02:12:12 CST 2021
2.5 java.util.Calendar类(日历类)
java.util.Calendar类是一个抽象类,主要用于完成日期字段之间相互操作的功能
①实例化
方式一:创建其子类(GregorianCalendar)的对象
方式二:调用其静态方法getInstance()
Calendar c1 = Calendar.getInstance();//因为Calendar是抽象类,
//所以getInstance得到的不是Calendar类,而是其子类GregorianCalendar,
//这是一个匿名子类的非匿名对象
②常用方法
- int get(int field):获取想得到的时间信息;field可取YEAR,MONTH,DAY_OF_WEEK,DAY_OF_YEAR,HOUR_OF_DAY,MINUTE,SECOND,即获取年份、月份、这一周的第几天、这一年的第几天,这一天的第几个小时,分钟,秒。
int days = c1.get(Calendar.DAY_OF_MONTH);//本月的第几天:此时为8月22日,所以是第22天
- void set(int field, int value):修改日历中field为value,
c1.set(Calendar.DAY_OF_MONTH,23);//修改本月的第几天为第23天
- void add(int field, int amount):把日历中filed加上amount。
c1.add(Calendar.DAY_OF_MONTH,3);//把本月的第几天加上3即23+3=26 c1.add(Calendar.DAY_OF_MONTH,-1);//把本月的第几天减去1天即25
- final Date getTime():日历类转换成Date,
Date d1 = c1.getTime();
- final void setTime(Date date):Date转换成日历类,
Date d2 = new Date(); c1.setTime(d2);//没有返回值,直接把d2的时间赋给c1
注意:Calendar有偏移量
即获取月份时:一月是0,二月是1…十二月是11。
获取星期时,周日是1,周一是2…周六是7
2.6 练习题:”三天打鱼两天晒网“
/**
* @author JiaMing
* @create 08-22 21:50
* @description 练习二:从1990-01-01开始”三天打鱼两天晒网“,在2022年5月15日是打鱼还是晒网
* 分析:可知五天一循环,则可以求这一天是五天中的第几天(总天数%5==1,2,3:打鱼,总天数%5==4,0:晒网),即求出2022-05-15与1990-01-01中间隔了多少天
*/
public class DaYu480 {
@Test
public void test() throws ParseException {
String s1="1990-01-01";
String s2="2022-05-15";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d1 = sdf.parse(s1);
Date d2 = sdf.parse(s2);
long time=d2.getTime()-d1.getTime();//两个时间段中间隔了多少毫秒
long days=0;//两个时间段中间隔了多少天
//一天有24*60*60*1000毫秒
if(time%(24*60*60*1000)==0){
//当全部除尽时,那么中间隔了天就是算出来的商
days=time/(24*60*60*1000);
}else {
//当除不尽时就向上取整
days=time/(24*60*60*1000)+1;
}
long doWhat=days%5;//五天循环中的第几天
if(doWhat==1 || doWhat==2 || doWhat==3){
System.out.println("打鱼");
}else System.out.println("晒网");
}
}
上述几个时间类之间的关系
3 JDK8 中新日期时间API
引出
由于JDK8之前的关于时间的包、类等具有可变性(如Calendar可以设置改变)、偏移性(如Date中的年份是从1900开始算的,月份是从0开始的,这和生活中不符)、格式化麻烦、线程不安全、不能处理闰秒等,因此JDK8之后有了更合适的关于时间操作的API:java.time,java.time.chrono,java.time.format,java.time.temporal,java.time.zone等
- java.time:包含值对象的基础包
- java.format:格式化和解析时间和日期
- java.time.temporal:包含底层框架和扩展特性
- java.time.zone:包含时区支持的类
3.1 java.time.LocalDateTime类
java.time使用的最频繁,包含本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(zoneddatetime)、持续时间(Duration)、时间线上的瞬时点(Instant)。其中LocalDateTime使用的频率更高,以下以LocalDateTime为例使用其方法,LocalDate、LocalTime类似
LocalDateTime ldt = LocalDateTime.Now();
System.out.println(ldt);//2022-08-23T10:32:54.146038400
- of():(静态方法)设置指定的年、月、日、时、分、秒;并且没有偏移量。也属于实例化
LocalDateTime ldt2 = LocalDateTime.of(2022, 8, 23, 10, 6, 23);
//也可以只设置时分秒,具体选择of()方法中的参数
System.out.println(ldt.getDayOfMonth());//获取本月的第几天:23
System.out.println(ldt.getMinute());//获取现在是这个小时的几分:11
System.out.println(ldt.getMonth());//获取月份:AUGUST
System.out.println(ldt.getMonthValue());//获取月份的的数字:8
System.out.println(ldt.getDayOfWeek());//获取星期几:TUESDAY
- withXxx():设置相关属性;具有不可变性
LocalDateTime ldt3 = ldt.withDayOfMonth(30);
//此时ldt是今天的日期23,ldt3是返回修改过的日期30,因为ldt没有变,
//所以是不可变性
- plusXxx(),minusXxx():加减相关属性,具有不可变性
LocalDateTime ldt5 = ldt.plusMonths(3);//ldt5是ldt月份往后推3个月的日期
LocalDateTime ldt6 = ldt.minusDays(6);//ldt6是ldt往前推6天的日期