Apache Common

Apache Common包简介

Apache Commons是对JDK的拓展,包含了很多开源的工具,用于解决平时编程经常会遇到的问题,减少重复劳动。官网网址:http://commons.apache.org

Common包包含哪些类?

请参考:Apache Common官方网站

常用包梳理

| 包名         | 介绍     |
| ————- |:————-:| 
| commons-lang      | 提供字符串处理、数值处理、对象等工具方法 |
| commons-collections | 集合工具类,用来操作各种集合     |
| commons-io         | IO流操作的工具类     |
| commons-beanutils  | Java Bean 操作和封装工具     |  
| commons-dbcp2     | 数据库连接池实现  |
| commons-pool2      | 对象池实现,用于数据库连接池等 |
| commons-fileupload | HTML文件上传工具     | 
| commons-codec      | 编码/解码算法,如Base64、Hex、MD5等 |  
| commons-net        | 网络工具,如FTP、Telnet、DNS等 |
| commons-configuration | 配置文件解析器   |
| commons-vfs        | 虚拟文件系统,对各种文件系统的包装| 
| commons-digester   | XML解析器    | 
| commons-logging    | 日志记录器抽象层|
| commons-validator  | 数据校验器|

Commons BeanUtils

针对Bean的一个工具集。由于Bean往往是有一堆get和set组成,所以BeanUtils也是在此基础上进行一些包装。它利用Java的反射机制,从动态的生成对bean的getter和setter的调用代码,到模拟创建一个动态的bean,等等。
这个包看似简单,却是很多开源项目的基石:如在著名的Struts和Spring Framework中,我们都能找到BeanUtils的影子。大家猜猜看,有哪位名人是BeanUtils的作者之一?没错,就是Struts的创始人Craig McClanahan。
一个比较常用的功能是Bean Copy,也就是copy bean的属性。如果做分层架构开发的话就会用到,比如从PO(Persistent Object)拷贝数据到VO(Value Object)。

模块介绍

包名介绍
beanutils核心包,主要是一些常用的工具类及接口定义
converters转换String到需要类型的类,实现Converter接口
这些转换器实现了 LocaleConverter 接口,并提供了根据本地语言环境格式化对象到字符串以及解析字符串到对象的功能。
主要用途是:
国际化: 根据不同语言环境解析和格式化数值
本地化的 Bean 操作: 解析不同本地语言环境的请求参数到 Bean
expression统一的表达式语言接口,定义表达式解析、求值和设置属性值等方法。

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import lombok.Data;  
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.converters.DateConverter;
import org.junit.Test;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
* CovertTest * @author will */public class BeanUtilTest {

//将字符串转换为指定类型
@Test
public void testCovertToInteger() {
Object i = ConvertUtils.convert("123", Integer.class);
if (i instanceof Integer) {
System.out.println("it can cast to integer");
System.out.println("the number result is " + (Integer) i);
} else {
System.err.print("it can not cast to target Instance!");
}
}
//将字符串数组转换为指定类型数组
@Test
public void testConvertArrToIntegerArr() {
Object convert = ConvertUtils.convert(new String[]{"1", "2", "3"}, Integer.class);
if (convert instanceof Integer[]) {
System.out.println("it can cast to Integer Array");
Integer[] integers = (Integer[]) convert;
Arrays.stream(integers).forEach(System.out::println);
} else {
System.err.print("it can not cast to target Instance!");
}
}
//将字符串转换为日期类型
@Test
public void testCovertToDate () {
// 日期格式转换
DateConverter dateConverter = new DateConverter();
dateConverter.setPatterns(new String[]{"yyyy-MM-dd","yyyy-MM-dd HH:mm:ss"});
ConvertUtils.register(dateConverter, Date.class);
Object convert = ConvertUtils.convert("2023-12-01", Date.class);
System.out.println(convert);
// 常规时间类型转换
System.out.println(ConvertUtils.convert("2023-06-30 09:53:55", Date.class));
}

@Test
public void testBeanUtil() throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "will");
map.put("age", "18");

// 将map数据拷贝到Java Bean中
Person p = new Person();
BeanUtils.populate(p, map);
System.out.println("p is " + p);
// 对象拷贝
Person p1 = new Person();
BeanUtils.copyProperties(p1, p);
System.out.println("p1 is " + p1);
// 拷贝指定的属性
Person p2 = new Person();
BeanUtils.copyProperty(p2,"name","will_yang");
System.out.println("p2 is " + p2);
// 设置指定的属性
BeanUtils.setProperty(p2, "age", 20);
System.out.println("p2 is " + p2);
//对象克隆
Object bean = BeanUtils.cloneBean(p2);
System.out.println("bean = " + bean);
}

@Data
public static class Person{
private String name;
private String age;
}
}

Commons Codec

是编码和解码组件,提供常用的编码和解码方法,如DES、SHA1、MD5、Base64、URL和Soundx等。

二进制相关

二进制包主要提供16进制、Base64、Base32等的编解码工具类。

16进制(Hex类)

