转载自网络。
Apache Commons
Apache Commons是Apache软件基金会的项目。Commons的目的是提供可重用的、开源的Java代码。
Apache Commons提供了很多工具类库,他们几乎不依赖其他第三方的类库,接口稳定,集成简单,可以大大提高编码效率和代码质量。
Apache Commons Lang
Apache Commons Lang是对java.lang的扩展,基本上是commons中最常用的工具包。
目前Lang包有两个commons-lang3和commons-lang。
lang最新版本是2.6,最低要求Java1.2以上,目前官方已不再维护。lang3目前最新版本是3.12.0,最低要求Java8以上。相对于lang来说完全支持Java8的特性,废除了一些旧的API。该版本无法兼容旧有版本,于是为了避免冲突改名为lang3。
Java8以上的用户推荐使用lang3代替lang,下面我们主要以lang3 - 3.12.0版本为例做说明。
以下为整体包结构:
org.apache.commons.lang3
org.apache.commons.lang3.builder
org.apache.commons.lang3.concurrent
org.apache.commons.lang3.event
org.apache.commons.lang3.exception
org.apache.commons.lang3.math
org.apache.commons.lang3.mutable
org.apache.commons.lang3.reflect
org.apache.commons.lang3.text
org.apache.commons.lang3.text.translate
org.apache.commons.lang3.time
org.apache.commons.lang3.tuple
01. 日期相关
在Java8之前,日期只提供了java.util.Date类和java.util.Calendar类,说实话这些API并不是很好用,而且也存在线程安全的问题,所以Java8推出了新的日期API。如果你还在用旧的日期API,可以使用DateUtils和DateFormatUtils工具类。
1. 字符串转日期
final String strDate = "2021-07-04 11:11:11";
final String pattern = "yyyy-MM-dd HH:mm:ss";
// 原生写法
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date date1 = sdf.parse(strDate);
// commons写法
Date date2 = DateUtils.parseDate(strDate, pattern);
2. 日期转字符串
final Date date = new Date();
final String pattern = "yyyy年MM月dd日";
// 原生写法
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
String strDate = sdf.format(date);
// 使用commons写法
String strDate = DateFormatUtils.format(date, pattern);
3. 日期计算
final Date date = new Date();
// 原生写法
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DATE, 5); // 加5天
cal.add(Calendar.HOUR_OF_DAY, -5); // 减5小时
// 使用commons写法
Date newDate1 = DateUtils.addDays(date, 5); // 加5天
Date newDate2 = DateUtils.addHours(date, -5); // 减5小时
Date newDate3 = DateUtils.truncate(date, Calendar.DATE); // 过滤时分秒
boolean isSameDay = DateUtils.isSameDay(newDate1, newDate2); // 判断是否是同一天
02. 字符串相关
字符串是Java中最常用的类型,相关的工具类也可以说是最常用的,下面直接看例子
1. 字符串判空
String str = "";
// 原生写法
if (str == null || str.length() == 0) {
// Do something
}
// commons写法
if (StringUtils.isEmpty(str)) {
// Do something
}
/* StringUtils.isEmpty(null) = true
* StringUtils.isEmpty("") = true
* StringUtils.isEmpty(" ") = false
* StringUtils.isEmpty("bob") = false
* StringUtils.isEmpty(" bob ") = false
*/
相关方法:
// isEmpty取反
StringUtils.isNotEmpty(str);
/*
* null, 空串,空格为true
* StringUtils.isBlank(null) = true
* StringUtils.isBlank("") = true
* StringUtils.isBlank(" ") = true
* StringUtils.isBlank("bob") = false
* StringUtils.isBlank(" bob ") = false
*/
StringUtils.isBlank(str);
// isBlank取反
StringUtils.isNotBlank(str);
// 任意一个参数为空则结果为true
StringUtils.isAnyEmpty(str1, str2, str3);
// 所有参数为空则结果为true
StringUtils.isAllEmpty(str1, str2, str3);
2. 字符串去空格
// 去除两端空格,不需要判断null
String newStr = StringUtils.trim(str);
/*
* 去除两端空格,如果是null则转换为空字符串
* StringUtils.trimToEmpty(null) = ""
* StringUtils.trimToEmpty("") = ""
* StringUtils.trimToEmpty(" ") = ""
* StringUtils.trimToEmpty("abc") = "abc"
* StringUtils.trimToEmpty(" abc ") = "abc"
*/
newStr = StringUtils.trimToEmpty(str);
/*
* 去除两端空格,如果结果是空串则转换为null
* StringUtils.trimToNull(null) = null
* StringUtils.trimToNull("") = null
* StringUtils.trimToNull(" ") = null
* StringUtils.trimToNull("abc") = "abc"
* StringUtils.trimToNull(" abc ") = "abc"
*/
newStr = StringUtils.trimToNull(str);
/*
* 去两端 给定字符串中任意字符
* StringUtils.strip(null, *) = null
* StringUtils.strip("", *) = ""
* StringUtils.strip("abc", null) = "abc"
* StringUtils.strip(" abc", null) = "abc"
* StringUtils.strip("abc ", null) = "abc"
* StringUtils.strip(" abc ", null) = "abc"
* StringUtils.strip(" abcyx", "xyz") = " abc"
*/
newStr = StringUtils.strip(str, "stripChars");
// 去左端 给定字符串中任意字符
newStr = StringUtils.stripStart(str, "stripChars");
// 去右端 给定字符串中任意字符
newStr = StringUtils.stripEnd(str, "stripChars");
3. 字符串分割
/*
* 按照空格分割字符串 结果为数组
* StringUtils.split(null) = null
* StringUtils.split("") = []
* StringUtils.split("abc def") = ["abc", "def"]
* StringUtils.split("abc def") = ["abc", "def"]
* tringUtils.split(" abc ") = ["abc"]
*/
StringUtils.split(str);
// 按照某些字符分割 结果为数组,自动去除了截取后的空字符串
StringUtils.split(str, ",");
4. 取子字符串
// 获得"ab.cc.txt"中最后一个.之前的字符串
StringUtils.substringBeforeLast("ab.cc.txt", "."); // ab.cc
// 相似方法
// 获得"ab.cc.txt"中最后一个.之后的字符串(常用于获取文件后缀名)
StringUtils.substringAfterLast("ab.cc.txt", "."); // txt
// 获得"ab.cc.txt"中第一个.之前的字符串
StringUtils.substringBefore("ab.cc.txt", "."); // ab
// 获得"ab.cc.txt"中第一个.之后的字符串
StringUtils.substringAfter("ab.cc.txt", "."); // cc.txt
// 获取"ab.cc.txt"中.之间的字符串
StringUtils.substringBetween("ab.cc.txt", "."); // cc
// 看名字和参数应该就知道干什么的了
StringUtils.substringBetween("a(bb)c", "(", ")"); // bb
5. 其他
// 首字母大写
StringUtils.capitalize("test"); // Test
// 字符串合并
StringUtils.join(new int[]{1,2,3}, ",");// 1,2,3
// 缩写
StringUtils.abbreviate("abcdefg", 6);// "abc..."
// 判断字符串是否是数字
StringUtils.isNumeric("abc123");// false
// 删除指定字符
StringUtils.remove("abbc", "b"); // ac
// ... ... 还有很多,感兴趣可以自己研究
6. 随机字符串
// 随机生成长度为5的字符串
RandomStringUtils.random(5);
// 随机生成长度为5的"只含大小写字母"字符串
RandomStringUtils.randomAlphabetic(5);
// 随机生成长度为5的"只含大小写字母和数字"字符串
RandomStringUtils.randomAlphanumeric(5);
// 随机生成长度为5的"只含数字"字符串
RandomStringUtils.randomNumeric(5);
03. 反射相关
反射是Java中非要重要的特性,原生的反射API代码冗长,Lang包中反射相关的工具类可以很方便的实现反向相关功能,下面看例子
1. 属性操作
public class ReflectDemo {
private static String sAbc = "111";
private String abc = "123";
public void fieldDemo() throws Exception {
ReflectDemo reflectDemo = new ReflectDemo();
// 反射获取对象实例属性的值
// 原生写法
Field abcField = reflectDemo.getClass().getDeclaredField("abc");
abcField.setAccessible(true);// 设置访问级别,如果private属性不设置则访问会报错
String value = (String) abcField.get(reflectDemo);// 123
// commons写法
String value2 = (String) FieldUtils.readDeclaredField(reflectDemo, "abc", true);//123
// 方法名如果不含Declared会向父类上一直查找
}
}
注:方法名含Declared的只会在当前类实例上寻找,不包含Declared的在当前类上找不到则会递归向父类上一直查找。
相关方法:
public class ReflectDemo {
private static String sAbc = "111";
private String abc = "123";
public void fieldRelated() throws Exception {
ReflectDemo reflectDemo = new ReflectDemo();
// 反射获取对象属性的值
String value2 = (String) FieldUtils.readField(reflectDemo, "abc", true);//123
// 反射获取类静态属性的值
String value3 = (String) FieldUtils.readStaticField(ReflectDemo.class, "sAbc", true);//111
// 反射设置对象属性值
FieldUtils.writeField(reflectDemo, "abc", "newValue", true);
// 反射设置类静态属性的值
FieldUtils.writeStaticField(ReflectDemo.class, "sAbc", "newStaticValue", true);
}
}
2. 获取注解方法
// 获取被Test注解标识的方法
// 原生写法
List<Method> annotatedMethods = new ArrayList<Method>();
for (Method method : ReflectDemo.class.getMethods()) {
if (method.getAnnotation(Test.class) != null) {
annotatedMethods.add(method);
}
}
// commons写法
Method[] methods = MethodUtils.getMethodsWithAnnotation(ReflectDemo.class, Test.class);
3. 方法调用
private static void testStaticMethod(String param1) {}
private void testMethod(String param1) {}
public void invokeDemo() throws Exception {
// 调用函数"testMethod"
ReflectDemo reflectDemo = new ReflectDemo();
// 原生写法
Method testMethod = reflectDemo.getClass().getDeclaredMethod("testMethod");
testMethod.setAccessible(true); // 设置访问级别,如果private函数不设置则调用会报错
testMethod.invoke(reflectDemo, "testParam");
// commons写法
MethodUtils.invokeExactMethod(reflectDemo, "testMethod", "testParam");
// ---------- 类似方法 ----------
// 调用static方法
MethodUtils.invokeExactStaticMethod(ReflectDemo.class, "testStaticMethod", "testParam");
// 调用方法(含继承过来的方法)
MethodUtils.invokeMethod(reflectDemo, "testMethod", "testParam");
// 调用static方法(当前不存在则向父类寻找匹配的静态方法)
MethodUtils.invokeStaticMethod(ReflectDemo.class, "testStaticMethod", "testParam");
}
其他还有ClassUtils,ConstructorUtils,TypeUtils等不是很常用,有需求的可以现翻看类的源码。
04. 系统相关
主要是获取操作系统和JVM一些信息,下面看例子
// 判断操作系统类型
boolean isWin = SystemUtils.IS_OS_WINDOWS;
boolean isWin10 = SystemUtils.IS_OS_WINDOWS_10;
boolean isWin2012 = SystemUtils.IS_OS_WINDOWS_2012;
boolean isMac = SystemUtils.IS_OS_MAC;
boolean isLinux = SystemUtils.IS_OS_LINUX;
boolean isUnix = SystemUtils.IS_OS_UNIX;
boolean isSolaris = SystemUtils.IS_OS_SOLARIS;
// ... ...
// 判断java版本
boolean isJava6 = SystemUtils.IS_JAVA_1_6;
boolean isJava8 = SystemUtils.IS_JAVA_1_8;
boolean isJava11 = SystemUtils.IS_JAVA_11;
boolean isJava14 = SystemUtils.IS_JAVA_14;
// ... ...
// 获取java相关目录
File javaHome = SystemUtils.getJavaHome();
File userHome = SystemUtils.getUserHome();// 操作系统用户目录
File userDir = SystemUtils.getUserDir();// 项目所在路径
File tmpDir = SystemUtils.getJavaIoTmpDir();
Apache Commons IO
Apache Commons IO是对java.io的扩展,主要是对Java中的bio封装了一些好用的工具类,nio涉及的较少,关于bio和nio问题我们后续再聊。
Commons IO目前最新版本是2.10.0,最低要求Java8以上。
以下为整体结构:
org.apache.commons.ioorg.apache.commons.io.comparator
org.apache.commons.io.file
org.apache.commons.io.filefilter
org.apache.commons.io.function
org.apache.commons.io.inputorg.apache.commons.io.monitor
org.apache.commons.io.outputorg.apache.commons.io.serialization
下面只列举其中常用的加以说明,其余感兴趣的可以自行翻阅源码研究。
01.IOUtils
IOUtils可以说是Commons IO中最常用的了,下面直接看例子。
1. 关闭流
InputStream inputStream = new FileInputStream("test.txt");
OutputStream outputStream = new FileOutputStream("test.txt");
// 原生写法
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// commons写法(可以传任意数量的流)
IOUtils.closeQuietly(inputStream, outputStream);
2. 读取流
// ==== 输入流转换为byte数组 ====
// 原生写法
InputStream is = new FileInputStream("foo.txt");
byte[] buf = new byte[1024];
int len;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while ((len = is.read(buf)) != -1) {
out.write(buf, 0, len);
}
byte[] result = out.toByteArray();
// commons写法
byte[] result2 = IOUtils.toByteArray(is);
// ---------------------------------------
// ==== 输入流转换为字符串 ====
// 原生写法
InputStream is = new FileInputStream("foo.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
String result = sb.toString();
// commons写法
String result2 = IOUtils.toString(is, "UTF-8");
// IOUtils.toString 还有很多重载方法,保证有你想要的
// 将reader转换为字符串
String toString(Reader reader, String charset) throws IOException;
// 将url转换为字符串,也就是可以直接将网络上的内容下载为字符串
String toString(URL url, String charset) throws IOException;
3. 其他
// 按照行读取结果
InputStream is = new FileInputStream("test.txt");
List<String> lines = IOUtils.readLines(is, "UTF-8");
// 将行集合写入输出流
OutputStream os = new FileOutputStream("newTest.txt");
IOUtils.writeLines(lines, System.lineSeparator(), os, "UTF-8");
// 拷贝输入流到输出流
InputStream inputStream = new FileInputStream("src.txt");
OutputStream outputStream = new FileOutputStream("dest.txt");
IOUtils.copy(inputStream, outputStream);
02. 文件相关
文件相关主要有FileUtils:文件工具类,FilenameUtils:文件名工具类,PathUtils:路径工具类(主要是操作JDK7新增的java.nio.file.Path类)
1. 文件读写
File readFile = new File("test.txt");
// 读取文件
String str = FileUtils.readFileToString(readFile, "UTF-8");
// 读取文件为字节数组
byte[] bytes = FileUtils.readFileToByteArray(readFile);
// 按行读取文件
List<String> lines = FileUtils.readLines(readFile, "UTF-8");
File writeFile = new File("out.txt");
// 将字符串写入文件
FileUtils.writeStringToFile(writeFile, "测试文本", "UTF-8");
// 将字节数组写入文件
FileUtils.writeByteArrayToFile(writeFile, bytes);
// 将字符串列表一行一行写入文件
FileUtils.writeLines(writeFile, lines, "UTF-8");
2. 移动和复制
File srcFile = new File("src.txt");
File destFile = new File("dest.txt");
File srcDir = new File("/srcDir");
File destDir = new File("/destDir");
// 移动/拷贝文件
FileUtils.moveFile(srcFile, destFile);
FileUtils.copyFile(srcFile, destFile);
// 移动/拷贝文件到目录
FileUtils.moveFileToDirectory(srcFile, destDir, true);
FileUtils.copyFileToDirectory(srcFile, destDir);
// 移动/拷贝目录
FileUtils.moveDirectory(srcDir, destDir);
FileUtils.copyDirectory(srcDir, destDir);
// 拷贝网络资源到文件
FileUtils.copyURLToFile(new URL("http://xx"), destFile);
// 拷贝流到文件
FileUtils.copyInputStreamToFile(new FileInputStream("test.txt"), destFile);
// ... ...
3. 其他文件操作
File file = new File("test.txt");
File dir = new File("/test");
// 删除文件
FileUtils.delete(file);
// 删除目录
FileUtils.deleteDirectory(dir);
// 文件大小,如果是目录则递归计算总大小
long s = FileUtils.sizeOf(file);
// 则递归计算目录总大小,参数不是目录会抛出异常
long sd = FileUtils.sizeOfDirectory(dir);
// 递归获取目录下的所有文件
Collection<File> files = FileUtils.listFiles(dir, null, true);
// 获取jvm中的io临时目录
FileUtils.getTempDirectory();
// ... ...
4. 文件名称相关
// 获取名称,后缀等
String name = "/home/xxx/test.txt";
FilenameUtils.getName(name); // "test.txt"
FilenameUtils.getBaseName(name); // "test"
FilenameUtils.getExtension(name); // "txt"
FilenameUtils.getPath(name); // "/home/xxx/"
// 将相对路径转换为绝对路径
FilenameUtils.normalize("/foo/bar/.."); // "/foo"
5. JDK7的Path操作
// path既可以表示目录也可以表示文件
// 获取当前路径
Path path = PathUtils.current();
// 删除path
PathUtils.delete(path);
// 路径或文件是否为空
PathUtils.isEmpty(path);
// 设置只读
PathUtils.setReadOnly(path, true);
// 复制
PathUtils.copyFileToDirectory(Paths.get("test.txt"), path);
PathUtils.copyDirectory(Paths.get("/srcPath"), Paths.get("/destPath"));
// 统计目录内文件数量
Counters.PathCounters counters = PathUtils.countDirectory(path);
counters.getByteCounter(); // 字节大小
counters.getDirectoryCounter(); // 目录个数
counters.getFileCounter(); // 文件个数
// ... ...
03. 流相关
org.apache.commons.io.input和org.apache.commons.io.output包下有许多好用的过滤流,下面列举几个做下说明
1. 自动关闭的输入流 AutoCloseInputStream
/**
* AutoCloseInputStream是一个过滤流,用来包装其他流,读取完后流会自动关掉
* 实现原理很简单,当读取完后将底层的流关闭,然后创建一个ClosedInputStream赋值给它包装的输入流。
* 注:如果输入流没有全部读取是不会关掉底层流的
*/
public void autoCloseDemo() throws Exception {
InputStream is = new FileInputStream("test.txt");
AutoCloseInputStream acis = new AutoCloseInputStream(is);
IOUtils.toByteArray(acis); // 将流全部读完
// 可以省略关闭流的逻辑了
}
2. 倒序文件读取 ReversedLinesFileReader
// 从后向前按行读取
try (ReversedLinesFileReader reader = new ReversedLinesFileReader(new File("test.txt"), Charset.forName("UTF-8"))) {
String lastLine = reader.readLine(); // 读取最后一行
List<String> line5 = reader.readLines(5); // 从后再读5行
}
3. 带计数功能的流 CountingInputStream,CountingOutputStream
/**
* 大家都知道只给一个输入流咱们是没办法准确的知道它的大小的,虽说流提供了available()方法
* 但是这个方法只有在ByteArrayInputStream的情况下拿到的是准确的大小,其他如文件流网络流等都是不准确的
* (当然用野路子也可以实现,比如写入临时文件通过File.length()方法获取,然后在将文件转换为文件流)
* 下面这个流可以实现计数功能,当把文件读完大小也就计算出来了
*/
public void countingDemo() {
InputStream is = new FileInputStream("test.txt");
try (CountingInputStream cis = new CountingInputStream(is)) {
String txt = IOUtils.toString(cis, "UTF-8"); // 文件内容
long size = cis.getByteCount(); // 读取的字节数
} catch (IOException e) {
// 异常处理
}
}
4. 可观察的输入流 ObservableInputStream
可观察的输入流(典型的观察者模式),可实现边读取边处理
比如将某些字节替换为另一个字节,计算md5摘要等
当然你也可以完全写到文件后在做处理,这样相当于做了两次遍历,性能较差。
这是一个基类,使用时需要继承它来扩展自己的流,示例如下:
private class MyObservableInputStream extends ObservableInputStream {
class MyObserver extends Observer {
@Override
public void data(final int input) throws IOException {
// 做自定义处理
}
@Override
public void data(final byte[] input, final int offset, final int length) throws IOException {
// 做自定义处理
}
}
public MyObservableInputStream(InputStream inputStream) {
super(inputStream);
}
}
5. 其他
-
BOMInputStream: 同时读取文本文件的bom头
-
BoundedInputStream:有界的流,控制只允许读取前x个字节
-
BrokenInputStream: 一个错误流,永远抛出IOException
-
CharSequenceInputStream: 支持StringBuilder,StringBuffer等读取
-
LockableFileWriter: 带锁的Writer,同一个文件同时只允许一个流写入,多余的写入操作会跑出IOException
-
StringBuilderWriter: StringBuilder的Writer
-
... ...
04. 文件比较器
org.apache.commons.io.compare包有很多现成的文件比较器,可以对文件排序的时候直接拿来用。
DefaultFileComparator:默认文件比较器,直接使用File的compare方法。(文件集合排序( Collections.sort() )时传此比较器和不传效果一样)
DirectoryFileComparator:目录排在文件之前
ExtensionFileComparator:扩展名比较器,按照文件的扩展名的ascii顺序排序,无扩展名的始终排在前面
LastModifiedFileComparator:按照文件的最后修改时间排序
NameFileComparator:按照文件名称排序
PathFileComparator:按照路径排序,父目录优先排在前面
SizeFileComparator:按照文件大小排序,小文件排在前面(目录会计算其总大小)
CompositeFileComparator:组合排序,将以上排序规则组合在一起
使用示例如下:
List<File> files = Arrays.asList(new File[]{
new File("/foo/def"),
new File("/foo/test.txt"),
new File("/foo/abc"),
new File("/foo/hh.txt")});
// 排序目录在前
Collections.sort(files, DirectoryFileComparator.DIRECTORY_COMPARATOR); // ["/foo/def", "/foo/abc", "/foo/test.txt", "/foo/hh.txt"]
// 排序目录在后
Collections.sort(files, DirectoryFileComparator.DIRECTORY_REVERSE); // ["/foo/test.txt", "/foo/hh.txt", "/foo/def", "/foo/abc"]
// 组合排序,首先按目录在前排序,其次再按照名称排序
Comparator dirAndNameComp = new CompositeFileComparator(
DirectoryFileComparator.DIRECTORY_COMPARATOR,
NameFileComparator.NAME_COMPARATOR);
Collections.sort(files, dirAndNameComp); // ["/foo/abc", "/foo/def", "/foo/hh.txt", "/foo/test.txt"]
05. 文件监视器
org.apache.commons.io.monitor包主要提供对文件的创建、修改、删除的监听操作,下面直接看简单示例。
public static void main(String[] args) throws Exception {
// 监听目录下文件变化。可通过参数控制监听某些文件,默认监听目录所有文件
FileAlterationObserver observer = new FileAlterationObserver("/foo");
observer.addListener(new myListener());
FileAlterationMonitor monitor = new FileAlterationMonitor();
monitor.addObserver(observer);
monitor.start(); // 启动监视器
Thread.currentThread().join(); // 避免主线程退出造成监视器退出
}
private class myListener extends FileAlterationListenerAdaptor {
@Override
public void onFileCreate(File file) {
System.out.println("fileCreated:" + file.getAbsolutePath());
}
@Override
public void onFileChange(File file) {
System.out.println("fileChanged:" + file.getAbsolutePath());
}
@Override
public void onFileDelete(File file) {
System.out.println("fileDeleted:" + file.getAbsolutePath());
}
}