1. JAVA常用类库
1.1 字符串结构支持
String类存在一个不可修改的问题,可修改的字符串操作结构可以通过StringBuffer或者StringBuilder来实现
1.1.1 StringBuffer类
此类的常用方法有
- StringBuffer(int cap):定义一个指定大小的StringBuffer
- append(object o ):向StringBuffer中添加数据
- delete(int start,int end):删除指定索引范围内的内容
- insert(int offset,object o):在指定位置增加数据
- reverse():实现字符串的翻转
- replace(int start,int end,String str):替换指定范围内的字符串
StringBuffer类的内部是基于数组实现的,最终实现数据存储的实际上是一个字符数组,该数组的默认长度是16
个字符,如果开发者最终修改的数据的总长度为16个字符,则可以使用默认构造方法,如果总长度超过16个字符,则建议在实例化StringBufferr类对象时配置好字节数组长度,以免产生过多的垃圾
private transient char[] toStringCache;//字符数组
String和StringBuffer的区别
- String和StringBuffer都可以实现字符串的保存
- String的特点是一旦声明内容则不可改变,所改变的知识字符串的引用,StringBuffer的特点是可以改变内容,内容的改变需要进行严格的长度控制,如果内容超过了指定的长度,会自动进行扩容
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;//左移一位就是*2相当于是原来的长度*2+1
if (newCapacity - minCapacity < 0) {//如果尝试扩容之后的长度还是不够,那么就直接扩容到需要的大小
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
- StringBuffer类还提供了几个好用的API:
reverse()
、delete()
、insert()
1.1.2 StringBuilder
其API和StringBuffer所提供的API大同小异,主要是其底层方法的实现不一致
StringBuffer和StringBuilder的区别
- StringBuffer的字符串处理操作方法全部使用了
synchronized
关键字,因此StringBuffer采用线程同步机制 - StringBuilder类中的相关方法没有同步支持,全部基于异步处理,是非线程安全的
1.1.3 CharSequence接口
String、StringBuffer、StringBuilder这3个类在JAVA中同属于CharSequence接口的子类,也就是说他们实现了CharSequence所规定的接口标准
CharSequence提供了一个字符序列的标准操作接口,StringBuffer和StringBuilder类都可以直接接收CharSequence接口实例(向上转型)
1.2 AutoCloseable接口
在程序开发中经常需要使用到一些网络资源的处理操作,而在进行网络资源操作之前往往需要进行资源的连接,在每次操作完毕之后还需要手动进行资源的释放,AutoCloseable接口结合异常处理语句实现自动关闭处理机制
使用示例
public interface IMessage extends AutoCloseable{
void send(String msg);
}
public class NetMessage implements IMessage{
public NetMessage() {
System.out.println("连接远程服务器中...");
}
@Override
public void send(String msg) {
System.out.println("发送消息中:"+msg);
}
@Override
public void close() throws Exception {
System.out.println("关闭资源中");
}
}
public static void main(String[] args) {
try(NetMessage netMessage = new NetMessage()){
netMessage.send("消息1");
}catch (Exception e){
e.printStackTrace();
}
}
输出结果
连接远程服务器中...
发送消息中:消息1
关闭资源中
1.3 Runtime类
Runtime描述的是一种JVM(Java Virtual Machine,Java虚拟机)的运行时状态,每一个JVM进程都会自动地帮助用户维护一个Runtime类的实例化对象。RunTime对象实例实现本地硬件信息以及操作系统的进程管理
Runtime类使用了单例设计模式
由于运行时的状态是以进程为单位进行保存的,因此每一个JVM之中所创建的线程都将获得相同的Runtime类对象实例。整个JVM进程中只会存在一个RunTime类的对象实例
RunTime类的常用方法
- Process exec(String command):开启新的子进程
- long maxMemory():获取当前JVM可以使用的最大内存
- long totalMemory():获取初始化内存
- long freeMemory():获取空闲内存
- void gc():手动执行gc操作
Java中垃圾收集机制总结
- JAVA中所有GC属于守护线程,守护线程是伴随主线程存在的,其主要目的是进行垃圾的垃圾以及堆内存空间的释放
- GC处理在JVM中有两种形式:一种是自动的垃圾收集,一种是手动的垃圾收集(通过runtime的gc())
1.4 System类
System常用方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
//此方法用来实现数组的复制
- currentTimeMillis():获取当前的时间戳
- exit():结束JVM进程,调用的是Runtime类中的exit()
- gc():同理,调用的页是Runtime类中的gc方法
1.5 Cleaner类
在JVM中保存的每一个对象实例在被GC线程回收之后,都会执行一个对象释放前的收尾操作,然而这样的对象收尾操作中如果出现了某些死锁问题,回收处理就有可能无法正确执行完毕,从而影响当前程序的执行性能。从JDK9开始为了更加高效地实现回收处理,可以单独设计一个单独的回收线程,这样在出现问题时就不会对执行线程产生影响。
实现回收线程的操作,可以使用一个名为Cleaner的回收处理类。直接使用该类进行回收对象(回收对象的类必须实现Runnable接口)的驻车,并返回一个Cleamer.cleanable接口实例,这样就可以利用该接口提供clean()方法实现回收线程的启动
public class Book implements Runnable{
public Book() {
System.out.println("【构造】用心编写了一本优秀的原创技术图书。");
}
public void read() {
System.out.println("【读书】认真学习李兴华老师的编程技术课程。");
}
@Override
public void run() {
}
}
public class ThreadCleaner implements AutoCloseable{
private static final Cleaner cleaner = Cleaner.create() ; // 创建一个回收对象
private Cleaner.Cleanable cleanable ; // 回收管理
public ThreadCleaner(Book book) {
this.cleanable = cleaner.register(this, book) ; // 注册一个回收线程
}
@Override
public void close() throws Exception {
this.cleanable.clean(); // 释放时进行垃圾的清除
}
}
public static void main(String[] args) throws Exception { // 沐言科技:www.yootk.com
Book book = new Book(); // 实例化新的类对象
try (ThreadCleaner bc = new ThreadCleaner(book)) { // 自动关闭
book.read(); // 相关业务操作
} catch (Exception e) {}
}
本程序创建一个Book类,该类实现了Runnable多线程接口,随后将此线程对象注入Cleaner类对象实例。由于当前的ThreadCleaner类的对象实例在try语句中定义,因此该对象的操作执行完成后就会自动类中的close()方法,以实现对象资源的释放操作
JVM对象的生命周期
JAVA中每一个对象从创建到回收都会经历以下的过程
- 创建阶段:每当使用关键字new就表示要开辟新的堆内存空间,同时每一个新的对象实例化的时候都一定要执行类中的构造方法,构造方法的目的是初始化类中的成员
- 应用阶段:利用指定的对象名称可以直接进行类中方法的调用处理
- 不可见阶段:现在某一个方法内部已经有一个对象,该方法执行完毕后该对象将不再被使用
- 不可达阶段:某一个堆内存空间已经不再被任何栈内存所指向,之后此空间将成为垃圾空间
- 收集阶段:JVM会自动地进行垃圾空间的标记,标记之后将通过GC释放。JDK1.8以及之前的版本都是使用
finalize()
方法,而JDK9以及以后的版本推荐使用Cleaner
来完成
1.6 对象克隆
每一个对象实例化之后都会在堆内存
空间保存各自的属性内容,JAVA所提供的对象克隆机制可以将一个堆内存空间中保存的数据复制到另一个堆内存空间,从而实现新的实例化对象的创建。
**Cloneable与Object.clone()**之间的关系:
public interface Cloneable{}//与序列化接口一样,是标记性接口
protected Object clone() throws CloneNotSupportedException
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
由于Object类中的clone()方法使用了protected访问权限控制,因此在子类中对此方法进行覆写并调用父类clone()方法实现(对象克隆是由JVM处理的)。通过最终的对象输出内容可以发现,这两个对象属于不同的堆内存空间,最终实现了对象克隆
对象克隆属于浅克隆
- 深克隆:对所有与对象有关的引用类型进行克隆
- 浅克隆:只克隆当前类中的基本属性内容,Object提供的clone方法实际上属于浅克隆操作
- 基本属性的克隆包含这八种基本属性的克隆以及String类的克隆,其他类是不会进行克隆的
基本数据类型 包装类类型
*int Integer
*char Character
*除这两种外其他6种基本类型的包装类都是首字母大写
*double Double
*short Short
*float Float
*byte Byte
*long Long
*boolean Boolean
1.7 数字操作类
1.7.1 Math数学计算
Math类没有提供任何构造方法,其构造方法是私有的,同时所有的方法都是采用static定义的,这样的话通过类名就能直接使用,常用方法:
- double abs(double a):获取绝对值
- double pow(doublea,double b):计算a^b
- double log(double a):获取以e为底的对数
- long round(double a):四舍五入,注意负数的四舍五入也是和正数一样的,先abs再按照规则进行四舍五入
小数点四舍五入存在的问题
这个四舍五入的方法会将全部的小数位进行整体的进位处理。摘录源码如下
public static long round(double a) {
long longBits = Double.doubleToRawLongBits(a);
long biasedExp = (longBits & DoubleConsts.EXP_BIT_MASK)
>> (DoubleConsts.SIGNIFICAND_WIDTH - 1);
long shift = (DoubleConsts.SIGNIFICAND_WIDTH - 2
+ DoubleConsts.EXP_BIAS) - biasedExp;
if ((shift & -64) == 0) { // shift >= 0 && shift < 64
// a is a finite number such that pow(2,-64) <= ulp(a) < 1
long r = ((longBits & DoubleConsts.SIGNIF_BIT_MASK)
| (DoubleConsts.SIGNIF_BIT_MASK + 1));
if (longBits < 0) {
r = -r;
}
// In the comments below each Java expression evaluates to the value
// the corresponding mathematical expression:
// (r) evaluates to a / ulp(a)
// (r >> shift) evaluates to floor(a * 2)
// ((r >> shift) + 1) evaluates to floor((a + 1/2) * 2)
// (((r >> shift) + 1) >> 1) evaluates to floor(a + 1/2)
return ((r >> shift) + 1) >> 1;
} else {
// a is either
// - a finite number with abs(a) < exp(2,DoubleConsts.SIGNIFICAND_WIDTH-64) < 1/2
// - a finite number with ulp(a) >= 1 and hence a is a mathematical integer
// - an infinity or NaN
return (long) a;
}
}
1.7.2 Random随机数
Random是一个专门用于生成随机数的工具类。
- Random(long seed):设置Random生成的种子数值
- int nextInt(int bound):生成”0-bound”的整数
种子的作用
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
next()方法是该类的核心,无论是nextInt()还是nextDoubles()都是基于此方法实现的,其计算公式中会用到种子,而且种子是唯一可变参数,如果种子是固定的话,那么在这个世界上的任意一个地方,生成的随机数都是相同的
1.7.3 BigInteger
- BigInteger(String val):将字符串中的数字转为大数字
- add(BigInteger val):大数字加法运算
- subtract(BigInteger vak):减法
- multiply:乘法
- divide():除法,但不暴力小数
- BigInteger[] divideAndRemainder(BigInteger val):触发计算,第一位保留商,第二位保留余数
- BigInteger mod(BigInteger m):求模
- 可以通过
longValue()
、doubleValue()
将字符串转为基本数据类型的数据
1.7.4 BigDecimal
BigDecimal是一个描述浮点数数据的大数字处理类,与BigInteger不同,它可以实现小数的保存,可以将BigInteger对象、字符串、整形数据、双精度浮点型数据直接转为BigDecimal对象实例
注:在进行除法计算的时候,需要为其设置进位模式,如HALF_UP(四舍五入等)
在一些数字精度较高的系统之中,往往使用BigDecimal类来代替double基础类型,主要的原因是double类型占64位
,在计算时可能会出现同步与计算精度方面的问题,而使用BigDecimal
类保存数据则可以避免这些问题
1.8 日期时间数据处理
1.8.1 Date类
Date类的常用方法
- Date():获取当前的日期时间对象
- Date(long date):将时间戳转为Date类对象
- getTime():将日期时间数据以long类型数据的形式返回
- boolean after(Date when):是否在指定日期之后
- boolean before(Date when):是否在指定日期之前
在java中日期时间、内存大小、文件大小都使用long类型数据来进行描述,在描述日期时间时long类型数据代表的是毫秒数
1.8.2 Calendar类
Calendar是一个描述日历的操作类,通过该类,可以方便地得到当前的日期时间,可以得到准确的月初、年初、年末以及指定月份所在季的日期时间,Calendar类在内部提供了getInstance()
方法实现该类对象的实例化操作,而后就可以通过get()
方法并结合指定的日期时间标记来获取所需要到的日期时间数据
1.8.3 SimpleDateFormat类
使用Date类和Calendar类都可以获取当前的日期时间数据,但是在最近进行内容输出的时候都各有缺陷,所以为了更加方便的实现当前日期时间的获取操作,在实际的JAVA开发中一般都会使用SimpleDateFormat
- SimpleDateFormat(String pattern):定义日期时间匹配格式
- SimpleDateFormat(String pattern,Locale locale):定义日期时间匹配格式与位置
- String format(Date date):格式化日期为字符串
- Date parse(String src) throws ParseException:将字符串转为Date对象实例
SimpleDateFormat类可以直接实现Date类对象格式的转化,该匹配格式有多个重要的日期时间单位,分别为年(yyyy)、月(MM)、日(dd)、时(HH)、分(mm)、秒(ss)、毫秒(sss),这样就可以通过format()
方法来完成最终的格式化处理
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
System.out.println(simpleDateFormat.format(date));
String dateStr = "2022-18-28 18:59:59:321";
try {
System.out.println(simpleDateFormat.format(simpleDateFormat.parse(dateStr)));
//2023-06-28 18:59:59:321
} catch (ParseException e) {
e.printStackTrace();
}
使用parse()
进行转换时,如果字符串所表示的日期时间超过合法时间,例如,将月份设置为18,则在实现转换后,会自动向年份进位
数据类型转换
字符串转为基本数据类型的数据一般可以通过包装类来完成,而基本数据类型的数据或者其他类型的数据要转为字符串则必须依靠String.valueOf()方法来实现,除此之外,可以通过SimpleDateFromat来实现字符串与日期时间数据之间的转换处理
1.8.4 LocalDate类
在JDK8及其之后的版本中,JAVA中追加了一个新的日期时间处理包,java.time。这个包主要提供3个类:LocalDate、LocalTime、LocalDateTime
- 通过调用**now()**方法可以分别获取日期,时间,日期时间
LocalDate today = LocalDate.now();
today.get(ChronoField.ALIGNED_WEEK_OF_MONTH);//一月中的第几周
today.get(ChronoField.ALIGNED_WEEK_OF_YEAR);//一年中的第几周
闰年判断
isLeapYear():该方法可以用于直接判断指定的日期所在年是否为闰年
LocalDate日期推算
LocalDate localDate = LocalDate.parse("1987-09-15");
localDate.with(TemporalAdjusters.firstDayOfMonth());//所在月的第一天
localDate.withDayOfMonth(2);//当月的第二天
localDate.with(TemporalAdjusters.lastDayOfMonth());//所在月的最后一天
localDate.plusYears(300);
localDate.plusDays(300);
localDate.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));//日期所处月的第一个周一
1.8.5 多线程环境下的时间格式化
在多线程的开发环境下,如果每一个线程都保存一个SimpleDateFormat对象实例(转换格式相同),则比如造成大量而且无用的SimpleDateFormat实例产生,从而影响程序的执行性能
想要解决这样的问题,最佳的做法是在转换格式相同的情况下,若干多个线程共享同一个SimpleDateFormat的对象实例,这样就可以减少对象的个数,从而提高程序的性能
但是这样会存在一个问题,如下图所示:
这是因为SimpleDateFormat并没有提供数据的同步处理,所以出现了不同线程设置数据不完整情况下的转换异常
- 通过LocalDateTime类实现并发转换
public static void main(String[] args) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ZoneId zoneId = ZoneId.systemDefault();//代表着时区
for(int i = 0;i<10;i++){
new Thread(()->{
LocalDateTime localDateTime = LocalDateTime.parse("1988-02-17 21:15:32",dateTimeFormatter);
Instant instant = localDateTime.atZone(zoneId).toInstant();//获取一个标准时间
Date from = Date.from(instant);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(Thread.currentThread().getName()+" "+simpleDateFormat.format(from));
},"第"+i+"号线程").start();
}
}
1.9 正则表达式
为了保证数据类型转换的正确性,需要对字符串的组成结构进行判断,常规的做法是基于正则表达式
1.9.1 常用正则表达式标记
- 单个字符匹配
- x:表示的是一位任意的字符
- //:匹配任意的一位
"\"
(java中的转义字符) - \t:匹配制符表
- \n:匹配换行符
- 字符范围
[abc]
匹配的是abc中的任意一位[^abc]
:匹配的不是abc中的任意一位[a-zA-Z]
:匹配所有字母(所有的大写字母和小写字母),[a-z]就是匹配所有的小写字母,[A-Z]就是匹配所有的大写字母[0-9]
匹配所有的数字信息
- 简化表达式
.
:表示匹配任意的字符\d
:表示匹配任意的一位数字\D
: 表示匹配的任意的一位非数字\s
:匹配任意的空格\S
:匹配任意非空格\w
:匹配字母、数字、下划线\W
:匹配的是非字母、数字、下划线的字符
- 数量表达式
?
表示该正则表达式出现0次或者1次*
表示该正则表达式出现0次、1次、多次+
表示出现至少一次{n}
表示正好出现n次{n,}
表示出现n次以上{n,m}
表示可以出现n~m次
- 逻辑运算
- A表达式B表达式,在A之后紧跟B表达式
- 表达式A|表达式B,两个表达式二选一
1.9.2 String类对正则表达式的支持
支持方法
- matches(String reg):使用字符串与特定的正则表达式进行匹配
- split(String reg):使用正则表达式作为拆分符号
- split(String reg,int limit):使用正则表达式作为拆分符号
- replaceFirst(String reg,String replace):替换首个匹配字符
- replaceAll(String reg,String replace):替换全部匹配字符
正则匹配并实现日期或者日期时间转换
public class YootkDemo {
public static DateTimeFormatter dateFormatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd"); // 日期格式
public static DateTimeFormatter datetimeFormatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 日期时间格式
public static ZoneId zoneId = ZoneId.systemDefault(); // 获取当前时区的ID
public static void main(String[] args) throws Exception { // 沐言科技:www.yootk.com
String str = "1998-02-19 21:15:23"; // 字符串
String dateRegex = "\\d{4}-\\d{2}-\\d{2}"; // 日期正则匹配
String datetimeRegex = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"; // 日期时间正则匹配
if (str.matches(dateRegex)) { // 日期结构
LocalDate localDate = LocalDate.parse(str, dateFormatter);
Instant instant = localDate.atStartOfDay().atZone(zoneId).toInstant();
Date date = Date.from(instant);
System.out.println("【字符串转日期】" + date);
}
if (str.matches(datetimeRegex)) { // 日期时间结构
LocalDateTime localDateTime = LocalDateTime.parse(str, datetimeFormatter);
Instant instant = localDateTime.atZone(zoneId).toInstant();
Date date = Date.from(instant); // 转换
System.out.println("【字符串转换日期时间】" + date);
}
}
}
Email正则匹配
public static void main(String[] args) throws Exception { // 沐言科技:www.yootk.com
String str = "muyan.yootk-lixinghua@yootk.com"; // 邮箱字符串
String regex = "[a-zA-Z_][a-zA-Z0-9\\-_\\.]+@[a-zA-Z0-9\\-]+"
+ "\\.(com|com\\.cn|net|net\\.cn|gov|edu)"; // 日期正则匹配
System.out.println(str.matches(regex)); // 正则匹配
}
1.9.3 ju.regex程序包
java.util.regex.Pattern类:该类主要功能是进行正则表达式的编译处理,所以如果要想获得本类的实例化对象一般都使用其内部提供的compile()方法,这个方法需要传入一个正则,而后可以通过该类提供的split()方法实现字符串拆分。
public static void main(String[] args) throws Exception {
String str = "yootk.com828238edu.yootk.com8923892389yootk.ke.qq.com"; // 字符串
String regex = "\\d+"; // 日期正则匹配
Pattern pattern = Pattern.compile(regex); // 编译正则
String result[] = pattern.split(str); // 拆分字符串
for (String temp : result) { // 输出字符串数组
System.out.println(temp);
}
}
java.util.regex.Matcher:实现正则标记匹配,如果要想获取本类对象实例必须依靠Pattern类所提供的matcher()方法来完成,这样就可以通过Matcher类的对象实现字符串匹配、替换等功能。
public static void main(String[] args) throws Exception {
String str = "100.00"; // 字符串
String regex = "\\d+(\\.\\d+)?"; // 日期正则匹配
Pattern pattern = Pattern.compile(regex); // 编译正则
Matcher matcher = pattern.matcher(str); // 创建正则匹配对象
System.out.println(matcher.matches()); // 匹配结果
}
Matcher数据分组:通过指定的正则规则可以匹配一个字符串之中的多个数据,而后就可以基于分组的形式循环获取每一个匹配的数据
public static void main(String[] args) throws Exception {
String sql = "INSERT INTO dept(deptno,dname,loc) "
+ " VALUES (#{deptno}, #{dname}, #{loc})"; // 字符串
String regex = "#\\{\\w+\\}"; // 日期正则匹配
Pattern pattern = Pattern.compile(regex); // 编译正则
Matcher matcher = pattern.matcher(sql); // 创建正则匹配对象
while (matcher.find()) { // 存在下一个分组
String name = matcher.group(0); // 获取分组
System.out.print(name.replaceAll("#|\\{|\\}", "") + "、");
}
}
1.10 国际化应用
1.10.1 Locale类
Locale类是Java提供的一个可以进行国家以及语言切换的描述类,可以实现所需要的信息(如文字、日期时间等)的处理
1.10.2 资源文件
所有的资源文件都应该保存在项目的CLASSPATH路径下,为了便于资源文件的统一管理,JAVA对资源文件的定义有如下要求
- 资源文件的扩展名为.properties
- 资源文件保存时按照类的形式来进行存储,也可以保存在对应的包中
- 资源文件中的所有内容采用”key=value”的形式来声明
- 如果需要在资源文件中定义注释,可以使用”#”来进行标记
1.10.3 ResourceBundle读取资源文件
资源文件按照设计的要求保存在项目中只会就需要实现资源文件的读取操作,Java提供了ResourceBundle工具类来实现此功能。在进行数据读取的时候,首先需要设置资源文件的名称,而后可以根据key实现value数据读取,常用方法
- ResourceBundle getBundle(String baseName):获取指定的资源对象
- getBundle(String baseName,Locale locale):获取指定区域下的资源对象
- getString(String key):根据key获取value
1.10.4 国际化数据读取
国际化程序开发中需要针对不同的语言环境设置不同的资源文件,同时每一个资源文件必须可以清楚地标记出与之匹配的Locale代码,这样在通过ResuorceBundle绑定资源文件时就可以结合Locale实例来实现不同资源文件中指定key数据的加载操作
- 定义公共资源文件(Message.properties)
welcome.info=hello world!
author.info=Lumxi
- 定义中文资源文件(Message_zh_CN.properties)
welcome.info=这是给中文用户的
- 定义英文资源文件(Message_en_US.properties)
welcome.info=this is for users whose main lanuage is English.
- 读取资源文件并绑定实例
Locale locale = Locale.CHINA;
ResourceBundle resourceBundle = ResourceBundle.getBundle("resources.Message",locale);//资源绑定
通过该案例可以简单总结一下JAVA中的路径问题
/
代表位置,分两种情况,目标资源是给谁使用的
给服务器使用的: / 表示在当前web应用的根目录
给浏览器使用的: / 表示 在webapps的根目录下
.
地址问题,.
代表java命令运行目录
对于JAVA项目而言,.
代表Class的位置,也就是bin目录下,以Eclipse为例的IDE会自动执行一个动作,就是Set pathClass = class
位置,这样无论在哪里都可以运行这个class,但是.
就代表着当前运行的位置,所以.
代表着当前项目的根目录
- getRealPath():读取,返回资源文件的绝对路径
- getResourceAsStream():得到资源文件
classPath
是什么
classpath通常会在指定一些配置/资源文件的时候会使用到,同时在打jar包的时候也会经常使用到,因此弄清楚是很有必要的
一般来说,在一个web项目或者普通的项目被打包后,会产生一个.classes文件目录,这个文件目录就是classpath,通常可以使用classpath:
这个前缀来指代它
- 所以说,在一般情况下难以确定路径的话,尽量使用target目标文件夹来观察编译产生的文件情况,这样可以确保你使用的路径是正确的
1.10.5 格式化文本数据
在实际开发中的所使用的固定文本格式可能是需要动态替换的,Java中提供了MessageFormat消息格式化类用于实现动态文本的替换处理,开发者在资源文件中利用el表达式”{索引号}”的形式定义好占位符的标记,即可实现消息的完整显示
Locale locale = Locale.CHINA;
ResourceBundle resourceBundle = ResourceBundle.getBundle("com.example.resource.Message",locale);//资源绑定
String string = resourceBundle.getString("welcome.info");
System.out.println(MessageFormat.format(string, "1", "1"));
1.10.6 数字格式化
一般来说需要考虑3种格式数据的处理:文本数据
、日期时间数据
、数字数据
,一般来说文本数据的格式化可以通过MessageFormat
来实现,日期时间数据可以通过SimpleFormat
来实现,数字数据的格式化可以通过DecimalFormat
来实现
NumberFormat类进行数字格式化处理的时候,需要考虑数字或者货币的显示风格,这样就需要与Locale相结合,根据当前使用者所处的区域来实现最终的数据处理
- **getInstance()**获取NumberFormat对象实例
- **getInstance(Locale)**绑定指定的Locale的对象实例
- **getPercentInstance()**获取计算百分比的实例
- **getPercentInstance(Locale)**绑定Locale以计算百分比
- **getCurrencyInstance()**获取默认的Locale的货币实例
- getCurrencyInstance(Locale inLocale):获取指定Locale的货币实例
NumberFormat instance = NumberFormat.getInstance();
System.out.println(instance.format("28292535.928531"));//数字格式.化
DecimalFormat instance1 = (DecimalFormat) NumberFormat.getInstance();
instance1.applyPattern("####,####,####.000");//保留3位小数
instance1.setRoundingMode(RoundingMode.DOWN);//不进位
instance1.setPositivePrefix("当年的流水收入");//格式化前缀
instance1.setMinimumFractionDigits(5);//保留5位小数
System.out.println(instance1.format("28292535.928531"));
NumberFormat percentInstance = NumberFormat.getPercentInstance();
System.out.println(percentInstance.format("0.9892367"));
//如果对百分比格式化有自定义格式要求,则必须进行强制向下转型
DecimalFormat percentInstance1 = (DecimalFormat) percentInstance;
percentInstance1.setMinimumFractionDigits(5);//保留五位小数
System.out.println(percentInstance1.format("0.9892367"));
NumberFormat.getCurrencyInstance(Locale.CHINA).format(787.98);//国际化货币使用
1.11 Base64的加密与解密
为了保证数据传输以及存储的安全性,往往会对数据进行有效的加密与解密处理,这样畸变数据再传输过程中出现了泄漏,也会因为没有使用铭文传输而保证其安全性
Base64主要应用与HTTP传输
Base64是当前网络上最常见的用于传输8B字节码的编码方式之一,是一种基于64个可打印字符来表示二进制数据的方法。
String message = "Lumxi";
Base64.Encoder encoder = Base64.getEncoder();
byte[] encode = encoder.encode(message.getBytes(StandardCharsets.UTF_8));
System.out.println(new String(encode));
Base64.Decoder decoder = Base64.getDecoder();
String string = new String(decoder.decode(encode));
System.out.println(string);;
1.12 UUID
在项目中对一些不确定的资源
进行编号时需要随机生成识别码,为此提供了UUID类
它根据当前的时间生成一个不会重复的编码
System.out.println(UUID.randomUUID().toString());
1.13 Optional
Optional
类可以用来处理null数据的处理
- Optional of(T val):保存一个数据,该数据不允许为空
- ofNullable(T val):可以保存一个恐数据
- T get():从Option里面取出对应的数据
- isEmpty():是否为一个空的Optional
- isPresent():是否存在内容,如果存在则返回true,否则返回false
在一些获取事项对象实例的处理方法中,为了避免返回null,可以将方法的返回值类型设置为Optional,同时把所需要返回的对象实例保存在Optional类的对象实例中,这样在进行该方法的调用的时候,开发者通过返回值类型即可完成判断,不用考虑null的判断处理。
package com.example.demo;
import java.util.Optional;
@FunctionalInterface
interface IMessage{
void send(String msg);
}
public class Factory {
private Factory(){
}
//此时方法返回Optional,所以该方法返回的实例不会为null
public static Optional<IMessage> getInstance(){
return Optional.of(msg -> {
System.out.println(msg);
});
}
public static void main(String[] args) {
IMessage iMessage = Factory.getInstance().get();
iMessage.send("hi");
}
}
通过Optional.of()
方法进行对象保存时,如果内容为空,则方法会出现错误,如果内容不为空,外部的调用者就可以直接通过Optional
获取对象实例,从而避免了null的判断
1.14 ThreadLocal
ThreadLocal是一种用于保证多线程场景下数据安全操作的常用处理类,该类会将每一个当前操作线程作为数据存储的key,而其对应的value则是该线程所操作的引用数据,这样每一个线程只允许操作属于自己的VALUE
package com.yootk;
class Message { // 信息的操作类
private String content; // 类中的属性
public void setContent(String content) { // 属性保存
this.content = content;
}
public String getContent() { // 属性获取
return content;
}
}
class MessagePrint { // 信息打印
public static void print() {
System.out.println("【MessagePrint】" +
Resource.MESSAGES.get().getContent()); // 获取当前线程保存对象
}
}
class Resource { // 资源存储类
public static final ThreadLocal<Message> MESSAGES = new ThreadLocal<>();
}
public class YootkDemo { // 李兴华高薪就业编程训练营
public static void main(String[] args) throws Exception { // 沐言科技:www.yootk.com
String[] values = new String[] { "沐言科技:www.yootk.com", "李兴华编程训练营:edu.yootk.com",
"课程讲师:李兴华" }; // 每个线程执行各自的内容输出
for (String msg : values) {
new Thread(() -> {
Resource.MESSAGES.set(new Message());
Resource.MESSAGES.get().setContent(msg);
MessagePrint.print(); // 内容输出
}).start();
}
}
}
本程序为了便于数据操作创建了一个Resource资源管理类,在该类中通过ThreadLocal对象实例实现了不同线程的资源管理,这样不同的线程在获取数据时只会获取自己所保存的数据
1.15 定时调度
在项目开发中,经常需要在系统的后台设置一些定时任务,例如,每隔30秒检查一次用户的存活状态,或者每隔5min清理一次不重要的文件,这些任务就可以利用util包下中提供的Timer类与TimerTask抽象类来实现
常用方法
- schedule(TimerTask task,long delay):设置延迟多少毫秒执行定时任务
- schedule(TimerTask task,long delay,long period):设置延迟多少毫秒执行定时任务,同时定义间隔调度的时间
- schedule(TimerTask task,Date time):若达到指定的日期时间则进行调度
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerDemo(),1000,2000);//1000ms后开始任务,2000ms调度一次该任务
}
@Override
public void run() {
System.out.println("正在执行定时任务");
}
1.16 自定义事件
事件操作是一种业务之间常用的解耦和处理模型,在整个的事件的处理机制过程中,一般会由一个业务在特定的逻辑处理中产生了某些事件,同时该时间都会绑定某一个时间监听处理器,一旦某些操作引起了变化,则会触发监听,并可以根据时间源来完成某些特定的业务处理
在时间操作的模型中,核心的业务逻辑和具体的事件之间可能没有任何的强关联性,并且事件处理也有可能在不同的线程之中完成,所有与时间有关的核心对象都会保存在事件源之中,这样事件监听处理类就可以根据事件源获取所需要的内容,在Java中的时间处理机制一般由以下三个核心的组成部分
- EventObeject:是事件状态对象的基类,它封装了事件源对象以及事件相关的信息,所有事件类都需要继承该类,同时将需要传递的事件相关对象保存在此类中
- EventListener:事件监听处理标记接口,该接口没有提供任何的事件处理方法标准,但是所有事件监听器都需要实现该接口,并根据自己的需要进行事件处理方法的定义。所有的事件监听器都需要
注册
在事件源上,当事件源的属性或者状态改变的时候,调用响应监听器内的回调方法 - Source:是一个逻辑上的概念(并不是具体的某一个类或者接口),所有的时间不需要实现任何接口或者继承任何的父类,它是事件最初发生的地方,因为事件源需要注册事件监听器,所以事件源内需要保存有全部的事件监听器实例
package com.example.demo;
import java.util.EventListener;
import java.util.EventObject;
//实现一个事件类,并将一个监听器注册到该事件上
class myEventObject extends EventObject{
private Object source;//事件对象
public myEventObject(Object source){//构造器
super(source);
this.source = source;
}
@Override
public Object getSource() {
return this.source;
}
public void setSource(Object source) {
this.source = source;
}
}
class EventSourceObject{//定义数据源
private String message; //消息内容
private myEventListener[] listeners;//事件监听器
private int foot = 0;//操作角标
public EventSourceObject(String message){
this.listeners = new myEventListener[5];
this.message = message;
}
//添加事件监听器,(当监听器列表中还有空位剩余时),将自定义监听器加入监听器列表
public void addListener(myEventListener eventListener){
if(this.foot <this.listeners.length){
this.listeners[this.foot++] = eventListener;
}
}
//调用触发
public void notifyAndInvoke(){
for (myEventListener listener : this.listeners) {
//如果存在该监听器
if(listener != null){
listener.fire(new myEventObject(this));//监听处理
}
}
}
public String getMessage() {
return message;
}
//模拟事件触发器,当成员变量message的值发生变化的时候,触发事件,此时会调用事件处理
public void setMessage(String message) {
if(this.message != message){
this.message = message;
this.notifyAndInvoke();
}
}
}
class myEventListener implements EventListener{//定义监听器
//注册事件处理方法
public void fire(myEventObject myEventObject){
EventSourceObject eventSourceObject = (EventSourceObject) myEventObject.getSource();
System.out.println("数据修改:"+eventSourceObject.getMessage());
}
}
public class EventObjectDemo {
public static void main(String[] args) {
EventSourceObject my_message = new EventSourceObject("my message");
my_message.addListener(new myEventListener());
my_message.setMessage("哈哈");
}
}
以上程序实现了一个属性修改后的事件处理操作,每当用户调用EventSourceObject
中setMessage()
方法进行message
属性修改时,就会自动触发注册的myEventListener
,并使用fire()方法实现更新数据的获取
自定义数据的获取
早期的JAVA开发中存在一种观察者
设计模式,该模式与现在所使用的事件处理机制类似,但是该模式在JDK9及其之后的版本已经被废弃了,同时Spring开发框架也对自定义事件进行了功能结构上的补充,综合来讲,事件处理是一种重要的解耦思想。
1.17 Arrays
Arrays常用方法
- binarySearch(数据类型[] a,数据类型key)
- compapre(数据类型[] a,数据类型[] b):两个数组的大小比较
- copyOf(数据类型[] src,int newLength):数组拷贝,从原数组拷贝内容到新数组
- equals(数据类型[] a,数据类型[] b):两个数组的相等比较(顺序要相同)
- fill(数据类型[] a,数据类型 val):数组填充
- hashCode(数据类型[] a):根据数组内容生成一个新的Hash码
- sort(数据类型[] a):数组排序
- toString():将数组转成字符串结构
private static int binarySearch0(long[] a, int fromIndex, int toIndex,
long key) {
int low = fromIndex;
int high = toIndex - 1;//定义搜索区间是[left,right]
while (low <= high) {
int mid = (low + high) >>> 1;//其实这样写容易溢出,写成low+(high-low)/2比较好
long midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.-1也行
1.18 比较器
Java中的数组分为基本类型数组和对象数组两类,Arrays类在设计时已经充分考虑到了对象数组的排序与比较的需要,在二分搜索,排序等需要进行比较的操作中,可以自定义比较规则,类似于C++中的重载bool比较函数的操作,从而达到自定义排序、比较的目标
java中提供的比较器有两种Comparable、Comparator
1.18.1 Comparable接口
Comparable是一个JAVA内部支持的数据结构比较丰富的常用比较器接口
使用示例
- 定义domain
public class Book implements Comparable<Book> {
public Book(int prices) {
this.prices = prices;
}
private int prices;
@Override
public int compareTo(Book o) {
return this.prices - o.prices;//运算结果>0的时候,返回的是一个true的值,表示这个值较大,往后排
}
}
- 定义测试用例
public static void main(String[] args) {
Book[] books = new Book[]{new Book(1),new Book(2),new Book(3)};
Arrays.sort(books);
System.out.println(Arrays.toString(books));
}
1.18.2 Comparator接口
Comparable接口是基于类定义结构实现的,而Java对未实现Comparable接口并且需要进行比较的操作类又提供了用于挽救的比较器接口,Comparable是在类定义时就已经明确要实现的排序处理接口,但是如果此时已经存在了一个开发完整的程序类,在无法修改的前提下,就可以定义一个专属的排序工具来实现该操作
Book[] books = new Book[]{new Book(1),new Book(2),new Book(3)};
Arrays.sort(books, Comparator.comparingInt(Book::getPrices));
System.out.println(Arrays.toString(books));
1.19 二叉树
本节以工程的方法实现一棵BST的CRUD操作
package com.example.demo.BSTDemo;
public class BinaryTreeImpl<E> implements IBinaryTree<E>{
private Node root;//二叉树的实现结构
private int count;
private int foot;
@Override
public void add(E data) {
if(data == null){
throw new NullPointerException("插入的节点不允许为空");
}
if(!(data instanceof Comparable)){
throw new ClassCastException("对象数据所在类没有实现Comparable接口");
}
Node newNode = new Node<E>((Comparable)data);
if(this.root == null){//初始化二叉树
this.root = newNode;
}else {
Node currentNode = this.root;
while (currentNode != null){//BST的插入操作
Node temp = null;
if(currentNode.getData().compareTo(data) <= 0){//比根节点要大,那么就找右子树
temp = currentNode.getRight();
if(temp == null){
currentNode.setRight(new Node<>((Comparable)data));
break;
}
currentNode = temp;
}else{//否则就找左子树
temp = currentNode.getLeft();
if(temp == null){
currentNode.setLeft(new Node<>((Comparable)data));
break;
}
currentNode = temp;
}
}
}
this.count++;
}
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public int count() {
return this.getCount();
}
private void traverse(Object[] data,Node root){
if(root == null){
return;
}
traverse(data,root.getLeft());
//中序遍历代码位置
data[this.foot++] = root.getData();
traverse(data,root.getRight());
}
private Node searchForNode(Node root,E data){
if(root == null){
return null;
}
Node leftC = searchForNode(root.getLeft(), data);
if(leftC == data){
return leftC;
}
Node rightC = searchForNode(root.getRight(), data);
if(rightC == data){
return rightC;
}
return null;
}
@Override
public Object[] toArray() {
Object[] data = new Object[this.count];
traverse(data,this.root);
return data;
}
public Node deleteNode(Node root, E key) {
if(root == null){
return root;
}
if(root.getData() == key){//遍历查找得到了val为key的节点,进行删除
//情况1:这个节点没有子节点
if(root.getLeft() == null && root.getRight() == null){
return null;
}
//情况2:这个节点只有一个子节点
if(root.getLeft() == null){//这里省略了root.right != null,因为前一个分支条件语句覆盖掉了
//这时候只需要把右子树给接替上来就好了
return root.getRight();
}
if(root.getRight() == null){
return root.getLeft();
}
//情况3,左右子树都不是空,这时候我们需要找到一个节点,这个节点可以是左子树最大的节点,可以是右子树最小的节点
Node max = getMax(root.getLeft());//这里找的是右子树的最小节点
//然后进行删除,这里根据BST的数据结构特点来进行操作,注意,无论是最大的节点还是最小的节点,我们发现它都是叶子节点,那么我们剩下就是要删除叶子节点嘛
//我们就是在编写删除操作,于是删除这个事情我们把它交给递归就好了
root.setData(max.getData());
root.setLeft(deleteNode(root.getLeft(),(E)root.getData()));
}
//否则的话就继续遍历,这里的话key比root的值要大,那么就要遍历值更大的右子树
else if(root.getData().compareTo(key) < 0){
root.setRight(deleteNode(root.getRight(),key));
}else if(root.getData().compareTo(key) > 0){
root.setLeft(deleteNode(root.getLeft(),key));
}
return root;
}
private Node getMin(Node root){
//向左遍历
while(root.getLeft() !=null){
root = root.getLeft();
}
return root;
}
private Node getMax(Node root){
//向左遍历
while(root.getRight() !=null){
root = root.getRight();
}
return root;
}
}