十六进制常用于将二进制以更简短的方式展示,比如MD5是128位,展现起来太长,而转换为16进制后只需要32个字符即可表示出来。
示例代码如下:

1
2
3
4
5
6
// byte数组转为16进制字符串
String hex = Hex.encodeHexString("123".getBytes());
System.out.println(hex);
// 16进制字符串解码
byte[] src = Hex.decodeHex(hex);
System.out.println(new String(src));

2. Base64,Base32,Base16

Base64是网络上最常见的用于传输二进制数据的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。Base32就是使用32个可打印字符,Base16就是使用16个(实际上相当于16进制)。

名称编码表字符串位数不足是否会补全 =
base16数字09 和 字母 AF不会,位数刚好是 4 的倍数
base32大写字母AZ 和 数字27
base64Base大写字母A-Z,小写字母a-z,数字0~9以及”+”,”/“
1
2
3
4
5
6
7
8
9
10
// base64编码
String base64 = Base64.encodeBase64String("测试".getBytes());
System.out.println(base64);
// base64解码
byte[] src = Base64.decodeBase64(base64);
System.out.println(new String(src));
// 字符串是否是base64
Base64.isBase64(base64);

// Base32 Base16 同理

Codec还提供了Base系列的流处理,以流的方式去处理Base编解码,示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 以流方式提供Base64编码和解码
// 附:"123"的base64编码为"MTIz"

// 对输入流做base64编码
InputStream is = new ByteArrayInputStream("123".getBytes());
Base64InputStream ebis = new Base64InputStream(is, true);
String enc = IOUtils.toString(ebis, "UTF-8"); // MTIz

// 对base64数据流做解码
is = new ByteArrayInputStream(enc.getBytes());
Base64InputStream dbis = new Base64InputStream(is, false);
String dec = IOUtils.toString(dbis, "UTF-8"); // 123

// -----------------------

// 将数据做base64编码写入输出流
final String data = "123";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Base64OutputStream ebos = new Base64OutputStream(baos, true);
IOUtils.write(data, ebos, "UTF-8");
String enc2 = baos.toString(); // MTIz

// 将base64数据做解码写入输出流
baos = new ByteArrayOutputStream();
Base64OutputStream dbos = new Base64OutputStream(baos, false);
IOUtils.write(data, dbos, "UTF-8");
String dec2 = dbos.toString(); // 123

URL相关

URL之所以要进行编码,是因为URL中有些字符会引起歧义。

例如URL参数字符串中使用key=value键值对这样的形式来传参,键值对之间以&符号分隔,如/s?q=abc&ie=utf-8。如果你的value字符串中包含了=或者&,那么势必会造成接收URL的服务器解析错误,因此必须将引起歧义的&和=符号进行转义,也就是对其进行编码。

又如URL的编码格式采用的是ASCII码,而不是Unicode,这也就是说你不能在URL中包含任何非ASCII字符,例如中文。否则如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,中文可能会造成问题。

URL编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。

编解码示例代码如下:

1
2
3
4
5
6
7
URLCodec urlCodec = new URLCodec();
// url编码
String encUrl = urlCodec.encode("http://x.com?f=哈");
System.out.println(encUrl);
// url解码
String decUrl = urlCodec.decode(encUrl);
System.out.println(decUrl);

摘要算法

摘要算法是一种单向的散列算法,它满足以下几个特点。

  • 输入长度是任意的
  • 输出长度是固定的
  • 对每一个给定的输入,计算输出是很容易的
  • 不可逆,无法通过输出推算出原数据
  • 输出不依赖于输入。就是输入数据变动一个字节结果会相差很多

由于摘要算法以上特点,主要用于数据完整性校验。例如网上的资源一般会提供一个摘要值(一般用MD5算法),用户下载后可以通过工具对资源做MD5后和网上给定的值比较,如果不一致说明文件不完整了,可能是下载过程网络波动内容有丢失,也可能被人篡改过。

也可以做数据的指纹,比如网盘秒传,就是利用摘要值做判断。客户端上传前先对文件做摘要值,传给服务端,服务端发现有相同摘要的文件说明两个文件内容是一致的,这样就无需上传直接将文件存储路径指向这个文件就可以了,既实现了秒传,还节约了服务器磁盘空间(不同用户相同内容的文件实际上指向的是同一份文件)。

很多系统也将密码做md5后存储,其中这种方式并不安全。md5已经很很多公开结果了,并且使用彩虹表碰撞也很容易破解了。所以并不建议使用md5存储密码。密码推荐使用BCrypt算法。

摘要算法主要有以下几个

  • MD(Message Digest):消息摘要
  • SHA(Secure Hash Algorithm):安全散列
  • MAC(Message Authentication Code):消息认证码

1. MD系列

主要有MD2、MD4、MD5,目前一般常用MD5

1
2
3
4
// 如果使用Java自带的api需要十多行才能实现md5算法

// 对数据做md5,参数支持字符串,字节数据,输入流
String md5 = DigestUtils.md5Hex("测试");

