第15章 常用类库

通过本章掌握StringBuffer类、StringBuilder类的特点以及两者的区别和常用处理方法。掌握CharSequence接口的作用预计String、StringBuffer、StringBuilder之间的联系。掌握AutoCloseable接口的作用以及关闭操作的实现模型,掌握日期操作类型以及格式化操作类的使用,掌握两种比较器的作用以及格式化操作类的使用。掌握两种比较器的所用以及二叉树的实现原理。掌握正则表达式的定义和适用,掌握Optional空处理的意义以及常用方法的使用。掌握ThreadLocal与引用传递之间的联系以及实现机制,掌握国际化程序的主要作用以及Local、ResourceBunble等工具类的使用,理解Runtime类、System类、Math类、Random类、Cleaner类、Base64类、定时调度的使用。
        现代的程序开发现需要依附于其所在的平台的支持,平台支持的功能越完善,开发也就越简单。Java拥有者世界上最庞大的开发支持,除了丰富的第三方开发仓库之外,JDK自身也提供有丰富的类库工开发者使用。本章将讲解一些常用的支持类库以及使用说明。

15.1 StringBuffer类

在项目开发过程中String是一个必不可少的工具类,但是String类自身有一个最大的缺陷:内容一旦声明则不可以改变。所以在JDK中为了方便用户金额已修改字符串的内容提供有StringBuffer类。
        StringBuffer()类并不想String类那样可以直接通过声明字符串常亮的方式进行实例化,而是必须向普通类对象使用一样,首先通过构造方法进行对象实例化,而后才可以调用方法执行处理。StringBuffer类常用的方法如下:
 

No方法类型描述
1public StringBuffer()构造创建一个空的StringBuffer对象
2public StringBuffer(String str)构造将接收到的String内容变为StringBuffer内容
3public StringBuffer append(数据类型 变量)普通内容链接,等价于String类中的+操作
4public StringBuffer insert(int offset,数据类型 变量)普通在指定索引位置处插入数据
5public StringBuffer delete(int start,int end)普通删除制定索引范围内之内的数据
6public StringBuffer reverse()普通内容翻转

范例:修改StringBuffer内容

package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String []args)
{
StringBuffer buf=new StringBuffer("www.");
change(buf);
String data=buf.toString();

}
public static void change(StringBuffer temp)
{
temp.append("mldn").append(".cn");
}
}

本程序将实例化好的StringBuffer类对象传递到了change()方法中,而通过最终的执行结果可以发现,change()方法对StringBuffer类对象所作的修改得到了保存,所以可以得出结论:StringBuffer的内容可以被修改。

提示:关于字符串常量池的问题
在第7章中奖结果字符串常量池的概念,字符串常量池在使用+进行字符串连接时最终会成为一个整体的String类对象,所以与直接生命完整字符串的差别不大。

范例:观察静态常亮池

package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String args)
{
String strA="QQQQ";
String strB="QQ"+"QQ";
System.out.println(strA==strB);
}
}

实际上,用户使用String strB="QQ"+"QQ"定义字符串时,程序编译后的结果等价于以下操作。
StringBuffer buf=new StringBuffer();
buf.append("QQ").append("QQ");
StringBuffer类中除了拥有可修改内容的能力外,还提供了一些String类所不具备的方法:
范例:插入数据
package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(STring []args)
{
StringBuffer buf=new StringBuffer();
buf.append(".cn").insert(0,"www").insert(4,"mldn");
}
}
本程序首先追加了一个字符串".cn",之后在第0个索引的位置上插入了字符串".www",最后又在第4个索引位置上插入了字符串"mldn";

范例:删除制定范围内的内容

package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String []args)
{
StringBuffer buf=new StringBuffer();
buf.append("Hello World").deletete(6,12).insert(6,"MLDN");

}
}

本程序首先删除了索引6-12的数据,并且在第6个索引位置插入了新的字符串。

范例:字符串翻转

pakkage cn.mldn.demo;
public class JavaDemo
{
public static void main(String []args)
{
StringBuffer buf=new StringBuffer();
buf.append("qqqq");
buf.reverse();
}
}

本程序利用reverse()方法将StringBuffer中的数据进行翻转处理,这也是StringBuffer类中最有特点的一个方法。

提示:StringBuilder与StringBuffer
StringBuffer类是在JDK1.0版本中提供的,但是JDK1.5后有提供了StringBuilder类。这两个类的功能类似,都是可修改的字符串类型,唯一的区别在于:StringBuffer类中的方法使用了synchronized关键字定义,适合于多线程并发访问下的同步实现;而StringBuilder类中的方法没有使用同步关键字,属于非线程安全的方法。

15.2 CharSequence接口

CharSequence是从JDK1.4开始提供的一个描述字符串标准的接口,常见的子类有三个:String、StringBuffer、StringBuilder

CharSequence可以进行字符串数据的保存,该接口提供3个方法
范例:使用CharSequence接口

package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String args[])
{
CharSequence str="www.mldn.cn";
CharSequence sub=str.subSequence(4,8);

}
}

String类是CharSequence接口子类,所以本程序利用对象向上转型的操作通过字符串匿名对象实现了CharSequence父接口对象实例化,随后调用了subSequence()方法实现了子字符串的截取操作。
提示;开发中优先考虑String类
StringBuffer类与StringBuilder类在日后主要用于频繁修改字符串操作上,但是在任何的开发中,面对字符串操作,大部分情况下都先考虑String,只有频繁修改这一操作中才会使用STringBuffer或StringBuilder。

15.3 AutoCloseable接口

在项目开发中,网络服务器或数据库的资源都是极为宝贵的,在每次操作完成之后一定要及时释放才能供更多的用户使用。在最初的JDK设计版本都是各个程序类中提供了相应的资源释放操作,而从JDK1.7版本开始提供AutoCloseable接口,该接口的主要功能是结合异常出处理结构在资源操作完成后实现自动释放功能,该接口定义如下
public interface AutoCloseable
{
public void close() throws Exception;
}
范例:使用AutoCloseable自动释放资源

package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
try(IMessage nm=new NetMessage("www.mldn.cn"))
{
nm.send();
}catch(Exception e)
{
e.printStackTrace();
}
}


}
interface IMessage extends 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 trus;
}
@Override
publlic void send()
{
if(this.open())
{
if(this.msg.contains("mldn"))
{
throw new RuntimeException("www.mldn....");
}
System.out.println("发送消息"+this.msg);
}
}

public void close() throws Exception
{
System.out.println("【close】关闭链接资源");
}
}
程序执行结果
【open】获得消息发送链接资源
【close】关闭链接资源

本程序实现了自动关闭处理,并且通过执行结果可以发现,不管是否产生了异常都会调用close()方法进行资源释放。

15.4 Runtime类

