课程链接:https://edu.aliyun.com/course/1012
第1~6章 关于线程:https://blog.csdn.net/weixin_43494837/article/details/113764127?spm=1001.2014.3001.5501
(思维导图在最后)
————————————————————————————————————
第7章:Java基础类库
课时26:StringBuffer类
-
String类是所有项目开发中一定会使用到的一个功能类,并且这个类拥有如下的特点:
- 每个字符串的常量都属于一个String类的匿名对象,并且不可更改;
- String类有两个常量池:静态常量池、运行时常量池;
- String类的对象实例化,建议使用直接赋值的形式完成,这样可以直接将对象保存在常量池中,以方便重用。
-
String类的弊端:内容不允许修改。所以为了解决此问题,Java专门提供了StringBuffer类来实现字符串内容的修改处理。
-
String与StringBuffer对比:
-
引用传递:
-
String类对象引用传递:
public class JavaDemo{ public static void main(String[] args) { String str = "Hello "; change(str); System.out.println(str); // 输出:Hello } public static void change(String temp) { temp += "World !"; // 内容并没有发生改变 } }
-
StringBuffer类对象引用传递:
public class JavaDemo{ public static void main(String[] args) { StringBuffer buf = new StringBuffer("Hello "); change(buf); System.out.println(buf); // 输出:Hello World ! } public static void change(StringBuffer temp) { temp.append("World !"); // 内容发生了改变 } }
-
-
-
实际上大部分的情况下,很少会出现有字符串内容的改变,这种改变指的并不是针对静态常量池的改变。
public class JavaDemo{ public static void main(String[] args) { String strA = "pikaqiu"; String strB = "pi" + "ka" + "qiu"; System.out.println(strA == strB); // 输出:true } }
上述strB对象的内容并不算是改变,它在程序编译后,实际上相当于:
StringBuffer buf = new StringBuffer(); buf.append("pi").append("ka").append("qiu");
所有的“+”在编译之后都变为了StringBuffer中的append()方法,并且在程序中,StringBuffer类与String类对象之间本来就可以直接互相转换:
- String类对象变为StringBuffer类对象:依靠StringBuffer类的构造方法;
- 所有类对象都可以通过toString()方法变为String类型。
-
在StringBuffer类中,除了支持字符串内容的修改外,也提供有String类所不具备的方法:
-
插入数据:public StringBuffer insert(int offset, 数据类型 b)
public class JavaDemo{ public static void main(String[] args) { StringBuffer buf = new StringBuffer(); buf.append("qiu").insert(0, "pi").insert(2, "ka"); System.out.println(buf); // 输出:pikaqiu } }
-
删除指定范围的数据:public StringBuffer delete(int start, int end)
public class JavaDemo{ public static void main(String[] args) { StringBuffer buf = new StringBuffer(); buf.append("Hello World!").delete(5, 11); System.out.println(buf); // 输出:Hello! } }
-
字符串内容反转:public StringBuffer reverse()
public class JavaDemo{ public static void main(String[] args) { StringBuffer buf = new StringBuffer(); buf.append("Hello World!").reverse(); System.out.println(buf); // 输出:!dlroW olleH } }
-
-
与StringBuffer类相似的还有一个StringBuilder类,它是在JDK1.5开始提供的,并且提供的方法与StrIngBuffer功能相同,区别是:StringBuffer类中的方法是线程安全的,全部使用了sybchronized关键字,而StringBuilder类是非线程安全的。
-
面试题:请解释String、StringBuffer、StringBuilder的区别?
- String类是字符串的首选类型,其最大的特点是内容不允许修改,而StringBuffer与StringBuilder类的内容允许修改;
- StringBuffer是在JDK1.0的时候就提供的,是线程安全的操作,而StringBuilder类是在JDK1.5的时候才提供的,是非线程安全的操作。
课时27:CharSequence接口
-
CharSequene是一个描述字符串结构的接口,在这个接口里面一般有三种常用的子类:
-
String类:
public final class String extends Object implements Serializable, Comparable<String>, CharSequence
-
StringBuffer类:
public final class StringBuffer extends Object implements Serializable, CharSequence
-
StringBuilder类:
public final class StringBuilder extends Object implements Serializable, CharSequence
-
-
只要有字符串就可以为CharSequence实例化:
public class JavaDemo{ public static void main(String[] args) { CharSequence str = "pikaqiu"; // 子类实例向父接口转型 } }
-
CharSequence接口的操作方法:
-
获取指定索引字符:public char charAt(int index)
-
获取字符串的长度:public int length()
-
截取部分字符串:public CharSequence subSequence(int start, int end)
public class JavaDemo{ public static void main(String[] args) { CharSequence str = "pikaqiu"; // 子类实例向父接口转型 CharSequence sub = str.subSequence(2, 7); System.out.println(sub); // 输出:kaqiu } }
-
课时28:AutoCloseable接口
-
AutoCloseable主要是用于日后进行资源开发的处理上,以实现资源(如文件、网络、数据库)的自动关闭(释放资源)。
-
AutoCloseable接口时在JDK1.7时提供的,并且该接口只提供一个方法:
- 关闭方法:public void close() throws Exception
-
范例(发送消息):
-
手工实现资源的处理:
interface IMessage { public void send(); // 发送消息 } class NetMessage implements IMessage { private String msg; public NetMessage(String msg) { this.msg = msg; } public boolean open() { System.out.println("【OPEN】打开消息发送通道。"); return true; } @Override public void send() { if (this.open()) System.out.println("【*** 发送消息 ***】"); } public void close() { System.out.println("【CLOSE】关闭消息发送通道。"); } } public class JavaDemo{ public static void main(String[] args) { NetMessage nm = new NetMessage("pikaqiu"); nm.send(); nm.close(); } }
-
实现自动关闭处理(使用AutoCloseable + 异常处理):
interface IMessage extends AutoCloseable { // 继承AutoCloseable接口 public void send(); // 发送消息 } class NetMessage implements IMessage { private String msg; public NetMessage(String msg) { this.msg = msg; } public boolean open() { System.out.println("【OPEN】打开消息发送通道。"); return true; } @Override public void send() { if (this.open()) System.out.println("【*** 发送消息 ***】"); } public void close() { System.out.println("【CLOSE】关闭消息发送通道。"); } } public class JavaDemo{ public static void main(String[] args) { // 以下的try...catch...,为JDK1.7以后提供的 // 它是带资源try语句(try-with-resource)的简化形式 // 使用:实现AutoCloseable接口的实例放在try()中,离开try{}块时将自动调用close()方法 try (IMessage nm = new NetMessage("pikaqiu")) { // 需要进行异常处理 nm.send(); // 只发送,无需手动关闭 } catch (Exception e) {} } }
-
课时29:Runtime类
-
Runtime描述的是运行时的状态,也就是说在整个JVM中,Runtime类是唯一一个与JVM运行状态有关的类,并且会默认提供有一个该类的实例化对象。
-
查看Runtime类源码可以发现,其采用的是单例设计模式,所以在每个JVM进程里面是只提供有一个Runtime类对象的。
-
由于Runtime类属于单例设计模式,所以如果要想获取实例化对象,就可以依靠类中的getRuntime()方法:
-
获取实例化对象:public static Runtime getRuntime()
-
获取本机CPU内核数:public int availableProcessors()
(内核数实际上决定了并发访问量的最佳状态)
public class JavaDemo{ public static void main(String[] args) { Runtime run = Runtime.getRuntime(); System.out.println(run.availableProcessors()); } }
-
获取最大可用内存空间:public long maxMemory()
-
获取可用内存空间:public long totalMemory()
-
获取空闲内存空间:public long freeMemory()
-
手工进行GC处理:public void gc()
-
public class JavaDemo{
public static void main(String[] args) throws Exception {
Runtime run = Runtime.getRuntime();
System.out.println("【1】MAX_MEMORY:" + run.maxMemory());
System.out.println("【1】TOTAL_MEMORY:" + run.totalMemory());
System.out.println("【1】FREE_MEMORY:" + run.freeMemory());
String str = "";
for (int i = 0; i < 10000; i++) {
str += i; // 产生大量垃圾空间
}
System.out.println("【2】MAX_MEMORY:" + run.maxMemory());
System.out.println("【2】TOTAL_MEMORY:" + run.totalMemory());
System.out.println("【2】FREE_MEMORY:" + run.freeMemory());
Thread.sleep(2000);
run.gc(); // 手工GC
System.out.println("【3】MAX_MEMORY:" + run.maxMemory());
System.out.println("【3】TOTAL_MEMORY:" + run.totalMemory());
System.out.println("【3】FREE_MEMORY:" + run.freeMemory());
}
}
- 面试题:请问什么是GC?如何处理?
- GC(Garbage Collector)垃圾收集器:可以由系统自动调用,或使用Runtime类的gc()手工调用的垃圾释放功能。
课时30:System类
-
系统输出采用的就是System类,System类的其它方法如下:
- 数组拷贝:public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
- 获取当前的日期实际数值:public static long currenTimeMillis()
- 进行垃圾回收:public static void gc()
-
范例:
-
操作耗时的统计:
public class JavaDemo{ public static void main(String[] args) { long start = System.currentTimeMillis(); Runtime run = Runtime.getRuntime(); String str = ""; for (int i = 0; i < 10000; i++) { str += i; // 产生大量垃圾空间 } long end = System.currentTimeMillis(); System.out.println("操作耗时:" + (end - start)); } }
-
-
在System类里也提供由gc()方法,但其实它就是调用了Runtime类中的gc():Runtime.getRuntime().gc();
-
在Java中,手工进行GC处理只有Runtime类的gc()。
课时31:Cleaner类
-
Cleaner类是在JDK1.9之后提供的一个对象清理操作,其主要的功能是进行finialize()方法的替代。
-
在C++语言里有两种特殊的函数:构造函数(功能跟Java一样)、析构函数(对象手工回收),而在Java里面所有的垃圾空间都是通过GC自动回收的,所以很多情况下是不需要使用析构函数的。
-
但Java本身依然提供了给用户收尾的操作,每个实例化对象在回收之前至少给它一个喘息的机会,最初实现对象收尾的处理就是Object类中所提供的finalize()方法:
@Deprecated(since="9") protected void finalize() throws Throwable
-
-
范例:
-
传统回收:
-
Member类:
class Member { public Member() { System.out.println("【构造】在一个雷电交加的日子里,林强诞生了。"); } public void finalize() throws Exception { System.out.println("【回收】最终你都要挂的。"); throw new Exception("我还要再活500年~"); } }
-
主类实现:
-
系统自动GC:
public class JavaDemo{ public static void main(String[] args) { Member mem = new Member(); // 诞生 mem = null; // 成为垃圾 // 输出: // 【构造】在一个雷电交加的日子里,林强诞生了。 } }
-
手动GC:
public class JavaDemo{ public static void main(String[] args) { Member mem = new Member(); // 诞生 mem = null; // 成为垃圾 System.gc(); // 输出: // 【构造】在一个雷电交加的日子里,林强诞生了。 // 【回收】最终你都要挂的。 } }
-
GC语句后输出:
public class JavaDemo{ public static void main(String[] args) { Member mem = new Member(); // 诞生 mem = null; // 成为垃圾 System.gc(); System.out.println("太阳照常升起,地球照样转动。"); // 输出: // 【构造】在一个雷电交加的日子里,林强诞生了。 // 太阳照常升起,地球照样转动。 // 【回收】最终你都要挂的。 } }
-
但是从JDK1.9开始,这一操作已不建议使用,而是建议使用AutoCloseable或java.lang.ref.Cleaner(Cleaner也支持有AutoCloseable)。
-
-
使用Cleaner回收:
import java.lang.ref.Cleaner; class Member implements Runnable { public Member() { System.out.println("【构造】在一个雷电交加的日子里,林强诞生了。"); } @Override public void run() { System.out.println("【回收】最终你都要挂的。"); } } class MemberCleaning implements AutoCloseable { // 实现清除的处理 private static final Cleaner cleaner = Cleaner.create(); // 创建一个清除处理 private Member member; private Cleaner.Cleanable cleanable; public MemberCleaning() { this.member = new Member(); // 创建新对象 this.cleanable = this.cleaner.register(this, this.member); // 注册使用的对象 } @Override public void close() throws Exception { this.cleanable.clean(); // 启动多线程 } } public class JavaDemo{ public static void main(String[] args) throws Exception { try (MemberCleaning mc = new MemberCleaning()) { } catch (Exception e) {} // 输出: // 【构造】在一个雷电交加的日子里,林强诞生了。 // 【回收】最终你都要挂的。 } }
-
-
在新一代的清除回收处理过程中,更多情况下考虑的是多线程的使用,即:为了防止有可能造成的延迟处理,所以许多对象回收前的处理都是单独通过一个线程来完成的。
课时32:对象克隆
-
对象克隆:就是对象的复制,而且属于全新的复制。即:使用已有对象内容创建 一个新的对象。
-
对象克隆需要使用Object类中的clone()方法:
- 对象克隆:protected Object clone() throws CloneNotSupportedExcepton
-
所有的类都会继承Object类,所以所有的类都一定会有clone()方法,但并不是所有的类都希望被克隆,所以如果想要实现对象克隆,那么对象所在的类需要实现Cloneable接口,而此接口并没有提供任何方法,因为它描述的是一种能力。(接口有三大主要作用,但如果只按基础性开发的理解,只有两个:标准、能力。)
-
范例:
class Member implements Cloneable { private String name; private int age; public Member(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "【" + super.toString() + "】name = " + this.name + ",age = " + this.age; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class JavaDemo{ public static void main(String[] args) throws Exception { Member memberA = new Member("林强", 30); Member memberB = (Member) memberA.clone(); System.out.println(memberA); System.out.println(memberB); // 输出: // 【Member@75412c2f】name = 林强,age = 30 // 【Member@3796751b】name = 林强,age = 30 } }
-
在开发中,如果不是非常特别的需求下,很少会出现有对象克隆的需求。
第8章:数字操作类
课时33:Math数学计算类
-
程序就是一个数学的处理过程,所以Java语言本身也提供有相应的数字处理的类库支持。
-
Math类:进行数学计算,提供基础的计算公式。其构造方法被私有化,并且提供的所有方法都是静态方法,即:这些方法都可以通过类名称直接调用。
public class JavaDemo{
public static void main(String[] args) {
System.out.println(Math.abs(-10.1)); // 绝对值:10.1
System.out.println(Math.max(10.2, 20.3)); // 较大值:20.3
System.out.println(Math.log(2)); // 对数:0.6931471805599453
System.out.println(Math.round(15.1)); // 四舍五入:15
System.out.println(Math.round(-15.5)); // 四舍五入:-15
System.out.println(Math.round(15.51)); // 四舍五入:16
System.out.println(Math.pow(2, 3)); // 幂次:8.0(2的3次方)
}
}
-
自定义四舍五入并设置保留指定小数位的方法(Math类中未提供,它只提供基础的数学公式):
class MathUtil { private MathUtil() {} /** * 实现数值的四舍五入,并可设置保留小数位数 * @param num 进行四舍五入的数字 * @param scale 要保留的小数位数 * @return 四舍五入后的结果 */ public static double round(double num, int scale) { return Math.round(num * Math.pow(10, scale)) / Math.pow(10, scale); } } public class JavaDemo{ public static void main(String[] args) { System.out.println(MathUtil.round(19.86273, 2)); // 19.86 } }
课时34:Random随机数生成类
-
java.util.Random类:产生随机数。
-
Random类的方法:
- 产生一个不大于边界的随机正整数:public int nextInt(int bound)
-
案例:
-
生成随机数:
import java.util.Random; public class JavaDemo{ public static void main(String[] args) { Random rand = new Random(); for (int i = 0; i < 10; i++) { System.out.print(rand.nextInt(100) + " "); } } }
-
随机生成彩票号:
import java.util.Random; public class JavaDemo{ public static void main(String[] args) { int[] data = new int[7]; Random rand = new Random(); int foot = 0; while(7 > foot) { int num = rand.nextInt(37); if (isUse(num, data)) data[foot ++] = num; } java.util.Arrays.sort(data); for (int i = 0; i < data.length; i++) { System.out.print(data[i] + " "); } } /** * 判断输入的数字是否可以使用(不为0且不在数组中) * @param num 判断的数字 * @param temp 判断的数组 * @return 可以使用就返回true,否则就返回false */ public static boolean isUse(int num, int[] temp) { if (0 == num) return false; for (int i = 0; i < temp.length; i++) { if (num == temp[i]) return false; } return true; } }
-
课时35:大数字处理类
- 大数字操作类:可以实现海量数字的计算,但能提供的也只是基础计算。
- 如数字很大,超过了double范围,那么就没有数值类型可以保存了。而大数字如果要进行加法计算,那么就要逐位拆分进行计算,并且还要进行进位处理。因此,为了解决此问题,提供了两个大数字操作类:BigInteger、BigDecimal。
-
当数字很大时,只能使用字符串来进行描述:
- BigInteger类构造方法:public BigInteger(String val)
- BigDecimal类构造方法:public BigDecimal(String val)
-
BigInteger类:
import java.math.BigInteger; public class JavaDemo{ public static void main(String[] args) { BigInteger bigA = new BigInteger("2323232325678973132133232233232232323"); BigInteger bigB = new BigInteger("2323232323232233232232323"); System.out.println("加法操作:" + bigA.add(bigB)); System.out.println("减法操作:" + bigA.subtract(bigB)); System.out.println("乘法操作:" + bigA.multiply(bigB)); System.out.println("除法操作:" + bigA.divide(bigB)); } }
- 求余:public BigInteger[] divideAndRemainder(BigInteger val)
import java.math.BigInteger; public class JavaDemo{ public static void main(String[] args) { BigInteger bigA = new BigInteger("2323232325678973132133232233232232323"); BigInteger bigB = new BigInteger("2323232323232233232232323"); BigInteger[] result = bigA.divideAndRemainder(bigB); System.out.println("商:" + result[0] + ",余:" + result[1]); } }
-
BigDecimal类:
import java.math.BigDecimal; public class JavaDemo{ public static void main(String[] args) { BigDecimal bigA = new BigDecimal("2323232325678973132133232233232232323"); BigDecimal bigB = new BigDecimal("2323232323232233232232323"); System.out.println("加法操作:" + bigA.add(bigB)); System.out.println("减法操作:" + bigA.subtract(bigB)); System.out.println("乘法操作:" + bigA.multiply(bigB)); BigDecimal[] result = bigA.divideAndRemainder(bigB); System.out.println("商:" + result[0] + ",余:" + result[1]); } }
- 除法(存在数据进位问题):public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
- 实现四舍五入处理:
import java.math.BigDecimal; import java.math.RoundingMode; class MathUtil { private MathUtil() {} public static double round(double num, int scale) { return new BigDecimal(num).divide(new BigDecimal(1.0), scale, RoundingMode.HALF_UP).doubleValue(); } } public class JavaDemo{ public static void main(String[] args) { System.out.println(MathUtil.round(19.6352, 2)); // 19.64 } }
-
如果计算的数,没有超过基本数据类型所包含的位数,强烈不建议使用大数字类,因为它们计算性能很差。
-
Math类的处理,由于使用的都是基本数据类型,所以性能一定要高于大数字类。
第9章:日期操作类
课时36:Date日期处理类
-
从整体的java来讲,一直在强调简单java类组合的主要设计来自于数据表的结构,那么在数据表的结构常用的类型有:数字、字符串、日期。
-
在Java里提供有一个java.util.Date的类,可以直接实例化这个类来获取当前的日期时间。
import java.util.Date;
public class JavaDemo{
public static void main(String[] args) {
Date date = new Date();
System.out.println(date); // 输出:Fri Jan 22 16:35:44 CST 2021
}
}
-
观察Date类的构造方法:
public Date() { this(System.currentTimeMillis()); }
public Date(long date) { fastTime = date; }
可以发现:Date类,只是对long数据的包装。
-
Date与long的转换:
- 将long转为Date:public Date(long date)
- 将Date转为long:public long getTime()
import java.util.Date; public class JavaDemo{ public static void main(String[] args) { Date date = new Date(); System.out.println(date); // 输出:Fri Jan 22 16:43:44 CST 2021 long current = date.getTime(); current += 864000 * 1000; // 10天的毫秒数 System.out.println(new Date(current)); // 输出:Mon Feb 01 16:43:44 CST 2021 } }
课时37:SimpleDateFormat日期处理类
- 虽然Date类可以获取当前的日期时间,但默认情况下Date类输出的格式并不为我们所习惯,所以为了格式化日期,java.text包中提供有SimpleDateFormat类。
- SimpleDateFormat类提供的方法:
- 格式化日期(继承DateFormat类):public final String format(Date date)
- 将字符串转为日期(继承DateFormat类):public Date parse(String source) throws ParseException
- 构造方法:public SimpleDateFormat(String pattern)
- 日期格式:年(yyyy)、月(MM)、日(dd)、时(HH)、分(mm)、秒(ss)、毫秒(SSS)
import java.text.SimpleDateFormat;
import java.util.Date;
public class JavaDemo{
public static void main(String[] args) {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String str = sdf.format(date);
System.out.println(str); // 输出:2021-01-22 16:53:36.101
}
}
- 字符串与日期之间的转换:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class JavaDemo{
public static void main(String[] args) throws ParseException {
String birthday = "1846-11-11 11:11:11.111";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Date date = sdf.parse(birthday);
System.out.println(date); // 输出:Wed Nov 11 11:11:11 CST 1846
}
}
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class JavaDemo{
public static void main(String[] args) throws ParseException {
String birthday = "1846-13-11 11:11:11.111"; // 月份为13月
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Date date = sdf.parse(birthday);
System.out.println(date); // 输出:Mon Jan 11 11:11:11 CST 1847
}
}
当字符串定义的日期时间数字超过指定范围时,会自动进行进位。
- String类字符串,可以向所有类型转换,包括基本数字类型和日期类型,所以其是一个万能的类型。
第10章:正则表达式
课时38:认识正则表达式
-
String是一个非常万能的类型,因为String不仅仅可以支持各种字符串的处理操作,也支持向各数据类型转换的功能,所以在开发中,只要是用户输入的信息,基本上都用String来表示。但向其它数据类型转换的时候,为了保证转换的正确性,往往需要进行一些复杂的验证处理。
-
范例:
-
传统数字判断:
public class JavaDemo{ public static void main(String[] args) { String str = "123"; if (isNumber(str)) { int num = Integer.parseInt(str); System.out.println(num * 2); // 246 } } public static boolean isNumber(String str) { char[] data = str.toCharArray(); for (int i = 0; i < data.length; i++) { if ('9' < data[i] || '0' > data[i]) return false; } return true; } }
-
使用正则表达式进行数字判断:
public class JavaDemo{ public static void main(String[] args) { String str = "123"; if (str.matches("\\d+")) { int num = Integer.parseInt(str); System.out.println(num * 2); // 246 } } }
-
-
正则表达式最早是从Perl语言里发展来的,但在JDK1.4之前,如果要用正则表达式则需要引入其它的jar文件;在JDK1.4之后,才被JDK所支持,提供有java.util.regex开发包,并且String类中也有方法直接支持正则处理。
课时39:常用正则标记
-
要想进行正则的处理操作,那么首先就要对常用的正则标记有所掌握。
-
从JDK1.4开始提供有java.util.regex开发包,这个包里提供有一个Pattern类,这个类里定义有所有支持的正则标记:
-
单个字符匹配:
-
任意字符:表示由任意字符组成;
public class JavaDemo{ public static void main(String[] args) { String str = "a"; // 要判断的数据 String regex = "a"; // 正则表达式 System.out.println(str.matches(regex)); // 输出:true } }
-
\\:匹配“\”
-
\n:匹配换行;
-
\t:匹配制表符;
-
-
单个字符集(可以从里面任选一个字符进行匹配):
-
[abc]:表示可以是字母a、b、c中的任意一个:
public class JavaDemo{ public static void main(String[] args) { String regex = "[abc]"; // 正则表达式 System.out.println("a".matches(regex)); // 输出:true System.out.println("q".matches(regex)); // 输出:false System.out.println("aq".matches(regex)); // 输出:false } }
-
[^abc]:表示不是字母a、b、c中的任意一个:
public class JavaDemo{ public static void main(String[] args) { String regex = "[^abc]"; // 正则表达式 System.out.println("a".matches(regex)); // 输出:false System.out.println("q".matches(regex)); // 输出:true System.out.println("aq".matches(regex)); // 输出:false } }
-
[a-zA-Z]:表示为一个任意字母,不区分大小写:
public class JavaDemo{ public static void main(String[] args) { String regex = "[a-zA-Z]"; // 正则表达式 System.out.println("a".matches(regex)); // 输出:true System.out.println("aa".matches(regex)); // 输出:false System.out.println("A".matches(regex)); // 输出:true System.out.println("AA".matches(regex)); // 输出:false System.out.println("1".matches(regex)); // 输出:false } }
-
[0-9]:表示为一位数字:
public class JavaDemo{ public static void main(String[] args) { String regex = "[0-9]"; // 正则表达式 System.out.println("a".matches(regex)); // 输出:false System.out.println("1".matches(regex)); // 输出:true System.out.println("11".matches(regex)); // 输出:false } }
-
-
单个简化字符集:
-
.:匹配一个任意字符:
public class JavaDemo{ public static void main(String[] args) { String regex = "."; // 正则表达式 System.out.println("a".matches(regex)); // 输出:true System.out.println("aa".matches(regex)); // 输出:false System.out.println("1".matches(regex)); // 输出:true System.out.println("11".matches(regex)); // 输出:false } }
-
\d:等价于“[0-9]”:
public class JavaDemo{ public static void main(String[] args) { String regex = "\\d"; // 正则表达式 System.out.println("a".matches(regex)); // 输出:false System.out.println("1".matches(regex)); // 输出:true System.out.println("11".matches(regex)); // 输出:false } }
-
\D:等价于“[^0-9]”:
public class JavaDemo{ public static void main(String[] args) { String regex = "\\D"; // 正则表达式 System.out.println("a".matches(regex)); // 输出:true System.out.println("1".matches(regex)); // 输出:false System.out.println("11".matches(regex)); // 输出:false } }
-
\s:匹配空格/换行/制表符:
public class JavaDemo{ public static void main(String[] args) { String regex = "\\s"; // 正则表达式 System.out.println("a".matches(regex)); // 输出:false System.out.println(" ".matches(regex)); // 输出:true } }
-
\w:匹配字母/数字/下划线,等价于“[a-zA-Z0-9_]”;
-
\W:匹配非字母/数字/下划线,等价于“[^\w]”,等价于“[^a-zA-Z0-9_]”;
-
-
边界匹配:
- ^:匹配边界开始;
- $:匹配边界结束。
-
数量表示(默认加上数量表达式才可以匹配多个字符):
- ?:表示可以出现0或1次;
- *:表示可以出现0或1或多次;
- +:表示可以出现1或多次;
- {n}:匹配的长度正好为n次;
- {n,}:匹配的长度为n次以上;
- {n, m}:匹配的长度为n~m次。
-
逻辑表达式:连接多个正则:
- 表达式X表达式Y:表达式X后紧跟表达式Y;
- 表达式X|表达式Y:满足表达式X或表达式Y即可。
- (表达式):为表达式设置一个整体描述,可以为整体描述设置数量单位。
-
课时40:String类对正则的支持
-
在进行正则表达式大部分处理的情况下,都会基于String类来完成。
-
String类提供的正则相关方法:
No 方法名称 类型 描述 1 public boolean matches(String regex) 普通 对字符串进行正则匹配 2 public String replaceAll(String regex, String replacement) 普通 替换全部符合正则的内容 3 public String replaceFirst(String regex, String replacement) 普通 替换首个符合正则的内容 4 public String[] split(String regex) 普通 根据正则进行拆分 5 public String[] split(String regex, int limit) 普通 根据正则进行拆分,并指定结果个数 -
范例:
public class JavaDemo{ public static void main(String[] args) { String str = "a1b22c333"; System.out.println(str.replaceAll("\\d", "0")); // a0b00c000 System.out.println(str.replaceFirst("\\d", "0")); // a0b22c333 String[] arr = str.split("\\d"); // a,b,,c, for (String temp : arr) { System.out.print(temp + ","); } System.out.println(); arr = str.split("\\d", 2); // a,b22c333, for (String temp : arr) { System.out.print(temp + ","); } } }
- 判断一个字符串是否为小数:
public class JavaDemo{ public static void main(String[] args) { String regex = "\\d+\\.\\d+"; System.out.println("100".matches(regex)); // false System.out.println("100.".matches(regex)); // false System.out.println("100.1".matches(regex)); // true } }
- 判断一个字符串是否为日期:
import java.text.SimpleDateFormat; public class JavaDemo{ public static void main(String[] args) throws Exception { // String str = "1990-01-01"; // Mon Jan 01 00:00:00 CST 1990 String str = "2345-20-50"; // Thu Sep 19 00:00:00 CST 2346 String regex = "\\d{4}-\\d{2}-\\d{2}"; if (str.matches(regex)) { System.out.println(new SimpleDateFormat("yyyy-MM-dd").parse(str)); } } }
🔺请注意!正则只能判断格式!
- 判断字符串是否为电话号码:
- 格式一:12345678,\d{7,8}
- 格式二:01012345678,(\d{3,4})?\d{7,8}
- 格式三:(010)-12345678
public class JavaDemo{ public static void main(String[] args) { String regex = "((\\d{3,4})|(\\(\\d{3,4}\\)-))?\\d{7,8}"; System.out.println("12345678".matches(regex)); // true System.out.println("01012345678".matches(regex)); // true System.out.println("(010)-12345678".matches(regex)); // true } }
- 判断字符串是否为email地址:
- email的用户名:可以由字母、数字、下划线组成;
- email的域名:可以由字母、数字、下划线、中划线组成;
- 域名的后缀必须是:.cn、.com、.com.cn、.gov
public class JavaDemo{ public static void main(String[] args) { String regex = "[a-zA-Z0-9]\\w+@\\w+\\.(cn|com|com.cn|gov)"; System.out.println("mldnjava888@mldn.cn".matches(regex)); // true System.out.println("_12345678@qq.com".matches(regex)); // false System.out.println("12345678@qq.aaa".matches(regex)); // false } }
课时41:java.util.regex包支持
-
大部分情况下可以利用String类实现正则的操作,但有些情况下会需要使用到java.uti.regex开发包中提供的正则处理,这个包里一共定义有两个类:Pattern(正则表达式编译)、Matcher(正则表达式匹配)。
-
Pattern类:
-
正则表达式的编译处理:public static Pattern compile(String regex)
-
字符串的拆分操作:public String[] split(CharSequence input)
import java.util.regex.Pattern; public class JavaDemo{ public static void main(String[] args) { String str = "a1b22c333"; String regex = "\\d"; Pattern pattern = Pattern.compile(regex); // 编译正则表达式 String[] arr = pattern.split(str); // 拆分字符串 for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + ","); } // 输出:a,b,,c, } }
-
-
Matcher类(对象实例化要靠Pattern类完成):
-
获取Matcher对象(Pattern类提供的方法):public Matecher matcher(CharSequence input)
-
正则匹配:public boolean matcher()
-
字符串替换:public String replaceAll(String replacement)
import java.util.regex.Matcher; import java.util.regex.Pattern; public class JavaDemo{ public static void main(String[] args) { String str = "a1b22c333"; String regex = "\\d"; Pattern pattern = Pattern.compile(regex); // 编译正则表达式 Matcher matcher = pattern.matcher(str); System.out.println(matcher.replaceAll("0")); // a0b00c000 } }
-
如果纯粹拆分/替换/匹配,直接使用String类操作即可,但Matcher类里提供的一种分组功能是String类所不具备的。
import java.util.regex.Matcher; import java.util.regex.Pattern; public class JavaDemo{ public static void main(String[] args) { // 要求取出“#{内容}”标记中的内容 String str = "INSERT INTO dept(dno,dname,loc) VALUES(#{dno},#{dname},#{loc})"; String regex = "#\\{\\w+\\}"; Pattern pattern = Pattern.compile(regex); // 编译正则表达式 Matcher matcher = pattern.matcher(str); while(matcher.find()) { String temp = matcher.group(); System.out.print(temp + " "); System.out.println(temp.replaceAll("#|\\{|\\}", "")); } // 输出: // #{dno} dno // #{dname} dname // #{loc} loc } }
-
第11章:国际化程序实现
课时42:国际化程序实现原理
- 国际化程序:同一个程序代码可以根据不同的国家实现不同的语言描述,但核心业务是相同的。
通过分析可以发现,要想实现国际化的程序开发,需要解决以下问题:
1. 如何定义保存文字的文件信息;
2. 如何根据不同的区域语言的编码读取指定的资源信息。
课时43:Locale类
-
在java.util包里,提供有一个专门描述区域和语言编码的类:Locale类。
-
Locale类的方法:
-
构造方法:
- public Locale(String language)
- public Locale(String language, String country)
国家和语言的代码:
- 中文:zh_CN
- 美国英语:en_US
import java.util.Locale; public class JavaDemo{ public static void main(String[] args) { Locale loc = new Locale("zh", "CN"); // 中文环境 System.out.println(loc); // 输出:zh_CN } }
-
获取本地默认环境:public static Locale getDefault()
import java.util.Locale; public class JavaDemo{ public static void main(String[] args) { Locale loc = Locale.getDefault(); // 获取默认环境 System.out.println(loc); // 输出:zh_CN } }
-
-
Locale类中将一些常用的国家和编码设置为了常量:
import java.util.Locale; public class JavaDemo{ public static void main(String[] args) { Locale loc = Locale.CHINA; // 获取中文环境 System.out.println(loc); // 输出:zh_CN } }
课时44:ResourceBundle读取资源文件
-
现在已经准备好了资源文件,所以接着就是读取资源文件的操作了。读取资源文件依靠的是java.util.ResourceBundle类。
-
ResourceBundle类定义:
public abstract class ResourceBundle extends Object
ResourceBundle类是一个抽象类,所以要想实例化该类对象,可以直接利用其提供的一个static方法:
- 获取ResourceBundle类对象:public static final ResourceBundle getBundle(String baseName)
- baseName:资源文件的名称,不包括后缀
- 读取资源内容:public final String getString(String key)
- 获取ResourceBundle类对象:public static final ResourceBundle getBundle(String baseName)
// Welcome.properties文件,与JavaDemo同包
info=欢迎皮卡丘同学光临!
import java.util.ResourceBundle;
public class JavaDemo{
public static void main(String[] args) {
ResourceBundle rb = ResourceBundle.getBundle("Welcome");
String val = rb.getString("info");
System.out.println(val); // 欢迎皮卡丘同学光临!
// 如输出的中文为乱码,将读取的文件用记事本保存为UTF8编码即可
}
}
如资源文件和程序文件不在同一个包里,则资源文件的名称要加上包名。
如资源文件中的key不存在,则会抛出如下异常:
Exception in thread "main" java.util.MissingResourceException: Can't find resource for bundle java.util.PropertyResourceBundle, key infos
课时45:实现国际化程序开发
-
现在国际化程序实现的前期准备已经全部完成了,也就是说依靠资源文件、Locale类、ResourceBundle类就可以实现国际化处理操作,那么下面就来进行国际化程序的实现(核心关键:读取资源信息)。
-
实现:
-
在src下建立资源文件:
-
Welcome.properties(默认资源):
info=欢迎访问!
-
Welcome_zh_CN.properties(中文资源):
info=欢迎您的访问!
-
Welcome_en_US.properties(英文资源):
info=Welcome!
-
-
通过程序进行指定区域资源文件的加载(加载默认环境的资源文件):
import java.util.ResourceBundle; public class JavaDemo{ public static void main(String[] args) { ResourceBundle rb = ResourceBundle.getBundle("Welcome"); String val = rb.getString("info"); System.out.println(val); // 输出:欢迎您的访问! } }
-
修改当前的Locale环境:
import java.util.Locale; import java.util.ResourceBundle; public class JavaDemo{ public static void main(String[] args) { Locale loc = new Locale("en", "US"); ResourceBundle rb = ResourceBundle.getBundle("Welcome", loc); String val = rb.getString("info"); System.out.println(val); // 输出:Welcome! } }
-
如果不存在对应环境的资源文件,则读取默认环境的资源文件:
import java.util.Locale; import java.util.ResourceBundle; public class JavaDemo{ public static void main(String[] args) { Locale loc = Locale.CANADA; ResourceBundle rb = ResourceBundle.getBundle("Welcome", loc); String val = rb.getString("info"); System.out.println(val); // 输出:欢迎您的访问! } }
-
-
资源文件读取顺序:指定环境资源文件 > 默认环境资源文件 > 默认资源文件。
课时46:格式化文本显示
-
在资源文件中定义变量:
-
修改资源文件:
-
Welcome.properties(默认资源):
info=欢迎{0}访问{1}!
-
Welcome_zh_CN.properties(中文资源):
info=欢迎{0}的访问!这里是{1}
-
Welcome_en_US.properties(英文资源):
info=Welcome {0} to {1}!
-
-
主类实现:
import java.text.MessageFormat; import java.util.Locale; import java.util.ResourceBundle; public class JavaDemo{ public static void main(String[] args) { Locale loc = Locale.CANADA; ResourceBundle rb = ResourceBundle.getBundle("Welcome", loc); String val = rb.getString("info"); System.out.println(MessageFormat.format(val, "pikaqiu", "java板块")); // 输出:欢迎pikaqiu的访问!这里是java板块 } }
-
第12章:开发支持类库
课时47:UUID类
import java.util.UUID;
public class JavaDemo{
public static void main(String[] args) {
System.out.println(UUID.randomUUID());
}
}
-
UUID:一种生成无重复字符串的程序类,是根据时间戳实现的一个自动无重复(出现概率很低而已,可能只有千万亿分之一)的字符串。
-
UUID相关的方法:
- 获取UUID对象:public static UUID randomUUID()
- 根据字符串获取UUID内容:public static UUID fromString(String name)
-
在对一些文件进行自动命名处理的情况下,UUID类型非常好用。
课时48:Optional类
-
Optional类是在JDK1.8之后提供的,它的主要功能:进行null的相关处理(如空指向异常)。
-
Optional类提供的操作方法:
- 返回空数据:public static Optional empty()
- 获取数据:public T get()
- 保存数据
- 不允许出现null:public static Optional of(T value)
- 允许出现null:public static Optional ofNullable(T value)
- null的时候返回其它数据:public T orElse(T other)
-
范例:
-
传统的引用传递问题:
interface IMessage { String getContent(); } class MessageImpl implements IMessage { @Override public String getContent() { return "pikaqiu"; } } class MessageUtil { private MessageUtil() {} public static IMessage getMessage() { return new MessageImpl(); } public static void useMessage(IMessage msg) { // 有可能因为出现null,而抛出空指针异常 if (null != msg) System.out.println(msg.getContent()); } } public class JavaDemo{ public static void main(String[] args) { MessageUtil.useMessage(null); } }
-
使用Optional类:
-
保存时为null:
public class JavaDemo{ public static void main(String[] args) { // Optional.ofNullable(null); // 无异常 Optional.of(null); // 抛出空指针异常 } }
-
获取时的null:
import java.util.Optional; interface IMessage { String getContent(); } class MessageImpl implements IMessage { @Override public String getContent() { return "pikaqiu"; } } class MessageUtil { private MessageUtil() {} public static Optional<IMessage> getMessage() { return Optional.ofNullable(null); // 保存的数据为null } public static void useMessage(IMessage msg) { System.out.println(msg.getContent()); } }
public class JavaDemo{ public static void main(String[] args) { // get的数据为null,抛出java.util.NoSuchElementException异常 IMessage temp = MessageUtil.getMessage().get(); MessageUtil.useMessage(temp); } }
public class JavaDemo{ public static void main(String[] args) { // 使用orElse来获取,给默认对象 IMessage temp = MessageUtil.getMessage().orElse(new MessageImpl()); MessageUtil.useMessage(temp); } }
-
-
课时49:ThreadLocal类
-
多线程的影响:
class Message { private String info; public void setInfo(String info) { this.info = info; } public String getInfo() { return this.info; } } class Channel { private static Message message; private Channel() {} public static void setMessage(Message msg) { message = msg; } public static void send() { System.out.println("【" + Thread.currentThread().getName() + " 发送消息】" + message.getInfo()); } } public class JavaDemo{ public static void main(String[] args) { new Thread(()->{ Message msg = new Message(); msg.setInfo("第一个线程的消息"); Channel.setMessage(msg); Channel.send(); }, "消息发送者A").start(); new Thread(()->{ Message msg = new Message(); msg.setInfo("第二个线程的消息"); Channel.setMessage(msg); Channel.send(); }, "消息发送者B").start(); new Thread(()->{ Message msg = new Message(); msg.setInfo("第三个线程的消息"); Channel.setMessage(msg); Channel.send(); }, "消息发送者C").start(); // 输出如: // 【消息发送者C 发送消息】第三个线程的消息 // 【消息发送者B 发送消息】第一个线程的消息 // 【消息发送者A 发送消息】第一个线程的消息 } }
在保持Channel(所有发送通道)核心结构不改变的情况下,需要考虑到每个线程的独立操作问题,然后可以发现,对于Channel类而言,除了要保留有发送的消息之外,还应该多存放有一个每个线程的标记(当前线程,类似于储物柜,独立存储)。
-
ThreadLocal类提供的操作方法:
- 构造方法:public ThreadLocal()
- 设置数据:public void set(T value)
- 取出数据:public T get()
- 删除数据:public void remove()
- 使用ThreadLocal类后的Channel类:
class Channel {
private static final ThreadLocal<Message> THREAD_LOCAL = new ThreadLocal<>();
private Channel() {}
public static void setMessage(Message msg) {
THREAD_LOCAL.set(msg);
}
public static void send() {
System.out.println("【" + Thread.currentThread().getName() + " 发送消息】" + THREAD_LOCAL.get().getInfo());
}
}
每个线程通过ThreadLocal只允许保存一个数据。
课时50:定时调度
-
在Java中提供有定时任务的支持,但这种任务的处理只是实现了一种间隔触发的操作。
-
要想实现定义的处理操作,需要一个定时操作的主体类和一个定时任务的控制类:
-
java.util.TimerTask类:实现定时任务的处理
-
java.util.Timer类:进行定时任务的启动:
-
只触发一次:public void schedule(TimerTask task, long delay)
import java.util.Timer; import java.util.TimerTask; class MyTask extends TimerTask { @Override public void run() { System.out.println("定时任务:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis()); } } public class JavaDemo{ public static void main(String[] args) { Timer timer = new Timer(); // 延迟时间为0毫秒,表示立即启动 timer.schedule(new MyTask(), 0); } }
-
间隔触发:public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
public class JavaDemo{ public static void main(String[] args) { Timer timer = new Timer(); // 延迟时间为0毫秒,表示立即启动,每秒执行一次 timer.scheduleAtFixedRate(new MyTask(), 0, 1000); } }
-
这种定时是由JDK最原始的方式提供的支持,但实际开发中利用此方式进行的定时处理,实现的代码会非常地复杂。
-
课时51:Base64加密与解密
- JDK1.8开始,提供有新的加密处理操作类:Base64处理类,在这个类里有两个内部类:
- Base64.Encoder内部类:进行加密处理。
- 加密处理:public byte[] encode(byte[] src)
- Base64.Decoder内部类:进行解密处理。
- 解密处理:public byte[] decode(String src)
- Base64.Encoder内部类:进行加密处理。
import java.util.Base64;
public class JavaDemo{
public static void main(String[] args) {
String msg = "pikaqiu";
String encMsg = new String(Base64.getEncoder().encode(msg.getBytes())); // 加密
System.out.println(encMsg);
String decMsg = new String(Base64.getDecoder().decode(encMsg)); // 解密
System.out.println(decMsg);
}
}
- 虽然Base64可以实现加密与解密的处理,但由于其是一个公版的算法,直接使用并不安全,所以最好的做法应该是使用盐值操作:
import java.util.Base64;
public class JavaDemo{
public static void main(String[] args) {
String salt = "pikapika"; // 盐值
String msg = "pikaqiu" + "{" + salt + "}"; // 要发送的数据加盐
String encMsg = new String(Base64.getEncoder().encode(msg.getBytes())); // 加密
System.out.println(encMsg);
String decMsg = new String(Base64.getDecoder().decode(encMsg)); // 解密
System.out.println(decMsg);
}
}
- 想要更安全,可以使用多次加密:
import java.util.Base64;
class StringUtil {
private static final String SALT = "pikapika"; // 公共的盐值
private static final int REPEAT = 5; // 加密次数
/**
* 进行加密处理
* @param str 要加密的字符串
* @return 加密后的字符串
*/
public static String encode(String str) { // 加密处理
String temp = str + "{" + SALT + "}"; // 盐值对外不公布
byte[] data = temp.getBytes(); // 将字符串变为字节数组
for (int i = 0; i < REPEAT; i++) {
data = Base64.getEncoder().encode(data); // 重复加密
}
return new String(data);
}
/**
* 进行解密处理
* @param str 要解密的字符串
* @return 解密后的字符串
*/
public static String decode(String str) {
byte[] data = str.getBytes(); // 将字符串变为字节数组
for (int i = 0; i < REPEAT; i++) {
data = Base64.getDecoder().decode(data); // 重复解密
}
int length = data.length - (SALT.length() + 2);
str = new String(data).substring(0, length);
return str;
}
}
public class JavaDemo{
public static void main(String[] args) {
String str = StringUtil.encode("pikaqiu");
System.out.println(str);
str = StringUtil.decode(str);
System.out.println(str);
}
}
- 最好的做法是使用2~3种加密程序,同时再找到一些完全不可解密的加密算法。
第13章:比较器
课时52:比较器问题引出
-
比较器:进行大小关系确定的判断。
-
自定义类型的比较:
import java.util.Arrays; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "【Person】姓名:" + this.name + ",年龄:" + this.age; } } public class JavaDemo{ public static void main(String[] args) { Person[] arr = new Person[] { new Person("小强", 80), new Person("小明", 50), new Person("小六", 100) }; Arrays.sort(arr); // 进行对象数组的排序,会抛出java.lang.ClassCastException的异常 System.out.println(Arrays.toString(arr)); } }
课时53:Comparable比较器
- 对象的比较需要有比较器来指定比较规则,而比较规则就通过Comparable来实现。
public interface Comparable<T> {
public int compareTo(T o);
}
- 使用Comparable实现自定义类型的比较:
import java.util.Arrays;
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "【Person】姓名:" + this.name + ",年龄:" + this.age + "\n";
}
@Override
public int compareTo(Person person) {
return this.age - person.age; // 根据年龄进行比较
}
}
public class JavaDemo{
public static void main(String[] args) {
Person[] arr = new Person[] {
new Person("小强", 80),
new Person("小明", 50),
new Person("小六", 100)
};
Arrays.sort(arr); // 进行Person类对象数组的排序
System.out.println(Arrays.toString(arr));
// 输出:
// [【Person】姓名:小明,年龄:50
// , 【Person】姓名:小强,年龄:80
// , 【Person】姓名:小六,年龄:100
// ]
}
}
课时54:Comparator比较器
- Comparator属于一种用来挽救的比较器,其主要目的是解决一些没有使用Comparable排序的类的对象数组排序。
- 在Arrays类里,排序又另一种实现方式:
- 基于Comparator的排序处理:public static void sort(T[] a, Comparator<? super T> c)
- 在java.util.Comparator里,最初只定义又一个排序的compare()方法,但是后来又出现了许多static方法。
import java.util.Arrays;
import java.util.Comparator;
class Person { // 未设计比较功能的类
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "【Person】姓名:" + this.name + ",年龄:" + this.age + "\n";
}
}
class PersonComparator implements Comparator<Person> { // 实现Comparator的比较类
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
public class JavaDemo{
public static void main(String[] args) {
Person[] arr = new Person[] {
new Person("小强", 80),
new Person("小明", 50),
new Person("小六", 100)
};
Arrays.sort(arr, new PersonComparator()); // 进行Person类对象数组的排序
System.out.println(Arrays.toString(arr));
// 输出:
// [【Person】姓名:小明,年龄:50
// , 【Person】姓名:小强,年龄:80
// , 【Person】姓名:小六,年龄:100
// ]
}
}
- 如果非必须的情况下,强烈不建议使用Comparator,最好以Comparable为主。
- 面试题:请解释Comparable与Comparator的区别?
- java.lang.Comparable:是需要在类定义时实现的接口,主要用于定义排序规则,里面只有一个compareTo()方法。
- java.util.Comparator:是挽救的比较器操作,需要设置单独的比较器规则类来进行实现,里面有compare()方法。
课时55:二叉树结构简介
- 在进行链表结构的开发中可以发现,如所有的数据按照首尾相连的状态进行保存,那么查询数据时的时间复杂度是”O(n)“,而当数据量小(不超过30个)时,性能是不会有太大差别的,否则则可能会严重损耗程序的运行性能。那么此时的数据结构就必须发生改变,应该以尽可能减少检索次数为出发点来进行设计。对于现在的数据结构而言,最好的性能就是”O(log n)“,所以可以利用二叉树的结构来实现。
- 二叉树基本实现原理:
- 第一个数据作为根节点进行保存
- 第二个之后的数据
- 如小于第一个数据,放在根节点的左子树;
- 如大于第一个数据,放在根节点的右子树。
-
二叉树的时间复杂度就是O(log n)
-
获取二叉树数据的形式:
- 前序遍历:根 → 左 → 右
- 中序遍历:左 → 根 → 右
- 后序遍历:左 → 右 → 根
课时56:二叉树基础实现
-
在实现二叉树的处理中,最为关键性的问题在于,数据的保存,而且由于牵扯到对象比较的问题,那么一定要有比较器的支持,这个比较器的首选一定时Comparable。
-
实现:
-
先编写一个Person类:
class Person implements Comparable<Person> { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "【Person】姓名:" + this.name + ",年龄:" + this.age + "\n"; } @Override public int compareTo(Person person) { return this.age - person.age; } }
-
需要一个节点类,使用Comparable(可以区分大小):
/** * 实现二叉树操作 * @param <T> 要进行二叉树实现的参数 */ class BinaryTree<T extends Comparable<T>> { private class Node { private Comparable<T> data; // 存放Comparable,可以比较大小 private Node parent; // 父节点 private Node left; // 左节点 private Node right; // 右节点 public Node(Comparable<T> data) { // 构造方法直接负责数据的存储 this.data = data; } /** * 实现非根节点数据的存储 * @param newNode 要存储的新节点 */ public void addNode(Node newNode) { // 新节点比当前节点小或相等,放在左边 if (newNode.data.compareTo((T)this.data) <= 0) { // 当前节点没有左节点,则将新节点保存为当前节点的左节点 if (null == this.left) { this.left = newNode; newNode.parent = this; } // 当前节点有左节点,则继续递归 else this.left.addNode(newNode); } // 新节点比当前节点大,放在右边 else { // 当前节点没有右节点,则将新节点保存为当前节点的右节点 if (null == this.right) { this.right = newNode; newNode.parent = this; } // 当前节点有右节点,则继续递归 else this.right.addNode(newNode); } } /** * 进行数据的检索处理 * @param data 要检索的数据 * @return 找到返回true,没有找到返回false */ public boolean containsNode(Comparable<T> data) { if (0 == data.compareTo((T) this.data)) return true; else if (0 > data.compareTo((T) this.data)) { if (null != this.left) return this.left.containsNode(data); else return false; } else { if (null != this.right) return this.right.containsNode(data); else return false; } } /** * 实现所有数据的获取处理,按照中序遍历的形式来完成 */ public void toArrayNode() { if (null != left) this.left.toArrayNode(); BinaryTree.this.returnData[BinaryTree.this.foot ++] = this.data; if (null != right) this.right.toArrayNode(); } } // —————— 以下为二叉树的功能实现 —————— private Node root; // 根节点 private int count; // 保存节点个数 private int foot; // 脚标 private Object[] returnData; // 返回的数据 /** * 进行数据的保存 * @param data 要保存的数据内容 * @exception NullPointerException 保存数据为空时抛出的异常 */ public void add(Comparable<T> data) { if (null == data) throw new NullPointerException("保存的数据不允许为空!"); // 所有的数据本身不具备有节点关系的匹配,所以一定要将其包装在Node类中 Node newNode = new Node(data); // 如没有根节点,则此节点作为根节点 if (null == this.root) this.root = newNode; // 如已有根节点,则交由Node类来处理 else this.root.addNode(newNode); this.count ++; } /** * 依靠Comparable实现进行数据比较的检索 * @param data 要检索的数据 * @return 找到返回true,没有找到返回false */ public boolean contains(Comparable<T> data) { if (0 == this.count) return false; return this.root.containsNode(data); } /** * 以对象数组的形式返回全部数据,如果没有数据则返回null * @return 全部数据 */ public Object[] toArray() { if (0 == this.count) return null; this.returnData = new Object[this.count]; this.foot = 0; // 脚标清零 this.root.toArrayNode(); // 交由Node类来处理 return this.returnData; // 返回数据 } }
-
主类:
import java.util.Arrays; public class JavaDemo{ public static void main(String[] args) { BinaryTree<Person> bTree = new BinaryTree<>(); bTree.add(new Person("小强", 80)); bTree.add(new Person("小明", 12)); bTree.add(new Person("小李", 53)); bTree.add(new Person("小四", 64)); bTree.add(new Person("小五", 58)); bTree.add(new Person("小六", 28)); bTree.add(new Person("小狗", 37)); System.out.println(Arrays.toString(bTree.toArray())); // 输出: // [【Person】姓名:小明,年龄:12 // , 【Person】姓名:小六,年龄:28 // , 【Person】姓名:小狗,年龄:37 // , 【Person】姓名:小李,年龄:53 // , 【Person】姓名:小五,年龄:58 // , 【Person】姓名:小四,年龄:64 // , 【Person】姓名:小强,年龄:80 // ] } }
-
课时57:二叉树数据删除
(情况三补充说明:用删除节点的右子节点的最左节点,来替换删除节点,具体参考说明及原版代码链接:https://developer.aliyun.com/article/745110)
-
个人实现(原版源码请查看上面的链接):
-
在Node内部类里增加getNode()方法:
/** * 根据数据进行节点的获取 * @param data 要获取节点的数据 * @return 根据数据获取到的节点 */ public Node getNode(Comparable<T> data) { if (0 == data.compareTo((T) this.data)) return this; else if (0 > data.compareTo((T) this.data)) { if (null != this.left) return this.left.getNode(data); else return null; } else { if (null != this.right) return this.right.getNode(data); else return null; } }
-
在BinaryTree类中增加remove()方法:
/** * 进行数据的删除处理 * @param data 要删除的数据 */ public void remove(Comparable<T> data) { Node removeNode = this.root.getNode(data); // 找到要删除的节点 if (null != removeNode) { // 删除的是根节点 if (this.root == removeNode) { // 情况一:根节点没有子节点 直接将根节点赋null // 情况二:根节点只有左/右子节点 直接将该子节点变为根节点 // 情况三:根节点有两个子节点 将右子节点/右子节点的最左子节点变为根节点 if (null == this.root.left && null == this.root.right) this.root = null; else if (null == this.root.left && null != this.root.right) { this.root.right.parent = null; this.root = this.root.right; } else if (null != this.root.left && null == this.root.right) { this.root.left.parent = null; this.root = this.root.left; } else { // 情况一:右子节点没有左子节点 右子节点直接变为根节点 // 情况二:右子节点有左子节点 右子节点的最左子节点变为根节点 Node moveNode = this.root.right; if (null != moveNode.left) { while (null != moveNode.left) { moveNode = moveNode.left; } if (null != moveNode.right) { // 最左子节点有右子节点,右子节点替换最左子节点 moveNode.right.parent = moveNode.parent; moveNode.parent.left = moveNode.right; } } moveNode.parent = null; moveNode.left = this.root.left; this.root.left.parent = moveNode; this.root = moveNode; } } // 删除的不是根节点 else { Node parentNode = removeNode.parent; // 情况一:没有子节点 父节点删除连接 // 情况二:只有一个子节点(左/右子节点) 父节点连接其子节点 // 情况三:有两个子节点 父节点连接其右子节点的最左子节点 if (null == removeNode.left && null == removeNode.right) { // 删除节点是其父节点的左子节点 if (removeNode == parentNode.left) parentNode.left = null; // 删除节点是其父节点的右子节点 else parentNode.right = null; } else if (null != removeNode.left && null == removeNode.right) { if (removeNode == parentNode.left) parentNode.left = removeNode.left; else parentNode.right = removeNode.left; } else if (null == removeNode.left && null != removeNode.right) { if (removeNode == parentNode.left) parentNode.left = removeNode.right; else parentNode.right = removeNode.right; } else { // 情况一:右子节点没有左子节点 右子节点直接替换删除节点 // 情况二:右子节点有左子节点 右子节点的最左子节点替换删除节点 Node moveNode = removeNode.right; if (null != moveNode.left) { while (null != moveNode.left) { moveNode = moveNode.left; } if (null != moveNode.right) { moveNode.right.parent = moveNode.parent; moveNode.parent.left = moveNode; } moveNode.right = removeNode.right; } moveNode.left = removeNode.left; moveNode.parent = removeNode.parent; if (removeNode == removeNode.parent.left) removeNode.parent.left = moveNode; else removeNode.parent.right = moveNode; } } } this.count --; }
-
-
这种数据结构的删除操作是非常返回的,所以如果不是必须的情况下不建议使用删除。但是增加、删除、查询,是数据结构里必须会的三种形式。
课时58:红黑树原理简介
- 二叉树的主要特点:数据查询时可以提供更好的查询性能。但二叉树的结构时有明显缺陷的,它可能存在不平衡的状态。
-
红黑树的本质:就是在节点上追加了一个表示颜色的操作信息。
enum Color { RED,BLACK; } class BinaryTree<T> { private class Node { private T data; private Node parent; private Node left; private Node right; private Color color; // 增加颜色属性 } }
🔺 红色节点之后绝对不可能是红色节点,但是黑色节点之后可以是黑色节点(不可以红-红,可以黑-黑)。
-
红黑树的数据插入平衡修复:
在进行红黑树处理时,为了方便操作,会使用红色来描述新节点,所以在设置根节点的时候,需要将节点涂黑。
(以下数据插入平衡处理规则中,示例图只是一个红黑树里的局部部分。)
在红黑树进行修复处理中,它需要根据当前节点以及当前节点的父节点和叔叔节点之间的颜色来推断树是否需要进行修复处理。
-
红黑树的数据删除平衡修复:
-
在红黑树中,修复的目的是,为了保证树结构中,黑色节点的数量平衡,这样才可能得到”O(log n)“的查询性能。修复过程,一方面是红黑色处理,另一方面是黑色子节点的保存层次。
第14章:类库使用案例分析
课时59:StringBuffer使用
- 定义一个StringBuffer类对象,然后通过append()方法向对象添加26个小写字母,要求每次只添加一个,共添加26次,然后按照逆序的方式输出,然后删除前5个字符。
public class JavaDemo{
public static void main(String[] args) {
StringBuffer buf = new StringBuffer();
for (int i = 'a'; i <= 'z'; i++) {
buf.append((char) i);
}
buf.reverse().delete(0, 5);
System.out.println(buf);
}
}
课时60:随机数组
- 利用Random类产生5个1~30之间(包括1和30)的随机整数。
import java.util.Arrays;
import java.util.Random;
class NumberFactory {
private static Random random = new Random();
/**
* 通过随机数来生成一个数组的内容,并且该内容不包括有0
* @param len 数组长度
* @return 随机数数组
*/
public static int[] create(int len) {
int[] data = new int[len];
int foot = 0;
while (foot < data.length) {
int num = random.nextInt(30);
if (0 != num) data[foot ++] = num;
}
return data;
}
}
public class JavaDemo{
public static void main(String[] args) {
int[] arr = NumberFactory.create(5);
System.out.println(Arrays.toString(arr));
}
}
课时61:Email验证
- 输入一个Email地址,然后使用正则表达式验证Email地址格式是否正确。
class Validator { // 验证类
private Validator() {}
public static boolean isEmail(String email) {
if (null == email || "".equals(email.trim())) return false;
String regex = "\\w+@\\w+\\.\\w+";
return email.matches(regex);
}
}
public class JavaDemo{
public static void main(String[] args) {
if (0 == args.length) {
System.out.println("请先输入email地址");
System.exit(1);
}
String email = args[0];
if (Validator.isEmail(email)) System.out.println("email地址验证通过~");
else System.out.println("email地址验证不通过!");
}
}
课时62:扔硬币
- 编写程序,用0~1之间的随机数来模拟扔硬币实验,统计扔1000次后出现正、反面的次数并输出。
import java.util.Random;
class Coin { // 硬币类
private int head; // 正面
private int tail; // 背面
private Random random = new Random();
public void flipCoin(int count) {
for (int i = 0; i < count; i++) {
int result = random.nextInt(2);
if (0 == result) this.head ++;
else this.tail ++;
}
}
public int getHead() {
return this.head;
}
public int getTail() {
return this.tail;
}
}
public class JavaDemo{
public static void main(String[] args) {
Coin coin = new Coin();
coin.flipCoin(1000);
System.out.println("正面:" + coin.getHead() + ",反面:" + coin.getTail());
}
}
课时63:IP验证
-
编写正则表达式,判断给定的是否是一个合法的IP地址。
IP地址的组成就是数字,对于数字的组成有一个基础的要求,第一位的内容只能是无、1、2,后面的内容可以0-9、第三位的内容是0-9。(WT???不能理解视频里这个IP地址的规则,所以实现代码也并非此规则,视频代码请参考:https://developer.aliyun.com/article/745223?spm=a2c6h.12873639.0.0.6461dcc6pJbXsH)
-
个人实现(ipv4地址格式:(1255).(0255).(0255).(0255)):
class Validator {
public static boolean isIp(String ip) {
if (null == ip || "".equals(ip.trim())) return false;
// 第一位:1~255:([1-9] | [1-9]\\d | 1\\d{2} | 2[0-5]{2})
// 第二~四位:0~255:(\\d | [1-9]\\d | 1\\d{2} | 2[0-5]{2})
String regex = "([1-9]|[1-9]\\d|1\\d{2}|2[0-5]{2})((\\.(\\d|[1-9]\\d|1\\d{2}|2[0-5]{2})){3})";
return ip.matches(regex);
}
}
课时64:HTML拆分
-
给定下面的Html代码:
<font face="Arial,Serif" size="+2" color="red">
要对内容进行拆分,拆分之后的结果是:
face Arial,Serif size +2 color red
-
实现:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JavaDemo{
public static void main(String[] args) {
String html = "<font face=\"Arial,Serif\" size=\"+2\" color=\"red\">";
String regex = "\\w+=\"(\\w|,|\\+)+\"";
Matcher matcher = Pattern.compile(regex).matcher(html);
while (matcher.find()) {
String temp = matcher.group();
String[] arr = temp.split("=");
System.out.println(arr[0] + " " + arr[1].substring(1, arr[1].length()-1));
}
}
}
课时65:国家代码
-
编写程序,实现国际化应用,从命令行输入国家的代号,例如1表示中国,2表示美国,然后根据输入代号的不同调用不同的资源文件显示信息。
-
实现:
-
定义资源文件:
-
中文资源文件 Welcome_zh_CN.properties:
info=欢迎!
-
英文资源文件 Welcome_en_US.properties:
info=Welcome!
-
-
读取资源文件:
import java.util.Locale; import java.util.ResourceBundle; class MessageUtil { public static final int CHINA = 1; public static final int USA = 2; private static final String KEY = "info"; private static final String BASENAME = "Welcome"; public String getMessage(int num) { Locale loc = this.getLocale(num); if (null == loc) return ""; else return ResourceBundle.getBundle(BASENAME, loc).getString(KEY); } private Locale getLocale(int num) { switch (num) { case CHINA: return new Locale("zh", "CN"); case USA: return new Locale("en", "US"); default: return null; } } } public class JavaDemo{ public static void main(String[] args) { System.out.println(new MessageUtil().getMessage(1)); System.out.println(new MessageUtil().getMessage(2)); } }
-
课时66:学生信息比较
- 按照“姓名:年龄:成绩|姓名:年龄:成绩”的格式定义字符串 “张三:21:98|李四:22:89|王五:20:70”, 要求将每组值分别保存在Student对象之中,并对这些对象进行排序,排序的原则为:按照成绩由高到低排序,如果成绩相等,则按照年龄由低到高排序。
import java.util.Arrays;
class Student implements Comparable<Student> {
private String name;
private int age;
private double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
public String toString() {
return "【学生】姓名:" + this.name + ",年龄:" + this.age + ",成绩:" + this.score + "\n";
}
@Override
public int compareTo(Student student) {
if (this.score > student.score) return 1;
else if (this.score < student.score) return -1;
else if (this.age > student.age) return 1;
else if (this.age < student.age) return -1;
else return 0;
}
}
public class JavaDemo{
public static void main(String[] args) {
String input = "张三:21:98|李四:22:89|王五:20:70|吴六:18:70";
String[] arr = input.split("\\|");
Student[] stuArr = new Student[arr.length];
for (int i = 0; i < arr.length; i++) {
String[] tempArr = arr[i].split(":");
stuArr[i] = new Student(tempArr[0], Integer.parseInt(tempArr[1]), Double.parseDouble(tempArr[2]));
}
Arrays.sort(stuArr);
System.out.println(Arrays.toString(stuArr));
}
}
————————————————————————————————————