2. SHA系列

SHA系列有SHA-1、SHA-224、SHA-256、SHA-384、SHA-512,SHA3-224、SHA3-256、SHA3-384、SHA3-512等。目前安全起见一般选择256以上,推荐384以上。当然摘要越长则计算耗时也越长,需要根据需求权衡。

1
2
3
4
5
6
7
8
// 参数支持字符串,字节数据,输入流
String sha1 = DigestUtils.sha1Hex("测试");
String sha256 = DigestUtils.sha256Hex("测试");
String sha384 = DigestUtils.sha384Hex("测试");
String sha512 = DigestUtils.sha512Hex("测试");
String sha3_256 = DigestUtils.sha3_256Hex("测试");
String sha3_384 = DigestUtils.sha3_384Hex("测试");
String sha3_512 = DigestUtils.sha3_512Hex("测试");

3. HMAC系列

HMAC(keyed-Hash Message Authentication Code)系列是包含密钥的散列算法,包含了MD和SHA两个系列的消息摘要算法。融合了MD,SHA:

MD系列:HMacMD2,HMacMD4,HMacMD5

SHA系列:HMacSHA1,HMacSHA224,HMacSHA256,HMacSHA38

,HMacSHA512

1
2
3
4
5
6
7
8
String key = "asdf3234asdf3234asdf3234asdf3234";
String valueToDigest = "测试数据"; // valueToDigest参数支持字节数据,流,文件等
// 做HMAC-MD5摘要
String hmacMd5 = new HmacUtils(HmacAlgorithms.HMAC_MD5, key).hmacHex(valueToDigest);
// 做HMAC-sha摘要
String hmacSha256 = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, key).hmacHex(valueToDigest);
String hmacSha384 = new HmacUtils(HmacAlgorithms.HMAC_SHA_384, key).hmacHex(valueToDigest);
String hmacSha512 = new HmacUtils(HmacAlgorithms.HMAC_SHA_512, key).hmacHex(valueToDigest);

Commons Collections

Apache Commons Collections 是对 java.util.Collection 的扩展。

目前 Collections 包有两个:

  • commons-collections
  • commons-collections4
    commons-collections 最新版本是3.2.2,不支持泛型,目前官方已不在维护。
    collections4 目前最新版本是4.4,最低要求 Java8 以上。
    相对于 collections 来说完全支持 Java8 的特性并且支持泛型,该版本无法兼容旧有版本,于是为了避免冲突改名为 collections4。推荐直接使用该版本。(注:两个版本可以共存,使用时需要注意)。
    这里使用3.2.2版本的中包名做下介绍:
包名介绍
collectionsCommonsCollections自定义的一组公用的接口和工具类
bag实现Bag接口的一组类
bidimap实现BidiMap系列接口的一组类
buffer实现Buffer接口的一组类
collection实现java.util.Collection接口的一组类
comparators实现java.util.Comparator接口的一组类
functorsCommons Collections自定义的一组功能类
iterators实现java.util.Iterator接口的一组类
keyvalue实现集合和键/值映射相关的一组类
list实现java.util.List接口的一组类
map实现Map系列接口的一组类
set实现Set系列接口的一组类

工具类

CollectionUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String str = null;
List list1 = Arrays.asList(new String[]{"1", "2", "3"});
List list2 = Arrays.asList(new String[]{"1", "2", "4"});
// 判断是否为空(null或空list都为true)
CollectionUtils.isEmpty(list1);
// 添加元素(忽略null元素)
CollectionUtils.addIgnoreNull(list1, str);
// list是否包含subList中的所有元素
CollectionUtils.containsAll(list1, list2); // false
// list是否包含subList中的任意一个元素
CollectionUtils.containsAny(list1, list2); // true
// list1 减去 list2
CollectionUtils.subtract(list1, list2); // ["3"]
// 合并两个list并去重
CollectionUtils.union(list1, list2); //["1", "2", "3", "4"]
// 取两个list同时存在的元素
CollectionUtils.intersection(list1, list2); // [1", "2"]

ListUtils

1
2
3
4
5
6
7
8
9
10
11
12
List list1 = Arrays.asList(new String[]{"1", "2", "3"});
List list2 = Arrays.asList(new String[]{"1", "2", "4"});
// 同CollectionUtils, 返回结果为List
ListUtils.subtract(list1, list2); // ["3"]
ListUtils.union(list1, list2); //["1", "2", "3", "4"]
ListUtils.intersection(list1, list2); // [1", "2"]
// 判断两个集合中的内容是否完全相同(顺序也一致)
ListUtils.isEqualList(list1, list2); // false
// list1如果为null则转换为空List
ListUtils.emptyIfNull(list1);
// list1中所有元素做Hash
ListUtils.hashCodeForList(list1);

除了以上介绍了两个还有 MapUtils,SetUtils,EnumerationUtils,IterableUtils 等不是很常用就不多做介绍了。

集合扩展

FixedSizeList

