爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      HuTool

      首页 知识中心 其他 文章详情页

      HuTool

      2024-04-23 09:50:36 阅读次数:38

      克隆

      • 支持泛型的克隆接口和克隆类

      空接口

      • JDK中的Cloneable接口只是一个空接口,并没有定义成员;

      HuTool

      它存在的意义仅仅是指明一个类的实例化对象支持位复制(就是对象克隆),如果不实现这个类,调用对象的clone()方法就会抛出CloneNotSupportedException异常。而且,因为clone()方法在Object对象中,返回值也是Object对象,因此克隆后我们需要自己强转下类型。

      泛型克隆接口

      因此,cn.hutool.core.clone.Cloneable 接口应运而生。此接口定义了一个返回泛型的成员方法,这样,实现此接口后会提示必须实现一个public的clone方法,调用父类clone方法即可:

      @Data
      public class CatByHuTool implements Cloneable<CatByHuTool> {
      /**
      * 名字
      */
      private String name = "miaomiao";
      /**
      * 年龄
      */
      private int age = 2;

      @Override
      public CatByHuTool clone() {
      try {
      return (CatByHuTool) super.clone();
      } catch (CloneNotSupportedException e) {
      throw new CloneRuntimeException(e);
      }
      }
      }

      泛型克隆类

      但是实现此接口依旧有不方便之处,就是必须自己实现一个public类型的clone()方法,还要调用父类(Object)的clone方法并处理异常。于是 cn.hutool.clone.CloneSupport 类产生,这个类帮我们实现了上面的clone方法,因此只要继承此类,不用写任何代码即可使用clone()方法:

      /**
      * @author BNTang
      * @version 1.0
      * @description
      * @since 2023-16-07
      **/
      @Getter
      @Setter
      @ToString
      public class DogByInherit extends CloneSupport<DogByInherit> {
      /**
      * 名字
      */
      private String name = "wangwang";
      /**
      * 年龄
      */
      private int age = 3;
      }

      当然,使用CloneSupport的前提是你没有继承任何的类,谁让Java不支持多重继承呢(你依旧可以让父类继承这个类,如果可以的话)。如果没办法继承类,那实现 cn.hutool.clone.Cloneable 也是不错的主意,因此hutool提供了这两种方式,任选其一,在便捷和灵活上都提供了支持。

      深克隆

      我们知道实现Cloneable接口后克隆的对象是浅克隆,要想实现深克隆,请使用:

      ObjectUtil.cloneByStream(obj)

      前提是对象必须实现 ​​Serializable​​ 接口。

      ObjectUtil同样提供一些静态方法:clone(obj)、cloneIfPossible(obj) 用于简化克隆调用。

      类型转换

      • 痛点

      在Java开发中我们要面对各种各样的类型转换问题,尤其是从命令行获取的用户参数、从HttpRequest获取的Parameter等等,这些参数类型多种多样,我们怎么去转换他们呢?常用的办法是先整成String,然后调用XXX.parseXXX方法,还要承受转换失败的风险,不得不加一层try catch,这个小小的过程混迹在业务代码中会显得非常难看和臃肿。

      Convert 类

      Convert 类可以说是一个工具方法类,里面封装了针对Java常见类型的转换,用于简化类型转换。Convert 类中大部分方法为 toXXX,参数为Object,可以实现将任意可能的类型转换为指定类型。同时支持第二个参数 defaultValue 用于在转换失败时返回一个默认值。

      Java 常见类型转换

      • 转换为字符串:
      public static void paramToString() {
      int a = 1;
      // aStr为"1"
      String aStr = Convert.toStr(a);
      System.out.println("aStr = " + aStr);

      long[] b = {1, 2, 3, 4, 5};
      // bStr为:"[1, 2, 3, 4, 5]"
      String bStr = Convert.toStr(b);
      System.out.println("b = " + b);
      System.out.println("bStr = " + bStr);
      }
      • 转换为指定类型数组:
      public static void paramToArray() {
      String[] b = {"1", "2", "3", "4"};
      //结果为Integer数组
      Integer[] intArray = Convert.toIntArray(b);
      System.out.println("intArray = " + intArray);

      long[] c = {1, 2, 3, 4, 5};
      //结果为Integer数组
      Integer[] intArray2 = Convert.toIntArray(c);
      System.out.println("intArray2 = " + intArray2);
      }
      • 转换为日期对象:
      public static void paramToDate() {
      String a = "2017-05-06";
      Date value = Convert.toDate(a);
      System.out.println("value = " + value);
      }
      • 转换为集合:
      public static void paramToList() {
      Object[] a = {"a", "你", "好", "", 1};
      List<?> list = Convert.convert(List.class, a);
      System.out.println("list = " + list);

      //从4.1.11开始可以这么用
      List<?> list2 = Convert.toList(a);
      System.out.println("list2 = " + list2);

      Student student = new Student();
      List<?> objects = Convert.toList(student);
      System.out.println(objects);
      }

      其它类型转换

      标准类型

      通过 ​​Convert.convert(Class<T>, Object)​​​ 方法可以将任意类型转换为指定类型,Hutool 中预定义了许多类型转换,例如转换为 URI、URL、Calendar 等等, 这些类型的转换都依托于 ​​ConverterRegistry​​​ 类。通过这个类和 ​​Converter​​ 接口,我们可以自定义一些类型转换,详细的使用请参阅 “自定义类型转换” 一节。

      泛型类型

      通过​​convert(TypeReference<T> reference, Object value)​​​方法,自行new一个​​TypeReference​​​对象可以对嵌套泛型进行类型转换。例如,我们想转换一个对象为​​List<String>​​类型,此时传入的标准Class就无法满足要求,此时我们可以这样:

      Object[] a = { "a", "你", "好", "", 1 };
      List<String> list = Convert.convert(new TypeReference<List<String>>() {}, a);

      通过 TypeReference 实例化后制定泛型类型,即可转换对象为我们想要的目标类型。

      半角和全角转换

      在很多文本的统一化中这两个方法非常有用,主要对标点符号的全角半角转换。​​全角和半角的区别及使用方式​​

      • 半角转全角:
      public static void HalfAngleToFullAngle() {
      String a = "123456789";

      //结果为:"123456789"
      String sbc = Convert.toSBC(a);
      System.out.println("sbc = " + sbc);
      }
      • 全角转半角:
      public static void fullAngleToHalfAngle() {
      String a = "123456789";

      //结果为"123456789"
      String dbc = Convert.toDBC(a);
      System.out.println("dbc = " + dbc);
      }

      (Hex) 16 进制

      在很多加密解密,以及中文字符串传输(比如表单提交)的时候,会用到 16 进制转换,就是 Hex 转换,为此Hutool中专门封装了 HexUtil 工具类,考虑到 16 进制转换也是转换的一部分,因此将其方法也放在 Convert 类中,便于理解和查找,使用同样非常简单:

      • 转为16进制(Hex)字符串
      public static void convertToSixTeenBase() {
      String a = "我是一个小小的可爱的字符串";

      //结果:"e68891e698afe4b880e4b8aae5b08fe5b08fe79a84e58fafe788b1e79a84e5ad97e7aca6e4b8b2"
      String hex = Convert.toHex(a, CharsetUtil.CHARSET_UTF_8);
      System.out.println("hex = " + hex);
      }
      • 将16进制(Hex)字符串转为普通字符串:
      public static void convertSixTeenBaseToString() {
      String hex = "e68891e698afe4b880e4b8aae5b08fe5b08fe79a84e58fafe788b1e79a84e5ad97e7aca6e4b8b2";

      //结果为:"我是一个小小的可爱的字符串"
      //String raw = Convert.hexStrToStr(hex, CharsetUtil.CHARSET_UTF_8);

      //注意:在4.1.11之后hexStrToStr将改名为hexToStr
      String raw2 = Convert.hexToStr(hex, CharsetUtil.CHARSET_UTF_8);
      System.out.println("raw2 = " + raw2);
      }

      因为字符串牵涉到编码问题,因此必须传入编码对象,此处使用UTF-8编码。 toHex 方法同样支持传入 byte[],同样也可以使用 hexToBytes 方法将16进制转为byte[]

      Unicode 和字符串转换

      与16进制类似,Convert类同样可以在字符串和Unicode之间轻松转换:

      public static void unicodeAndStringConvert() {
      String a = "我是一个小小的可爱的字符串";

      //结果为:"\\u6211\\u662f\\u4e00\\u4e2a\\u5c0f\\u5c0f\\u7684\\u53ef\\u7231\\u7684\\u5b57\\u7b26\\u4e32"
      String unicode = Convert.strToUnicode(a);
      System.out.println("unicode = " + unicode);

      //结果为:"我是一个小小的可爱的字符串"
      String raw = Convert.unicodeToStr(unicode);
      System.out.println("raw = " + raw);
      }

      很熟悉吧?如果你在properties文件中写过中文,你会明白这个方法的重要性。

      编码转换

      在接收表单的时候,我们常常被中文乱码所困扰,其实大多数原因是使用了不正确的编码方式解码了数据。于是Convert.convertCharset方法便派上用场了,它可以把乱码转为正确的编码方式:

      public static void codingConvert() {
      String a = "我不是乱码";
      //转换后result为乱码
      String result = Convert.convertCharset(a, CharsetUtil.UTF_8, CharsetUtil.ISO_8859_1);
      String raw = Convert.convertCharset(result, CharsetUtil.ISO_8859_1, "UTF-8");
      // Assert.assertEquals(raw, a);

      System.out.println("raw.equals(a) = " + raw.equals(a));
      }

      !> 注意 经过测试,UTF-8编码后用GBK解码再用GBK编码后用UTF-8解码会存在某些中文转换失败的问题。

      时间单位转换

      ​​Convert.convertTime​​方法主要用于转换时长单位,比如一个很大的毫秒,我想获得这个毫秒数对应多少分:

      public static void timeUnitConvert() {
      long a = 4535345;

      //结果为:75
      long minutes = Convert.convertTime(a, TimeUnit.MILLISECONDS, TimeUnit.MINUTES);

      System.out.println("minutes = " + minutes);
      }

      金额大小写转换

      面对财务类需求,Convert.digitToChinese将金钱数转换为大写形式:

      public static void amountCaseConvert() {
      double a = 67556.32;

      //结果为:"陆万柒仟伍佰伍拾陆元叁角贰分"
      String digitUppercase = Convert.digitToChinese(a);

      System.out.println("digitUppercase = " + digitUppercase);
      }

      !> 注意 转换为大写只能精确到​​分​​(小数点儿后两位),之后的数字会被忽略。

      数字转换

      • 数字转为英文表达
      public static void digitalConvertEnglishExpress() {
      // ONE HUNDRED AND CENTS TWENTY THREE ONLY
      String format = Convert.numberToWord(100.23);

      System.out.println("format = " + format);
      }
      • 数字简化
      public static void digitalSimplify() {
      // 1.2k
      String format = Convert.numberToSimple(1200);

      System.out.println("format = " + format);
      }
      • 数字转中文

      !> 数字转中文方法中,只保留两位小数

      public static void digitalConvertChinese() {
      // 一万零八百八十九点七二
      String f1 = Convert.numberToChinese(10889.72356, false);
      System.out.println("f1 = " + f1);

      // 使用金额大写
      // 壹万贰仟陆佰伍拾叁
      String f2 = Convert.numberToChinese(12653, true);
      System.out.println("f2 = " + f2);
      }
      • 数字中文表示转换为数字
      public static void chineseNumberToNumber() {
      // 1012
      int f1 = Convert.chineseToNumber("一千零一十二");
      System.out.println("f1 = " + f1);
      }

      原始类和包装类转换

      有的时候,我们需要将包装类和原始类相互转换(比如 Integer.class 和 int.class),这时候我们可以:

      public static void originalClassPackagingConvert() {
      //去包装
      Class<?> wrapClass = Integer.class;

      //结果为:int.class
      Class<?> unWraped = Convert.unWrap(wrapClass);
      System.out.println("unWraped = " + unWraped);

      //包装
      Class<?> primitiveClass = long.class;
      //结果为:Long.class
      Class<?> wraped = Convert.wrap(primitiveClass);
      System.out.println("wraped = " + wraped);
      }

      自定义类型转换 ConverterRegistry

      • 由来

      Hutool中类型转换最早只是一个工具类,叫做“Convert”,对于每一种类型转换都是用一个静态方法表示,但是这种方式有一个潜在问题,那就是扩展性不足,这导致Hutool只能满足部分类型转换的需求。

      • 解决

      为了解决这些问题,我对Hutool中这个类做了扩展。思想如下:

      • Converter 类型转换接口,通过实现这个接口,重写convert方法,以实现不同类型的对象转换;
      • ConverterRegistry 类型转换登记中心。将各种类型Convert对象放入登记中心,通过convert方法查找目标类型对应的转换器,将被转换对象转换。在此类中,存放着默认转换器和自定义转换器,默认转换器是Hutool中预定义的一些转换器,自定义转换器存放用户自定的转换器;

      通过这种方式,实现类灵活的类型转换。使用方式如下:

      public static void customTypeConvert() {
      int a = 3423;
      ConverterRegistry converterRegistry = ConverterRegistry.getInstance();
      String result = converterRegistry.convert(String.class, a);
      // Assert.assertEquals("3423", result);

      System.out.println("\"3423\".equals(result) = " + "3423".equals(result));
      }

      自定义转换

      Hutool的默认转换有时候并不能满足我们自定义对象的一些需求,这时我们可以使用​​ConverterRegistry.getInstance().putCustom()​​方法自定义类型转换。

      • 自定义转换器
      public static class CustomConverter implements Converter<String> {
      @Override
      public String convert(Object value, String defaultValue) throws IllegalArgumentException {
      return "Custom: " + value.toString();
      }
      }
      • 注册转换器
      public static ConverterRegistry registeredConverter() {
      ConverterRegistry converterRegistry = ConverterRegistry.getInstance();

      // 此处做为示例自定义String转换,因为Hutool中已经提供String转换,请尽量不要替换
      // 替换可能引发关联转换异常(例如覆盖String转换会影响全局)
      converterRegistry.putCustom(String.class, CustomConverter.class);

      return converterRegistry;
      }
      • 执行转换
      public static void execConvert() {
      int a = 454553;

      ConverterRegistry converterRegistry = registeredConverter();

      String result = converterRegistry.convert(String.class, a);
      // Assert.assertEquals("Custom: 454553", result);

      System.out.println("\"Custom: 454553\".equals(result) = " + "Custom: 454553".equals(result));
      }

      !> 注意:convert(Class type, Object value, T defaultValue, boolean isCustomFirst)方法的最后一个参数可以选择转换时优先使用自定义转换器还是默认转换器。convert(Class type, Object value, T defaultValue)和convert(Class type, Object value)两个重载方法都是使用自定义转换器优先的模式。

      ConverterRegistry 单例和对象模式

      ConverterRegistry提供一个静态方法getInstance()返回全局单例对象,这也是推荐的使用方式,当然如果想在某个限定范围内自定义转换,可以实例化ConverterRegistry对象。

      日期时间

      介绍

      日期时间包是Hutool的核心包之一,提供针对JDK中Date和Calendar对象的封装,封装对象如下:

      日期时间工具

      • ​​DateUtil​​ 针对日期时间操作提供一系列静态方法
      • ​​DateTime​​ 提供类似于Joda-Time中日期时间对象的封装,继承自Date类,并提供更加丰富的对象方法。
      • ​​FastDateFormat​​​ 提供线程安全的针对Date对象的格式化和日期字符串解析支持。此对象在实际使用中并不需要感知,相关操作已经封装在​​DateUtil​​​和​​DateTime​​的相关方法中。
      • ​​DateBetween​​​ 计算两个时间间隔的类,除了通过构造新对象使用外,相关操作也已封装在​​DateUtil​​​和​​DateTime​​的相关方法中。
      • ​​TimeInterval​​​ 一个简单的计时器类,常用于计算某段代码的执行时间,提供包括毫秒、秒、分、时、天、周等各种单位的花费时长计算,对象的静态构造已封装在​​DateUtil​​中。
      • ​​DatePattern​​​ 提供常用的日期格式化模式,包括​​String​​​类型和​​FastDateFormat​​两种类型。

      日期枚举

      考虑到​​Calendar​​​类中表示时间的字段(field)都是使用​​int​​​表示,在使用中非常不便,因此针对这些​​int​​​字段,封装了与之对应的Enum枚举类,这些枚举类在​​DateUtil​​​和​​DateTime​​相关方法中做为参数使用,可以更大限度的缩小参数限定范围。

      这些定义的枚举值可以通过​​getValue()​​​方法获得其与​​Calendar​​​类对应的int值,通过​​of(int)​​​方法从​​Calendar​​中int值转为枚举对象。

      与​​Calendar​​对应的这些枚举包括:

      • ​​Month​​ 表示月份,与Calendar中的int值一一对应。
      • ​​Week​​ 表示周,与Calendar中的int值一一对应

      月份枚举

      通过月份枚举可以获得某个月的最后一天

      public static void dateTimeMonthEnum() {
      // 31
      int lastDay = Month.of(Calendar.JANUARY).getLastDay(false);
      System.out.println("lastDay = " + lastDay);
      }

      另外,Hutool还定义了季度枚举。Season.SPRING为第一季度,表示1~3月。季度的概念并不等同于季节,因为季节与月份并不对应,季度常用于统计概念。

      时间枚举

      时间枚举​​DateUnit​​主要表示某个时间单位对应的毫秒数,常用于计算时间差。

      例如:​​DateUnit.MINUTE​​​表示分,也表示一分钟的毫米数,可以通过调用其​​getMillis()​​方法获得其毫秒数。

      DateUtil 日期时间工具

      由来

      考虑到Java本身对日期时间的支持有限,并且Date和Calendar对象的并存导致各种方法使用混乱和复杂,故使用此工具类做了封装。这其中的封装主要是日期和字符串之间的转换,以及提供对日期的定位(一个月前等等)。

      对于Date对象,为了便捷,使用了一个DateTime类来代替,继承自Date对象,主要的便利在于,覆盖了toString()方法,返回yyyy-MM-dd HH:mm:ss形式的字符串,方便在输出时的调用(例如日志记录等),提供了众多便捷的方法对日期对象操作,关于DateTime会在相关章节介绍。

      方法

      • 转换

      Date、long、Calendar之间的相互转换:

      public static void dateLongCalendarConvert() {
      // 当前时间
      Date date = DateUtil.date();
      System.out.println("date = " + date);

      // 当前时间
      Date date2 = DateUtil.date(Calendar.getInstance());
      System.out.println("date2 = " + date2);

      // 当前时间
      Date date3 = DateUtil.date(System.currentTimeMillis());
      System.out.println("date3 = " + date3);

      // 当前时间字符串,格式:yyyy-MM-dd HH:mm:ss
      String now = DateUtil.now();
      System.out.println("now = " + now);

      // 当前日期字符串,格式:yyyy-MM-dd
      String today = DateUtil.today();
      System.out.println("today = " + today);
      }
      • 字符串转日期

      ​​DateUtil.parse​​方法会自动识别一些常用格式,包括:

      • yyyy-MM-dd HH:mm:ss
      • yyyy/MM/dd HH:mm:ss
      • yyyy.MM.dd HH:mm:ss
      • yyyy年MM月dd日 HH时mm分ss秒
      • yyyy-MM-dd
      • yyyy/MM/dd
      • yyyy.MM.dd
      • HH:mm:ss
      • HH时mm分ss秒
      • yyyy-MM-dd HH:mm
      • yyyy-MM-dd HH:mm:ss.SSS
      • yyyyMMddHHmmss
      • yyyyMMddHHmmssSSS
      • yyyyMMdd
      • EEE, dd MMM yyyy HH:mm:ss z
      • EEE MMM dd HH:mm:ss zzz yyyy
      • yyyy-MM-dd'T'HH:mm:ss'Z'
      • yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
      • yyyy-MM-dd'T'HH:mm:ssZ
      • yyyy-MM-dd'T'HH:mm:ss.SSSZ
      public static void stringConvertDate() {
      String dateStr = "2017-03-01";
      Date date = DateUtil.parse(dateStr);
      System.out.println("date = " + date);
      }

      我们也可以使用自定义日期格式转化:

      public static void customDateFormatStringToDate() {
      String dateStr = "2017-03-01";
      Date date = DateUtil.parse(dateStr, "yyyy-MM-dd");
      System.out.println("date = " + date);
      }
      • 格式化日期输出
      public static void formatDateOutPut() {
      String dateStr = "2017-03-01";
      Date date = DateUtil.parse(dateStr);

      // 结果 2017/03/01
      String format = DateUtil.format(date, "yyyy/MM/dd");
      System.out.println("format = " + format);

      // 常用格式的格式化,结果:2017-03-01
      String formatDate = DateUtil.formatDate(date);
      System.out.println("formatDate = " + formatDate);

      // 结果:2017-03-01 00:00:00
      String formatDateTime = DateUtil.formatDateTime(date);
      System.out.println("formatDateTime = " + formatDateTime);

      // 结果:00:00:00
      String formatTime = DateUtil.formatTime(date);
      System.out.println("formatTime = " + formatTime);
      }
      • 获取Date对象的某个部分
      public static void getDatePart() {
      Date date = DateUtil.date();
      // 获得年的部分
      System.out.println("DateUtil.year(date) = " + DateUtil.year(date));

      // 获得月份,从0开始计数
      System.out.println("DateUtil.month(date) = " + DateUtil.month(date));

      // 获得月份枚举
      System.out.println("DateUtil.monthEnum(date) = " + DateUtil.monthEnum(date));
      // .....
      }
      • 开始和结束时间

      有的时候我们需要获得每天的开始时间、结束时间,每月的开始和结束时间等等,DateUtil也提供了相关方法:

      public static void getDateStartAndEndTime() {
      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 = " + beginOfDay);

      // 一天的结束,结果:2017-03-01 23:59:59
      Date endOfDay = DateUtil.endOfDay(date);
      System.out.println("endOfDay = " + endOfDay);
      }
      • 日期时间偏移

      日期或时间的偏移指针对某个日期增加或减少分、小时、天等等,达到日期变更的目的。Hutool也针对其做了大量封装:

      public static void dateTimeOffSet() {
      String dateStr = "2017-03-01 22:33:23";
      Date date = DateUtil.parse(dateStr);

      // 结果:2017-03-03 22:33:23
      Date newDate = DateUtil.offset(date, DateField.DAY_OF_MONTH, 2);
      System.out.println("newDate = " + newDate);

      // 常用偏移,结果:2017-03-04 22:33:23
      DateTime newDate2 = DateUtil.offsetDay(date, 3);
      System.out.println("newDate2 = " + newDate2);

      // 常用偏移,结果:2017-03-01 19:33:23
      DateTime newDate3 = DateUtil.offsetHour(date, -3);
      System.out.println("newDate3 = " + newDate3);
      }
      • 针对当前时间,提供了简化的偏移方法(例如昨天、上周、上个月等):
      public static void currentDateOffSet() {
      // 昨天
      System.out.println("DateUtil.yesterday() = " + DateUtil.yesterday());

      // 明天
      System.out.println("DateUtil.tomorrow() = " + DateUtil.tomorrow());

      // 上周
      System.out.println("DateUtil.lastWeek() = " + DateUtil.lastWeek());

      // 下周
      System.out.println("DateUtil.nextWeek() = " + DateUtil.nextWeek());

      // 上个月
      System.out.println("DateUtil.lastMonth() = " + DateUtil.lastMonth());

      // 下个月
      System.out.println("DateUtil.nextMonth() = " + DateUtil.nextMonth());
      }
      • 日期时间差

      有时候我们需要计算两个日期之间的时间差(相差天数、相差小时数等等),Hutool将此类方法封装为between方法:

      public static void dateTimeDifference(){
      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天
      long betweenDay = DateUtil.between(date1, date2, DateUnit.DAY);
      System.out.println("betweenDay = " + betweenDay);
      }
      • 格式化时间差

      有时候我们希望看到易读的时间差,比如XX天XX小时XX分XX秒,此时使用​​DateUtil.formatBetween​​方法:

      public static void formatTimeDifference() {
      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);

      long between = DateUtil.betweenMs(date1, date2);

      // Level.MINUTE表示精确到分
      String formatBetween = DateUtil.formatBetween(between, BetweenFormatter.Level.MINUTE);

      // 输出:31天1小时
      Console.log(formatBetween);
      }
      • 星座和属相
      public static void constellationAndChineseZodiac() {
      // "双子座"
      String zodiac = DateUtil.getZodiac(Month.MAY.getValue(), 25);
      System.out.println("zodiac = " + zodiac);

      // "蛇"
      String chineseZodiac = DateUtil.getChineseZodiac(2001);
      System.out.println("chineseZodiac = " + chineseZodiac);
      }
      • 其它
      public static void other() {
      // 年龄
      System.out.println("DateUtil.ageOfNow(\"2001-05-25\") = " + DateUtil.ageOfNow("2001-05-25"));

      // 是否闰年
      System.out.println("DateUtil.isLeapYear(2023) = " + DateUtil.isLeapYear(2023));
      }

      DateTime 日期时间对象

      由来

      考虑工具类的局限性,在某些情况下使用并不简便,于是​​DateTime​​​类诞生。​​DateTime​​对象充分吸取Joda-Time库的优点,并提供更多的便捷方法,这样我们在开发时不必再单独导入Joda-Time库便可以享受简单快速的日期时间处理过程。

      说明

      DateTime类继承于java.util.Date类,为Date类扩展了众多简便方法,这些方法多是​​DateUtil​​静态方法的对象表现形式,使用DateTime对象可以完全替代开发中Date对象的使用。

      使用

      • 新建对象

      ​​DateTime​​对象包含众多的构造方法,构造方法支持的参数有:

      • Date
      • Calendar
      • String(日期字符串,第二个参数是日期格式)
      • long 毫秒数

      构建对象有两种方式:​​DateTime.of()​​​和​​new DateTime()​​:

      public static void newObj() {
      Date date = new Date();

      // new方式创建
      DateTime time = new DateTime(date);
      Console.log(time);

      // of方式创建
      DateTime now = DateTime.now();
      DateTime dt = DateTime.of(date);

      Console.log(now);
      Console.log(dt);
      }
      • 使用对象

      ​​DateTime​​​的成员方法与​​DateUtil​​中的静态方法所对应,因为是成员方法,因此可以使用更少的参数操作日期时间。

      示例:获取日期成员(年、月、日等)

      public static void useObj() {
      DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);

      // 年,结果:2017
      int year = dateTime.year();
      System.out.println("year = " + year);

      // 季度(非季节),结果:Season.SPRING
      // Season season = dateTime.seasonEnum();

      // 月份,结果:Month.JANUARY
      Month month = dateTime.monthEnum();
      System.out.println("month = " + month);

      // 日,结果:5
      int day = dateTime.dayOfMonth();
      System.out.println("day = " + day);
      }

      更多成员方法请参阅API文档。

      • 对象的可变性

      DateTime对象默认是可变对象(调用offset、setField、setTime方法默认变更自身),但是这种可变性有时候会引起很多问题(例如多个地方共用DateTime对象)。我们可以调用​​setMutable(false)​​​方法使其变为不可变对象。在不可变模式下,​​offset​​​、​​setField​​​方法返回一个新对象,​​setTime​​方法抛出异常。

      public static void objVariability() {
      DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);

      // 默认情况下DateTime为可变对象,此时offset == dateTime
      DateTime offset = dateTime.offset(DateField.YEAR, 0);
      System.out.println("offset = " + offset);
      System.out.println("offset == dateTime = " + (offset == dateTime));

      // 设置为不可变对象后变动将返回新对象,此时offset != dateTime
      dateTime.setMutable(false);
      offset = dateTime.offset(DateField.YEAR, 0);
      System.out.println("offset = " + offset);
      System.out.println("offset == dateTime = " + (offset == dateTime));
      }
      • 格式化为字符串

      调用​​toString()​​​方法即可返回格式为​​yyyy-MM-dd HH:mm:ss​​​的字符串,调用​​toString(String format)​​可以返回指定格式的字符串。

      public static void formatString() {
      DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);

      // 结果:2017-01-05 12:34:23
      String dateStr = dateTime.toString();
      System.out.println("dateStr = " + dateStr);

      // 结果:2017/01/05
      String dateStr2 = dateTime.toString("yyyy/MM/dd");
      System.out.println("dateStr2 = " + dateStr2);
      }

      ChineseDate 农历日期

      介绍

      农历日期,提供了生肖、天干地支、传统节日等方法。

      使用

      • 构建 ChineseDate 对象

      ChineseDate表示了农历的对象,构建此对象既可以使用公历的日期,也可以使用农历的日期。

      public static void createChineseDateObj() {
      // 通过农历构建
      ChineseDate chineseDate = new ChineseDate(1992, 12, 14);
      System.out.println("chineseDate = " + chineseDate);

      // 通过公历构建
      ChineseDate chineseDate2 = new ChineseDate(DateUtil.parseDate("1993-01-06"));
      System.out.println("chineseDate2 = " + chineseDate2);
      }
      • 基本使用
      public static void chineseDateBasicUse() {
      // 通过公历构建
      // ChineseDate date = new ChineseDate(DateUtil.parseDate("2023-01-13"));
      ChineseDate date = new ChineseDate(DateUtil.parseDate("2020-01-25"));

      // 一月
      System.out.println("date.getChineseMonth() = " + date.getChineseMonth());

      // 正月
      System.out.println("date.getChineseMonthName() = " + date.getChineseMonthName());

      // 初一
      System.out.println("date.getChineseDay() = " + date.getChineseDay());

      // 庚子
      System.out.println("date.getCyclical() = " + date.getCyclical());

      // 生肖:鼠
      System.out.println("date.getChineseZodiac() = " + date.getChineseZodiac());

      // 传统节日(部分支持,逗号分隔):春节
      System.out.println("date.getFestivals() = " + date.getFestivals());

      // 庚子鼠年 正月初一
      System.out.println("date = " + date);
      }
      • 获取天干地支

      从​​5.4.1​​开始,Hutool支持天干地支的获取:

      public static void getChineseEra() {
      // 通过公历构建
      ChineseDate chineseDate = new ChineseDate(DateUtil.parseDate("2020-08-28"));

      // 庚子年甲申月癸卯日
      String cyclicalYMD = chineseDate.getCyclicalYMD();
      System.out.println("cyclicalYMD = " + cyclicalYMD);
      }

      LocalDateTime 工具 LocalDateTimeUtil

      介绍

      从Hutool的5.4.x开始,Hutool加入了针对JDK8+日期API的封装,此工具类的功能包括​​LocalDateTime​​​和​​LocalDate​​的解析、格式化、转换等操作。

      使用

      • 日期转换
      public static void dateConvert() {
      String dateStr = "2020-01-23T12:23:56";
      DateTime dt = DateUtil.parse(dateStr);

      // Date对象转换为LocalDateTime
      LocalDateTime of = LocalDateTimeUtil.of(dt);
      System.out.println("of = " + of);

      // 时间戳转换为LocalDateTime
      of = LocalDateTimeUtil.ofUTC(dt.getTime());
      System.out.println("of = " + of);
      }
      • 日期字符串解析
      public static void dateStringAnalysis() {
      // 解析ISO时间
      LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");
      System.out.println("localDateTime = " + localDateTime);

      // 解析自定义格式时间
      localDateTime = LocalDateTimeUtil.parse("2020-01-23", DatePattern.NORM_DATE_PATTERN);
      System.out.println("localDateTime = " + localDateTime);
      }

      解析同样支持​​LocalDate​​:

      public static void dateStringAnalysisToLocalDate() {
      LocalDate localDate = LocalDateTimeUtil.parseDate("2020-01-23");
      System.out.println("localDate = " + localDate);

      // 解析日期时间为LocalDate,时间部分舍弃
      localDate = LocalDateTimeUtil.parseDate("2020-01-23T12:23:56", DateTimeFormatter.ISO_DATE_TIME);
      System.out.println("localDate = " + localDate);
      }
      • 日期格式化
      public static void dateFormat() {
      LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

      // "2020-01-23 12:23:56"
      String format = LocalDateTimeUtil.format(localDateTime, DatePattern.NORM_DATETIME_PATTERN);
      System.out.println("format = " + format);
      }
      • 日期偏移
      public static void dateOffSet() {
      final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

      // 增加一天
      // "2020-01-24T12:23:56"
      LocalDateTime offset = LocalDateTimeUtil.offset(localDateTime, 1, ChronoUnit.DAYS);
      System.out.println("offset = " + offset);
      }

      如果是减少时间,offset第二个参数传负数即可:

      public static void dateOffSetReduce() {
      final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

      // 增加一天
      // "2020-01-24T12:23:56"
      LocalDateTime offset = LocalDateTimeUtil.offset(localDateTime, -1, ChronoUnit.DAYS);
      System.out.println("offset = " + offset);
      }
      • 计算时间间隔
      public static void calculateDateInterval() {
      LocalDateTime start = LocalDateTimeUtil.parse("2019-02-02T00:00:00");
      LocalDateTime end = LocalDateTimeUtil.parse("2020-02-02T00:00:00");

      Duration between = LocalDateTimeUtil.between(start, end);

      // 365
      System.out.println("between.toDays() = " + between.toDays());
      }
      • 一天的开始和结束
      public static void getOneDayStartEnd() {
      LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56");

      // "2020-01-23T00:00"
      LocalDateTime beginOfDay = LocalDateTimeUtil.beginOfDay(localDateTime);
      System.out.println("beginOfDay = " + beginOfDay);

      // "2020-01-23T23:59:59.999999999"
      LocalDateTime endOfDay = LocalDateTimeUtil.endOfDay(localDateTime);
      System.out.println("endOfDay = " + endOfDay);
      }

      TimeInterval 计时器工具

      介绍

      Hutool通过封装​​TimeInterval​​实现计时器功能,即可以计算方法或过程执行的时间。

      ​​TimeInterval​​支持分组计时,方便对比时间。

      使用

      public static void useTimeInterval() {
      TimeInterval timer = DateUtil.timer();

      //---------------------------------
      //------- 这是执行过程
      //---------------------------------
      Timer timerVar = new Timer();
      TimerTask task = new TimerTask() {
      @Override
      public void run() {
      //添加需要执行的代码
      System.out.println("业务代码!");
      }
      };
      // 设置定时任务,5000 毫秒后执行一次
      timerVar.schedule(task, 1000);

      // 花费毫秒数
      long ms = timer.interval();
      System.out.println("ms = " + ms);

      // 返回花费时间,并重置开始时间
      long time = timer.intervalRestart();
      System.out.println("time = " + time);

      // 花费分钟数
      long minutes = timer.intervalMinute();
      System.out.println("minutes = " + minutes);
      }
      • 也可以实现分组计时:
      public static void groupTim() {
      final TimeInterval timer = new TimeInterval();

      // 分组1
      timer.start("1");
      ThreadUtil.sleep(800);

      // 分组2
      timer.start("2");
      ThreadUtil.sleep(900);

      Console.log("Timer 1 took {} ms", timer.intervalMs("1"));
      Console.log("Timer 2 took {} ms", timer.intervalMs("2"));
      }

      IO 流相关

      ?> 由来

      IO的操作包括读和写,应用场景包括网络操作和文件操作。IO操作在Java中是一个较为复杂的过程,我们在面对不同的场景时,要选择不同的​​InputStream​​​和​​OutputStream​​​实现来完成这些操作。而如果想读写字节流,还需要​​Reader​​​和​​Writer​​的各种实现类。这些繁杂的实现类,一方面给我们提供了更多的灵活性,另一方面也增加了复杂性。

      ?> 封装

      io包的封装主要针对流、文件的读写封装,主要以工具类为主,提供常用功能的封装,这包括:

      • ​​IoUtil​​ 流操作工具类
      • ​​FileUtil​​ 文件读写和操作的工具类。
      • ​​FileTypeUtil​​ 文件类型判断工具类
      • ​​WatchMonitor​​ 目录、文件监听,封装了JDK1.7中的WatchService
      • ​​ClassPathResource​​针对ClassPath中资源的访问封装
      • ​​FileReader​​ 封装文件读取
      • ​​FileWriter​​ 封装文件写入

      ?> 流扩展

      除了针对JDK的读写封装外,还针对特定环境和文件扩展了流实现。

      包括:

      • ​​BOMInputStream​​针对含有BOM头的流读取
      • ​​FastByteArrayOutputStream​​ 基于快速缓冲FastByteBuffer的OutputStream,随着数据的增长自动扩充缓冲区(from blade)
      • ​​FastByteBuffer​​ 快速缓冲,将数据存放在缓冲集中,取代以往的单一数组(from blade)

      IoUtil IO工具类

      ?> 由来

      IO工具类的存在主要针对InputStream、OutputStream、Reader、Writer封装简化,并对NIO相关操作做封装简化。总体来说,Hutool对IO的封装,主要是工具层面,我们努力做到在便捷、性能和灵活之间找到最好的平衡点。

      ?> 方法

      • 拷贝

      流的读写可以总结为从输入流读取,从输出流写出,这个过程我们定义为拷贝。这个是一个基本过程,也是文件、流操作的基础。

      以文件流拷贝为例:

      public static void copy() {
      BufferedInputStream in = FileUtil.getInputStream("D:/Java/io/hutool/IoUtil/test.txt");
      BufferedOutputStream out = FileUtil.getOutputStream("D:/Java/io/hutool/IoUtil/test2.txt");

      long copySize = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE);
      System.out.println("copySize = " + copySize);
      }

      copy方法同样针对Reader、Writer、Channel等对象有一些重载方法,并提供可选的缓存大小。默认的,缓存大小为​​1024​​个字节,如果拷贝大文件或流数据较大,可以适当调整这个参数。

      针对NIO,提供了​​copyByNIO​​方法,以便和BIO有所区别。我查阅过一些资料,使用NIO对文件流的操作有一定的提升,我并没有做具体实验。相关测试请参阅博客:​​javascript:void(0)​​

      • Stream转Reader、Writer
      • ​​IoUtil.getReader​​​:将​​InputStream​​​转为​​BufferedReader​​用于读取字符流,它是部分readXXX方法的基础。
      • ​​IoUtil.getWriter​​​:将​​OutputStream​​​转为​​OutputStreamWriter​​用于写入字符流,它是部分writeXXX的基础。

      本质上这两个方法只是简单new一个新的Reader或者Writer对象,但是封装为工具方法配合IDE的自动提示可以大大减少查阅次数(例如你对BufferedReader、OutputStreamWriter不熟悉,是不需要搜索一下相关类)

      • 读取流中的内容

      读取流中的内容总结下来,可以分为read方法和readXXX方法。

      1. ​​read​​方法有诸多的重载方法,根据参数不同,可以读取不同对象中的内容,这包括:
      • ​​InputStream​​
      • ​​Reader​​
      • ​​FileChannel​​

      这三个重载大部分返回String字符串,为字符流读取提供极大便利。

      1. ​​readXXX​​方法主要针对返回值做一些处理,例如:
      • ​​readBytes​​ 返回byte数组(读取图片等)
      • ​​readHex​​ 读取16进制字符串
      • ​​readObj​​ 读取序列化对象(反序列化)
      • ​​readLines​​ 按行读取
      1. ​​toStream​​方法则是将某些对象转换为流对象,便于在某些情况下操作:
      • ​​String​​​ 转换为​​ByteArrayInputStream​​
      • ​​File​​​ 转换为​​FileInputStream​​

      ?> 写入到流

      • ​​IoUtil.write​​​方法有两个重载方法,一个直接调用​​OutputStream.write​​方法,另一个用于将对象转换为字符串(调用toString方法),然后写入到流中。
      • ​​IoUtil.writeObjects​​ 用于将可序列化对象序列化后写入到流中。

      ​​write​​方法并没有提供writeXXX,需要自己转换为String或byte[]。

      ?> 关闭

      对于IO操作来说,使用频率最高(也是最容易被遗忘)的就是​​close​​​操作,好在Java规范使用了优雅的​​Closeable​​接口,这样我们只需简单封装调用此接口的方法即可。

      关闭操作会面临两个问题:

      1. 被关闭对象为空
      2. 对象关闭失败(或对象已关闭)

      ​​IoUtil.close​​方法很好的解决了这两个问题。

      在JDK1.7中,提供了​​AutoCloseable​​​接口,在​​IoUtil​​中同样提供相应的重载方法,在使用中并不能感觉到有哪些不同。

      FileUtil 文件工具类

      ?> 简介

      在 IO 操作中,文件的操作相对来说是比较复杂的,但也是使用频率最高的部分,我们几乎所有的项目中几乎都躺着一个叫做FileUtil或者FileUtils的工具类,我想Hutool应该将这个工具类纳入其中,解决用来解决大部分的文件操作问题。

      总体来说,FileUtil类包含以下几类操作工具:

      1. 文件操作:包括文件目录的新建、删除、复制、移动、改名等
      2. 文件判断:判断文件或目录是否非空,是否为目录,是否为文件等等。
      3. 绝对路径:针对ClassPath中的文件转换为绝对路径文件。
      4. 文件名:主文件名,扩展名的获取
      5. 读操作:包括类似IoUtil中的getReader、readXXX操作
      6. 写操作:包括getWriter和writeXXX操作

      在FileUtil中,我努力将方法名与Linux相一致,例如创建文件的方法并不是createFile,而是​​touch​​,这种统一对于熟悉Linux的人来说,大大提高了上手速度。当然,如果你不熟悉Linux,那FileUtil工具类的使用则是在帮助你学习Linux命令。这些类Linux命令的方法包括:

      • ​​ls​​ 列出目录和文件
      • ​​touch​​ 创建文件,如果父目录不存在也自动创建
      • ​​mkdir​​ 创建目录,会递归创建每层目录
      • ​​del​​ 删除文件或目录(递归删除,不判断是否为空),这个方法相当于Linux的delete命令
      • ​​copy​​ 拷贝文件或目录

      这些方法提供了人性化的操作,例如​​touch​​​方法,在创建文件的情况下会自动创建上层目录(我想对于使用者来说这也是大部分情况下的需求),同样​​mkdir​​也会创建父目录。

      !> 需要注意的是,​​del​​方法会删除目录而不判断其是否为空,这一方面方便了使用,另一方面也可能造成一些预想不到的后果(比如拼写错路径而删除不应该删除的目录),所以请谨慎使用此方法。

      关于FileUtil中更多工具方法,请参阅API文档。

      FileTypeUtil 文件类型判断

      ?> 由来

      在文件上传时,有时候我们需要判断文件类型。但是又不能简单的通过扩展名来判断(防止恶意脚本等通过上传到服务器上),于是我们需要在服务端通过读取文件的首部几个二进制位来判断常用的文件类型。

      ?> 使用

      这个工具类使用非常简单,通过调用​​FileTypeUtil.getType​​即可判断,这个方法同时提供众多的重载方法,用于读取不同的文件和流。

      public static void getType() {
      File file = FileUtil.file("E:/电脑壁纸/ROG.jpg");
      String type = FileTypeUtil.getType(file);

      // 输出jpg则说明确实为jpg文件
      Console.log(type);
      }

      ?> 原理和局限性

      这个类是通过读取文件流中前N个byte值来判断文件类型,在类中我们通过Map形式将常用的文件类型做了映射,这些映射都是网络上搜集而来。也就是说,我们只能识别有限的几种文件类型。但是这些类型已经涵盖了常用的图片、音频、视频、Office文档类型,可以应对大部分的使用场景。

      !> 对于某些文本格式的文件我们并不能通过首部byte判断其类型,比如​​JSON​​,这类文件本质上是文本文件,我们应该读取其文本内容,通过其语法判断类型。

      ?> 自定义类型

      为了提高​​FileTypeUtil​​​的扩展性,我们通过​​putFileType​​方法可以自定义文件类型。

      public static void customFileType() {
      FileTypeUtil.putFileType("ffd8ffe000104a464946", "new_jpg");
      }

      第一个参数是文件流的前N个byte的16进制表示,我们可以读取自定义文件查看,选取一定长度即可(长度越长越精确),第二个参数就是文件类型,然后使用​​FileTypeUtil.getType​​即可。

      !> 注意 xlsx、docx本质上是各种XML打包为zip的结果,因此会被识别为zip格式。

      WatchMonitor 文件监听

      ?> 由来

      很多时候我们需要监听一个文件的变化或者目录的变动,包括文件的创建、修改、删除,以及目录下文件的创建、修改和删除,在JDK7前我们只能靠轮询方式遍历目录或者定时检查文件的修改事件,这样效率非常低,性能也很差。因此在JDK7中引入了​​WatchService​​。不过考虑到其API并不友好,于是Hutool便针对其做了简化封装,使监听更简单,也提供了更好的功能,这包括:

      • 支持多级目录的监听(WatchService只支持一级目录),可自定义监听目录深度
      • 延迟合并触发支持(文件变动时可能触发多次modify,支持在某个时间范围内的多次修改事件合并为一个修改事件)
      • 简洁易懂的API方法,一个方法即可搞定监听,无需理解复杂的监听注册机制。
      • 多观察者实现,可以根据业务实现多个​​Watcher​​来响应同一个事件(通过WatcherChain)

      ?> WatchMonitor

      在Hutool中,​​WatchMonitor​​​主要针对JDK7中​​WatchService​​​做了封装,针对文件和目录的变动(创建、更新、删除)做一个钩子,在​​Watcher​​中定义相应的逻辑来应对这些文件的变化。

      ?> 内部应用

      在hutool-setting模块,使用WatchMonitor监测配置文件变化,然后自动load到内存中。WatchMonitor的使用可以避免轮询,以事件响应的方式应对文件变化。

      ?> 使用

      ​​WatchMonitor​​提供的事件有:

      • ​​ENTRY_MODIFY​​ 文件修改的事件
      • ​​ENTRY_CREATE​​ 文件或目录创建的事件
      • ​​ENTRY_DELETE​​ 文件或目录删除的事件
      • ​​OVERFLOW​​ 丢失的事件

      这些事件对应​​StandardWatchEventKinds​​中的事件。

      下面我们介绍WatchMonitor的使用:

      ?> 监听指定事件

      public static void listenAppointEvent() {
      File file = FileUtil.file("classpath:example/example.properties");

      // 这里只监听文件或目录的修改事件
      WatchMonitor watchMonitor = WatchMonitor.create(file, WatchMonitor.ENTRY_MODIFY);

      watchMonitor.setWatcher(new Watcher() {
      @Override
      public void onCreate(WatchEvent<?> event, Path currentPath) {
      Object obj = event.context();
      Console.log("创建:{}-> {}", currentPath, obj);
      }

      @Override
      public void onModify(WatchEvent<?> event, Path currentPath) {
      Object obj = event.context();
      Console.log("修改:{}-> {}", currentPath, obj);
      }

      @Override
      public void onDelete(WatchEvent<?> event, Path currentPath) {
      Object obj = event.context();
      Console.log("删除:{}-> {}", currentPath, obj);
      }

      @Override
      public void onOverflow(WatchEvent<?> event, Path currentPath) {
      Object obj = event.context();
      Console.log("Overflow:{}-> {}", currentPath, obj);
      }
      });

      // 设置监听目录的最大深入,目录层级大于制定层级的变更将不被监听,默认只监听当前层级目录
      watchMonitor.setMaxDepth(3);
      // 启动监听
      watchMonitor.start();
      }

      ?> 监听全部事件

      其实我们不必实现​​Watcher​​​的所有接口方法,Hutool同时提供了​​SimpleWatcher​​类,只需重写对应方法即可。

      同样,如果我们想监听所有事件,可以:

      public static void listenAllEvent() {
      File file = FileUtil.file("classpath:example/example.properties");

      WatchMonitor.createAll(file, new SimpleWatcher() {
      @Override
      public void onModify(WatchEvent<?> event, Path currentPath) {
      Console.log("EVENT modify");
      }
      }).start();
      }

      ​​createAll​​方法会创建一个监听所有事件的WatchMonitor,同时在第二个参数中定义Watcher来负责处理这些变动。

      ?> 延迟处理监听事件

      在监听目录或文件时,如果这个文件有修改操作,JDK会多次触发modify方法,为了解决这个问题,我们定义了​​DelayWatcher​​,此类通过维护一个Set将短时间内相同文件多次modify的事件合并处理触发,从而避免以上问题。

      public static void delayFileChangeListenEvent() {
      File file = FileUtil.file("classpath:example/example.properties");
      WatchMonitor monitor = WatchMonitor.createAll(file, new DelayWatcher(new SimpleWatcher() {
      @Override
      public void onModify(WatchEvent<?> event, Path currentPath) {
      Console.log("EVENT modify");
      }
      }, 500));
      monitor.start();
      }

      文件

      FileReader 文件读取

      ?> 由来

      在​​FileUtil​​中本来已经针对文件的读操作做了大量的静态封装,但是根据职责分离原则,我觉得有必要针对文件读取单独封装一个类,这样项目更加清晰。当然,使用FileUtil操作文件是最方便的。

      ?> 使用

      在JDK中,同样有一个FileReader类,但是并不如想象中的那样好用,于是Hutool便提供了更加便捷FileReader类。

      public static void useFileReader() {
      // 默认UTF-8编码,可以在构造中传入第二个参数做为编码
      FileReader fileReader = new FileReader("classpath:example/example.properties");
      String result = fileReader.readString();
      System.out.println("result = " + result);
      }

      FileReader提供了以下方法来快速读取文件内容:

      • ​​readBytes​​
      • ​​readString​​
      • ​​readLines​​

      同时,此类还提供了以下方法用于转换为流或者BufferedReader:

      • ​​getReader​​
      • ​​getInputStream​​

      FileWriter 文件写入

      相应的,文件读取有了,自然有文件写入类,使用方式与​​FileReader​​也类似:

      public static void useFileWriter() {
      FileWriter writer = new FileWriter("classpath:example/example.properties");
      writer.write("leader_tang");
      Console.log("写入完毕!");
      }

      写入文件分为追加模式和覆盖模式两类,追加模式可以用​​append​​​方法,覆盖模式可以用​​write​​方法,同时也提供了一个write方法,第二个参数是可选覆盖模式。

      同样,此类提供了:

      • ​​getOutputStream​​
      • ​​getWriter​​
      • ​​getPrintWriter​​

      这些方法用于转换为相应的类提供更加灵活的写入操作。

      FileAppender 文件追加

      ?> 由来

      顾名思义,​​FileAppender​​类表示文件追加器。此对象持有一个一个文件,在内存中积累一定量的数据后统一追加到文件,此类只有在写入文件时打开文件,并在写入结束后关闭。因此此类不需要关闭。

      在调用append方法后会缓存于内存,只有超过容量后才会一次性写入文件,因此内存中随时有剩余未写入文件的内容,在最后必须调用flush方法将剩余内容刷入文件。

      也就是说,这是一个支持缓存的文件内容追加器。此类主要用于类似于日志写出这类需求所用。

      ?> 使用

      public static void useFileAppender() {
      File file = new File("D:/organization/dromara/hutool/io/IoUtil/FileAppender.txt");

      FileAppender appender = new FileAppender(file, 16, true);
      appender.append("123");
      appender.append("abc");
      appender.append("xyz");

      appender.flush();
      appender.toString();
      }

      Tailer 文件跟随

      ?> 由来

      有时候我们要启动一个线程实时“监控”文件的变化,比如有新内容写出到文件时,我们可以及时打印出来,这个功能非常类似于Linux下的​​tail -f​​命令。

      ?> 使用

      public static void useTailer() {
      Tailer tailer = new Tailer(FileUtil.file("D:/organization/dromara/hutool/file/test.log"), Tailer.CONSOLE_HANDLER, 2);
      tailer.start();
      }

      其中​​Tailer.CONSOLE_HANDLER​​表示文件新增内容默认输出到控制台。

      当然我们也可以自定义行处理器,实现 LineHandler 接口,然后重写 handle 方法即可。

      /**
      * 命令行打印的行处理器
      *
      * @author looly
      * @since 4.5.2
      */
      public static class ConsoleLineHandler implements LineHandler {
      @Override
      public void handle(String line) {
      Console.log(line);
      }
      }

      我们也可以实现自己的LineHandler来处理每一行数据。

      !> 注意: 此方法会阻塞当前线程

      FileNameUtil 文件名工具

      ?> 由来

      文件名操作工具类,主要针对文件名获取主文件名、扩展名等操作,同时针对Windows平台,清理无效字符。

      此工具类在​​5.4.1​​​之前是​​FileUtil​​​的一部分,后单独剥离为​​FileNameUtil​​工具。

      ?> 使用

      1. 获取文件名
      public static void getFileName() {
      File file = FileUtil.file("D:/organization/dromara/hutool/file/test.log");

      // test.txt
      String name = FileNameUtil.getName(file);
      System.out.println("name = " + name);
      }
      1. 获取主文件名和扩展名
      public static void getMainFileNameAndExtension() {
      File file = FileUtil.file("D:/organization/dromara/hutool/file/test.log");

      // "test"
      String mainName = FileNameUtil.mainName(file);
      System.out.println("mainName = " + mainName);

      // "log"
      String extName = FileNameUtil.extName(file);
      System.out.println("extName = " + extName);
      }

      !> 注意,此处获取的扩展名不带​​.​​​。 ​​FileNameUtil.mainName​​​和​​FileNameUtil.getPrefix​​​等价,同理​​FileNameUtil.extName​​​和​​FileNameUtil.getSuffix​​等价,保留两个方法用于适应不同用户的习惯。

      资源

      ?> 由来

      资源(Resource)在Hutool中是一个广泛的概念,凡是存储数据的地方都可以归类到资源,那为何要提供一个如此抽象的接口呢?

      在实际编码当中,我们需要读取一些数据,比如配置文件、文本内容、图片甚至是任何二进制流,为此我们要加入很多的重载方法,比如:

      read(File file){...}

      read(InputStream in){...}

      read(byte[] bytes){...}

      read(URL url){...}

      等等如此,这样会造成整个代码变得非常冗余,查找API也很费劲。其实无论数据来自哪里,最终目的是,我们想从这些地方读到byte[]或者String。那么,我们就可以抽象一个Resource接口,让代码变得简单:

      read(Resource resource){...}

      用户只需传入Resource的实现即可。

      ?> 定义

      常见的,我们需要从资源中获取流(getStream),获取Reader来读取文本(getReader),直接读取文本(readStr),于是定义如下:

      public interface Resource {
      String getName();
      URL getUrl();
      InputStream getStream();
      BufferedReader getReader(Charset charset);
      String readStr(Charset charset);
      }

      !> 关于Resource的详细定义见:​​Resource.java​​

      定义了Resource,我们就可以预定义一些特别的资源:

      • ​​BytesResource​​ 从byte[]中读取资源
      • ​​InputStreamResource​​ 从流中读取资源
      • ​​StringResource​​ 从String中读取资源
      • ​​UrlResource​​ 从URL中读取资源
      • ​​FileResource​​ 从文件中读取资源
      • ​​ClassPathResource​​ 从classpath(src/resources下)中读取资源
      • ​​WebAppResource​​ 从web root中读取资源
      • ​​MultiResource​​ 从多种资源中混合读取资源
      • ​​MultiFileResource​​ 从多个文件中混合读取资源

      当然,我们还可以根据业务需要自己实现Resource接口,完成自定义的资源读取。

      !> 为了便于资源的查找,可以使用​​ResourceUtil​​快捷工具来获得我们需要的资源。

      ResourceUtil 资源工具

      ?> 介绍

      ​​ResourceUtil​​提供了资源快捷读取封装。

      ?> 使用

      ​​ResourceUtil​​​中最核心的方法是​​getResourceObj​​​,此方法可以根据传入路径是否为绝对路径而返回不同的实现。比如路径是:​​file:/opt/test​​​,或者​​/opt/test​​​都会被当作绝对路径,此时调用​​FileResource​​​来读取数据。如果不满足以上条件,默认调用​​ClassPathResource​​读取classpath中的资源或者文件。

      同样,此工具类还封装了​​readBytes​​​和​​readStr​​用于快捷读取bytes和字符串。

      举个例子,假设我们在classpath下放了一个​​test.xml​​,读取就变得非常简单:

      test.xml:

      <?xml version="1.0" encoding="UTF-8" ?>
      <BNTnag>

      </BNTnag>
      public static void useResourceUtilReadStr() {
      String str = ResourceUtil.readUtf8Str("test.xml");
      System.out.println("str = " + str);
      }

      假设我们的文件存放在​​src/resources/config​​目录下,则读取改为:

      public static void useResourceUtilReadStrByNewPath() {
      String str = ResourceUtil.readUtf8Str("config/test.xml");
      System.out.println("str = " + str);
      }

      !> 注意 在IDEA中,新加入文件到​​src/resources​​目录下,需要重新import项目,以便在编译时顺利把资源文件拷贝到target目录下。如果提示找不到文件,请去target目录下确认文件是否存在。

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.51cto.com/u_15652665/6091624,作者:qq628b229e2808e,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:【实战问题】-- 聊聊礼品领取的架构设计中setnx相关的细节

      下一篇:(全排列)信封全部分配错误的方案有多少种

      相关文章

      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5275140

      查看更多

      热门标签

      linux java python javascript 数组 前端 docker Linux vue 函数 shell git 节点 容器 示例
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号