Runtime描述的是运行时状态,是每一个JVM金成都会提供的唯一一个Runtime类实例化对象,开发者可以通过Runtime类对象获取与JVM的运行时状态,起作用如下:
由于Runtime类中只存在一个实例化对象,所以在Runtime类中默认将其构造方法封装(单例模式),这样开发者就必须利用Runtime类中提供的getRuntime()方法(为static方法)来获取实例化对象,随后就可以获取一些系统地相关信息。范例:获取本即的CPU处理器数量
package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String args[])throws Exception
{
Runtime runtime=Runtime.getRuntime();
System.out.println(runtime.availableProcessors));
}
}

由于笔者所使用的为4核CPU,遮阳挡使用availableProcessors()返回的内容就是8.
程序执行中除了需要CPU外,也提供有内存的支持,在Runtime类中定义有3个内存数据的返回方法:maxMemory()、totalMemory()、freeMemory().
注意:取得内存信息时,返回得数为long
在Runtime类中的maxMemory()、totalMemory()、freeMemory()3个方法可以获得JVM的内存方法,而这三个方法的返回数据类型是long。在第2章中讲解基本数据类型的时候强调long型数据的使用主要由两种情况:表示文件大小和表示日期时间。

范例:获取主机内存信息
package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String args[])throws Exception
{
Rumtime rumtimr=Rumtime.getRuntime();
System.out.println(""+runtimr.maxMemory());
System.out.println(""+runtimr.totalMemory());
System.out.println(""+runtimr.freeMemory());
}
}

本程序获取了当前可用的内存信息,实际上当用户不进行任何操作时,最大的可用内存(maxMemory())为本机内存的1/4,而可用内存(totalMemory())为本即内存的1/64.
        Java本身提供有垃圾收集机制,对于GC线程而言,除了可以不定期处理外,也可以利用Runtime类中提供的gc()方法进行手动内存释放。

范例:观察GC操作

package cn.demo.demo;
publlic class JavaAPIDemo
{
public static void main(String []args) throws Exception
{
Runtime runtimr=Runtime.getRuntime();
System.out.println("1【TOTAL_MEMORY】+runtime.totalMemory()");
System.out.println("1【FREE_MEMORY】+runtime.freeMemory()");
String str="";
for(int x=0;x<3000;x++)
{
str+=x;
}
System.out.println("2【TOTAL_MEMORY】+runtime.totalMemory()");
System.out.println("2【FREE_MEMORY】+runtime.freeMemory()");
runtime.gc();
System.out.println("3【TOTAL_MEMORY】+runtime.totalMemory()");
System.out.println("3【FREE_MEMORY】+runtime.freeMemory()");
}
}

本程序演示了通过空闲的对比实现演示了垃圾产生前后的内存空间大小以及CG之后的空闲内存空间大小。

15.5 System类

System是一个系统类,其最主要的功能就是信息的打印输出:
System类中可以利用currentTimeMills()方法获取当前的时间戳,在开发中可以利用该方法来执行时间统计。
范例:统计操作所花费的时间。

package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
long strat=System.currentTimeMillis();
String str="";
for(int x=0;x<3000;x++)
{
str+=x;
}
long end=System.currrentTimeMills();
System.out.println("花费的时间+(end-start)"+"ms");
}
}

本程序在计算前和计算后分别得到了一个日期事件,所有的数据以long型数据返回,而在程序的最后执行减法操作,就可以取得本操作所花费的事件。

提示:关于数组复制
数组复制曾经在第6章讲解数组操作的时候奖结果,而当时考虑到学习的层次性问题,给出的定义格式不同
        之前的格式:System.arraycopy(原数组名称,原数组开始点,目标数组名称,目标数组开始点,长度).
System类定义:public static void arrcopy(Object src,int srcPos,Object dest,int destPos,int length);按照Object类的概念来讲,Object可以接收数组引用。

15.6 Cleaner类

在Java中对象的整个周期大致可以分为7个阶段:创建阶段(Created)、应用阶段(In Use)、不可见阶段(Invisible)、不可达阶段(Unreachable)、收集阶段(Collected)、终结阶段(Finalized)与释放阶段(Free)

提示:关于finalize()方法的说明。
在Object类中提供了一个finalize()方法,该方法的主要作用是在对象回收前执行收尾操作(类似于C++语言中析构函数的作用),该方法定义如下:
@Deprecated(since="9")
protected void finalize() throws Throwable
在finalize()方法中会抛出Throwable类型的异常,但是不管出现何种异常都不会影响到程序的正常执行,并且该方法在JDK1.9后就被彻底放弃了。
范例:旧时代对象回收执行方法

package cn.mldn.demo;
class Member
{
public Member
{
System.out.println("构造方法");
}
@Override
protected void finalize() throws Throwsable
{
Member mem=new Member();
System.out.println("对象回访,大家的终点都是一样的,一路走好");
throw new Exception("啊啊啊啊啊");

}
}

public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
Member mem=new Member();
mem=null;
System.gc();
System.out.pritnln("太阳照常升起");
}
}

程序执行结果:
构造方法
太阳照常升起
对象回访,大家的终点都是一样的,一路走好
通过本程序的执行可以发现,当对象回收前一定会调用finalize()方法进行对象回收前的收尾操作,但是此类操作也有可能影响JVM对象分配和回收速度,或者可能造成该对象的咋子次复活,所以从JDK1.9后不再推荐此类方式。
        传统的对象回收前处理操作依靠finalize()方法,而从JDK1.9之后开始提供了新的代替者:java.lang.ref.Cleaner类。此类清理方式会启动一个新的清理线程,并且基于AutoCloseable接口实现资源释放。

范例:Cleaner释放资源

package cn.mldn.demo;
import java.lang.ref.Cleaner;
class Member implements Runnable
{
public Member()
{
System.out.pritnln("构造方法");
}
@Override
public void run()
{
System.out.println("对象回收,大家的终点都一样");
}
}
class MemberCleaning implements AutoCloseable
{
private static final Cleaner cleaner=Cleaner.create();
private Cleaner.Cleanable cleanable();
public MemberCleaning(Member member)
{
this.cleanable=cleaner.register(this,member);
}
@Override 
public void close() throws Exception
{
this.cleanable.clean();
}
}

public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
Member mem=new Member();
System.gc();
,MmemberCleaning mc=new MemberCleaning(mem);
System.out.println("太阳照常升起");
}
}

15.7 对象克隆

Java中支持对象的复制(克隆)处理操作,可以直接利用已有的对象克隆出一个成员属性内容完全相同的实例化对象,对象的克隆可以使用Object类中提供的clone()方法,此方法的定义如下:
克隆方法:protected Object clone() throws CloneNotSupportedException
clone()方法抛出了一个CloneNotSupportException(不支持的克隆异常),这个异常表示的是,要克隆的类必须实现Clonable接口。但是Cloneable接口没有任何方法,所以这个接口属于标识接口,只表示一种能力。

范例:实现对象克隆

package cn.mldn.demo;
public class JavaDemo
{
public static void main(String []args)throws Exception
{
Member memberA=new Member("MLDN",30);
Member memberB=(Member)memberA.clone();
member.setName("LXH");
System.out.println(memberA);
System.out.println(memberB);
}
}

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 name=""+this.name+this.age;
}
@Override
protected Object clone() throws CloneNotSupportedException{return super.clone();}
}