FixedSizeList 用于装饰另一个 List 以阻止修改其大小。不支持添加、删除、清除等操作。set 方法是允许的(因为它不会改变列表大小),下面看代码示例

1
2
3
4
5
6
7
8
9
10
List<String> sourceList = new ArrayList<>();
sourceList.add("1");
// 装饰一下原list
List<String> list = FixedSizeList.fixedSizeList(sourceList);
list.set(0, "11");
println(list); // [11,2,3]
// 以下改变容器size的操作会抛出异常
list.add("4"); // UnsupportedOperationException("List is fixed size")
list.remove("5"); // UnsupportedOperationException("List is fixed size")
list.clear(); // UnsupportedOperationException("List is fixed size")

SetUniqueList

SetUniqueList 用来装饰另一个 List 以确保不存在重复元素,内部使用了 Set 来判断重复问题

1
2
3
4
5
6
7
8
List<String> sourceList = new ArrayList<>();
sourceList.add("1");
sourceList.add("2");
// 元素不重复的list
SetUniqueList<String> list = SetUniqueList.setUniqueList(sourceList);
// 存在则不处理,不会影响原来顺序
list.add("2");
println(list); // [1,2]

TransformedList

TransformedList 装饰另一个 List 以转换添加的对象。add 和 set 方法受此类影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<String> sourceList = new ArrayList<>();
sourceList.add("1");
sourceList.add("2");
// 转换list,在添加元素的时候会通过第二个参数Transformer转换一下
// (Transformer接口只有一个抽象方法可以使用lambda表达式)

// transformingList不会对原list的已有元素做转换
TransformedList<String> list = TransformedList.transformingList(sourceList, e -> e.concat("_"));
list.add("a");
println(list); // [1, 2, a_]

// transformedList会对原list的已有元素做转换
list = TransformedList.transformedList(sourceList, e -> e.concat("_"));
list.add("a");
println(list); // [1_, 2_, a_]

PredicatedList

PredicatedList 装饰另一个 List ,装饰后的 List 在添加元素的时候会调用 Predicate 接口来判断元素,匹配通过才会被添加到集合中。

1
2
3
4
5
6
7
8
List<String> sourceList = new ArrayList<>();
// 在添加元素的时候会通过第二个参数Predicate判断一下是否符合要求,符合要求才添加进来
PredicatedList<String> list = PredicatedList.predicatedList(new ArrayList<>(), e -> e.startsWith("_"));
list.add("_4");
println(list); // [_4]

// 以下会抛异常:java.lang.IllegalArgumentException: Cannot add Object '4'
list.add("4");

ListOrderedSet

ListOrderedSet 有序的Set,顺序按照元素添加顺序排列,类似 List

1
2
3
4
5
6
// 有序的set,按照插入顺序排序
Set<String> set = new ListOrderedSet<>();
set.add("aa");
set.add("11");
set.add("哈哈");
println(set); // [aa,11,哈哈]

Bag

Bag 接口是带计数功能的集合扩展,它继承了 Collection 接口,可以当做集合来使用

1
2
3
4
5
6
7
// bag 带计数功能的集合
Bag<String> bag = new HashBag<>();
bag.add("a");
bag.add("b");
bag.add("a");
println(bag.size()); // 3
println(bag.getCount("a")); // 2

Map扩展

MultiValuedMap

MultiValuedMap 和正常的 Map 有点区别,同一个 key 允许存放多个 value,这些 value 会放到一个 List 中。这个功能如果用 Java 的 Map 我们需要构造一个 Map<String, List<String>> 加个各种操作来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// list实现,允许value重复
ListValuedMap<String, String> map = new ArrayListValuedHashMap<>();
map.put("user", "张三");
map.put("user", "李四");
map.put("user", "张三");
map.put("age", "12");
// 注意:value的泛型是String, 但是get方法返回的是List<String>
List<String> users2 = map.get("user"); // [张三,李四,张三]

// multiMap的其他方法
map.containsKey("user"); // true
map.containsValue("张三"); // true
map.containsMapping("user", "张三"); // true

int size = map.size(); // 4

Collection<String> ss = map.values();// [张三,李四,张三,12]
map.remove("user"); // 清空user的所有value
// 转换为原生map
Map<String, Collection<String>> jMap = map.asMap();

CaseInsensitiveMap

key大小写不敏感的Map

1
2
3
4
5
6
// key大小写不敏感
Map<String, Integer> map = new CaseInsensitiveMap<>();
map.put("one", 1);
map.put("two", 2);
Integer o = map.get("ONE");
println(o); // 1

OrderedMap

有顺序的 Map,按照插入顺序排序。如果使用 hashMap 的话 key 会按照 hash 值排序,可能和插入顺序一样,也可能不一样。key 数量和不同 JDK 版本都可能影响顺序,这是由于不同版本 jdk map 的 hash 算法有区别,hash 算法和当前 map 的容量也有关系。

