本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Hutool
官网
//官网 https://hutool.cn/
// 中文文档 https://www.hutool.cn/docs/#/
//API文档 https://apidoc.gitee.com/dromara/hutool/
注意点
注意 Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 如果你的项目使用JDK7,请使用Hutool 4.x版本
功能简介
一个Java基础工具类,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类,同时提供以下组件:
| 模块 | 介绍 |
|---|---|
| hutool-aop | JDK动态代理封装,提供非IOC下的切面支持 |
| hutool-bloomFilter | 布隆过滤,提供一些Hash算法的布隆过滤 |
| hutool-cache | 简单缓存实现 |
| hutool-core | 核心,包括Bean操作、日期、各种Util等 |
| hutool-cron | 定时任务模块,提供类Crontab表达式的定时任务 |
| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 |
| hutool-db | JDBC封装后的数据操作,基于ActiveRecord思想 |
| hutool-dfa | 基于DFA模型的多关键字查找 |
| hutool-extra | 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等) |
| hutool-http | 基于HttpUrlConnection的Http客户端封装 |
| hutool-log | 自动识别日志实现的日志门面 |
| hutool-script | 脚本执行封装,例如Javascript |
| hutool-setting | 功能更强大的Setting配置文件和Properties封装 |
| hutool-system | 系统参数调用封装(JVM信息等) |
| hutool-json | JSON实现 |
| hutool-captcha | 图片验证码实现 |
| hutool-poi | 针对POI中Excel和Word的封装 |
| hutool-socket | 基于Java的NIO和AIO的Socket封装 |
可以根据需求对每个模块单独引入,也可以通过引入hutool-all方式引入所有模块。
1.导入jar包
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.8</version>
</dependency>
2.HTTP相关工具
由来
在Java的世界中,Http客户端之前一直是Apache家的HttpClient占据主导,但是由于此包较为庞大,API又比较难用,因此并不使用很多场景。而新兴的OkHttp、Jodd-http固然好用,但是面对一些场景时,学习成本还是有一些的。很多时候,我们想追求轻量级的Http客户端,并且追求简单易用。而JDK自带的HttpUrlConnection可以满足大部分需求。Hutool针对此类做了一层封装,使Http请求变得无比简单。
介绍
Hutool-http针对JDK的HttpUrlConnection做一层封装,简化了HTTPS请求、文件上传、Cookie记忆等操作,使Http请求变得无比简单。
Hutool-http的核心集中在两个类:
HttpRequest HttpResponse 同时针对大部分情境,封装了HttpUtil工具类。
hutool-http优点
- 根据URL自动判断是请求HTTP还是HTTPS,不需要单独写多余的代码。
- 表单数据中有File对象时自动转为
multipart/form-data表单,不必单做做操作。- 默认情况下Cookie自动记录,比如可以实现模拟登录,即第一次访问登录URL后后续请求就是登录状态。
- 自动识别304跳转并二次请求
- 自动识别页面编码,即根据header信息或者页面中的相关标签信息自动识别编码,最大可能避免乱码。
- 自动识别并解压Gzip格式返回内容
2.1 简单请求
==GET==
最简单的使用莫过于用HttpUtil工具类快速请求某个页面:
// 最简单的HTTP请求,可以自动通过header等信息判断编码,不区分HTTP和HTTPS
String content = HttpUtil.get("http://kjfeng44.usa3v.vip/show.html");
System.out.println(content);//打印结果为 访问当前网页 鼠标右键->查看源代码 一样的信息
// 最简单的HTTP请求,可以自动通过header等信息判断编码,不区分HTTP和HTTPS
String result1= HttpUtil.get("https://www.baidu.com");
// 当无法识别页面编码的时候,可以自定义请求页面的编码
String result2= HttpUtil.get("https://www.baidu.com", CharsetUtil.CHARSET_UTF_8);
//可以单独传入http参数,这样参数会自动做URL编码,拼接在URL中
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("city", "北京");
//类似 https://www.baidu.com?city='北京'
String result3= HttpUtil.get("https://www.baidu.com", paramMap);
一行代码即可搞定,当然Post请求也很简单:
==Post==请求只需使用Map预先制定form表单项即可
//POST请求
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("city", "北京");
String result1 = HttpUtil.post(url, paramMap);
2.2 文件上传下载
==上传文件 使用post请求==
HashMap<String, Object> paramMap = new HashMap<>();
//文件上传只需将参数中的键指定(默认file),值设为文件对象即可,对于使用者来说,文件上传与普通表单提交并无区别
paramMap.put("file", FileUtil.file("D:\face.jpg"));
String result= HttpUtil.post("https://www.baidu.com", paramMap);
==下载文件==
因为Hutool-http机制问题,请求页面返回结果是一次性解析为byte[]的,如果请求URL返回结果太大(比如文件下载),那内存会爆掉,因此针对文件下载HttpUtil单独做了封装。文件下载在面对大文件时采用流的方式读写,内存中只是保留一定量的缓存,然后分块写入硬盘,因此大文件情况下不会对内存有压力。
String fileUrl = "http://android-screenimgs.25pp.com/9/1043977_137265314002.jpg";
//将文件下载后保存在E盘,返回结果为下载文件大小 //第二参数 指定存放位置
long size = HttpUtil.downloadFile(fileUrl, FileUtil.file("e:/"));
System.out.println("Download size: " + size);
2.3 HttpRequest请求
本质上,HttpUtil中的get和post工具方法都是HttpRequest对象的封装,因此如果想更加灵活操作Http请求,可以使用HttpRequest。
普通表单
通过链式构建请求,我们可以很方便的指定Http头信息和表单信息,最后调用execute方法即可执行请求,返回HttpResponse对象。HttpResponse包含了服务器响应的一些信息,包括响应的内容和响应的头信息。通过调用body方法即可获取响应内容。
我们以POST请求为例:
String url="http://localhost:8080/test/hello";
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("name", "张三");
//链式构建请求
String result2 = HttpRequest.post(url)
.header(Header.USER_AGENT, "Hutool http")//头信息,多个头信息多次调用此方法即可
.form(paramMap)//表单内容 即参数
.timeout(20000)//超时,毫秒
//.body() 执行后返回的字符串 去掉.body()后 返回的是HttpResponse 对象
.execute().body();
//获得请求对应方法的响应结果
Console.log(result2);
RestFul风格请求 ==json字符串==
String url="http://localhost:8080/test/hello";
//构建参数对象
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("name", "zhangsan");
paramMap.put("realName","张三");
//转换为json字符串
String json = JSONUtil.toJsonStr(paramMap);
String result2 = HttpRequest.post(url)
.body(json)//携带字符串
//.body() 执行后返回的字符串 去掉.body()后 返回的是HttpResponse 对象
.execute().body();
System.out.println(result2);//HelloWord | zhangsan | 张三 打印请求方法返回的字符串
其他自定义项
- 指定请求头
- 自定义Cookie(cookie方法)
- 指定是否keepAlive(keepAlive方法)
- 指定表单内容(form方法)
- 指定请求内容,比如rest请求指定JSON请求体(body方法)
- 超时设置(timeout方法)
- 指定代理(setProxy方法)
- 指定SSL协议(setSSLProtocol)
- 简单验证(basicAuth方法)
2.4 httpresponse响应
HttpResponse是HttpRequest执行execute()方法后返回的一个对象,我们可以通过此对象获取服务端返回的:
- Http状态码(getStatus方法)
- 返回内容编码(contentEncoding方法)
- 是否Gzip内容(isGzip方法)
- 返回内容(body、bodyBytes、bodyStream方法)
- 响应头信息(header方法)
此对象的使用非常简单,最常用的便是body方法,会返回字符串Http响应内容。如果想获取byte[]则调用bodyBytes即可。
获取响应参数
获取状态码 ==.getstatus()==
获取头信息 ==.header(Header.CONTENT_ENCODING)==
获取响应结果==.body()==
String url="http://localhost:8080/test/hello";
//构建参数对象
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("name", "zhangsan");
paramMap.put("realName","张三");
//转换为json字符串
String json = JSONUtil.toJsonStr(paramMap);
HttpResponse res = HttpRequest.post(url)
.body(json)//携带字符串
//.body() 执行后返回的字符串 去掉.body()后 返回的是HttpResponse 对象
.execute()/*.body()*/;
System.out.println(res.getStatus());//200 返回状态码
Console.log(res.header(Header.CONTENT_ENCODING));//预定义的头信息
Console.log(res.header("Content-Disposition"));//自定义头信息
Console.log(res.body());//请求结果
2.5 ==爬取页面标题==
其实核心就前两行代码,第一行请求页面内容,第二行正则定位所有标题行并提取标题部分。
这里我解释下正则部分:
ReUtil.findAll方法用于查找所有匹配正则表达式的内容部分,
- 第一个参数是正则表达式 (.*?) 有几个就写几个,每个是一个分组 从1开始 下面例子 取得就是第二个分组内容
- 第二个参数 是要匹配的正文内容 即 页面 鼠标右键 查看源代码所见到的内容
- 第三个参数2表示提取第一个括号(分组)中的内容,0表示提取所有正则匹配到的内容。
这个方法可以看下core模块中ReUtil章节了解详情。
//请求列表页
String listContent = HttpUtil.get("https://www.chinanews.com/china.shtml");
//使用正则获取所有标题
List<String> titles = ReUtil.findAll("<a href="/gn/2021/03-26/(.*?).shtml">(.*?)</a>", listContent, 2);
for (String title : titles) {
//打印标题
Console.log(title);
}
2.6 代码搭建简易服务器
public static void main(String[] args) {
HttpUtil.createServer(8888)
//参数一为访问路径 参数二lambda表达式 类比httpservlet的 dopost doget方法
.addAction("/", (req, res)->{
//中文支持
res.setContentType("text/html;charset=UTF-8");
res.write("测试");
})
.start();
}
3.类型转换功能
==Convert==类可以说是一个工具方法类,里面封装了针对Java常见类型的转换,用于简化类型转换。Convert类中大部分方法为toXXX,参数为Object,可以实现将任意可能的类型转换为指定类型。同时支持第二个参数defaultValue用于在转换失败时返回一个默认值。
3.1转换为字符串
//将int类型转换为String类型
int a = 1;
//aStr为"1"
String aStr = Convert.toStr(a);
//将数组转换为String
long[] b = {1,2,3,4,5};
//bStr为:"[1, 2, 3, 4, 5]"
String bStr = Convert.toStr(b);
3.2转换为指定类型数组
//String类型转换为Integer数组
String[] b = { "1", "2", "3", "4" };
//结果为Integer数组
Integer[] intArray = Convert.toIntArray(b);
//long类型数组 转化为 int类型数组
long[] c = {1,2,3,4,5};
//结果为Integer数组
Integer[] intArray2 = Convert.toIntArray(c);
3.3 转换为日期对象
从以下例子可以看出
- ==支持符号"-" 跟 "/" 不支持"年月日"==
- ==支持yyyy-MM-dd 这种格式 不支持 时分秒==
//支持
String a = "2017-05-06";
Date value = Convert.toDate(a);
System.out.println(value);//2017-05-06 00:00:00
//不支持汉字式分割
a="2021年5月12号";
Date value2 = Convert.toDate(a);
System.out.println(value2);//null
//支持"/"
a="2021/05/12";
Date value3 = Convert.toDate(a);
System.out.println(value3);//2021-05-12 00:00:00
//不支持时分秒
a="2021/5/12/10/05/21";
Date value4 = Convert.toDate(a);
System.out.println(value4);//null
3.4 转换为集合
//数组转换为集合
Object[] a = {"a", "你", "好", "", 1};
List<?> list = Convert.convert(List.class, a);
System.out.println(list);
//从4.1.11开始可以这么用
List<?> list1 = Convert.toList(a);
System.out.println(list1);
4.日期时间功能-DateUtil
日期时间包是Hutool的核心包之一,提供针对JDK中Date和Calendar对象的封装,封装对象如下:
DateUtil针对日期时间操作提供一系列静态方法DateTime提供类似于Joda-Time中日期时间对象的封装,继承自Date类,并提供更加丰富的对象方法。FastDateFormat提供线程安全的针对Date对象的格式化和日期字符串解析支持。此对象在实际使用中并不需要感知,相关操作已经封装在DateUtil和DateTime的相关方法中。DateBetween计算两个时间间隔的类,除了通过构造新对象使用外,相关操作也已封装在DateUtil和DateTime的相关方法中。TimeInterval一个简单的计时器类,常用于计算某段代码的执行时间,提供包括毫秒、秒、分、时、天、周等各种单位的花费时长计算,对象的静态构造已封装在DateUtil中。DatePattern提供常用的日期格式化模式,包括String类型和FastDateFormat两种类型。
4.1 转换功能
==Date、long、Calendar之间的相互转换==
//当前时间
Date date = DateUtil.date();
System.out.println(date);//2021-03-26 09:30:39
//当前时间
Date date2 = DateUtil.date(Calendar.getInstance());
System.out.println(date2);//2021-03-26 09:30:39
//当前时间
Date date3 = DateUtil.date(System.currentTimeMillis());
System.out.println(date3);//2021-03-26 09:30:39
//当前时间字符串,格式:yyyy-MM-dd HH:mm:ss
String now = DateUtil.now();
System.out.println(now);//2021-03-26 09:30:39
//当前日期字符串,格式:yyyy-MM-dd
String today = DateUtil.today();
System.out.println(today);//2021-03-26
4.2字符串转日期
自动识别一些常用格式 比如:
- yyyy-MM-dd HH:mm:ss
- yyyy-MM-dd
- HH:mm:ss
- yyyy-MM-dd HH:mm
- yyyy-MM-dd HH:mm:ss.SSS
String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);
也可以这样操作 -指定当前输入的格式
String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd");
4.3 获取时间部分属性
Date date = DateUtil.date();
System.out.println(date);//2021-03-26 09:36:51
//获得年的部分
int year = DateUtil.year(date);
System.out.println(year);//2021
//获得月份,从0开始计数
int month = DateUtil.month(date);
System.out.println(month);//2
//获得月份枚举
Month anEnum = DateUtil.monthEnum(date);
System.out.println(anEnum);//MARCH
System.out.println(anEnum.getValue());//2
//使用枚举获取某个月份的最后一天 第一个参数为月 第二个参数为是否闰年
int lastDay = anEnum.getLastDay(month, false);
System.out.println(lastDay);//31
//.....
4.4 开始和结束时间
有的时候我们需要获得每天的开始时间、结束时间,每月的开始和结束时间等等,DateUtil也提供了相关方法:
String dateStr = "2017-03-01 22:33:23";
Date date = DateUtil.parse(dateStr);
//一天的开始,结果:2017-03-01 00:00:00
Date beginOfDay = DateUtil.beginOfDay(date);
System.out.println(beginOfDay);//2017-03-01 00:00:00
//一天的结束,结果:2017-03-01 23:59:59
Date endOfDay = DateUtil.endOfDay(date);
System.out.println(endOfDay);//2017-03-01 23:59:59
4.5 日期偏移计算
日期或时间的偏移指针对某个日期增加或减少分、小时、天等等,达到日期变更的目的。Hutool也针对其做了大量封装
String dateStr = "2017-03-01 22:33:23";
Date date = DateUtil.parse(dateStr);
//当前时间 加2天 参数1 当前时间 参数2 单位 参数3 值
Date newDate = DateUtil.offset(date, DateField.DAY_OF_MONTH, 2);
System.out.println(newDate);//2017-03-03 22:33:23
//常用偏移,当前时间加3天
DateTime newDate2 = DateUtil.offsetDay(date, 3);
System.out.println(newDate2);//2017-03-04 22:33:23
//常用偏移,当前时间 减3小时
DateTime newDate3 = DateUtil.offsetHour(date, -3);
System.out.println(newDate3);//2017-03-01 19:33:23
==针对当前时间,提供了简化的偏移方法(例如昨天、上周、上个月等) ==
//昨天
DateUtil.yesterday()
//明天
DateUtil.tomorrow()
//上周
DateUtil.lastWeek()
//下周
DateUtil.nextWeek()
//上个月
DateUtil.lastMonth()
//下个月
DateUtil.nextMonth()
4.6 日期时间差
有时候我们需要计算两个日期之间的时间差(相差天数、相差小时数等等),Hutool将此类方法封装为between方法:
==两个时间点的的位置可以颠倒==
String dateStr1 = "2017-03-01 22:33:23";
Date date1 = DateUtil.parse(dateStr1);
String dateStr2 = "2017-04-01 23:33:23";
Date date2 = DateUtil.parse(dateStr2);
//相差一个月,31天 /参数一 时间点1 /参数二 时间点2 /参数3 单位
long betweenDay = DateUtil.between(date1, date2, DateUnit.DAY);
System.out.println(betweenDay);
4.7 格式化时间差
有时候我们希望看到易读的时间差,比如XX天XX小时XX分XX秒,此时使用DateUtil.formatBetween方法:
//Level.MINUTE表示精确到分
String formatBetween = DateUtil.formatBetween(between, Level.MINUTE);
//输出:31天1小时
Console.log(formatBetween);
4.8 计时器功能
计时器用于计算某段代码或过程花费的时间
String dateStr1 = "2017-03-01 22:33:23";
Date date1 = DateUtil.parse(dateStr1);
String dateStr2 = "2017-03-15 23:33:23";
Date date2 = DateUtil.parse(dateStr2);
//Level.MINUTE表示精确到分 /参数一[顺序可颠倒] 时间点1 /参数二 时间点2 /参数三 单位
String formatBetween = DateUtil.formatBetween(date2,date1, BetweenFormatter.Level.MINUTE);
//输出:14天1小时
Console.log(formatBetween);
//直接输入Long类型的毫秒值
String formatBetween1 = DateUtil.formatBetween(9999999);
//输出:2小时46分39秒999毫秒
Console.log(formatBetween1);
//BetweenFormatter.Level.HOUR 输出多少小时 //参数1 Long类型的毫秒值 参数2 单位
String formatBetween2 = DateUtil.formatBetween(9999999, BetweenFormatter.Level.HOUR);
//输出:2小时
Console.log(formatBetween2);
4.9 其他
==计算年龄/ 是否闰年 /星座属相等==
//年龄
DateUtil.ageOfNow("1990-01-30");
//是否闰年
DateUtil.isLeapYear(2017);
// "摩羯座"
String zodiac = DateUtil.getZodiac(Month.JANUARY.getValue(), 19);
// "狗"
String chineseZodiac = DateUtil.getChineseZodiac(1994);
5.日期时间功能-DateTime
由来
考虑工具类的局限性,在某些情况下使用并不简便,于是DateTime类诞生。DateTime对象充分吸取Joda-Time库的优点,并提供更多的便捷方法,这样我们在开发时不必再单独导入Joda-Time库便可以享受简单快速的日期时间处理过程。
说明
DateTime类继承于java.util.Date类,为Date类扩展了众多简便方法,这些方法多是DateUtil静态方法的对象表现形式,使用DateTime对象可以完全替代开发中Date对象的使用。
5.1 创建DateTime对象
DateTime对象包含众多的构造方法,构造方法支持的参数有:
- Date
- Calendar
- String(日期字符串,第二个参数是日期格式)
- long 毫秒数
构建对象有两种方式:==DateTime.of()和new DateTime():==
Date date = new Date();//当前时间
//new方式创建
DateTime time = new DateTime(date);
Console.log(time);//2021-03-26 10:10:21
//静态方法 of方式创建
//静态方法 now() 获取当前时间
DateTime now = DateTime.now();
System.out.println(now);//2021-03-26 10:10:21
//需要传入date参数
DateTime dt = DateTime.of(date);
System.out.println(dt);//2021-03-26 10:10:21
5.2使用DateTime对象
DateTime的成员方法与DateUtil中的静态方法所对应,因为是成员方法,因此可以使用更少的参数操作日期时间。
示例:获取日期成员(年、月、日等)
//参数一 为时间字符串 参数二 提供大量格式化的 字符串解析方式
DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);
//年,结果:
int year = dateTime.year();
System.out.println(year);//2017
//月份,结果:一月
Month month = dateTime.monthEnum();
System.out.println(month);//JANUARY
//日, 1月 5号
int day = dateTime.dayOfMonth();
System.out.println(day);//5
//一周中的哪一天 星期四
int week = dateTime.dayOfWeek();
System.out.println(week);//5
5.3 DateTime对象可变性
DateTime对象默认是可变对象(调用offset、setField、setTime方法默认变更自身),
但是这种可变性有时候会引起很多问题(例如多个地方共用DateTime对象)。
我们可以调用
setMutable(false)方法使其变为不可变对象。在不可变模式下,
offset、setField方法返回一个新对象,setTime方法抛出异常。
DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);
//默认情况下DateTime为可变对象,此时offset == dateTime
DateTime offset = dateTime.offset(DateField.YEAR, 0);
//设置为不可变对象后变动将返回新对象,此时offset != dateTime
dateTime.setMutable(false);
offset = dateTime.offset(DateField.YEAR, 0);
5.4 格式化为字符串
调用toString()方法即可返回格式为yyyy-MM-dd HH:mm:ss的字符串,
调用toString(String format)可以返回指定格式的字符串。
DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);
//结果:默认yyyy-MM-dd HH:mm:ss 格式
String dateStr = dateTime.toString();
System.out.println(dateStr);//2017-01-05 12:34:23
//参数:yyyy/MM/dd 指定格式转字符串
String dateStr2 = dateTime.toString("yyyy/MM/dd");
System.out.println(dateStr2);//2017/01/05
6.IO流相关功能
io包的封装主要针对流、文件的读写封装,主要以工具类为主,提供常用功能的封装,这包括:
IoUtil流操作工具类FileUtil文件读写和操作的工具类。FileTypeUtil文件类型判断工具类WatchMonitor目录、文件监听,封装了JDK1.7中的WatchServiceClassPathResource针对ClassPath中资源的访问封装FileReader封装文件读取FileWriter封装文件写入
6.1 IoUtil工具类
IO工具类的存在主要针对InputStream、OutputStream、Reader、Writer封装简化,并对NIO相关操作做封装简化。总体来说,Hutool对IO的封装,主要是工具层面,我们努力做到在便捷、性能和灵活之间找到最好的平衡点。
拷贝
流的读写可以总结为从输入流读取,从输出流写出,这个过程我们定义为拷贝。这个是一个基本过程,也是文件、流操作的基础。
以文件流拷贝为例:
BufferedInputStream in = FileUtil.getInputStream("d:/test.txt");
BufferedOutputStream out = FileUtil.getOutputStream("d:/test2.txt");
long copySize = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE);
==copy==方法同样针对Reader、Writer、Channel等对象有一些重载方法,并提供可选的缓存大小。默认的,缓存大小为
1024个字节,如果拷贝大文件或流数据较大,可以适当调整这个参数。针对NIO,提供了==
copyByNIO==方法,以便和BIO有所区别。我查阅过一些资料,==使用NIO对文件流的操作有一定的提升!==
stream流转字符流
- ==
IoUtil.getReader==:将InputStream转为BufferedReader用于读取字符流,它是部分readXXX方法的基础。 - ==
IoUtil.getWriter==:将OutputStream转为OutputStreamWriter用于写入字符流,它是部分writeXXX的基础。
读取流中的内容
读取流中的内容总结下来,可以分为==read方法==和==readXXX==方法。
read方法有诸多的重载方法,根据参数不同,可以读取不同对象中的内容,这包括:
InputStreamReaderFileChannel这三个重载大部分返回==String字符串==,为字符流读取提供极大便利。
readXXX方法主要针对返回值做一些处理,例如:
readBytes返回byte数组(读取图片等)
readHex读取16进制字符串
readObj读取序列化对象(反序列化)
readLines按行读取
==
toStream==方法则是将某些对象转换为流对象,便于在某些情况下操作:
String转换为ByteArrayInputStreamFile转换为FileInputStream
写入到流
-
==
IoUtil.write==方法有两个重载方法,一个直接调用OutputStream.write方法,另一个用于将对象转换为字符串(调用toString方法),然后写入到流中。 -
==
IoUtil.writeObjects== 用于将可序列化对象序列化后写入到流中。write方法并没有提供writeXXX,需要自己转换为String或byte[]。
关闭流
关闭操作会面临两个问题:
- 被关闭对象为空
- 对象关闭失败(或对象已关闭)
==IoUtil.close==方法很好的解决了这两个问题。
6.2 FileUtil工具类
总体来说,FileUtil类包含以下几类操作工具:
- 文件操作:包括文件目录的新建、删除、复制、移动、改名等
- 文件判断:判断文件或目录是否非空,是否为目录,是否为文件等等。
- 绝对路径:针对ClassPath中的文件转换为绝对路径文件。
- 文件名:主文件名,扩展名的获取
- 读操作:包括类似IoUtil中的getReader、readXXX操作
- 写操作:包括getWriter和writeXXX操作
在FileUtil中,我努力将方法名与Linux相一致,例如创建文件的方法并不是createFile,而是touch,这种统一对于熟悉Linux的人来说,大大提高了上手速度。当然,如果你不熟悉Linux,那FileUtil工具类的使用则是在帮助你学习Linux命令。这些类Linux命令的方法包括:
ls列出目录和文件touch创建文件,如果父目录不存在也自动创建mkdir创建目录,会递归创建每层目录del删除文件或目录(递归删除,不判断是否为空),这个方法相当于Linux的delete命令copy拷贝文件或目录file创建文件或目录
这些方法提供了人性化的操作,例如touch方法,在创建文件的情况下会自动创建上层目录(我想对于使用者来说这也是大部分情况下的需求),同样mkdir也会创建父目录。
需要注意的是,
del方法会删除目录而不判断其是否为空,这一方面方便了使用,另一方面也可能造成一些预想不到的后果(比如拼写错路径而删除不应该删除的目录),所以请谨慎使用此方法。
7.图片相关
7.1图片工具ImgUtil
针对awt中图片处理进行封装,这些封装包括:缩放、裁剪、转为黑白、加水印等操作。
7.1.1 scale缩放图片
提供两种重载方法:其中一个是按照长宽缩放,另一种是按照比例缩放。
ImgUtil.scale(
FileUtil.file("d:/face.jpg"),
FileUtil.file("d:/face_result.jpg"),
0.5f//缩放比例
);
7.1.2 cut 剪裁图片
ImgUtil.cut(
FileUtil.file("d:/face.jpg"),
FileUtil.file("d:/face_result.jpg"),
new Rectangle(200, 200, 100, 100)//裁剪的矩形区域
);
7.1.3 slice 行列剪裁图片
按照行列剪裁切片(将图片分为20行和20列)
ImgUtil.slice(FileUtil.file("e:/test2.png"), FileUtil.file("e:/dest/"), 10, 10);
7.1.4 convert 图片类型转换
==支持GIF->JPG、GIF->PNG、PNG->JPG、PNG->GIF(X)、BMP->PNG等==
ImgUtil.convert(FileUtil.file("e:/test2.png"), FileUtil.file("e:/test2Convert.jpg"));
7.1.5 gray 彩色转黑白
ImgUtil.gray(FileUtil.file("d:/logo.png"), FileUtil.file("d:/result.png"));
7.1.6 pressText 添加文字水印
ImgUtil.pressText(//
FileUtil.file("e:/pic/face.jpg"), //
FileUtil.file("e:/pic/test2_result.png"), //
"版权所有", Color.WHITE, //文字
new Font("黑体", Font.BOLD, 100), //字体
0, //x坐标修正值。 默认在中间,偏移量相对于中间偏移
0, //y坐标修正值。 默认在中间,偏移量相对于中间偏移
0.8f//透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
);
7.1.7 pressImage 添加图片水印
ImgUtil.pressImage(
FileUtil.file("d:/picTest/1.jpg"),
FileUtil.file("d:/picTest/dest.jpg"),
ImgUtil.read(FileUtil.file("d:/picTest/1432613.jpg")), //水印图片
0, //x坐标修正值。 默认在中间,偏移量相对于中间偏移
0, //y坐标修正值。 默认在中间,偏移量相对于中间偏移
0.1f
);
7.1.8 rotate旋转图片
// 旋转180度
BufferedImage image = ImgUtil.rotate(ImageIO.read(FileUtil.file("e:/pic/366466.jpg")), 180);
ImgUtil.write(image, FileUtil.file("e:/pic/result.png"));
7.1.9 flip 水平翻转图片
ImgUtil.flip(FileUtil.file("d:/logo.png"), FileUtil.file("d:/result.png"));
7.2 Img 图片编辑器
7.2.1 图像切割
// 将face.jpg切割为原型保存为face_radis.png
Img.from(FileUtil.file("e:/pic/face.jpg"))
.cut(0, 0, 200)//
.write(FileUtil.file("e:/pic/face_radis.png"));
7.2.2 图片压缩
==图片压缩只支持Jpg文件==
Img.from(FileUtil.file("e:/pic/1111.png"))
.setQuality(0.8)//压缩比率
.write(FileUtil.file("e:/pic/1111_target.jpg"));
8.URL相关
URL的结构如下:
[scheme:]scheme-specific-part[#fragment] [scheme:][//authority][path][?query][#fragment] [scheme:][//host:port][path][?query][#fragment]按照这个格式,UrlBuilder将URL分成scheme、host、port、path、query、fragment部分,其中path和query较为复杂,又使用
UrlPath和UrlQuery分别封装。
8.1简单使用
相比URL对象,UrlBuilder更加人性化,例如:
URL url = new URL("www.hutool.cn")
此时会报java.net.MalformedURLException: no protocol的错误,而使用UrlBuilder则会有默认协议:
// 输出 http://www.hutool.cn/
String buildUrl = UrlBuilder.create().setHost("www.hutool.cn").build();
8.2完整构建
// https://www.hutool.cn/aaa/bbb?ie=UTF-8&wd=test
String buildUrl = UrlBuilder.create()
.setScheme("https")
.setHost("www.hutool.cn")
.addPath("/aaa").addPath("bbb")
.addQuery("ie", "UTF-8")
.addQuery("wd", "test")
.build();
8.3中文编码
当参数中有中文时,自动编码中文,默认UTF-8编码,也可以调用setCharset方法自定义编码。
// https://www.hutool.cn/s?ie=UTF-8&ie=GBK&wd=%E6%B5%8B%E8%AF%95
String buildUrl = UrlBuilder.create()
.setScheme("https")
.setHost("www.hutool.cn")
.addPath("/s")
.addQuery("ie", "UTF-8")
.addQuery("ie", "GBK")
.addQuery("wd", "测试")
.build();
8.4解析URL中文
当有一个URL字符串时,可以使用of方法解析:
我们发现这个例子中,原URL中的参数a是没有编码的,b是编码过的,当用户提供此类混合URL时,Hutool可以很好的识别并全部decode,当然,调用build()之后,会全部再encode。
UrlBuilder builder = UrlBuilder.ofHttp("www.hutool.cn/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8);
// 输出张三
Console.log(builder.getQuery().get("a"));
// 输出李四
Console.log(builder.getQuery().get("b"));
8.5 URL特殊符号解析
有时候URL中会存在&这种分隔符,谷歌浏览器会将此字符串转换为&使用,Hutool中也同样如此
UrlBuilder主要应用于http模块,在构建HttpRequest时,用户传入的URL五花八门,为了做大最好的适应性,减少用户对URL的处理,使用UrlBuilder完成URL的规范化。
String urlStr = "https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&mid=100000465&idx=1";
UrlBuilder builder = UrlBuilder.ofHttp(urlStr, CharsetUtil.CHARSET_UTF_8);
// https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&mid=100000465&idx=1
Console.log(builder.build());
9.DFA算法-关键字过滤
DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。因为本人算法学的不好
解释起来原理其实也不难,就是用所有关键字构造一棵树,然后用正文遍历这棵树,遍历到叶子节点即表示文章中存在这个关键字。
我们暂且忽略构建关键词树的时间,每次查找正文只需要O(n)复杂度就可以搞定。
针对DFA算法以及网上的一些实现,Hutool做了整理和改进,最终形成现在的Hutool-dfa模块。
9.1 构建关键词树
可以将关键词放入数据库中保存 用的时候进行构建即可
WordTree tree = new WordTree();
tree.addWord("大");
tree.addWord("大土豆");
tree.addWord("土豆");
tree.addWord("刚出锅");
tree.addWord("出锅");
9.2 查找关键词
//正文
String text = "我有一颗大土豆,刚出锅的";
==情况一:标准匹配,匹配到最短关键词,并跳过已经匹配的关键词==
// 匹配到【大】,就不再继续匹配了,因此【大土豆】不匹配
// 匹配到【刚出锅】,就跳过这三个字了,因此【出锅】不匹配(由于刚首先被匹配,因此长的被匹配,最短匹配只针对第一个字相同选最短)
List<String> matchAll = tree.matchAll(text, -1, false, false);
Assert.assertEquals(matchAll.toString(), "[大, 土豆, 刚出锅]");
==情况二:匹配到最短关键词,不跳过已经匹配的关键词==
// 【大】被匹配,最短匹配原则【大土豆】被跳过,【土豆继续被匹配】
// 【刚出锅】被匹配,由于不跳过已经匹配的词,【出锅】被匹配
matchAll = tree.matchAll(text, -1, true, false);
Assert.assertEquals(matchAll.toString(), "[大, 土豆, 刚出锅, 出锅]");
==情况三:匹配到最长关键词,跳过已经匹配的关键词==
// 匹配到【大】,由于到最长匹配,因此【大土豆】接着被匹配
// 由于【大土豆】被匹配,【土豆】被跳过,由于【刚出锅】被匹配,【出锅】被跳过
matchAll = tree.matchAll(text, -1, false, true);
Assert.assertEquals(matchAll.toString(), "[大, 大土豆, 刚出锅]");
==情况四:匹配到最长关键词,不跳过已经匹配的关键词(最全关键词)==
// 匹配到【大】,由于到最长匹配,因此【大土豆】接着被匹配,由于不跳过已经匹配的关键词,土豆继续被匹配
// 【刚出锅】被匹配,由于不跳过已经匹配的词,【出锅】被匹配
matchAll = tree.matchAll(text, -1, true, true);
Assert.assertEquals(matchAll.toString(), "[大, 大土豆, 土豆, 刚出锅, 出锅]");
除了
matchAll方法,==WordTree还提供了match和isMatch两个方法==,这两个方法只会查找第一个匹配的结果,这样一旦找到第一个关键字,就会停止继续匹配,大大提高了匹配效率。
特殊字符
有时候,正文中的关键字常常包含特殊字符,比如:"〓关键☆字",针对这种情况,Hutool提供了
StopChar类,专门针对特殊字符做跳过处理,这个过程是在match方法或matchAll方法执行的时候自动去掉特殊字符。
10.CronUtil-定时任务
CronUtil通过一个全局的定时任务配置文件,实现统一的定时任务调度。
10.1 配置文件
==corn.setting配置文件见同目录下附件==
对于Maven项目,首先在src/main/resources/config下放入cron.setting文件(默认是这个路径的这个文件),然后在文件中放入定时规则,规则如下:
#注意点 本配置文件需要放在 resources/config包下 config包下 config包下 重要事情说三遍
#注意 中括号是要执行任务的包名
[com.example.demo2222.job]
# TestJob 是定时任务类名 .run 是定时任务的方法名
# =号右边是 cron表达式 支持秒级需要开启前执行 CronUtil.setMatchSecond(true);
TestJob.run = */2 * * * * *
中括号表示分组,也表示需要执行的类或对象方法所在包的名字,这种写法有利于区分不同业务的定时任务。
==TestJob.run==表示需要执行的类名和方法名(通过反射调用,不支持Spring和任何框架的依赖注入),
==*/10 * * * *==表示定时任务表达式,此处表示每10分钟执行一次,以上配置等同于:
com.company.aaa.job.TestJob.run = */10 * * * *
com.company.aaa.job.TestJob2.run = */10 * * * *
10.2 启动定时任务
CronUtil.start();
如果想让执行的作业同定时任务线程同时结束,可以将定时任务设为守护线程,需要注意的是,此模式下会在调用stop时立即结束所有作业线程,请确保你的作业可以被中断:
//使用deamon模式,
CronUtil.start(true);
10.3 关闭定时任务
CronUtil.stop();
10.4 cron表达式 秒/年匹配
考虑到Quartz表达式的兼容性,且存在对于秒级别精度匹配的需求,==Hutool可以通过设置使用秒匹配模式来兼容==
//支持秒级别定时任务
CronUtil.setMatchSecond(true);
此时Hutool可以兼容Quartz表达式(5位表达式、6位表达式都兼容)
10.5 动态添加定时任务
当然,如果你想动态的添加定时任务,
使用CronUtil.schedule(String schedulingPattern, Runnable task)方法即可
(==使用此方法加入的定时任务不会被写入到配置文件==)。
public static void main(String[] args) {
CronUtil.schedule("*/2 * * * * *", new Task() {
@Override
public void execute() {
}
});
// 支持秒级别定时任务
CronUtil.setMatchSecond(true);
//开启定时任务
CronUtil.start();
}
11.生成二维码
由于大家对二维码的需求较多,对于二维码的生成和解析我认为应该作为简单的工具存在于Hutool中。考虑到自行实现的难度,因此Hutool针对被广泛接受的的zxing库进行封装。而由于涉及第三方包,因此归类到extra模块中。
11.1 导入jar包
考虑到Hutool的非强制依赖性,因此zxing需要用户自行引入:
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
11.2 生成二维码
public static void main(String[] args) {
// 生成指定url对应的二维码到文件,宽和高都是300像素
String url="http://kjfeng44.usa3v.vip/";
//参数一 为网址 参数二、三为:二维码宽高 参数四为 生成图片存放路径
QrCodeUtil.generate(url, 300, 300, FileUtil.file("d:/qrcode.jpg"));
}
11.2 自定义参数
通过==QrConfig==可以自定义二维码的生成参数,==例如长、宽、二维码的颜色、背景颜色、边距等参数 ,添加中央小logo== ,使用方法如下:
public static void main(String[] args) {
String url="http://kjfeng44.usa3v.vip/";
// 生成指定url对应的二维码到文件,宽和高都是300像素
QrConfig config = new QrConfig(300, 300);
// 设置边距,既二维码和背景之间的边距
config.setMargin(3);
// 设置前景色,既二维码颜色(青色)
config.setForeColor(Color.CYAN.getRGB());
// 设置背景色(灰色)
config.setBackColor(Color.GRAY.getRGB());
// 添加二维码中央 小logo
File generate = QrCodeUtil.generate(
url, //二维码内容
QrConfig.create().setImg("e:/logo_small.jpg"), //附带logo
FileUtil.file("e:/qrcodeWithLogo.jpg")//写出到的文件 生成二维码到文件,也可以到流
);
}
11.3 ==识别二维码==
识别逻辑在底部
public static void main(String[] args) {
String url="http://kjfeng44.usa3v.vip/";
// 生成指定url对应的二维码到文件,宽和高都是300像素
QrConfig config = new QrConfig(300, 300);
// 设置边距,既二维码和背景之间的边距
config.setMargin(3);
// 设置前景色,既二维码颜色(青色)
config.setForeColor(Color.CYAN.getRGB());
// 设置背景色(灰色)
config.setBackColor(Color.GRAY.getRGB());
// 添加二维码中央 小logo
File generate = QrCodeUtil.generate(
url, //二维码内容
QrConfig.create().setImg("d:/logo.jpg"), //附带logo
FileUtil.file("d:/qrcode.jpg")//写出到的文件 生成二维码到文件,也可以到流
);
//=======================下方代码为 识别二维码=========================
//打印结果 http://kjfeng44.usa3v.vip/
String decode = QrCodeUtil.decode(FileUtil.file("d:/qrcode.jpg"));//传入二维码文件
System.out.println(decode);
String decode1 = QrCodeUtil.decode(generate);//传入上面生成的二维码文件
System.out.println(decode1);
}