对象的克隆操作可以通过Object类提供的clone()方法来完成,由于此方法在Object中使用protected设置了访问权限,所以该类方法只能通过子类来调用,而对象克隆成功后就会利用已有的对象创建一个全新的对象,彼此吃见得操作不会有任何影响。

15.8 Math数学计算

程序的开发本质上就是数据处理,Java提供有java.lang.Math()类来帮助开发者进行常规的数学计算,例如,四舍五入、三角函数、乘方处理等。

范例:使用Math类进行数学计算

package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String args[])throws Exception
{
System.out.println(Math.abs(-10.1));
System.out.println(Math.max(10.2,20.3));
System.out.println(Math.log(5));
System.out.println(Math.round(-15.1));
}
}
本程序使用了一些基础的数学公式进行了计算操作,同时在Math类中最需要注意的是round()四舍五入方法。该方法将直接保留整数位,并且可以实现负数的四舍五入操作,如果设置的负数大于0.5,则会采用进位处理。但是很多情况下对于四舍五入操作往往都需要保留指定位数的小数。

范例:自定义四舍五入工具类

package cn.mldn.demo;
class MathUtil
{
private MathUtil
{};
public static double round(double num,int scale)
{
return Math.round(num*Math.pow(10.0,scale))/Math.pow(10.0,scale);
}
}

15.9 Ramdom随机数

java.util.Random类的主要功能是可以进行随机数的生成,开发者只需要设置一个随机数的范围边界就可以生成不大于次边界范围的正整数;
随机生成正整数:public int nextInt(int bound)

范例:随机生成正整数
package cn.mldn.demo;
import java.util.Ramdom;
public class JavaDemo(String args[])throws Exception
{
Ramdom rand=new Random();
for(int x=0;x<10;x++)
{
System.out.println(rand.nectInt(100)+"、");
}
}

提示:实现36选7的逻辑
在现实生活中会有这样一种随机的操作:从1-36个数字中,随机选取7个数字内容,并且这7个数字内容不能够为0,也不能重复,通过Random类来实现

范例:实现36选7
package cn.mldn.jaba;
import java.util.Ramdom
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
int data[]=new int[7];
Random rand=new Random();
int foot=0;
while(foot<7)
{
int num=rand.nextInt(37);
if(isUse(num,data))
{
data[foot++]=num;
}
}
java.util.Arrays.sort(data);
printArray(data);
}
public static voic printArray(int temp[])
{
for(int x=0;x<temp.length;x++)
{
System.out.println(temp[x]);
}
}
public static boolean isUse(int num,int temp[])
{
if(num==0)return false;
for(int x=0;x<temp.length;x++)
{
if(num==temp[x]);
return false;
}
}
}

本程序为了防止保存错误的随机数,定义了一个isUse()方法进行0和重复内容的判断1.由于Rndom类生成的随机数是没有顺序的,所以为了按顺序显示,在输出前利用Arrays.sort()实现了数组排序。

15.10大数字处理类

当一个数字非常大的时候,是无法进行数据结构接受的。在早期开发中碰到大数字的时候往往会使用String类进行接收,之后采用拆分的方式进行计算。但是这一些猎德操作过于繁琐,所以为了解决这样的问题,在java.math包中提供了大数字的操作类:BigInterger(整数)、BigDecimal(浮点数),这两个类都是Number子类,其继承关系如图

范例:使用BigInteger实现四则运算

package cn.mldn.demo;
import java.math.BigInterger;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
BigInterger bigA=new BigInterger("2121212121");
BigInterger bigB=new BigInterger("2232");
System.out.println("加法操作"+bigA.add(bigB));
System.out.println("减法操作"+bigA.subtract(bigB));
System.out.println("乘法操作"+bigA.multiply(bigB));
System.out.println("除法操作"+bigA.divide(bigB));
BigInterger result[]=bigA.divideAndRemaider(bigB);
System.out.println("商"+result[0]+"余数"+result[1]);
}
}

本程序基于BigInterger类实现了基础的四则运算,可以发现在实例化BigInterger类对象时的数据类型为String,这样就可以不受数据类型长度的限制。BigDecimal类的操作形式与BigInterger类似,但是BigDecimal类中提供有一个进位的除法操作,可以利用该方法实现四舍五入操作。
范例:使用BigDecimal实现四舍五入
package cn.mldn.demo;
import java.math.BigDecimal;
import java.math.RoundingMode;
class MathUtil
{
private MathY=Util(){}
public static double round(double num,int scale)
{
return new BigDecimal(num).divide(new BigDecimal(1.0),scale,RoundingMode.Half_up).doubleValue();
}
}

15.11 Date日期处理类

Java中如果要想获得当前的日期时间可以通过java.util.Date类来实现,此类的操作方法如下:
 

No方法类型描述
1public Date()构造实例化Date类对象
2public Date()构造将数字变为Date类对象,long为日期时间数据
3public long getTime()普通将当前的日期时间变为long型

范例:获取当前日期时间
package cn.mldn.demo;
import java.util.Date;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
Date date=new Date();
System.out.println(date);
}
}

本程序直接利用Date类提供的无参构造实例化了Date类对象,此时Date对象将会保存有当前的日期时间。

提示:关于Date类的构造方法

在Date类中提供有两个构造方法,为了弄清楚这两个类的构造方法作用:
public class Date
{
private transient long fastTime;
public Date(){this(System.currentTimeMills());}
public Date(long date){fastTime=date;}
}
通过构造方法可以发现,当调用Date类中无参构造方法会利用System类获取当前日期时间戳而后通过Date类的单参构造进行对象实例化。

范例:Date与long之间的转换处理

package cn.mldn.demo;
import java.util.Date;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
Date date=new Date();
long current=date.getTime();
current+=86400*1000;
System.out.println(new Date(current));
}
}

Date类对象保存的时间戳是以毫秒的形势记录的当前日期时间,所以在本程序中将当前的日期时间戳去除并加上10天之后的日期。
提示:JDK1.8开始提供有java.time.LocalDateTime类
从JDK1.8开始为了方便进行日期操作,提供有java.time支持包,此包可以进行日期时间操作。
范例:使用LocalDateTime类

package cn.mldn.demo;
import java.time.LocalDateTime;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
LocalDateTime local=LocalDateTime.now();
System.out.println(local);
}
}

使用LocalDateTime可以方便的获取当前日期时间数据,也可以方便进行日期时间的累加操作。

15.12 SimpleDateFormat

使用java.util.Date类可以获取当前日期时间数据,但是最终的数据显示格式并不方便阅读,那么此时就可以考虑对现实的结果进行格式化操作,而这一操作就需要通过java.text.SimpleDateFormat类完成,此类继承关系如上
通过上图可以发现,Format类是格式化操作的父类,其可以实现日期格式化、数字格式化以及文本格式化,本次要使用SimpleDateFromat是DateFormat的子类。
范例:将日期格式化为字符串