1
2
3
4
5
6
7
8
9
10
// key有序:按照插入顺序
OrderedMap<String, String> map = new ListOrderedMap<>();
map.put("哈哈", "1");
map.put("此处", "2");
map.put("cc", "3");
map.put("dd", "4");
// 得到的keySet有序
Set<String> set = map.keySet(); // 哈哈,此处,cc,dd
String nk = map.nextKey("此处"); // cc
String pk = map.previousKey("此处"); // 哈哈

LRUMap

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

各种缓存框架都有对 LRU 算法的支持,如 EhCache,GuavaCache,Redis 等,可以说是很常用的一种算法

1
2
3
4
5
6
7
8
9
10
LRUMap<String, String> map = new LRUMap<>(2);
map.put("aa", "1");
map.put("bb", "2");
map.put("cc", "3");
// 最早没有被使用的aa将被移出
println(map); // [bb:2, cc:3]
// 访问一次bb,此时在put的话将会移出最早没有被访问的cc
map.get("bb");
map.put("dd", "4");
println(map); // [bb:2, dd:4]

PassiveExpiringMap

装饰一个 Map 以在达到过期时间时删除过期条目。当在 Map 中放置键值对时,此装饰器使用 ExpirationPolicy 来确定条目应保持多长时间,由到期时间值定义。当对 Map 做操作的时候才会检查元素是否过期并触发删除操作。

1
2
3
4
5
6
7
8
9
10
11
// 存活一秒钟
int ttlMillis = 1000;
PassiveExpiringMap.ExpirationPolicy<String, String> ep = new PassiveExpiringMap.ConstantTimeToLiveExpirationPolicy<>(ttlMillis);
PassiveExpiringMap<String, String> map = new PassiveExpiringMap<>(ep);
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
// 等待一秒后在获取
Thread.sleep(1000);
String vc = map.get("c");
println(vc); // null

6. ReferenceMap

ReferenceMap 允许垃圾收集器删除映射。可以指定使用什么类型的引用来存储映射的键和值。如果使用的不是强引用,则垃圾收集器可以在键或值变得不可访问,或者 JVM 内存不足时删除映射。用它做一个简易的缓存不会导致存放内容过多导致内存溢出。

BidiMap

BidiMap 允许在 key 和 value 之间进行双向查找。其中一个键可以查找一个值,一个值可以同样轻松地查找一个键。这个接口扩展了 Map,value 不允许重复,如果重复将同时覆盖旧的键值对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 双向map, 可通过value获取key
// value也不允许重复,如果重复将会覆盖旧值
BidiMap<String, String> map = new TreeBidiMap<>();
map.put("dog", "狗");
map.put("cat", "猫");
// value重复的话key也会被覆盖,相当于"cat2:猫"会覆盖掉"cat:猫"
// map.put("cat2", "猫");
println(map); // {cat=猫, dog=狗}
String key = map.getKey("狗");
println(key); // dog

// 反向,value变为key,key变为value
BidiMap<String, String> iMap = map.inverseBidiMap();
println(iMap); // {狗=dog, 猫=cat}
println(iMap.get("狗")); // dog

// 对反向map操作同时影响原map
iMap.put("鱼", "fish");
println(iMap); // {狗=dog, 猫=cat, 鱼=fish}
println(map); // {cat=猫, dog=狗, fish=鱼}

Commons Compress

压缩、解压缩文件的组件,可以操作rar、cpio、Unix dump、tar、zip、gzip、XZ、Pack200和bzip2格式的压缩文件。

压缩

压缩:按某种算法减小文件所占用空间的大小
解压:按对应的逆向算法恢复文件

类名作用
GzipCompressorOutputStream压缩"*.gz“文件
GzipCompressorInputStream解压"*.gz“文件
BZip2CompressorOutputStream压缩"*.bz2“文件
BZip2CompressorInputStream解压"*.bz2“文件
XZCompressorOutputStream压缩"*.xz“文件
XZCompressorInputStream解压"*.xz“文件
FramedLZ4CompressorOutputStream压缩"*.lz4“文件
FramedLZ4CompressorInputStream解压"*.lz4“文件
BlockLZ4CompressorOutputStream压缩"*.block_lz4“文件
BlockLZ4CompressorInputStream解压"*.block_lz4“文件
Pack200CompressorOutputStream压缩"*.pack“文件
Pack200CompressorInputStream解压"*.pack“文件
DeflateCompressorOutputStream压缩"*.deflate“文件
DeflateCompressorInputStream解压"*.deflate“文件
LZMACompressorOutputStream压缩"*.lzma“文件
LZMACompressorInputStream解压"*.lzma“文件
FramedSnappyCompressorOutputStream压缩"*.sz“文件
FramedSnappyCompressorInputStream解压"*.sz“文件
ZCompressorInputStream解压"*.Z“文件

gzip

