导读
Comparator是一个自定义数据排序接口,实现它的子类可以是一种策略类,帮助我们完成对数据的自定义次序排列功能。
System类是一个系统标准类,可以通过它获取当前系统的各项操作参数。
Runtime类是一个JVM相关的Java类,通过它可以获取JVM在运行时的各项参数。
数字处理工具类:很显然就是一个数值类型的数据的各种功能操作技术类。
MD5是一种不可逆的加密算法,可以涉猎一下。(深入就需要很多信息来参考着去学习)
二叉树是一种数据存储算法,很重要。
JDK8新特性之一:Lambda表达式是函数式接口。(该接口只有一个抽象方法的时候可以定义为函数式接口)
一、Comparator接口
comparator 是javase中的接口,位于java.util包下,该接口抽象度极高,有必要掌握该接口的使用大多数文章告诉大家comparator是用来排序,但我想说排序是comparator能实现的功能之一,他不仅限于排序
该接口代表一个比较器,比较器具有可比性!大多数文章都写如何用comparator排序,是因为javase数组工具类和集合工具类中提供的sort方法sort就是使用Comparator接口来处理排序的,大家见久了都认为Comparator接口是用来排序的,按照java抽象的尿性来看,该接口如果为排序而生,应该叫Sortable,Sortor之类的名字吧!下面是javase一些使用到Comparator接口的地方:
代码如下:
Arrays.sort(T[],Comparator super T> c);Collections.sort(Listlist,Comparator super T> c);
什么场景需要做比较,那么什么场景就是Comparator接口的用武之地,我总结的两个场景:
1. 排序,需要比较两个对象谁排在前谁排在后(排序也可以让类实现Comparable接口,实现后该类的实例也具有排序能力)。
2. 分组,需要比较两个对象是否是属于同一组。
1.排序
在List或数组中的对象如果没有实现Comparable接口时,那么就需要调用者为需要排序的数组或List设置一个Compartor,Compartor的compare方法用来告诉代码应该怎么去比较两个实例,然后根据比较结果进行排序
代码如下——
package com.java.demo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @author puyf
*/
public class SortTest {
class Dog{
public int age;
public String name;
public Dog(int age, String name) {
super();
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Dog [age=" + age + ", name=" + name + "]";
}
}
public static void main(String[] args) {
List list= new ArrayList<>();
list.add(new SortTest().new Dog(5, "DogA"));
list.add(new SortTest().new Dog(6, "DogB"));
list.add(new SortTest().new Dog(7, "DogC"));
Collections.sort(list, new Comparator() {
@Override
public int compare(Dog o1, Dog o2) {
return o2.age - o1.age;
}
});
System.out.println("给狗狗按照年龄倒序:"+list);
Collections.sort(list, new Comparator() {
@Override
public int compare(Dog o1, Dog o2) {
return o1.name.compareTo(o2.name);
}
});
System.out.println("给狗狗按名字字母顺序排序:"+list);
}
}
2.分组
概述——
使用Comparator和for循环处理列表,来进行分类;通过调用者实现Comparator接口的比较逻辑,来告诉程序应该怎么比较,通过比较之后得结果来进行分组。比如生活中的拳击比赛,会有公斤级的概念,那么程序中应该实现的处理逻辑是只要两个人的体重在同一个区间则为同一组公斤级的选手。下面例子中分别按照狗狗的颜色和体重级别两个维度来进行分组,因此分组的核心逻辑其实就是比较逻辑。相面我抽了一个工具方法:dividerList,第一个参数为需要处理的数据源,第二参数是分组时的比较逻辑。
代码如下:
package com.java.demo;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* @author puyf
*/
public class GroupTest {
class Apple {
public String color;
public int weight;
public Apple(String color, int weight) {
super();
this.color = color;
this.weight = weight;
}
@Override
public String toString() {
return "Apple [color=" + color + ", weight=" + weight + "]";
}
}
/**
* @author puyf
* @Description:按条件分组
* @param datas
* @param c
* 是否为同一组的判断标准
* @return
*/
public static List> divider(Collection datas, Comparator super T> c) {
List> result = new ArrayList>();
for (T t : datas) {
boolean isSameGroup = false;
for (int j = 0; j < result.size(); j++) {
if (c.compare(t, result.get(j).get(0)) == 0) {
isSameGroup = true;
result.get(j).add(t);
break;
}
}
if (!isSameGroup) {
// 创建
List innerList = new ArrayList();
result.add(innerList);
innerList.add(t);
}
}
return result;
}
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(new GroupTest().new Apple("红", 205));
list.add(new GroupTest().new Apple("红", 131));
list.add(new GroupTest().new Apple("绿", 248));
list.add(new GroupTest().new Apple("绿", 153));
list.add(new GroupTest().new Apple("黄", 119));
list.add(new GroupTest().new Apple("黄", 224));
List> byColors = divider(list, new Comparator() {
@Override
public int compare(Apple o1, Apple o2) {
// 按颜色分组
return o1.color.compareTo(o2.color);
}
});
System.out.println("按颜色分组" + byColors);
List> byWeight = divider(list, new Comparator() {
@Override
public int compare(Apple o1, Apple o2) {
// 按重量级
return (o1.weight / 100 == o2.weight / 100) ? 0 : 1;
}
});
System.out.println("按重量级分组" + byWeight);
}
}
结果——
结果如下(为了方便看,手动回车换行格式化了下):
按颜色分组
[
[
Apple [color=红, weight=205],
Apple [color=红, weight=131]
],
[
Apple [color=绿, weight=248],
Apple [color=绿, weight=153]
],
[
Apple [color=黄, weight=119],
Apple [color=黄, weight=224]
]
]
按重量级分组
[
[
Apple [color=红, weight=205],
Apple [color=绿, weight=248],
Apple [color=黄, weight=224]
],
[
Apple [color=红, weight=131],
Apple [color=绿, weight=153],
Apple [color=黄, weight=119]
]
]
小结:
一般需要做比较的逻辑都可以使用的上Comparator,最常用的场景就是排序和分组,排序常使用Arrays和Collections的sort方法,而分组则可以使用上面提供的divider方法。
排序和分组的区别在于:
排序时,两个对象比较的结果有三种:大于,等于,小于。
分组时,两个对象比较的结果只有两种:等于(两个对象属于同一组),不等于(两个对象属于不同组)
System类分析——
概念:System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
由于该类的构造方法是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
1、成员变量
System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
例如:
System.out.println(“Test”);
该行代码的作用是将字符串”Test”输出到系统的标准输出设备上,也就是显示在屏幕上。
后续在学习完IO相关的知识以后,可以使用System类中的成员方法改变标准输入流等对应的设备,例如可以将标准输出流输出的信息输出到文件内部,从而形成日志文件等。
2、成员方法
System类中提供了一些系统级的操作方法,这些方法实现的功能分别如下:
a、arraycopy方法
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
该方法的作用是数组拷贝,也就是将一个数组中的内容复制到另外一个数组中的指定位置,由于该方法是native方法,所以性能上比使用循环高效。
使用示例:
int[] a = {1,2,3,4};
int[] b = new int[5];
System.arraycopy(a,1,b,3,2);
该代码的作用是将数组a中,从下标为1开始,复制到数组b从下标3开始的位置,总共复制2个。也就是将a[1]复制给b[3],将a[2]复制给b[4],这样经过复制以后数组a中的值不发生变化,而数组b中的值将变成{0,0,0,2,3}。
b、currentTimeMillis方法
public static long currentTimeMillis()
该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。例如:
long l = System. currentTimeMillis();
则获得的将是一个长整型的数字,该数字就是以差值表达的当前时间。
使用该方法获得的时间不够直观,但是却很方便时间的计算。例如,计算程序运行需要的时间则可以使用如下的代码:
long start = System. currentTimeMillis();
for(int i = 0;i < 100000000;i++){
int a = 0;
}
long end = System. currentTimeMillis();
long time = end – start;
则这里变量time的值就代表该代码中间的for循环执行需要的毫秒数,使用这种方式可以测试不同算法的程序的执行效率高低,也可以用于后期线程控制时的精确延时实现。
c、exit方法
public static void exit(int status)
该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
d、gc方法
public static void gc()
该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
e、getProperty方法
public static String getProperty(String key)
该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示。
属性名列表
属性名 |
属性说明 |
java.version |
Java 运行时环境版本 |
java.home |
Java 安装目录 |
os.name |
操作系统的名称 |
os.version |
操作系统的版本 |
user.name |
用户的账户名称 |
user.home |
用户的主目录 |
user.dir |
用户的当前工作目录 |
例如:
String osName = System.getProperty(“os.name”);
String user = System.getProperty(“user.name”);
System.out.println(“当前操作系统是:” + osName);
System.out.println(“当前用户是:” + user);
使用该方法可以获得很多系统级的参数以及对应的值。
Runtime引用于SUN的官方API,喜欢的朋友可以细心研读——
一、概述
Runtime类封装了运行时的环境。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。
一般不能实例化一个Runtime对象,应用程序也不能创建自己的 Runtime 类实例,但可以通过 getRuntime 方法获取当前Runtime运行时对象的引用。
一旦得到了一个当前的Runtime对象的引用,就可以调用Runtime对象的方法去控制Java虚拟机的状态和行为。
当Applet和其他不被信任的代码调用任何Runtime方法时,常常会引起SecurityException异常。
二、API预览
addShutdownHook(Thread hook)
注册新的虚拟机来关闭挂钩。
availableProcessors()
向 Java 虚拟机返回可用处理器的数目。
exec(String command)
在单独的进程中执行指定的字符串命令。
exec(String[] cmdarray)
在单独的进程中执行指定命令和变量。
exec(String[] cmdarray, String[] envp)
在指定环境的独立进程中执行指定命令和变量。
exec(String[] cmdarray, String[] envp, File dir)
在指定环境和工作目录的独立进程中执行指定的命令和变量。
exec(String command, String[] envp)
在指定环境的单独进程中执行指定的字符串命令。
exec(String command, String[] envp, File dir)
在有指定环境和工作目录的独立进程中执行指定的字符串命令。
exit(int status)
通过启动虚拟机的关闭序列,终止当前正在运行的 Java 虚拟机。
freeMemory()
返回 Java 虚拟机中的空闲内存量。
gc()
运行垃圾回收器。
InputStream getLocalizedInputStream(InputStream in)
已过时。 从 JDK 1.1 开始,将本地编码字节流转换为 Unicode 字符流的首选方法是使用 InputStreamReader 和 BufferedReader 类。
OutputStream getLocalizedOutputStream(OutputStream out)
已过时。 从 JDK 1.1 开始,将 Unicode 字符流转换为本地编码字节流的首选方法是使用 OutputStreamWriter、BufferedWriter 和 PrintWriter 类。
getRuntime()
返回与当前 Java 应用程序相关的运行时对象。
halt(int status)
强行终止目前正在运行的 Java 虚拟机。
load(String filename)
加载作为动态库的指定文件名。
loadLibrary(String libname)
加载具有指定库名的动态库。
maxMemory()
返回 Java 虚拟机试图使用的最大内存量。
removeShutdownHook(Thread hook)
取消注册某个先前已注册的虚拟机关闭挂钩。
runFinalization()
运行挂起 finalization 的所有对象的终止方法。
runFinalizersOnExit(value)
已过时。 此方法本身具有不安全性。它可能对正在使用的对象调用终结方法,而其他线程正在操作这些对象,从而导致不正确的行为或死锁。
totalMemory()
返回 Java 虚拟机中的内存总量。
traceInstructions(on)
启用/禁用指令跟踪。
traceMethodCalls(on)
启用/禁用方法调用跟踪。
三、常见的应用
1、内存管理:
Java提供了无用单元自动收集机制。通过totalMemory()和freeMemory()方法可以知道对象的堆内存有多大,还剩多少。
Java会周期性的回收垃圾对象(未使用的对象),以便释放内存空间。但是如果想先于收集器的下一次指定周期来收集废弃的对象,可以通过调用gc()方法来根据需要运行无用单元收集器。一个很好的试验方法是先调用gc()方法,然后调用freeMemory()方法来查看基本的内存使用情况,接着执行代码,然后再次调用freeMemory()方法看看分配了多少内存。下面的程序演示了这个构想。
//此实例来自《java核心技术》卷一
class MemoryDemo{
public
static
void main(String args[]){
Runtime r = Runtime.getRuntime();
long mem1,mem2;
Integer someints[] =
new Integer[1000];
System.out.println(
"Total memory is :" + r.totalMemory());
mem1 = r.freeMemory();
System.out.println(
"Initial free is : " + mem1);
r.gc();
mem1 = r.freeMemory();
System.out.println(
"Free memory after garbage collection : " + mem1);
//allocate integers
for(
int i=0; i<1000; i++) someints[i] =
new Integer(i);
mem2 = r.freeMemory();
System.out.println(
"Free memory after allocation : " + mem2);
System.out.println(
"Memory used by allocation : " +(mem1-mem2));
//discard Intergers
for(
int i=0; i<1000; i++) someints[i] =
null;
r.gc();
//request garbage collection
mem2 = r.freeMemory();
System.out.println(
"Free memory after collecting " +
"discarded integers : " + mem2);
}
}
编译后运行结果如下(不同的机器不同时间运行的结果也不一定一样):
Total memory is :2031616
Initial free is : 1818488
Free memory after garbage collection : 1888808
Free memory after allocation : 1872224
Memory used by allocation : 16584
Free memory after collecting discarded integers : 1888808
2、执行其他程序
在安全的环境中,可以在多任务操作系统中使用Java去执行其他特别大的进程(也就是程序)。ecec()方法有几种形式命名想要运行的程序和它的输入参数。ecec()方法返回一个Process对象,可以使用这个对象控制Java程序与新运行的进程进行交互。ecec()方法本质是依赖于环境。
下面的例子是使用ecec()方法启动windows的记事本notepad。这个例子必须在Windows操作系统上运行。
//此实例来自《Java核心技术》卷一
class ExecDemo {
public
static
void main(String args[]){
Runtime r = Runtime.getRuntime();
Process p =
null;
try{
p = r.exec(
"notepad");
}
catch (Exception e) {
System.out.println(
"Error executing notepad.");
}
}
}
ecec()还有其他几种形式,例子中演示的是最常用的一种。ecec()方法返回Process对象后,在新程序开始运行后就可以使用Process的方法了。可以用destory()方法杀死子进程,也可以使用waitFor()方法等待程序直到子程序结束,exitValue()方法返回子进程结束时返回的值。如果没有错误,将返回0,否则返回非0。下面是关于ecec()方法的例子的改进版本。例子被修改为等待,直到运行的进程退出:
//此实例来自《Java核心技术》卷一
class ExecDemoFini {
public static void main(String args[]){
Runtime r = Runtime.getRuntime();
Process p = null;
try{
p = r.exec("notepad");
p.waitFor();
} catch (Exception e) {
System.out.println("Error executing notepad.");
}
System.out.println("Notepad returned " + p.exitValue());
}
}
下面是运行的结果(当关闭记事本后,会接着运行程序,打印信息):
Notepad returned 0
请按任意键继续. . .
当子进程正在运行时,可以对标准输入输出进行读写。getOutputStream()方法和getInPutStream()方法返回对子进程的标准输入和输出。
四、来自SUN公司的java.long.Runtime类的API文档,网上有chm中文版的,很好找。为了查阅方便,我从SUN公司的JavaDoc站点上复制出来了Runtime类的API文档。
数字处理工具类——
package com.lyis.commons.util;
import java.math.BigDecimal;
import java.util.regex.Pattern;
/**
* 数字处理工具类
*
* @author Johnson
* @version Monday October 25th, 2010
*/
public class NumberUtils {
/**
* 判断当前值是否为整数
*
* @param value
* @return
*/
public static boolean isInteger(Object value) {
if (StringUtils.isEmpty(value)) {
return false;
}
String mstr = value.toString();
Pattern pattern = Pattern.compile("^-?\\d+{1}quot;);
return pattern.matcher(mstr).matches();
}
/**
* 判断当前值是否为数字(包括小数)
*
* @param value
* @return
*/
public static boolean isDigit(Object value) {
if (StringUtils.isEmpty(value)) {
return false;
}
String mstr = value.toString();
Pattern pattern = Pattern.compile("^-?[0-9]*.?[0-9]*{1}quot;);
return pattern.matcher(mstr).matches();
}
/**
* 将数字格式化输出
*
* @param value
* 需要格式化的值
* @param precision
* 精度(小数点后的位数)
* @return
*/
public static String format(Object value, Integer precision) {
Double number = 0.0;
if (NumberUtils.isDigit(value)) {
number = new Double(value.toString());
}
precision = (precision == null || precision < 0) ? 2 : precision;
BigDecimal bigDecimal = new BigDecimal(number);
return bigDecimal.setScale(precision, BigDecimal.ROUND_HALF_UP)
.toString();
}
/**
* 将数字格式化输出
*
* @param value
* @return
*/
public static String format(Object value) {
return NumberUtils.format(value, 2);
}
/**
* 将值转成Integer型,如果不是整数,则返回0
*
* @param value
* @param replace
* 如果为0或者null,替换值
* @return
*/
public static Integer parseInteger(Object value, Integer replace) {
if (!NumberUtils.isInteger(value)) {
return replace;
}
return new Integer(value.toString());
}
/**
* 将值转成Integer型,如果不是整数,则返回0
*
* @param value
* @return
*/
public static Integer parseInteger(Object value) {
return NumberUtils.parseInteger(value, 0);
}
/**
* 将值转成Long型
*
* @param value
* @param replace
* 如果为0或者null,替换值
* @return
*/
public static Long parseLong(Object value, Long replace) {
if (!NumberUtils.isInteger(value)) {
return replace;
}
return new Long(value.toString());
}
/**
* 将值转成Long型,如果不是整数,则返回0
*
* @param value
* @return
*/
public static Long parseLong(Object value) {
return NumberUtils.parseLong(value, 0L);
}
/**
* 将值转成Double型
*
* @param value
* @param replace
* replace 如果为0或者null,替换值
* @return
*/
public static Double parseDouble(Object value, Double replace) {
if (!NumberUtils.isDigit(value)) {
return replace;
}
return new Double(value.toString());
}
/**
* 将值转成Double型,如果不是整数,则返回0
*
* @param value
* @return
*/
public static Double parseDouble(Object value) {
return NumberUtils.parseDouble(value, 0.0);
}
/**
* 将char型数据转成字节数组
*
* @param value
* @return
*/
public static byte[] toBytes(char value) {
byte[] bt = new byte[2];
for (int i = 0; i < bt.length; i++) {
bt[i] = (byte) (value >>> (i * 8));
}
return bt;
}
/**
* 将short型数据转成字节数组
*
* @param value
* @return
*/
public static byte[] toBytes(short value) {
byte[] bt = new byte[2];
for (int i = 0; i < bt.length; i++) {
bt[i] = (byte) (value >>> (i * 8));
}
return bt;
}
/**
* 将int型数据转成字节数组
*
* @param value
* @return
*/
public static byte[] toBytes(int value) {
byte[] bt = new byte[4];
for (int i = 0; i < bt.length; i++) {
bt[i] = (byte) (value >>> (i * 8));
}
return bt;
}
/**
* 将long型数据转成字节数组
*
* @param value
* @return
*/
public static byte[] toBytes(long value) {
byte[] bt = new byte[8];
for (int i = 0; i < bt.length; i++) {
bt[i] = (byte) (value >>> (i * 8));
}
return bt;
}
/**
* 将short型数据插入到指定索引的字节数组中
*
* @param index
* 索引
* @param values
* 字节数组
* @param value
* 需要插入的值
*/
public static void insert(int index, byte[] values, short value) {
byte[] bt = NumberUtils.toBytes(value);
System.arraycopy(bt, 0, values, index, 2);
}
/**
* 将int型数据插入到指定索引的字节数组中
*
* @param index
* 索引
* @param values
* 字节数组
* @param value
* 需要插入的值
*/
public static void insert(int index, byte[] values, int value) {
byte[] bt = NumberUtils.toBytes(value);
System.arraycopy(bt, 0, values, index, 4);
}
/**
* 将long型数据插入到指定索引的字节数组中
*
* @param index
* 索引
* @param values
* 字节数组
* @param value
* 需要插入的值
*/
public static void insert(int index, byte[] values, long value) {
byte[] bt = NumberUtils.toBytes(value);
System.arraycopy(bt, 0, values, index, 8);
}
/**
* 将字节转换成整型
*
* @param value
* 字节类型值
* @return
*/
public static int byteToInt(byte value) {
if (value < 0) {
return value + 256;
}
return value;
}
}
java中使用MD5进行加密
在各种应用系统的开发中,经常需要存储用户信息,很多地方都要存储用户密码,而将用户密码直接存储在服务器上显然是不安全的,本文简要介绍工作中常用的 MD5加密算法,希望能抛砖引玉。
(一)消息摘要简介
一个消息摘要就是一个数据块的数字指纹。即对一个任意长度的一个数据块进行计算,产生一个唯一指印(对于SHA1是产生一个20字节的二进制数组)。消息摘要是一种与消息认证码结合使用以确保消息完整性的技术。主要使用单向散列函数算法,可用于检验消息的完整性,和通过散列密码直接以文本形式保存等,目前广泛使用的算法有MD4、MD5、SHA-1。
消息摘要有两个基本属性:
两个不同的报文难以生成相同的摘要
难以对指定的摘要生成一个报文,而可以由该报文反推算出该指定的摘要
代表:美国国家标准技术研究所的SHA1和麻省理工学院Ronald Rivest提出的MD5
(二)对字符串进行加密
/**利用MD5进行加密
* @param str 待加密的字符串
* @return 加密后的字符串
* @throws NoSuchAlgorithmException 没有这种产生消息摘要的算法
* @throws UnsupportedEncodingException
*/
public String EncoderByMd5(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException{
//确定计算方法
MessageDigest md5=MessageDigest.getInstance("MD5");
BASE64Encoder base64en = new BASE64Encoder();
//加密后的字符串
String newstr=base64en.encode(md5.digest(str.getBytes("utf-8")));
return newstr;
}
调用函数:
String str="0123456789"
System.out.println(EncoderByMd5(str));
输出:eB5eJF1ptWaXm4bijSPyxw==
(三)验证密码是否正确
因为MD5是基于消息摘要原理的,消息摘要的基本特征就是很难根据摘要推算出消息报文,因此要验证密码是否正确,就必须对输入密码(消息报文)重新计算其摘要,和数据库中存储的摘要进行对比(即数据库中存储的其实为用户密码的摘要),若两个摘要相同,则说明密码正确,不同,则说明密码错误。
/**判断用户密码是否正确
* @param newpasswd 用户输入的密码
* @param oldpasswd 数据库中存储的密码--用户密码的摘要
* @return
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public boolean checkpassword(String newpasswd,String oldpasswd) throws NoSuchAlgorithmException, UnsupportedEncodingException{
if(EncoderByMd5(newpasswd).equals(oldpasswd))
return true;
else
return false;
}