package cn.mldn.demo;
import java.util.Date;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String str=sdf.format(date);
System.out.println(str);
}
}

本程序通过SimpleDateFormat类依据指定格式将当前的日期时间进行格式化处理,这样式的信息阅读更加直观。

范例:将字符串转为Date对象

package cn.mldn.demo;
import java.text.SimpleDateFormat;
import java.util.Date;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
String birthday="2017-02-17 09:15:07.027";
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Date date=sdf.parse(birthday);
}
}

15.13 正则表达式

在项目开发中String是一个重要的程序类,String类除了可以实现数据的接收、各类数据类型的转换外,其本身也支持正则表达式(Regular Exception).利用正则表达式可以方便的实现数据的拆分、替换、验证登操作。
        正则表达式最早有UNIX系统的开发组件中发展而来的,在JDK1.4以前如果需要使用到正则表达式的相关定义则需要单独引入其他的*.jar文件,而从JDK1.4后正则已经默认被JDK所支持,并且提供有java.util.regex开发包,同样针对String类也做了一些修改,使其可以有方法直接支持正则处理:
范例:使用正则表达式
package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
String str="123";
if(str.matches("\\d+"))
{
int num=Interger.parseInt(str);

}
}
}

本程序的主要功能是判断字符串的组成是否全部为数字,在本程序中通过一个给定的正则标记"\\d+"(判断是否为多位数字)并且结合String类提供的matches()方法实现验证匹配,如果验证成功,则将字符串转为int型整数进行计算。

15.13.1 常用正则表达式

在正则表达式的处理中,最为重要的就是正则匹配标记的使用,所有的正则标记都在java.util.regex.Pattern类中定义,下面列举一些常用的正则标记。

字符:匹配单个字符
a:表示匹配字母a;
\\:表示转义字符\;
\t:表示转移自读\t;
\n:表示转义字符\n;
一组字符:任意匹配里面的一个或单个字符
[abc]:表示可能是字母a,也有可能是字母b或者字母c。
[^abc]:表示不是字母a、b、c中的任意一个。
[a-zA-Z]:表示全部字母中的任意一个
[0-9]:表示全部数字中的任意一个。

边界匹配:在以后编写JavaScript的时候使用正则时要使用到。
^:表示一组正则的开始
$:表示一组正则的结束。
简写表达式:每一位出现的简写标记也只表示一位
.:表示任意的一位字符
\d:表示任意的一位数字,等价于.[0-9]
\D:表示任意的一位非数字,等价于.[^0-9]
\w:表示任意的一位字母、数字、_,等价于.[a-zA-Z0-9_]
\W:表示任意的一位非数字、数组、_,等价于.[^a-zA-Z0-9_]
\s:表示任意的一位空格,例如“\n”“\t”等
\S:表示任意的一位非空格
数量表示:之前的所有正则都只是表示一位,如果要想表示多位,则需要数量表示。
正则表达式?:此正则表达式出现0次或1此
正则表达式*:此正则出现0次、1次或多次。
正则表达式+:此正则出现1次或多次。
正则表达式{n}:此正则刚好出现n次
正则表达式{n.}:此正则刚好出现n次以上
正则表达式{n,m}:此正则刚好出现n-m次

逻辑表示:与、或、非
正则表达式A 正则表达式B:表示表达式A之后紧跟表达式B
正则表达式A|正则表达式B:表示表达式A或则是表达式B,二者任选一个出现。
(正则表达式):将多个子表达式合成一个表示,作为一组出现。
提示:背下正则基本标记
以上讲解的6组符号,在实际的工作中经常用到,考虑到开发过程中以及笔试中会出现此类应用,建议全部记下来范例:熟悉爱你字符串替换(删除非字母与数字)

package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
String str="MLDN&(*@#*(@##@*()Java";
//正则表达式由非字母和数字组成的[^a-zA-Z0-9],数量在1个及多个的时候进行替换
String regex="[^a-zA-Z0-9]+";
System.out.println(str.replaceAll(regex,""));
}
}

程序执行结果:
MLDNJava
本程序将字符串中所有的非字母和数字的内容通过正则进行匹配,由于可能包含多个匹配内容,所以使用了+进行数量设置,并且结合replaceAll()方法将匹配内容成功替换。

范例:实现字符串拆分

package cn.mldn.demo;
public class JavaAPIDemo
{
String str="a1b22b333d4444e55555f666666g";
String regex="\\d+";
String result[]=str.split(regex);
for(int x=0;x<result.length;x++)
{
System.out.println(result[x]+"、");
}
}
程序执行结果a、b、c、d、e、f、g

本程序通过正则匹配字符串中的一个或多个数字进行数据的拆分,这样最终保存下来的就是非数字的内容。
范例:判断一个数据是否为小数,如果是小数则将其转换为double类型
package cn.mldn.demo;
public class JavaAPIDemo
{
String str="100.1";
String regex="\\d+(\\.\\d+)?";
if(str.matches(regx))
{
double num=Double.parseDouble(str);
..
}else
{
..
}
}

本程序在进行小数组成的正则判断时需要考虑到小数点与小数位的情况(两者必须同时出现)所以在定义正则时使用(\\.\\d+)?将两者绑定在一起。

范例:判断一个字符串是否由日期组成,如果是由日期组成则将其转为Date类型。
package cn.mldn.demo;
import java.text.SimpleDateFormat
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
String str="1981-20-15";
String regex="\\d{4}-\\d{2}-\\d{2}";
if(str.matches(regex))
{
System.out.println(new SimpleDateFormat("yyyy-MM-dd").parse(str));
}else
{
System.out.println("不是日期格式");
}
}
}

本程序首先对给定的字符串进行日期格式的判断,如果格式符合(无法判断数据)则将字符串通过SimpleDateFormat类变为Date实例。
        范例:判断电话号码格式是否正确,在本程序中电话号码有如下3种类型
电话号码1(7-9位数字):51283346(判断正则:"\\d{7,8}");
电话号码2(在电话号码前追加区号):01051283346(判断正则):“(\\d{3,4})?\\d{7,8}”;
电话号码类型3(区号单独包裹):(010)-51283346(判断正则)"((\\d{3,4})|(\\(\\d{3,4}\\)-))?\\d{7,8}"
package cn.mldn.demo;
public class JavaDemo
{
public static void main(String args[])
{
String str="(010)-51283346";
String regex="((\\d{3,4})|(\\(\\d{3,4}\\)-))?\\d{7,8}";
System.out.println(str.macthes(regex));
}
}

本程序需要通过一个正则实现三种电话格式的匹配,在进行区号匹配时由于要使用“()”,所以必须使用“\\”进行转移。