gzip是Unix,Linux上常用的压缩工具,也是当今的WEB站点上非常流行的压缩技术。其有压缩级别等概念,可以通过GzipParameters去设置。JDK8也自带了GZIPInputStream类,用法类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// gzip压缩
String file = "/test.js";
GzipParameters parameters = new GzipParameters();
parameters.setCompressionLevel(Deflater.BEST_COMPRESSION);
parameters.setOperatingSystem(3);
parameters.setFilename(FilenameUtils.getName(file));
parameters.setComment("Test file");
parameters.setModificationTime(System.currentTimeMillis());
FileOutputStream fos = new FileOutputStream(file + ".gz");
try (GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos, parameters);
InputStream is = new FileInputStream(file)) {
IOUtils.copy(is, gzos);
}
// gzip解压
String gzFile = "/test.js.gz";
FileInputStream is = new FileInputStream(gzFile);
try (GzipCompressorInputStream gis = new GzipCompressorInputStream(is)) {
GzipParameters p = gis.getMetaData();
File targetFile = new File("/test.js");
FileUtils.copyToFile(gis, targetFile);
targetFile.setLastModified(p.getModificationTime());
}

bz2

bz2是Linux下常见的压缩文件格式,是由具有高压缩率的压缩工具bzip2生成,以后缀为.bz2结尾的压缩文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 压缩bz2
String srcFile = "/test.tar";
String targetFile = "/test.tar.bz2";
FileOutputStream os = new FileOutputStream(targetFile);
try (BZip2CompressorOutputStream bzos = new BZip2CompressorOutputStream(os);
InputStream is = new FileInputStream(srcFile)) {
IOUtils.copy(is, bzos);
}
// 解压bz2
String bzFile = "/test.tar.bz2";
FileInputStream is = new FileInputStream(bzFile);
try (BZip2CompressorInputStream bzis = new BZip2CompressorInputStream(is)) {
File targetFile = new File("test.tar");
FileUtils.copyToFile(bzis, targetFile);
}

其他压缩算法的使用方式和bz2基本一致,这里就不做代码示例了。

归档

归档:将许多零散的文件整理为一个文件,文件总大小基本不变
解包:从归档文件中释放文件

类名作用
TarArchiveOutputStream归档*.tar文件
TarArchiveInputStream解包*.tar文件
ZipArchiveOutputStream归档压缩*.zip文件
ZipArchiveInputStream解包解压*.zip文件
JarArchiveOutputStream归档压缩*.jar文件
JarArchiveInputStream解包解压*.jar文件
DumpArchiveOutputStream归档*.dump文件
DumpArchiveInputStream解包*.dump文件
CpioArchiveOutputStream归档压缩*.cpio文件
CpioArchiveInputStream解包解压*.cpio文件
ArArchiveOutputStream归档压缩*.ar文件
ArArchiveInputStream解包解压*.ar文件
ArjArchiveInputStream解包解压*.arj文件
SevenZOutputFile归档压缩*.7z文件
SevenZFile解包解压*.7z文件

其中zip,jar,cpio,ar,7z既支持归档也支持压缩,能在归档的过程中做压缩处理。

由于他们会处理一个个零散的文件,所以会有ArchiveEntry的概念,即一个ArchiveEntry代表归档包内的一个目录或文件。

tar

tar是Unix和Linux系统上的常用的压缩归档工具,可以将多个文件合并为一个文件,打包后的文件后缀亦为”tar”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// tar压缩
public void tar() throws IOException {
File srcDir = new File("/test");
String targetFile = "/test.tar";
try (TarArchiveOutputStream tos = new TarArchiveOutputStream(
new FileOutputStream(targetFile))) {
tarRecursive(tos, srcDir, "");
}
}
// 递归压缩目录下的文件和目录
private void tarRecursive(TarArchiveOutputStream tos, File srcFile, String basePath) throws IOException {
if (srcFile.isDirectory()) {
File[] files = srcFile.listFiles();
String nextBasePath = basePath + srcFile.getName() + "/";
if (ArrayUtils.isEmpty(files)) {
// 空目录
TarArchiveEntry entry = new TarArchiveEntry(srcFile, nextBasePath);
tos.putArchiveEntry(entry);
tos.closeArchiveEntry();
} else {
for (File file : files) {
tarRecursive(tos, file, nextBasePath);
}
}
} else {
TarArchiveEntry entry = new TarArchiveEntry(srcFile, basePath + srcFile.getName());
tos.putArchiveEntry(entry);
FileUtils.copyFile(srcFile, tos);
tos.closeArchiveEntry();
}
}
// tar解压
public void untar() throws IOException {
InputStream is = new FileInputStream("/test.tar");
String outPath = "/test";
try (TarArchiveInputStream tis = new TarArchiveInputStream(is)) {
TarArchiveEntry nextEntry;
while ((nextEntry = tis.getNextTarEntry()) != null) {
String name = nextEntry.getName();
File file = new File(outPath, name);
//如果是目录,创建目录
if (nextEntry.isDirectory()) {
file.mkdir();
} else {
//文件则写入具体的路径中
FileUtils.copyToFile(tis, file);
file.setLastModified(nextEntry.getLastModifiedDate().getTime());
}
}
}
}

7z

7z 是一种全新的压缩格式,它拥有极高的压缩比。