范例:验证E-mail格式,现在要求一个合格的Email敌视的组成规则如下:
E-mail的用户名可以有字母、数字、_做组成(不应该使用_开头)
E-mail的域名可以由字母_数字、_、-所组成
域名的后缀必须是.cn、.com、.net、.com.cn、.gov
package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
String str="mldnjava888@mldn.cn";
String regex="[a-zA-Z0-9]\\w+@\\w+\\.(cn|com|com.cn|net|gov)";
System.out.println(str.matches(regex));
}
}
本程序按给定格式要求对E-mail组成进行正则验证,正则匹配结构:
 

字符串mldnjava@mldn.cn
正则符号[a-zA-Z0-9]\\w+@\\w+.(cn|com|com.cn|net|gov)

15.13.3 java.util.regex包支持

java.util.regex是从JDK1.4开始的正式提供的正则表达式开发包,在此包中定义有两个核心的正则操作类:Pattern(正则模式)和Matcher(匹配)
        java.util.regex.Pattern类的主要功能是进行正则表达式的编译以及获取Matcher类实例范例:使用Pattern类实现字符串拆分
package cn.mldn.demo;
import java.yuil.regex.Pattern;
public class JavaApiDemo
{
public static void main(String args[])throws Exception
{
String str="mldn()lixinghua$()java&*()#@Python";
String regex="[^a-zA-Z]";
Pattern pat=Pattern.compile(regex);
String result[]=pat.split(str);
for(int x=0;x<result.length;x++)
{
System.out.println(result[x]+"、");
}
}
}

本程序通过Pattern类中的compile()方法编译并获取了给定的正则表达式Pattern类实例对象,随后利用split()方法按照定义的正则进行拆分。

范例:使用Matcher类实现正则验证

package cn.mldn.demo;
import java.util.regex.Matcher;
impot java.util.regex.Pattern;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
String str="101";
String regex="\\d+";
Pattern pat=Pattern.compile(regex);
Matcher mat=pat.matcher(str);
System.out.println(mat.matches());
}
}

范例:使用Matcher类实现字符串替换
package cn.mldn.demo;
import java.util.regex.Matcher;
impot java.util.regex.Pattern:
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
String str="MLDN&(*@#**(@##@*()Java)";
String regex="[^a-zA-Z0-9]+";
Patter pat=pat.macthes(str);
System.out.println(mat.replaceAll(""));
}
}
输出:MLDNJ本程序利用Matcher类中的replaceAll()方法将字符串与正则匹配内容全部替换为空字符串。

范例:使用Matcher类实现数据分组操作

package cn.mldn.demo;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
//定义一个语法,获取#{}标记中的内容,此时就必须使用分组匹配操作
String str="INSERT INTO dept(deptno,dname,loc) VALUES(#{deptno},#{dname},#{loc})";
String regex="#\\{\\w+\\}";
Pattern pat=Pattern.compile(regex);
Matcher mat=pat.matcher(str);
while(mat.find())
{
String data=mat.group(0).replaceAll("#|\\{|\\}","");
}
}
}

最后System输出结果depno、dname、loc
本程序利用Mtcher类提供的分组操作功能,将给定完整字符串按照分组原则依次匹配输出。

15.14 国际化程序

当一个程序需要运行在全世界的各个国家,并且保持程序所处理的业务逻辑不变的情况下,就需要通过国际化程序实现机制,根据使用者所在区域的不同实现不同语言文字信息的切换:

15.14.1 Locale类

在国际化程序的实现过程中,对不同国家的区域和语言编码,可以通过java.util.Locale类的势力化定义:
在Locale类中除了可以根据当前系统自动获取实例化对象外,也可以利用Locale类提供的一系列常量来获取。例如,要想获得中国的Locale对象,可以使用Locale.CHINA常量完成。

范例:通过构造方法实例化Locale类对象

package cn.mldn.demo;
import java.util.Locale;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
Locale loc=new Locale("zh","CN");
System.out.println(loc);

}
}
程序执行结果zh_CN
本程序通过Locale类的构造方法设置了语言和国家编码实现了Locale类实例化对象的创建。如果不想手动设置区域编码,则也可以直接诶通过getDefault()方法获取当前操作系统的Locale实例。
范例:获取当前系统的Locale实例

package cn.mldn.demo;
import java.util.Locale;
public class javaAPIDemo
{
public static void main(String []args)throws Exception
{
Locale loc=Locale.getDefault();
System.out.println(loc);
}
}

本程序利用getDefault()方法获取当前所处的区域环境,由于当前的操作系统为中文环境,所以获得内容为zh_CN;

15.14.2 配置资料文件

国际化程序的实现过程中,语言文字是最为重要的内容,为了方便进行国际化的信息展示,可以将程序中所使用到的语言文字的信息直接保存在资料文件中,对于资料文件的定义要求如下。
        1 资源文件的后缀必须是".properties",一个项目中的资源文件有以下两类。
                公共资源文件:所有区域标记均可以读取到的内容,如Message.properties.
                具体区域的资源文件:需要在资源文件后面追加语言和国家代码,如Message_zh_CN.properties.
                所有的资源文件一定要定义到CLASSPATH中,允许资源文件保存在包中,例如,现在资源文件保存在了cn.mldn.message包中,则资源文件的完整名称为cn.mldn.message.Message.properties.
                资源文件中的所有数据采用字符串形式定义,利用key=value的形式进行保存,即在程序中读取时将通过key获取对应的value内容。

提示:资源文件也成为属性文件
        在java中只要后缀为*.properties并且组成结构为key=value形式的文件都可以称为属性文件,属性文件可以通过专门的Properties类进行操作。对于资源文件的命名与最初与类名称命名要求一致,但是随着技术的发展,也有许多资源文件全部采用小写字母的形式定义。
        范例:定义cn.mldn.message.Message.properties资源文件
edu.info=qqqq:www.baidu.com
资源文件必须使用key=value的形式进行定义,并且起数据类型都是字符串。
提示:关于资源文件内容的设置
在*.properties文件中所保存的内容必须进行编码才可以正确读取。在JDK1.9以前的版本里都会提供有native2ascii.exe编码工具,从JDK1.9开始支持在*.properties中使用UTF-8编码,所以此工具被取消了,读者只需保证文件编码的保存正确性就可以直接存储中文。

15.14.3 ResourceBundle读取资源文件

资源文件定义完成后程序通过java.util.ResourceBundle类来实现内容的获取,该类属于抽象类,可以利用类中提供的static方法(getBundle())来实现本类实例化对象的获取。ResourceBundle类常用的方法如下:
范例:根据key超找资源内容
package cn.mldn.demo;
import java.util.ResourceBundle;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
ResouceBundle resource=ResourceBundle.getBundle("cn.mldn.message.Messages");
String val=resource.getString("edu.info");
System.out.println(val);
}
}

本程序首先根据资源名称获取了对应的ResourceBundle对象实例,随后使用getString()方法获取指定key对应的内容。

提示:需要保证读取的key存在
        在使用ResourceBundle类读取资源时,如果对应的key不存在,则在程序执行时会出现异常。

15.14.4 国际化程序开发

国际化程序实现的关键在于根据用户所在的区域显示不用的文字信息,在国际化程序视线中,往往会提供有一个公共的资源文件,同时再根据不同的区域环境动态加载对应资源信息,所以首先需要建立3个资源文件
范例:加载默认语言环境下的资源文件

package cn.mldn.demo;
import java.util.ResourceBundle;
public class JavaAPIDemo
{
public static void main(String[]args)throws Exception
{
ResourceBundle resource=ResourceBundle.getBundle("cn.mldn.messsage.Messages");
String val=resource.getString("edu.info");

}
}

本程序根据当前系统所在的区域读取了资源文件,由于当前是中文环境,所以会匹配后缀为zh_CN的资源文件。

范例:通过Locale指定读取资源编码

package cn.mldn.demo;
import java.util.Locale;
import java.util.ResourceBundle;
public class JavaAPIDemo
{
public static void main(String[]args)
{
Locale loc=new Locale("en","US");
ResourceBundle resouce=ResourceBundle.getBundle("cn.mldn.message.Messages",loc);
String val=resource.getString("edu.info");

}
}

本程序制定了要读取区域编码,此时就会加载cn.mldn.message.Message_en_US.properties中保存的资源。

15.14.5 格式化文本显示

在前面的程序中,所有的资源内容都是固定的,但是输出的信息中要是饱含了一些动态文本的话,则必须使用占位符清楚地表示出动态文本的位置,占位符使用{编号}的格式出现。使用占位符后,程序可以直接通过MessageFormat对信息进行格式化,为占位符动态设置文本的内容。

范例:格式化文本显示数据

package cn.mldn.demo;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
Locale loc=new Locale("en","US");
ResourceBundle=resource.getString("edu.info");
System.out.println(MessageFormat(val,"mldn","www.mldn.cn"));
}
}

通过本程序的执行可以发现,在通过ResourceBundle类读取资源内容后,实际上读取出来的是一个带有占位符的数据,而只有通过MessageFormat类依据索引顺序传入内容后才可以显示正确的文本信息。

15.15 Arrays数组操作类

java.util.Arrays是一个专门实现数组操作的工具类:
范例:Arrays基本操作

package cn.mldn.demo;
import java.util.Arrays;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
int dataA=new int[]{1,2,3};
int dataB=new int[]{1,2,3};
System.out.println(Arrays.compare(dataA,dataB));
System.out.println(Arrays.equals(dataA,dataB));
int dataC[]=mew int[10];
Array.fill(dataC,3);
System.out.println(Arrays.toString(dataC));
}
}

本程序实现了数组的比较与内容的填充操作。需要注意的是,在使用compare()和equals()方法时需要保证数组出于排序后的状态,否则无法获得正确的比较结果。

范例:数据二分查找

package cn.mldn.demo;
import java.util.Arrays;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
int data[]=new int[]{1,5,7,2,3,6,0};
Arrays.sort(data);
System.out.println(Arrays.binarySearch(data,6));
System.out.println(Arrays.binarySearch(data,9));
}
}

本程序使用Arrays类提供的二分查找算法实现指定数据查找判断,如果可以查到数据,则可以返回对应数据的索引编号;如果没有查找到,则返回索引数据为负数。

提示:二分查找法
如果要向判断在数组中是否存在一个元素,最简单的做法就是利用循环的方式进行数据的以此判断,则此时程序的时间复杂度为O(n)(n为数组的长度)。对于数据量小的数组而言,这样的方式不会导致性能降低,但是当数组数据量增加时,此种模式一定会造成时间复杂度的攀升。而二分查找算法的出现可以将时间复杂度简化为O(log2n),这样就可以提升程序性能。二分查找法的基本实现思路二分查找法就是在已经排序的数组上不断进行查找索引范围的变更,这样可以减少无用的数据判断以提升性能:
private static int binarySearch0(int []a,int fromIndex,int toIndex,int key)
{
int low=fromIndex;
int high=toIndex-1;
while(low<=hign)
{
int mid=(low+high)>>>1;
int midVal=a[mid];
if(midval<key)low=mid+1;
else if(midVal>key)high=mid-1;
else return mid;
}
return -(low+1);
}

15.16 UUID无重复数据

UUID(Universally Unique Identifier,通用唯一识别符)是一种利用时间戳、始终序列、硬件识别号等唯一生成的编码的技术,利用此编码形式可以帮助开发者避免重复信息编号的出现,在Java中提供了java.util.UUID类来实现UUID编码的创建,UUID类常用的方法如下:
 

No方法类型描述
1public static UUID ramdomUUID()普通生成一个UUID数据
2public static UUID from String(String name)普通通过指定格式的字符串获取UUID数据

范例:随机生成UUID的数据

package cn.mldn.demo;
import java.util.UUID;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
UUID uid=UUID.randomUUID();
System.out.println(uid.toString());
}
}

本程序随机获取了一个UUID对象数据,并且根据时间戳和硬件编码动态生成,所以每次编码的结果都不同。

15.17 Optional空处理

在程序开发中常会出现由于null所带来的NullPointerException异常,所以从JDK1.8开始引入了java.util.Optianal类。利用此类可以实现null类型的提前判断与处理,合理的使用此类可以减少项目中NullPointerException异常地出现

由于Optional类拥有null判断的能力,所以在获取指定实例化对象时就可以利用Optional类进行保存:
范例:使用Optional实现对象返回

package cn.mldn.demo;
import java.uitil.Optional;
public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
Optional<IMessage>opt=MessageUtil.getMessage();
if(opt.isPresent())
{
IMessage temp=opt.get();
MessageUtil.useMessage(temp);
}
}
}

class MessageUtil
{
priavte MessageUtil(){}
public static OptionalM<IMessage>getMessage()
{
return Optional.of(new MessageImpl());
}
public static void useMessage(Imessage msg)
{
System.out.println(msg.getContent());
}
}

interface IMessage
{
public String getContent();
}

class MessageImpl implements IMessage
{
@Override 
public String getContent()
{
return "QQQ";
}
}

本程序在MessageUtil.getMessage()方法中使用了Optional作为方法的返回值,这样就表示该方法返回的一定是一个不为null的实例化对象,这样在调用时可以减少null内容的判断。

15.18 ThreadLocal

在多线程并发执行中,为了可以准确的实现每个线程中的数据成员访问,可以通过java.lang.ThreadLocal类实现数据的保存与存储。ThreadLocal类常用的方法如下:
通过上表可以发现,在ThreadLocal类中主要就是进行数据的保存、获取与删除操作。由于ThreadLocal类是在多线程并发访问时使用的,所以在数据保存时除了保存用户所需要的数据外,还会额外保存一个当前的线程独享,而在获取数据时也将通过当前线程独享来获取保存的数据内容,这样可以保证多线程并发访问下的数据操作安全。

提示:把ThreadLocal想象成公共储物柜
ThreadLocal最大的特点就是可以同时保存多个线程的数据,由于每个数据都有对对应的当前线程对象,这样就可以保证数据传输的正确性,可以简单把ThreadLocal想象成一个储物柜,每一个操作储物柜的客户想象成一个线程,每个线程只允许操作自己的储物柜,这样就保证了存储物品的安全性。