7z 格式的主要特征:

  • 开放的结构
  • 高压缩比
  • 强大的 AES-256 加密
  • 能够兼容任意压缩、转换、加密算法
  • 最高支持 16000000000 GB 的文件压缩
  • 以 Unicode 为标准的文件名
  • 支持固实压缩
  • 支持文件头压缩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 7z压缩
public void _7z() throws IOException {
try (SevenZOutputFile outputFile = new SevenZOutputFile(new File("/test.7z"))) {
File srcFile = new File("/test");
_7zRecursive(outputFile, srcFile, "");
}
}
// 递归压缩目录下的文件和目录
private void _7zRecursive(SevenZOutputFile _7zFile, File srcFile, String basePath) throws IOException {
if (srcFile.isDirectory()) {
File[] files = srcFile.listFiles();
String nextBasePath = basePath + srcFile.getName() + "/";
// 空目录
if (ArrayUtils.isEmpty(files)) {
SevenZArchiveEntry entry = _7zFile.createArchiveEntry(srcFile, nextBasePath);
_7zFile.putArchiveEntry(entry);
_7zFile.closeArchiveEntry();
} else {
for (File file : files) {
_7zRecursive(_7zFile, file, nextBasePath);
}
}
} else {
SevenZArchiveEntry entry = _7zFile.createArchiveEntry(srcFile, basePath + srcFile.getName());
_7zFile.putArchiveEntry(entry);
byte[] bs = FileUtils.readFileToByteArray(srcFile);
_7zFile.write(bs);
_7zFile.closeArchiveEntry();
}
}
// 7z解压
public void un7z() throws IOException {
String outPath = "/test";
try (SevenZFile archive = new SevenZFile(new File("test.7z"))) {
SevenZArchiveEntry entry;
while ((entry = archive.getNextEntry()) != null) {
File file = new File(outPath, entry.getName());
if (entry.isDirectory()) {
file.mkdirs();
}
if (entry.hasStream()) {
final byte [] buf = new byte [1024];
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int len = 0; (len = archive.read(buf)) > 0;) {
baos.write(buf, 0, len);
}
FileUtils.writeByteArrayToFile(file, baos.toByteArray());
}
}
}
}

修改归档文件

有时候我们会有修改归档内文件的需求,比如添加、删除一个文件,修改其中的文件内容等,当然我们也可以全部解压出来改完后在压缩回去。这样除了代码量多一些外,归档文件大也会导致操作时间过长。那么有没有办法用代码去动态的修改归档文件里的内容呢?

org.apache.commons.compress.changes包下正好就提供了一些类用于动态的修改归档文件里的内容。下面看一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
String tarFile = "/test.tar";
InputStream is = new FileInputStream(tarFile);
// 替换后会覆盖原test.tar,如果是windows可能会由于文件被访问而覆盖报错
OutputStream os = new FileOutputStream(tarFile);
try (TarArchiveInputStream tais = new TarArchiveInputStream(is);
TarArchiveOutputStream taos = new TarArchiveOutputStream(os)) {
ChangeSet changes = new ChangeSet();
// 删除"test.tar中"的"dir/1.txt"文件
changes.delete("dir/1.txt");
// 删除"test.tar"中的"t"目录
changes.delete("t");
// 添加文件,如果已存在则替换
File addFile = new File("/a.txt");
ArchiveEntry addEntry = taos.createArchiveEntry(addFile, addFile.getName());
// add可传第三个参数:true: 已存在则替换(默认值), false: 不替换
changes.add(addEntry, new FileInputStream(addFile));
// 执行修改
ChangeSetPerformer performer = new ChangeSetPerformer(changes);
ChangeSetResults result = performer.perform(tais, taos);
}

其他

简单工厂

commons-compress还提供了一些简单的工厂类用户动态的获取压缩流和归档流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用factory动态获取归档流
ArchiveStreamFactory factory = new ArchiveStreamFactory();
String archiveName = ArchiveStreamFactory.TAR;
InputStream is = new FileInputStream("/in.tar");
OutputStream os = new FileOutputStream("/out.tar");
// 动态获取实现类,此时ais实际上是TarArchiveOutPutStream
ArchiveInputStream ais = factory.createArchiveInputStream(archiveName, is);
ArchiveOutputStream aos = factory.createArchiveOutputStream(archiveName, os);
// 其他业务操作

// ------------------------

// 使用factory动态获取压缩流
CompressorStreamFactory factory = new CompressorStreamFactory();
String compressName = CompressorStreamFactory.GZIP;
InputStream is = new FileInputStream("/in.gz");
OutputStream os = new FileOutputStream("/out.gz");
// 动态获取实现类,此时ais实际上是TarArchiveOutPutStream
CompressorInputStream cis = factory.createCompressorInputStream(compressName, is);
CompressorOutputStream cos = factory.createCompressorOutputStream(compressName, os);
// 其他业务操作

同时解压解包

上面说了很多都是单一的操作,那么如果解压”test.tar.gz”这种归档和压缩于一体的文件呢?