范例:ThreadLocal使用

package cn.mldn.demo;
public class JavaAPIDemo
{
public static void main(String[]args)throws Exception
{
new Thread(()->{
Message msg=new Message();
msg.setInfo("A:www:mldn.cn");
Channel.setMessage(msg);
Channel.send();
},"消息发送者A").start();

new Thread(()->{
Message msg=new Message();
msg.setInfo("A:www:mldn.cn");
Channel.setMessage(msg);
Channel.send();
},"消息发送者B").start();
new Thread(()->{
Message msg=new Message();
msg.setInfo("A:www:mldn.cn");
Channel.setMessage(msg);
Channel.send();
},"消息发送者C").start();
}
}

class Chanel
{
private static finale ThreadLocal<Message>THREADLOCAL=new ThreadLocal<Messsage>();
private Channel(){}
public static void setMessage(Message m)
{
THREADLOCAL.set(m);
}
public static void send()
{
System.out.println(""+Thread.currentThread().getName()+THREADLOCAL.get().getInfo());
}
}

class Message
{
private String info;
}

本程序创建了3个线程对象,并且3个线程对象对公共的ThreadLocal类实例进行操作。

15.19定时调度

定时调度是指可以根据既定的时间安排实现程序任务的自动执行,在Java中所有定时调度任务都通过一个单独的线程进行管理。每一个调度任务类都需要继承java.util.TimerTask父类,任务的启动需要通过java.util.Timer类完成
这里需要说明的是,关于schedule()与scheduleAtFixedRate()方法的区别,两者的区别只在于重复执行任务时,对于时间间隔出现延迟的情况处理。
        schedule()方法执行时间间隔永远是固定的,如果之前出现了延迟的情况,之后也会继续按照设定好的间隔时间来执行。
        scheduleAtFixedRate()方法可以根据出现的延迟时间自动调整下一次间隔的执行时间。​​​​​​​

范例:实现间隔任务调度

package cn.mldn.demo;
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 JavaAPIDemo
{
public static void main(String[]args)throws Exception
{
Timer timer=new Timer();
timer.scheduleAtFixedRate(new MyTask(),100,1000);
}
}

15.20 Base644加密与解密

Base64是一种利用64个可打印字符来表示二进制数据的算法,也是在网络传输过程中较为常见的一种加密算法。从JDK1.8版本开始提供java.util.Base64的工具类,同时提供两个Base64的内部类实现数据加密与解密操作。
        【数据加密】java.util.Base64.Encoder,对象获取方法:public static Base64.Encoder getEncoder(),数据加密处理:public byte[]encoder(byte[]src)
        【数据解密】java.util.Base64.Decoder,对象获取方法:public static Base64.Decoder getDecoder(),数据解密处理:public byte[]decoder(String src);
范例:实现Base64加密与解密操作
package cn.mldn.demo;
import java.util.Base64;
public class JavaAPIDemo
{
public static void main(String[]args)throws Exception
{
String msg="www.mldn.cn";
String encMsg=new String(Base64.getEncoder().encode(msg.getByte()));
System.out.println(encMsg);
String oldMsg=new String(Base64.getDecoder().decode(encMsg));
System.out.println(oldMsg);
}
}

本程序直接利用Base64提供的方法获取了Base64.Excoder与Base64.Decoder实例化对象,并且对原始数据进行了加密与解密操作。但是需要注意的是,由于Base64数据JDK的原始实现,所以单纯地加密是不安全的,此时为了获取更加安全的加密操作,可以利用盐值(salt)、自定义格式以及多次加密的方式来保证项目中的数据安全。

范例:基于Base64定义复杂加密与解密操作
package cn.mldn.demo;
import java.util.Base64;
class StringUtil
{
private static final String SALT="mldnjava";
private static finale int REPEAT=5;
public static String encode(String str)
{
String temp=str+"{"+SALT+"}";
byte data[]=temp.getBytes();
for(int x=0;x<REPEAT;x++)
{
data=Base64.getEncoder.encode(data);
}
return new String(data);
}

public static String decode(String str)
{
byte data[]=str.getBytes();
for(int x=0;x<REPEAT;x++)
{
data=Base64.getDecoder().decode(data);
}

return new String(data).replaceAll("\\{\\w+\\}","");
}
}

public class JavaAPIDemo
{
public static void main(String []args)throws Exception
{
String str=StringUtil.encode("www");
System.out.println(StringUtil.decode(str));
}
}

本程序基于Base64类的功能实现了一个自定义加密与解密程序,为了保证加密后的数据安全,采用的盐值格式为"盐值{原始数据}",同时利用多次加密的形势确保了密文数据的可靠性。在实际开发中只要不对外公布盐值内容和加密次数就可以在较为安全的环境下进行数据传输。

15.21 比较器

在数组操作中排序是一种较为常见的算法,由于基本的数据类型都可以直接确定出数值的大小关系,所以只需将数组中的内容取出后就可以直接利用关系运算符进行比对,然而java中还存在引用数据类型,而引用数据类型如果要想确定大小关系就必须通过比较器来完成。在Java中为了方便开发者开发,提供有两类比较器:Comparable和Comparator。

15.21.1 Comparable比较器

java.lang.Comparable是一个从JDK1.2开始提供的用于数组排序的标准接口,Java在进行对象数组排序时,将默认利用此接口中的方法进行大小的关系比较,这样就可以确认两个同类型对象之间的大小。Comparable接口定义如下:
public interface Comparable<T>
{
public int compareTo(T o);
}

在Comparable接口中提供一个compareTo()方法,利用此方法可以定义出对象要使用的判断规则。会返回如下三种结果:
1:表示大于(返回值大于0即可,10,20表示同一个结果)。
-1:表示小于(返回值小于0即可,例如,-10、-20都表示同一个结果);
0 表示两者对象相等。
提示:关于Comparable的常见子类

在第7章讲解String类常用的方法曾经奖结果String类中的compareTo()方法,实际上String类本身属于Compareble接口子类,所以字符串对象可以直接使用Arrays.sort()方法实现排序。除了String之外,包装类和大部分操作类也实现了Comparable接口,所以哥哥包装类和大数字操作类也可以使用Arrays.sort()排序。继承结构如上图。

范例:使用Comparable比较器实现自定义类对象数组排序

package cn.mldn.demo;
import java.util.Arrays;
class Member implements Comparable<Member>
{
private String name;
private int age;
public Mmeber(String name,int age)
{
this.name=name;
this.age=age;
}
@Override
public int compareTo(Member mem)
{
if(this.age>mem.age){return 1;}
else if(this.age<mem.age){return -1;}
else {return 0;}
}
@Override
public String toString(){return ""+this.name+this.age;}
}

public class JavaAPIDemo
{
public satic void main(String []args)throws Exception
{
Member data[]=new Member[]{new Member("LXH",18),new Member("LXH",38),new Member("LXH",28)};
Arrays.sort(data);
}
}

本程序在Mmeber类定义时实现了Comparable接口,并且在实现Comprable接口时的泛型类型与Member类相同,这样可以保证参与比较数据的类型同意。本程序主要通过age属性进行排序,所以在覆写compareTo()方法时只进行了年龄的判断(两个年龄可以确定返回数据的结果,这样就可以利用Arrays.sort()实现对象数组排序。

15.21.2 Comparator比较器

在进行对象数组排序是,对象所在的类定义时就必须实现Comparable接口,这样才可以使用Arrays.sort()进行排序操作。而除了此种方式之外,Java也提供有一种挽救的比较器实现接口:java.util.Comparator,定义如下:
@FunctionalInterface
public interface Comparator<T>
{
public int compare( T o1,T o2);

}

草最结构如上

范例:使用Comprator实现对象数组排序

package cn.mldn.demo;
import java.util.Arrays;
import java.util.Comparator;
class MemberComparator implements Comparator<Memeber>
{
@Override
public int compare(Member o1,Member o2)
{
return o1.getAge()-o2.getAge();
}
}

class Member
{
public String name;
private int age;
public Member(String name,int age)
{
this.name=name;
this.age=age;
}
@Override
public String toString()
{
return ""+this.name+this.age;
}
}

public class JavaAPIDemo
{
public satic void main(String []args)throws Exception
{
Member data[]=new Member[]{new Member("LXH",18),new Member("LXH",38),new Member("LXH",28)};
Arrays.sort(data,new MemberComputer());
}

本程序在定义Member类的时候并没有实现Comparable接口,所以该类的对象数组无法使用内置排序操作,为此单独定义了一个MemberComparator比较器工具类,这样在使用Arrays.sort()排序时只需传入相应的比较器对象实例即可实现内置排序操作。

15.21.3 二叉树

基于链表机制可以动态实现对象数组的常见,由于链表采用顺序式的存储结构,所以在进行数据查询时其时间复杂度为O(n).该结构在数据量较小的情况下,一般可以获得较高的查询性能,而数据量一旦增加,则一定会造成检索性能的下降,如果要想提升大数据量下的检索性能,最好采用二叉树,二叉树查询的时候时间复杂度Ologn(n为保存元素的个数)。
        在二叉树结构中,所有的数据都被保存在节点(Node)中,每一个节点又会分左右两个子节点。在进行存储时比根节点数据小的保存在左子节点,比根节点数据大的保存在右子节点,没有子节点的节点成为叶子结点。
由于二叉树依据大小关系进行数据存储,当二叉树获取数据时就可以利用中序遍历原则(左-中-由)的方式获取排序后的结果:
提示:关于二叉树的学习
二叉树的学习需要大量的操作处理节点间的顺序,所以在学习本部分内容之前应保证已经熟练掌握了链表的实现原理与代码开发。完善的二叉树开发难度较高,本张杰支队特定的机构进行讲解。
       

15.21.3.1 二叉树基础实现

        在二叉树结构中数据的基本保存单位是一个节点(Node),每一个节点都可以保存两个子节点,所以对于子节点的选择就需要进行大小关系的判断。基本数据类型的比较可以直接依靠关系运算符来实现,而引用数据类型可以通过Comparable比较器实现。

范例:二叉树基础实现

package cn.mldn.demo;
import java.util.Arrays;

class BinaryTree<T extends Comparable<T>>
{
private Comparable<T> data;
private Node parent;
private Node left;
private Node right;
public Node(Comparable<T>data){this.data=data;}

private void add(Node newNode)
{
if(newNode.data.compareTo((T)this.data<=0))
{
if(this.left==null)
{
this.left=newNode;
newNode.parent=this;
}else{this.left.addNode(newNode);}
}else
{
if(this.right==null){this.right=newNode;newNode.parent=this;}
}else
{

if(this.right==null){this.right==newNode}else
{
this.right.addNode(newNode);
}
public void toArrayNode
{
if(this.left!=null)
{this.left.toArrayNode();}

BinaryTree.this.returnData[BinaryTree.this.foot++]=this.data;
if(this.right!=null)
{
this.right.toArrayNode();
​​​​​​​}
}
}

private Node root;
private int count;
private Object[]returnData;
private int foot=0;
public void add(Comparable<T>data)
{
if(data==null){throw new NullPointerException("不能为空")};
Node newNode=new Node(data);
if(this.root==null){this.root=newNode;}
else{this.root.addNode(newNode);}
this.count++;
}

public Object[]toArray()
{
if(this.count==0){return null;}
this.returnData=new Object[this.count];
this.foot=0;
this.foot.toArrayNode();
return this.returnData;
}
 

class Member implements Comparable<Member>
{
private String name;
private int age;
public Member(String name,int age)
{
this.name=name;
this.age=age;
}
@Override
public int compareTo(Member mem)
{
return this.age-mem.age;
}
@Override
public String toString
{
return ""+this.年龄+this.age;
}
}

本程序实现了一个自定义二叉树结构,由于二叉树需要进行大小比较,所以要求其保存的数据一定是Comparable接口子类。在使用toArray()获取数据时采用了中序遍历的姓氏,这样获取的数据将按照年龄由低祷告的顺序返回。

15.21.3.2 二叉树数据查询

二叉树中的所有数据都是依据大小关系顺序排列的,而采用这样的存储形式的核心数目的在于提升查询性能,下面将手动实现二叉树的数据检索操作。
(1)二叉树的所有数据操作都应该由Node类负责管理,可以再Node类中定义一个节点查询方法。

public boolean containsNode(Comparable<T>data)
{
if(data.compareTo((T)this.data==0){return true;}
else if(data.compareTo((T)this.data)<0)
{
if(this.left!=null){return this.left.containsNode(data);}else{return false;}
}else
{
if(this.right!=null){return this.right.containsNode(data);}else{return false;}
}
}

(2)在BinaryTree类中追加数据查询方法

public boolean contains(Comparable<T>data)
{
if(this.count==0){return false;}
return this.root.containsNode(data);
}

通过本程序可以发现,在进行数据查询时Node类中的containsNode()方法并不会遍历全部元素,而只会依据查询数据与当前节点的关系动态的选择左节点或有节点查询。

15.21.3.3 二叉树数据删除

二叉树中的每一个节点除了数据之外,还需要保存依据大小关系的做优两个子节点,这样在进行节点删除操作的时候,就需要考虑节点间数据的关系重拍问题。所以对于节点的数据删除需要考虑一下三种情况:
(1)在Node类中追加获得要删除节点方法

public Node getRemoveNode(Comparable<T>data)
{
if(data.compareTo((T)this.data)==0){return this;}
else if(data.compareTo((T)this.data)<0)
{
if(this.left!=null){return this.left.getRemoveNode(data);}
else{return null;}
}else
{
if(this.left!=null){return this.right.getRemoveNode(data);}
else{return null;}
}
}
(3)在BinaryTree类中追加节点删除操作方法
​​​​​​​

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值