其实很简单,我们不需要先解压在解包,可以一步同时完成解压和解包,只需要将对应的流包装一下即可(不得不感叹Java IO的装饰者模式设计真的很巧妙)。下面看代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 解压 解包test.tar.gz文件
String outPath = "/test";
InputStream is = new FileInputStream("/test.tar.gz");
// 先解压,所以需要先用gzip流包装文件流
CompressorInputStream gis = new GzipCompressorInputStream(is);
// 在解包,用tar流包装gzip流
try (ArchiveInputStream tgis = new TarArchiveInputStream(gis)) {
ArchiveEntry nextEntry;
while ((nextEntry = tgis.getNextEntry()) != null) {
String name = nextEntry.getName();
File file = new File(outPath, name);
// 如果是目录,创建目录
if (nextEntry.isDirectory()) {
file.mkdir();
} else {
// 文件则写入具体的路径中
FileUtils.copyToFile(tgis, file);
file.setLastModified(nextEntry.getLastModifiedDate().getTime());
}
}
}

Commons Configuration

是一个Java应用程序的配置管理工具,可以从properties或者xml文件中加载配置信息。

Commons CSV

是一个用来读写各种Comma Separated Value(CSV)格式文件的Java类库。

Commons Daemon

实现将普通的Java应用变成系统的后台服务,例如 Tomcat 就是利用这个项目来实现作为 Linux 和 Windows 的服务启动和停止的。

Commons DBCP

数据库连接池。

Commons DBUtils

是JDBC工具组件,对传统操作数据库的类进行二次封装,可以把结果集转化成List。

Commons Digester

是XML到Java对象的映射工具集。

Commons Email

是邮件操作组件,对Java Mail API进行了封装,提供了常用的邮件发送和接收类,简化邮件操作。该组件依赖Java Mail API。

Commons Exec

提供一些常用的方法用来执行外部进程,如执行exe文件或命令行。

Commons FileUpload

为Web应用程序或Servlet提供文件上传功能,Struts2和SpringMVC的文件上传组件。

Commons IO

是处理IO的工具类包,对java.io进行扩展,提供了更加方便的IO操作。

Commons JCI

提供通用的Java编译器接口。

Commons Lang3

是处理Java基本对象方法的工具类包,该类包提供对字符、数组等基本对象的操作,弥补了java.lang api基本处理方法上的不足。

工具类简介
ArrayUtils用于对数组的操作,如添加、查找、删除、子数组、倒序、元素类型转换等
BitField用于操作位元,提供了一些方便而安全的方法
BooleanUtils用于操作和转换boolean或者Boolean及相应的数组
CharEncoding包含了Java环境支持的字符编码,提供是否支持某种编码的判断
CharRange用于设定字符范围并做相应检查
CharSet用于设定一组字符作为范围并做相应检查
CharSetUtils用于操作CharSet
CharUtils用于操作char值和Character对象
ClassUtils用于对Java类的操作,不使用反射
ObjectUtils用于操作Java对象,提供null安全的访问和其他一些功能
RandomStringUtils用于生成随机的字符串
SerializationUtils用于处理对象序列化,提供比一般Java序列化更高级的处理能力
StringEscapeUtils用于正确处理转义字符,产生正确的Java、JavaScript、HTML、XML和SQL代码
StringUtils处理String的核心类,提供了相当多的功能
SystemUtils在java.lang.System基础上提供更方便的访问,如用户路径、Java版本、时区、操作系统等判断
Validate提供验证的操作,有点类似assert断言
WordUtils用于处理单词大小写、换行等

Commons Logging

提供统一的日志接口,同时兼顾轻量级和不依赖于具体的实现。类包给中间件/日志工具开发者一个简单的日志操作抽象,允许程序开发人员使用不同的具体日志实现工具。

Commons Math

轻量级自容器的数学和统计计算方法类包,包含大多数常用的数值算法。

Commons Net

封装了各种网络协议的客户端,支持FTP、NNTP、SMTP、POP3、Telnet等协议。

Commons Pool

提供了一整套用于实现对象池化的框架,以及若干各具特色的对象池实现,可以有效地减少处理对象池化时的工作量。类包用于提高像文件句柄、数据库连接、socket通信这类大对象的调用效率,简单的说就是一种对象一次创建多次使用的技术。

Commons Primitives

提供了一个更小,更快和更易使用的对Java基本类型的支持。

Commons Validator

提供了一个简单的、可扩展的框架来在一个XML文件中定义校验器(校验方法)和校验规则。支持校验规则的和错误消息的国际化。

Apache HttpClient

曾经是Apache Commons的子项目,后来独立出来。HttpClient简化HTTP客户端与服务器的各种通讯,实现HTTP客户端程序(也就是浏览器程序)的功能。


Apache Common
https://github.com/yangxiangnanwill/yangxiangnanwill.github.io/2024/01/03/好好码代码吖/JAVA/常用类库详解/Apache Common/
作者
will
发布于
2024年1月3日
许可协议