mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
clean history
This commit is contained in:
commit
6b011af032
5
.gitee/ISSUE_TEMPLATE.zh-CN.md
Normal file
5
.gitee/ISSUE_TEMPLATE.zh-CN.md
Normal file
@ -0,0 +1,5 @@
|
||||
### 使用的JDK版本和Hutool版本
|
||||
|
||||
### 问题描述(包括截图)
|
||||
|
||||
### 报错信息
|
10
.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
Normal file
10
.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
Normal file
@ -0,0 +1,10 @@
|
||||
#### 说明
|
||||
|
||||
1. 请确认你提交的PR是到'v4-dev'分支,否则我会手动修改代码并关闭PR。
|
||||
2. 请确认没有更改代码风格(如tab缩进)
|
||||
3. 新特性添加请确认注释完备,如有必要,请在src/test/java下添加Junit测试用例
|
||||
|
||||
### 修改描述(包括说明bug修复还是新特性添加)
|
||||
|
||||
1. [bug修复] balabala……
|
||||
2. [新特性] balabala……
|
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# Maven
|
||||
target/
|
||||
dependency-reduced-pom.xml
|
||||
pom.xml.versionsBackup
|
||||
.factorypath
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
#IDEA
|
||||
# idea ignore
|
||||
.idea/
|
||||
*.ipr
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
# temp ignore
|
||||
*.log
|
||||
*.cache
|
||||
*.diff
|
||||
*.patch
|
||||
*.tmp
|
||||
|
||||
# system ignore
|
||||
.DS_Store
|
||||
Thumbs.db
|
24
.travis.yml
Normal file
24
.travis.yml
Normal file
@ -0,0 +1,24 @@
|
||||
language: java
|
||||
|
||||
sudo: false # faster builds
|
||||
|
||||
install: true
|
||||
|
||||
jdk:
|
||||
- openjdk8
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- '$HOME/.m2'
|
||||
|
||||
script:
|
||||
- export TZ=Asia/Shanghai
|
||||
- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
|
||||
- mvn cobertura:cobertura -Dcobertura.report.format=xml -Dmaven.javadoc.skip.true
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
1316
CHANGELOG.md
Normal file
1316
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
121
LICENSE.txt
Normal file
121
LICENSE.txt
Normal file
@ -0,0 +1,121 @@
|
||||
木兰宽松许可证, 第1版
|
||||
|
||||
木兰宽松许可证, 第1版
|
||||
2019年8月 http://license.coscl.org.cn/MulanPSL
|
||||
|
||||
您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第1版(“本许可证”)的如下条款的约束:
|
||||
|
||||
0. 定义
|
||||
|
||||
“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
|
||||
|
||||
“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
|
||||
|
||||
“法人实体”是指提交贡献的机构及其“关联实体”。
|
||||
|
||||
“关联实体”是指,对“本许可证”下的一方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
|
||||
|
||||
“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
|
||||
|
||||
1. 授予版权许可
|
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
|
||||
|
||||
2. 授予专利许可
|
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括仅因您或他人修改“贡献”或其他结合而将必然会侵犯到的专利权利要求。如您或您的“关联实体”直接或间接地(包括通过代理、专利被许可人或受让人),就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
|
||||
|
||||
3. 无商标许可
|
||||
|
||||
“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
|
||||
|
||||
4. 分发限制
|
||||
|
||||
您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
|
||||
|
||||
5. 免责声明与责任限制
|
||||
|
||||
“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
|
||||
|
||||
条款结束。
|
||||
|
||||
如何将木兰宽松许可证,第1版,应用到您的软件
|
||||
|
||||
如果您希望将木兰宽松许可证,第1版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
|
||||
|
||||
1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
|
||||
|
||||
2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
|
||||
|
||||
3, 请将如下声明文本放入每个源文件的头部注释中。
|
||||
|
||||
Copyright (c) [2019] [name of copyright holder]
|
||||
[Software Name] is licensed under the Mulan PSL v1.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v1.
|
||||
You may obtain a copy of Mulan PSL v1 at:
|
||||
http://license.coscl.org.cn/MulanPSL
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
|
||||
PURPOSE.
|
||||
See the Mulan PSL v1 for more details.
|
||||
|
||||
|
||||
Mulan Permissive Software License,Version 1
|
||||
|
||||
Mulan Permissive Software License,Version 1 (Mulan PSL v1)
|
||||
August 2019 http://license.coscl.org.cn/MulanPSL
|
||||
|
||||
Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v1 (this License) with following terms and conditions:
|
||||
|
||||
0. Definition
|
||||
|
||||
Software means the program and related documents which are comprised of those Contribution and licensed under this License.
|
||||
|
||||
Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
|
||||
|
||||
Legal Entity means the entity making a Contribution and all its Affiliates.
|
||||
|
||||
Affiliates means entities that control, or are controlled by, or are under common control with a party to this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
|
||||
|
||||
Contribution means the copyrightable work licensed by a particular Contributor under this License.
|
||||
|
||||
1. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
|
||||
|
||||
2. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed, excluding of any patent claims solely be infringed by your or others’ modification or other combinations. If you or your Affiliates directly or indirectly (including through an agent, patent licensee or assignee), institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
|
||||
|
||||
3. No Trademark License
|
||||
|
||||
No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4.
|
||||
|
||||
4. Distribution Restriction
|
||||
|
||||
You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
|
||||
|
||||
5. Disclaimer of Warranty and Limitation of Liability
|
||||
|
||||
The Software and Contribution in it are provided without warranties of any kind, either express or implied. In no event shall any Contributor or copyright holder be liable to you for any damages, including, but not limited to any direct, or indirect, special or consequential damages arising from your use or inability to use the Software or the Contribution in it, no matter how it’s caused or based on which legal theory, even if advised of the possibility of such damages.
|
||||
|
||||
End of the Terms and Conditions
|
||||
|
||||
How to apply the Mulan Permissive Software License,Version 1 (Mulan PSL v1) to your software
|
||||
|
||||
To apply the Mulan PSL v1 to your work, for easy identification by recipients, you are suggested to complete following three steps:
|
||||
|
||||
i. Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
|
||||
ii. Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package;
|
||||
iii. Attach the statement to the appropriate annotated syntax at the beginning of each source file.
|
||||
|
||||
Copyright (c) [2019] [name of copyright holder]
|
||||
[Software Name] is licensed under the Mulan PSL v1.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v1.
|
||||
You may obtain a copy of Mulan PSL v1 at:
|
||||
http://license.coscl.org.cn/MulanPSL
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
|
||||
PURPOSE.
|
||||
|
||||
See the Mulan PSL v1 for more details.
|
175
README.md
Normal file
175
README.md
Normal file
@ -0,0 +1,175 @@
|
||||
<p align="center">
|
||||
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="400"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<strong>A set of tools that keep Java sweet.</strong>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://search.maven.org/search?q=g:%22cn.hutool%22%20AND%20a:%22hutool-all%22">
|
||||
<img src="https://img.shields.io/maven-central/v/cn.hutool/hutool-all.svg?label=Maven%20Central" ></img>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.apache.org/licenses/LICENSE-2.0.html">
|
||||
<img src="https://img.shields.io/:license-apache-blue.svg" ></img>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.oracle.com/technetwork/java/javase/downloads/index.html">
|
||||
<img src="https://img.shields.io/badge/JDK-1.7+-green.svg" ></img>
|
||||
</a>
|
||||
<a target="_blank" href="https://travis-ci.org/looly/hutool">
|
||||
<img src="https://travis-ci.org/looly/hutool.svg?branch=v4-master" ></img>
|
||||
</a>
|
||||
<a href="https://www.codacy.com/app/looly/hutool?utm_source=github.com&utm_medium=referral&utm_content=looly/hutool&utm_campaign=Badge_Grade"><img src="https://api.codacy.com/project/badge/Grade/3e1b8a70248c46579b7b0d01d60c6377"/>
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/looly/hutool">
|
||||
<img src="https://codecov.io/gh/looly/hutool/branch/v4-master/graph/badge.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://gitter.im/hutool/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge">
|
||||
<img src="https://badges.gitter.im/hutool/Lobby.svg" ></img>
|
||||
</a>
|
||||
<a target="_blank" href="https://gitee.com/loolly/hutool/stargazers">
|
||||
<img src='https://gitee.com/loolly/hutool/badge/star.svg?theme=dark' alt='gitee star'></img>
|
||||
</a>
|
||||
<a target="_blank" href='https://github.com/looly/hutool'>
|
||||
<img src="https://img.shields.io/github/stars/looly/hutool.svg?style=social" alt="github star"></img>
|
||||
</a>
|
||||
<a target="_blank" href='https://app.netlify.com/sites/hutool/deploys'>
|
||||
<img src="https://api.netlify.com/api/v1/badges/7e0824f9-5f9a-4df0-89dd-b2fccfbeccb1/deploy-status" alt="netlify"></img>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
-- 主页:<a href="https://hutool.cn">https://hutool.cn/</a> | <a href="https://www.hutool.club/">https://www.hutool.club/</a> --
|
||||
</p>
|
||||
<p align="center">
|
||||
-- QQ群③:<a href="https://shang.qq.com/wpa/qunwpa?idkey=35764b2247c46ffebe28e4541e5b2af8f5dee5efcf47ceec69d21e4521aa8c75">555368316</a> --
|
||||
-- QQ群④:<a href="https://shang.qq.com/wpa/qunwpa?idkey=309056e409a304a454c7ba250a10d38dd82b9b49cd0e1f180fedbde78b02ae0d">718802356</a> --
|
||||
</p>
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 简介
|
||||
|
||||
Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。它最初是作者项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。
|
||||
|
||||
Hutool是Hu + tool的自造词,谐音“糊涂”,寓意,追求“万事都作糊涂观,无所谓失,无所谓得”的境界。
|
||||
|
||||
### Hutool如何改变我们的coding方式
|
||||
|
||||
Hutool的目标是使用一个工具方法代替一段复杂代码,从而最大限度的避免“复制粘贴”代码的问题,彻底改变我们写代码的方式。
|
||||
|
||||
以计算MD5为例:
|
||||
|
||||
- 【以前】打开百度 -> 搜“Java MD5加密” -> 打开某篇博客-> 复制粘贴 -> 改改好用
|
||||
- 【现在】引入Hutool -> SecureUtil.md5()
|
||||
|
||||
同样,当我们想实现什么功能,脑袋中第一个想到的就是去找XXXUtil,而非百度。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 包含组件
|
||||
一个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的封装
|
||||
- hutool-socket 基于Java的NIO和AIO的Socket封装
|
||||
|
||||
可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 文档
|
||||
|
||||
[中文文档](https://www.hutool.cn/docs/)
|
||||
[中文文档(备用)](https://www.hutool.club/docs/)
|
||||
|
||||
[参考API](https://apidoc.gitee.com/loolly/hutool/)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 安装
|
||||
|
||||
### Maven
|
||||
在项目的pom.xml的dependencies中加入以下内容:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>4.6.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:4.6.2'
|
||||
```
|
||||
|
||||
### 非Maven项目
|
||||
|
||||
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/4.6.2/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/4.6.2/)
|
||||
|
||||
> 注意
|
||||
> Hutool支持JDK7+,对Android平台没有测试,不能保证所有工具类获工具方法可用。
|
||||
|
||||
### 编译安装
|
||||
|
||||
访问Hutool的码云主页:[https://gitee.com/loolly/hutool](https://gitee.com/loolly/hutool) 下载整个项目源码(v4-master或v4-dev分支都可)然后进入Hutool项目目录执行:
|
||||
|
||||
```sh
|
||||
./hutool.sh install
|
||||
```
|
||||
|
||||
然后就可以使用Maven引入了。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 添砖加瓦
|
||||
|
||||
### 提供bug反馈或建议
|
||||
|
||||
- [码云Gitee](https://gitee.com/loolly/hutool/issues)
|
||||
- [Github](https://github.com/looly/hutool/issues)
|
||||
|
||||
### 遵照的原则
|
||||
|
||||
Hutool欢迎任何人为Hutool添砖加瓦,贡献代码,不过作者是一个强迫症患者,为了照顾病人,需要提交的pr(pull request)符合一些规范,规范如下:
|
||||
|
||||
1. 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,如果愿意,也可以加上你的大名。
|
||||
2. Hutool的缩进按照Eclipse(不要跟我说IDEA多好用,作者非常懒,学不会)默认(tab)缩进,所以请遵守(不要和我争执空格与tab的问题,这是一个病人的习惯)。
|
||||
3. 新加的方法不要使用第三方库的方法,Hutool遵循无依赖原则(除非在extra模块中加方法工具)。
|
||||
4. 请pull request到`v4-dev`分支。Hutool在4.x版本后使用了新的分支:`v4-master`是主分支,表示已经发布中央库的版本,这个分支不允许pr,也不允许修改。`v4-dev`分支是开发分支,Hutool的下个版本或者SNAPSHOT版本在这个分支上开发,你可以pr到这个分支。
|
||||
|
||||
### 贡献代码的步骤
|
||||
|
||||
1. 在Gitee或者Github上fork项目到自己的repo
|
||||
2. 把fork过去的项目也就是你的项目clone到你的本地
|
||||
3. 修改代码(记得一定要修改v4-dev分支)
|
||||
4. commit后push到自己的库(v4-dev分支)
|
||||
5. 登录Gitee或Github在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
|
||||
6. 等待作者合并
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 捐赠
|
||||
|
||||
如果你觉得Hutool不错,可以捐赠请作者吃包辣条~,在此表示感谢^_^。
|
||||
|
||||
点击以下链接,将页面拉到最下方点击“捐赠”即可。
|
||||
|
||||
[前往捐赠](https://gitee.com/loolly/hutool)
|
7
bin/check_dependency_updates.sh
Executable file
7
bin/check_dependency_updates.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
#--------------------------------------
|
||||
# Check dependency, thanks to t-io
|
||||
#--------------------------------------
|
||||
|
||||
mvn versions:display-dependency-updates
|
6
bin/commit.sh
Executable file
6
bin/commit.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
git add .
|
||||
git commit -am "$1"
|
||||
|
||||
bin/push_dev.sh
|
3
bin/deploy.sh
Executable file
3
bin/deploy.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
mvn clean deploy -P release
|
3
bin/install.sh
Executable file
3
bin/install.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
exec mvn clean source:jar javadoc:javadoc install -Dmaven.test.skip=false -Dmaven.javadoc.skip=false
|
3
bin/javadoc.sh
Executable file
3
bin/javadoc.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
exec mvn javadoc:javadoc
|
11
bin/logo.sh
Executable file
11
bin/logo.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo '========================================'
|
||||
echo ' __ __ __ __ '
|
||||
echo ' / / / /__ __ / /_ ____ ____ / / '
|
||||
echo ' / /_/ // / / // __// __ \ / __ \ / / '
|
||||
echo ' / __ // /_/ // /_ / /_/ // /_/ // / '
|
||||
echo '/_/ /_/ \____/ \__/ \____/ \____//_/ '
|
||||
echo ''
|
||||
echo '-----------http://hutool.cn/------------'
|
||||
echo '========================================'
|
9
bin/push_dev.sh
Executable file
9
bin/push_dev.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo -e "\033[32mCheckout to v4-dev\033[0m"
|
||||
git checkout v4-dev
|
||||
|
||||
echo -e "\033[32mPush to origin v4-dev\033[0m"
|
||||
git push origin v4-dev
|
||||
echo -e "\033[32mPush to osc v4-dev\033[0m"
|
||||
git push osc v4-dev
|
12
bin/push_master.sh
Executable file
12
bin/push_master.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo -e "\033[32mCheckout to v4-master\033[0m"
|
||||
git checkout v4-master
|
||||
|
||||
echo -e "\033[32mMerge v4-dev branch\033[0m"
|
||||
git merge v4-dev -m 'Prepare release'
|
||||
|
||||
echo -e "\033[32mPush to origin v4-master\033[0m"
|
||||
git push origin v4-master
|
||||
echo -e "\033[32mPush to osc v4-master\033[0m"
|
||||
git push osc v4-master
|
34
bin/replaceVersion.sh
Executable file
34
bin/replaceVersion.sh
Executable file
@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
#-----------------------------------------------------------
|
||||
# 此脚本用于每次升级Hutool时替换相应位置的版本号
|
||||
#-----------------------------------------------------------
|
||||
|
||||
set -o errexit
|
||||
|
||||
pwd=$(pwd)
|
||||
|
||||
echo "当前路径:${pwd}"
|
||||
|
||||
if [ -n "$1" ];then
|
||||
new_version="$1"
|
||||
old_version=`cat ${pwd}/bin/version.txt`
|
||||
echo "$old_version 替换为新版本 $new_version"
|
||||
else
|
||||
# 参数错误,退出
|
||||
echo "ERROR: 请指定新版本!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ ! -n "$old_version" ]; then
|
||||
echo "ERROR: 旧版本不存在,请确认bin/version.txt中信息正确"
|
||||
exit
|
||||
fi
|
||||
|
||||
# 替换README.md中的版本
|
||||
sed -i "s/${old_version}/${new_version}/g" $pwd/README.md
|
||||
# 替换docs/index.html中的版本
|
||||
sed -i "s/${old_version}/${new_version}/g" $pwd/docs/js/version.js
|
||||
|
||||
# 保留新版本号
|
||||
echo "$new_version" > $pwd/bin/version.txt
|
3
bin/test.sh
Executable file
3
bin/test.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
exec mvn test
|
21
bin/update_version.sh
Executable file
21
bin/update_version.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
#------------------------------------------------
|
||||
# 升级Hutool版本,包括:
|
||||
# 1. 升级pom.xml中的版本号
|
||||
# 2. 替换README.md和docs中的版本号
|
||||
#------------------------------------------------
|
||||
|
||||
if [ ! -n "$1" ]; then
|
||||
echo "ERROR: 新版本不存在,请指定参数1"
|
||||
exit
|
||||
fi
|
||||
|
||||
# 替换所有模块pom.xml中的版本
|
||||
mvn versions:set -DnewVersion=$1
|
||||
|
||||
# 不带-SNAPSHOT的版本号,用于替换其它地方
|
||||
version=${1%-SNAPSHOT}
|
||||
|
||||
# 替换其它地方的版本
|
||||
$(pwd)/bin/replaceVersion.sh "$version"
|
1
bin/version.txt
Executable file
1
bin/version.txt
Executable file
@ -0,0 +1 @@
|
||||
4.6.2
|
0
docs/.nojekyll
Normal file
0
docs/.nojekyll
Normal file
74
docs/docs/index.html
Normal file
74
docs/docs/index.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hutool参考文档</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="Hutool,Java工具集">
|
||||
<meta name="keywords" content="hutool,java,orm,tools,工具" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
|
||||
<!-- QQ分享卡片信息 -->
|
||||
<meta itemprop="name" content="Hutool-Java工具集" />
|
||||
<meta itemprop="description" content="Hutool是一个Java工具包,它帮助我们简化代码和方法,让Java语言也可以甜甜的。" />
|
||||
<meta itemprop="image" content="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo_small.jpg" />
|
||||
|
||||
<link rel="icon" href="//cdn.jsdelivr.net/gh/looly/hutool-site/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css">
|
||||
|
||||
<!-- 百度统计 -->
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?f2c884fc06fca522c4105429259b8a73";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: '<img src="//cdn.jsdelivr.net/gh/looly/hutool-site/images/logo_small.jpg" style="height:100px" />',
|
||||
repo: 'looly/hutool',
|
||||
loadSidebar: true,
|
||||
alias: {
|
||||
'/.*/SUMMARY.md': 'https://cdn.jsdelivr.net/gh/looly/hutool-site/docs/SUMMARY.md',
|
||||
'.*?/CHANGELOG': 'https://cdn.jsdelivr.net/gh/looly/hutool/CHANGELOG.md'
|
||||
},
|
||||
homepage: 'https://cdn.jsdelivr.net/gh/looly/hutool/README.md',
|
||||
basePath: 'https://cdn.jsdelivr.net/gh/looly/hutool-site/docs/',
|
||||
subMaxLevel: 2,
|
||||
//封面
|
||||
//coverpage: true,
|
||||
// 加载 summary.md
|
||||
loadSidebar: 'SUMMARY.md',
|
||||
//自动显示标题
|
||||
autoHeader: true,
|
||||
//自动跳转到页面顶部
|
||||
auto2top: true,
|
||||
//小屏设备下合并导航栏到侧边栏
|
||||
mergeNavbar: true,
|
||||
//搜索插件
|
||||
search: {
|
||||
placeholder: '搜索',
|
||||
noData: '没有结果!',
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-java.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code"></script>
|
||||
|
||||
<script>
|
||||
((window.gitter = {}).chat = {}).options = {
|
||||
room: 'hutool/Lobby'
|
||||
};
|
||||
</script>
|
||||
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
|
||||
</body>
|
||||
</html>
|
497
docs/index.html
Normal file
497
docs/index.html
Normal file
@ -0,0 +1,497 @@
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!-->
|
||||
<html class="no-js">
|
||||
<!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="Hutool: A set of tools that keep Java sweet." />
|
||||
<meta name="keywords" content="hutool,java,orm,tools,工具" />
|
||||
<!-- 搜索引擎验证 -->
|
||||
<meta name="baidu-site-verification" content="3wiVWg7AdT" />
|
||||
<meta name="360-site-verification" content="02746194ea28ec4b021636a7edf0609e" />
|
||||
|
||||
<!-- QQ分享卡片信息 -->
|
||||
<meta itemprop="name" content="Hutool-Java工具集" />
|
||||
<meta itemprop="description" content="Hutool是一个Java工具包,它帮助我们简化代码和方法,让Java语言也可以甜甜的。" />
|
||||
<meta itemprop="image" content="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo_small.jpg" />
|
||||
|
||||
<title>Hutool — A set of tools that keep Java sweet.</title>
|
||||
|
||||
<link rel="shortcut icon" href="https://cdn.jsdelivr.net/gh/looly/hutool-site/favicon.ico">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/looly/hutool-site/css/animate.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/looly/hutool-site/css/icomoon.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/simple-line-icons@2.4.1/css/simple-line-icons.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/magnific-popup@1.1.0/dist/magnific-popup.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
|
||||
<!-- highlight.js -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@9.13.1/styles/zenburn.min.css">
|
||||
<!-- Theme style -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/looly/hutool-site/css/style.min.css">
|
||||
|
||||
<!-- Modernizr JS -->
|
||||
<script src="https://cdn.jsdelivr.net/gh/looly/hutool-site/js/modernizr-2.6.2.min.js"></script>
|
||||
<!-- FOR IE9 below -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://cdn.jsdelivr.net/gh/looly/hutool-site/js/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
<!-- 百度统计 -->
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?f2c884fc06fca522c4105429259b8a73";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Loader -->
|
||||
<div class="fh5co-loader"></div>
|
||||
<div class="fh5co-page" id="app">
|
||||
|
||||
<div id="fh5co-container">
|
||||
<div id="fh5co-home" class="js-fullheight-home" data-section="home" style="background-image: url(https://api.cn.berryapi.net/?service=App.Bing.Images&w=1920&h=1080);" data-stellar-background-ratio="0.5">
|
||||
<div class="container">
|
||||
<div class="col-md-6">
|
||||
<div class="js-fullheight-home fh5co-copy">
|
||||
<div class="js-fullheight-home fh5co-copy-inner" style="padding-left: 11%; background-color: rgba(0,106,180,0.4)">
|
||||
<h1>
|
||||
<b style="font-size: 180%;margin-left:-10px">Hutool</b>
|
||||
<span style="font-size: 50%">v{{version}}</span>
|
||||
</h1>
|
||||
<h2>A set of tools that keep Java sweet.</h2>
|
||||
<div style="margin-top: 100px">
|
||||
<div>
|
||||
<a href="https://gitee.com/loolly/hutool/" target="_blank" class="btn btn-primary btn-outline">Gitee</a>
|
||||
<a href="https://github.com/looly/hutool/" target="_blank" class="btn btn-primary btn-outline">Github</a>
|
||||
<a href="docs/" target="_blank" class="btn btn-primary btn-outline"><b>参考文档</b></a>
|
||||
<a href="https://apidoc.gitee.com/loolly/hutool" target="_blank" class="btn btn-primary btn-outline"><b>API文档</b></a>
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导航 -->
|
||||
<div class="js-sticky">
|
||||
<div class="fh5co-main-nav">
|
||||
<div class="container">
|
||||
<div class="fh5co-menu-1">
|
||||
<span><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" style="width: 120px;margin-right: 50px;"/></span>
|
||||
<a href="#" data-nav-section="home">主页</a>
|
||||
<a href="#" data-nav-section="start">开始</a>
|
||||
<a href="#" data-nav-section="docs">文档</a>
|
||||
<a href="#" data-nav-section="team">团队</a>
|
||||
<a href="#" data-nav-section="contact">加入讨论</a>
|
||||
<a href="#" data-nav-section="donate">赞助商</a>
|
||||
<a href="#" data-nav-section="friends">友情链接</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fh5co-about" data-section="start">
|
||||
<div class="fh5co-2col">
|
||||
|
||||
<div class="fh5co-2col-inner left">
|
||||
|
||||
<div class="fh5co-tabs-container">
|
||||
<ul class="fh5co-tabs fh5co-three">
|
||||
<li class="active">
|
||||
<a href="#" data-tab="marketing">简介</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" data-tab="analysis">设计哲学</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" data-tab="strategy">安装</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="fh5co-tab-content active" data-tab-content="marketing">
|
||||
<p>Hutool是Hu + tool的自造词,前者致敬我的“前任公司”,后者为工具之意,谐音“糊涂”,寓意追求“<b>万事都作糊涂观,无所谓失,无所谓得</b>”的境界。</p>
|
||||
<p>Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。</p>
|
||||
<ul class="checked">
|
||||
<li>Web开发</li>
|
||||
<li>与其它框架无耦合</li>
|
||||
<li>高度可替换</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="fh5co-tab-content" data-tab-content="analysis">
|
||||
<p>Hutool的设计思想是尽量减少重复的定义,让项目中的util这个package尽量少,总的来说有如下的几个思想:</p>
|
||||
<ul class="checked">
|
||||
<li>方法优先于对象</li>
|
||||
<li>自动识别优于用户定义</li>
|
||||
<li>便捷性与灵活性并存</li>
|
||||
<li>适配与兼容</li>
|
||||
<li>可选依赖原则</li>
|
||||
<li>无侵入原则</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="fh5co-tab-content" data-tab-content="strategy">
|
||||
<div>Maven:在项目的pom.xml的dependencies中加入以下内容:</div>
|
||||
<pre style="background: none; padding: 0;margin: 0;border: none;">
|
||||
<code><dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>{{version}}</version>
|
||||
</dependency></code>
|
||||
</pre>
|
||||
<div>Gradle:</div>
|
||||
<pre style="background: none; padding: 0;margin: 0;border: none;">
|
||||
<code>compile 'cn.hutool:hutool-all:{{version}}'</code>
|
||||
</pre>
|
||||
<p>
|
||||
<a href="http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22hutool-all%22" class="btn btn-primary btn-outline" target="_blank">从Maven安装</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="fh5co-2col fh5co-text" style="margin-top: 100px;">
|
||||
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/code_example.png" style="width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fh5co-services" data-section="docs">
|
||||
<div class="fh5co-video">
|
||||
<a href="movies/hutool-example1.mp4" class="popup-vimeo">
|
||||
<i class="icon-play2"></i>
|
||||
</a>
|
||||
<span>Watch Video</span>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="col-md-6 col-md-offset-3 text-center fh5co-heading">
|
||||
<h2>Hutool 是什么</h2>
|
||||
<p>Hutool是一个Java工具包类库,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="service">
|
||||
<div class="icon"><i class="icon-clock"></i></div>
|
||||
<h3>日期工具</h3>
|
||||
<p>通过DateUtil类,提供高度便捷的日期访问、处理和转换方式。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="service">
|
||||
<div class="icon"><i class="icon-compass"></i></div>
|
||||
<h3>HTTP客户端</h3>
|
||||
<p>通过HttpUtil对HTTP客户端的封装,实现便捷的HTTP请求,并简化文件上传操作。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="service">
|
||||
<div class="icon"><i class="icon-shuffle"></i></div>
|
||||
<h3>转换工具</h3>
|
||||
<p>通过Convert类中的相应静态方法,提供一整套的类型转换解决方案,并通过ConverterRegistry工厂类自定义转换。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="service">
|
||||
<div class="icon"><i class="icon-settings"></i></div>
|
||||
<h3>配置文件工具(Setting)</h3>
|
||||
<p>通过Setting对象,提供兼容Properties文件的更加强大的配置文件工具,用于解决中文、分组等JDK配置文件存在的诸多问题。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="service">
|
||||
<div class="icon"><i class="icon-note"></i></div>
|
||||
<h3>日志工具</h3>
|
||||
<p>Hutool的日志功能,通过抽象Log接口,提供对Slf4j、LogBack、Log4j、JDK-Logging的全面兼容支持。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="service">
|
||||
<div class="icon"><i class="icon-support"></i></div>
|
||||
<h3>JDBC工具类(DB模块)</h3>
|
||||
<p>通过db模块,提供对MySQL、Oracle等关系型数据库的JDBC封装,借助ActiveRecord思想,大大简化数据库操作。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 text-center">
|
||||
<p>Hutool的更多功能,期待你的探索:</p>
|
||||
<p>
|
||||
<a href="docs/" target="_blank" class="btn btn-primary btn-outline">参考文档</a>
|
||||
<a href="https://apidoc.gitee.com/loolly/hutool" target="_blank" class="btn btn-primary btn-outline" style="margin-left: 60px;">API 文档</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- END fh5co-services -->
|
||||
|
||||
<div id="fh5co-team" data-section="team">
|
||||
<div class="container">
|
||||
<div class="col-md-12 text-center fh5co-heading">
|
||||
<h2>开发团队</h2>
|
||||
<p>我们不是一个人在战斗</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="person">
|
||||
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/team/me.jpg" alt="" class="img-responsive">
|
||||
<h3>路小磊</h3>
|
||||
<h4>二手Java码农,Python和前端爱好者</h4>
|
||||
<p>一个非职业的码农,混迹于非IT圈子,利用8小时之外做自己喜欢的事情,爱前端,爱数码,爱美女。</p>
|
||||
<ul class="social">
|
||||
<li>
|
||||
<a href="http://www.luxiaolei.com/" target="_blank"><i class="icon-home"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://weibo.com/loolly" target="_blank"><i class="icon-weibo"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/looly" target="_blank"><i class="icon-github"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="person">
|
||||
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/team/shenshanmanong.jpg" alt="" class="img-responsive">
|
||||
<h3>深山码农</h3>
|
||||
<h4>崇拜自由的生活和善良的人性</h4>
|
||||
<p>深山耕耘互金行业多年,熟悉互金系统架构和设计,喜欢研究新技术,善于发现和解决问题</p>
|
||||
<ul class="social">
|
||||
<li>
|
||||
<a href="https://my.oschina.net/u/190652" target="_blank"><i class="icon-home"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://weibo.com/u/2647798525" target="_blank"><i class="icon-weibo"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://gitee.com/jokewang1/" target="_blank"><i class="icon-github"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="person">
|
||||
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/team/chinaboy.jpg" alt="" class="img-responsive">
|
||||
<h3>Chinaboy</h3>
|
||||
<h4>相信自己,明天会更好</h4>
|
||||
<p>一个奔波于IT圈子的程序猿,拥有自己的梦想,喜欢美女、喜欢音乐、爱打篮球儿...</p>
|
||||
<ul class="social">
|
||||
<li>
|
||||
<a href="https://my.oschina.net/u/1782401" target="_blank"><i class="icon-home"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://weibo.com/u/6413995744" target="_blank"><i class="icon-weibo"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/chinaboy-scl" target="_blank"><i class="icon-github"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="person">
|
||||
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/team/wangwang.jpg" alt="" class="img-responsive">
|
||||
<h3>汪汪90</h3>
|
||||
<h4>悲观的乐观主义者</h4>
|
||||
<p>Java程序员一枚,喜欢从生活中领悟技术,喜欢关注技术细节,ennio morricone 音乐的死忠粉。</p>
|
||||
<ul class="social">
|
||||
<li>
|
||||
<a href="https://my.oschina.net/wanglin123" target="_blank"><i class="icon-home"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/wanglin90" target="_blank"><i class="icon-github"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="person">
|
||||
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/team/puhuihui.jpg" alt="" class="img-responsive">
|
||||
<h3>普辉辉</h3>
|
||||
<h4>java码农,爱技术、爱旅游</h4>
|
||||
<p>java码农,爱技术、爱旅游、一直活跃在互联网技术圈。<br> </p>
|
||||
<ul class="social">
|
||||
<li>
|
||||
<a href="https://gitee.com/phhutool" target="_blank"><i class="icon-github"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="person">
|
||||
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/team/puxiangdong.jpg" alt="" class="img-responsive">
|
||||
<h3>普向东</h3>
|
||||
<h4>需求分析,项目沟通,提供解决方案</h4>
|
||||
<p>喜欢编程类工作,喜欢使用代码解决问题的成功感,信仰代码力量,码出高效。<br> </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END fh5co-team -->
|
||||
|
||||
<div id="fh5co-contact" data-section="contact">
|
||||
<div class="container">
|
||||
<div class="col-md-12 text-center fh5co-heading">
|
||||
<h2>加入讨论</h2>
|
||||
<p>通过以下方式加入讨论,或为Hutool添砖加瓦</p>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="col-md-3">
|
||||
<i class="icon-qq"></i>
|
||||
<a href="http://shang.qq.com/wpa/qunwpa?idkey=35764b2247c46ffebe28e4541e5b2af8f5dee5efcf47ceec69d21e4521aa8c75" target="_blank">555368316</a>
|
||||
| <a href="https://shang.qq.com/wpa/qunwpa?idkey=309056e409a304a454c7ba250a10d38dd82b9b49cd0e1f180fedbde78b02ae0d">718802356</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<i class="icon-mail2"></i>
|
||||
<a href="mailto:loolly@aliyun.com" target="_blank">loolly@aliyun.com</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<i class="icon-git"></i>
|
||||
<a href="https://gitee.com/loolly/hutool/issues" target="_blank">Gitee Issues</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<i class="icon-github"></i>
|
||||
<a href="https://github.com/looly/hutool/issues" target="_blank">Github Issues</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- END fh5co-contact -->
|
||||
|
||||
<div id="fh5co-donate" data-section="donate" style="padding: 7em 0; background-color: #E8ECF1;">
|
||||
<div class="container">
|
||||
<div class="col-md-6 col-md-offset-3 text-center fh5co-heading">
|
||||
<h2>赞助商</h2>
|
||||
<p>为Hutool提供赞助,也许他们也会为你提供更优惠的服务</p>
|
||||
</div>
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END fh5co-friends -->
|
||||
|
||||
<div id="fh5co-friends" data-section="friends" style="padding: 7em 0;">
|
||||
<div class="container">
|
||||
<div class="col-md-12 text-center fh5co-heading">
|
||||
<h2>友情链接</h2>
|
||||
<p>为Hutool提供各种帮助和支持的朋友们,我们一起共奋进</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="http://www.oschina.net/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/oschina.gif"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="http://t-io.org"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/tio.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="http://actframework.org/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/act.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="http://ibeetl.com/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/beetl.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="http://www.voovan.org/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/voovan.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="https://www.chanmir.cn/?u=3E1076"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/chanmir.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="http://www.flyfun.site/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/flyfun.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="http://www.jbolt.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/jbolt.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="https://www.publiccms.com/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/publiccms.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="https://www.peixun69.com/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/peixun69.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="https://gitom.top/blog/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/gitom.png"></a>
|
||||
</div>
|
||||
<div class="col-md-2 friend">
|
||||
<a target="_blank" href="http://gathersbar.com/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/friends/gathersbar.jpg"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END fh5co-friends -->
|
||||
|
||||
<footer id="fh5co-footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p>
|
||||
<small>© 2019 Hutool Project. All Rights Reserved. <br>
|
||||
Designed by <a href="http://luxiaolei.com/" target="_blank">Looly</a>, Hosted by <a href="https://www.coding.net/" target="_blank">Coding</a> Pages.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="fh5co-social">
|
||||
<li><a href="http://www.luxiaolei.com/" target="_blank"><i class="icon-home"></i></a></li>
|
||||
<li><a href="http://weibo.com/loolly" target="_blank"><i class="icon-weibo"></i></a></li>
|
||||
<li><a href="https://github.com/looly/hutool" target="_blank"><i class="icon-github"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<!-- END fh5co-container -->
|
||||
</div>
|
||||
<!-- END fh5co-pages -->
|
||||
|
||||
<div class="gototop js-top">
|
||||
<a href="#" class="js-gotop"><i class="icon-arrow-up"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- vue -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
|
||||
<!-- jQuery -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@2.2.4/dist/jquery.min.js"></script>
|
||||
<!-- jQuery Easing -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery.easing@1.4.1/jquery.easing.min.js"></script>
|
||||
<!-- Waypoints -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/waypoints@4.0.1/lib/jquery.waypoints.min.js"></script>
|
||||
<!-- Stellar Parallax -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery.stellar@0.6.2/jquery.stellar.min.js"></script>
|
||||
<!-- Magnific Popup -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/magnific-popup@1.1.0/dist/jquery.magnific-popup.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/looly/hutool-site/js/magnific-popup-options.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/highlight.js@9.13.1/lib/highlight.min.js"></script>
|
||||
<!-- Bootstrap -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
|
||||
<!-- Main JS -->
|
||||
<script src="https://cdn.jsdelivr.net/gh/looly/hutool-site/js/main.min.js"></script>
|
||||
<!-- version -->
|
||||
<script src="https://cdn.jsdelivr.net/gh/looly/hutool-site/js/version.js"></script>
|
||||
<script>
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
version: version
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<!-- gitter -->
|
||||
<script>
|
||||
((window.gitter = {}).chat = {}).options = {
|
||||
room: 'hutool/Lobby'
|
||||
};
|
||||
</script>
|
||||
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
|
||||
</body>
|
||||
</html>
|
1
docs/js/version.js
Normal file
1
docs/js/version.js
Normal file
@ -0,0 +1 @@
|
||||
var version = '4.6.2'
|
140
hutool-all/pom.xml
Normal file
140
hutool-all/pom.xml
Normal file
@ -0,0 +1,140 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>4.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>提供丰富的Java工具方法,此模块为Hutool所有模块的打包汇总,最终形式为一个jar包</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-script</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-system</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- 创建源码jar -->
|
||||
<createSourcesJar>true</createSourcesJar>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>${project.groupId}:*:*</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
38
hutool-all/src/main/java/cn/hutool/Hutool.java
Normal file
38
hutool-all/src/main/java/cn/hutool/Hutool.java
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2017 hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hutool;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Hutool是Hu + tool的自造词,前者致敬我的“前任公司”,后者为工具之意,谐音“糊涂”,寓意追求“万事都作糊涂观,无所谓失,无所谓得”的境界。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。<br>
|
||||
* Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。
|
||||
* </p>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class Hutool {
|
||||
|
||||
public static final String AUTHOR = "Looly";
|
||||
|
||||
private Hutool() {
|
||||
}
|
||||
}
|
12
hutool-all/src/main/java/cn/hutool/package-info.java
Normal file
12
hutool-all/src/main/java/cn/hutool/package-info.java
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Hutool是Hu + tool的自造词,前者致敬我的“前任公司”,后者为工具之意,谐音“糊涂”,寓意追求“万事都作糊涂观,无所谓失,无所谓得”的境界。
|
||||
*
|
||||
* <p>
|
||||
* Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。<br>
|
||||
* Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。
|
||||
* </p>
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool;
|
38
hutool-aop/pom.xml
Normal file
38
hutool-aop/pom.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>4.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 动态代理(AOP)</description>
|
||||
|
||||
<properties>
|
||||
<!-- versions -->
|
||||
<cglib.version>3.2.7</cglib.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib</artifactId>
|
||||
<version>${cglib.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
73
hutool-aop/src/main/java/cn/hutool/aop/ProxyUtil.java
Normal file
73
hutool-aop/src/main/java/cn/hutool/aop/ProxyUtil.java
Normal file
@ -0,0 +1,73 @@
|
||||
package cn.hutool.aop;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.proxy.ProxyFactory;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
|
||||
/**
|
||||
* 代理工具类
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public final class ProxyUtil {
|
||||
|
||||
/**
|
||||
* 使用切面代理对象
|
||||
*
|
||||
* @param <T> 切面对象类型
|
||||
* @param target 目标对象
|
||||
* @param aspectClass 切面对象类
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T proxy(T target, Class<? extends Aspect> aspectClass){
|
||||
return ProxyFactory.createProxy(target, aspectClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用切面代理对象
|
||||
*
|
||||
* @param <T> 被代理对象类型
|
||||
* @param aspect 切面对象
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T proxy(T target, Aspect aspect){
|
||||
return ProxyFactory.createProxy(target, aspect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动态代理对象<br>
|
||||
* 动态代理对象的创建原理是:<br>
|
||||
* 假设创建的代理对象名为 $Proxy0<br>
|
||||
* 1、根据传入的interfaces动态生成一个类,实现interfaces中的接口<br>
|
||||
* 2、通过传入的classloder将刚生成的类加载到jvm中。即将$Proxy0类load<br>
|
||||
* 3、调用$Proxy0的$Proxy0(InvocationHandler)构造函数 创建$Proxy0的对象,并且用interfaces参数遍历其所有接口的方法,这些实现方法的实现本质上是通过反射调用被代理对象的方法<br>
|
||||
* 4、将$Proxy0的实例返回给客户端。 <br>
|
||||
* 5、当调用代理类的相应方法时,相当于调用 {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])} 方法
|
||||
*
|
||||
*
|
||||
* @param <T> 被代理对象类型
|
||||
* @param classloader 被代理类对应的ClassLoader
|
||||
* @param invocationHandler {@link InvocationHandler} ,被代理类通过实现此接口提供动态代理功能
|
||||
* @param interfaces 代理类中需要实现的被代理类的接口方法
|
||||
* @return 代理类
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T newProxyInstance(ClassLoader classloader, InvocationHandler invocationHandler, Class<?>... interfaces) {
|
||||
return (T) Proxy.newProxyInstance(classloader, interfaces, invocationHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动态代理对象
|
||||
*
|
||||
* @param <T> 被代理对象类型
|
||||
* @param invocationHandler {@link InvocationHandler} ,被代理类通过实现此接口提供动态代理功能
|
||||
* @param interfaces 代理类中需要实现的被代理类的接口方法
|
||||
* @return 代理类
|
||||
*/
|
||||
public static <T> T newProxyInstance(InvocationHandler invocationHandler, Class<?>... interfaces) {
|
||||
return newProxyInstance(ClassUtil.getClassLoader(), invocationHandler, interfaces);
|
||||
}
|
||||
}
|
43
hutool-aop/src/main/java/cn/hutool/aop/aspects/Aspect.java
Normal file
43
hutool-aop/src/main/java/cn/hutool/aop/aspects/Aspect.java
Normal file
@ -0,0 +1,43 @@
|
||||
package cn.hutool.aop.aspects;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 切面接口
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.18
|
||||
*/
|
||||
public interface Aspect{
|
||||
|
||||
/**
|
||||
* 目标方法执行前的操作
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 参数
|
||||
* @return 是否继续执行接下来的操作
|
||||
*/
|
||||
boolean before(Object target, Method method, Object[] args);
|
||||
|
||||
/**
|
||||
* 目标方法执行后的操作
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 参数
|
||||
* @return 是否允许返回值(接下来的操作)
|
||||
*/
|
||||
boolean after(Object target, Method method, Object[] args);
|
||||
|
||||
/**
|
||||
* 目标方法抛出异常时的操作
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 参数
|
||||
* @param e 异常
|
||||
* @return 是否允许抛出异常
|
||||
*/
|
||||
boolean afterException(Object target, Method method, Object[] args, Throwable e);
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package cn.hutool.aop.aspects;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 简单切面类,不做任何操作<br>
|
||||
* 可以继承此类实现自己需要的方法即可
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class SimpleAspect implements Aspect, Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean before(Object target, Method method, Object[] args) {
|
||||
//继承此类后实现此方法
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean after(Object target, Method method, Object[] args) {
|
||||
//继承此类后实现此方法
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean afterException(Object target, Method method, Object[] args, Throwable e) {
|
||||
//继承此类后实现此方法
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package cn.hutool.aop.aspects;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import cn.hutool.core.date.TimeInterval;
|
||||
import cn.hutool.core.lang.Console;
|
||||
|
||||
/**
|
||||
* 通过日志打印方法的执行时间的切面
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class TimeIntervalAspect extends SimpleAspect{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private TimeInterval interval = new TimeInterval();
|
||||
|
||||
@Override
|
||||
public boolean before(Object target, Method method, Object[] args) {
|
||||
interval.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean after(Object target, Method method, Object[] args) {
|
||||
Console.log("Method [{}.{}] execute spend [{}]ms", target.getClass().getName(), method.getName(), interval.intervalMs());
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 切面实现,提供一些基本的切面实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.aop.aspects;
|
@ -0,0 +1,60 @@
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
/**
|
||||
* Cglib实现的动态代理切面
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class CglibInterceptor implements MethodInterceptor, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Object target;
|
||||
private Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
*/
|
||||
public CglibInterceptor(Object target, Aspect aspect) {
|
||||
this.target = target;
|
||||
this.aspect = aspect;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
Object result = null;
|
||||
if (aspect.before(target, method, args)) {
|
||||
try {
|
||||
// result = ReflectUtil.invoke(target, method, args);
|
||||
result = proxy.invokeSuper(obj, args);
|
||||
} catch (UtilException e) {
|
||||
final Throwable cause = e.getCause();
|
||||
if (e.getCause() instanceof InvocationTargetException) {
|
||||
aspect.afterException(target, method, args, ((InvocationTargetException) cause).getTargetException());
|
||||
} else {
|
||||
throw e;// 其它异常属于代理的异常,直接抛出
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aspect.after(target, method, args)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
/**
|
||||
* JDK实现的动态代理切面
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class JdkInterceptor implements InvocationHandler, Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Object target;
|
||||
private Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
*/
|
||||
public JdkInterceptor(Object target, Aspect aspect) {
|
||||
this.target = target;
|
||||
this.aspect = aspect;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
final Object target = this.target;
|
||||
final Aspect aspect = this.aspect;
|
||||
Object result = null;
|
||||
if (aspect.before(target, method, args)) {
|
||||
try {
|
||||
result = ReflectUtil.invoke(target, method, args);
|
||||
} catch (UtilException e) {
|
||||
final Throwable cause = e.getCause();
|
||||
if (e.getCause() instanceof InvocationTargetException) {
|
||||
aspect.afterException(target, method, args, ((InvocationTargetException) cause).getTargetException());
|
||||
} else {
|
||||
throw e;// 其它异常属于代理的异常,直接抛出
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aspect.after(target, method, args)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 代理拦截器实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.aop.interceptor;
|
7
hutool-aop/src/main/java/cn/hutool/aop/package-info.java
Normal file
7
hutool-aop/src/main/java/cn/hutool/aop/package-info.java
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* JDK动态代理封装,提供非IOC下的切面支持
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.aop;
|
@ -0,0 +1,25 @@
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.interceptor.CglibInterceptor;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
/**
|
||||
* 基于Cglib的切面代理工厂
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class CglibProxyFactory extends ProxyFactory{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T proxy(T target, Aspect aspect) {
|
||||
final Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(target.getClass());
|
||||
enhancer.setCallback(new CglibInterceptor(target, aspect));
|
||||
return (T) enhancer.create();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.ProxyUtil;
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.interceptor.JdkInterceptor;
|
||||
|
||||
/**
|
||||
* JDK实现的切面代理
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class JdkProxyFactory extends ProxyFactory{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T proxy(T target, Aspect aspect) {
|
||||
return (T) ProxyUtil.newProxyInstance(target.getClass().getClassLoader(), new JdkInterceptor(target, aspect), target.getClass().getInterfaces());
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
/**
|
||||
* 代理工厂<br>
|
||||
* 根据用户引入代理库的不同,产生不同的代理对象
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public abstract class ProxyFactory implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建代理
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
* @return 代理对象
|
||||
*/
|
||||
public abstract <T> T proxy(T target, Aspect aspect);
|
||||
|
||||
/**
|
||||
* 根据用户引入Cglib与否自动创建代理对象
|
||||
*
|
||||
* @param <T> 切面对象类型
|
||||
* @param target 目标对象
|
||||
* @param aspectClass 切面对象类
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T createProxy(T target, Class<? extends Aspect> aspectClass){
|
||||
return createProxy(target, ReflectUtil.newInstance(aspectClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户引入Cglib与否自动创建代理对象
|
||||
*
|
||||
* @param <T> 切面对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T createProxy(T target, Aspect aspect) {
|
||||
return create().proxy(target, aspect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户引入Cglib与否创建代理工厂
|
||||
*
|
||||
* @return 代理工厂
|
||||
*/
|
||||
public static ProxyFactory create() {
|
||||
try {
|
||||
return new CglibProxyFactory();
|
||||
} catch (NoClassDefFoundError e) {
|
||||
// ignore
|
||||
}
|
||||
return new JdkProxyFactory();
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 代理实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.aop.proxy;
|
60
hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java
Normal file
60
hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java
Normal file
@ -0,0 +1,60 @@
|
||||
package cn.hutool.aop.test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.aop.ProxyUtil;
|
||||
import cn.hutool.aop.aspects.TimeIntervalAspect;
|
||||
|
||||
/**
|
||||
* AOP模块单元测试
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class AopTest {
|
||||
|
||||
@Test
|
||||
public void aopTest() {
|
||||
Animal cat = ProxyUtil.proxy(new Cat(), TimeIntervalAspect.class);
|
||||
String result = cat.eat();
|
||||
Assert.assertEquals("猫吃鱼", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aopByCglibTest() {
|
||||
Dog dog = ProxyUtil.proxy(new Dog(), TimeIntervalAspect.class);
|
||||
String result = dog.eat();
|
||||
Assert.assertEquals("狗吃肉", result);
|
||||
}
|
||||
|
||||
static interface Animal {
|
||||
String eat();
|
||||
}
|
||||
|
||||
/**
|
||||
* 有接口
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
static class Cat implements Animal {
|
||||
|
||||
@Override
|
||||
public String eat() {
|
||||
return "猫吃鱼";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 无接口
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
static class Dog {
|
||||
public String eat() {
|
||||
return "狗吃肉";
|
||||
}
|
||||
}
|
||||
}
|
24
hutool-bloomFilter/pom.xml
Normal file
24
hutool-bloomFilter/pom.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>4.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 布隆过滤器</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,79 @@
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
import cn.hutool.bloomfilter.filter.DefaultFilter;
|
||||
import cn.hutool.bloomfilter.filter.ELFFilter;
|
||||
import cn.hutool.bloomfilter.filter.JSFilter;
|
||||
import cn.hutool.bloomfilter.filter.PJWFilter;
|
||||
import cn.hutool.bloomfilter.filter.SDBMFilter;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
|
||||
/**
|
||||
* BlommFilter 实现 <br>
|
||||
* 1.构建hash算法 <br>
|
||||
* 2.散列hash映射到数组的bit位置 <br>
|
||||
* 3.验证<br>
|
||||
* 此实现方式可以指定Hash算法
|
||||
*
|
||||
* @author Ansj
|
||||
*/
|
||||
public class BitMapBloomFilter implements BloomFilter{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private BloomFilter[] filters;
|
||||
|
||||
/**
|
||||
* 构造,使用默认的5个过滤器
|
||||
* @param m M值决定BitMap的大小
|
||||
*/
|
||||
public BitMapBloomFilter(int m) {
|
||||
int mNum =NumberUtil.div(String.valueOf(m), String.valueOf(5)).intValue();
|
||||
long size = (long) (1L * mNum * 1024 * 1024 * 8);
|
||||
|
||||
filters = new BloomFilter[]{
|
||||
new DefaultFilter(size),
|
||||
new ELFFilter(size),
|
||||
new JSFilter(size),
|
||||
new PJWFilter(size),
|
||||
new SDBMFilter(size)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定的多个过滤器建立BloomFilter
|
||||
*
|
||||
* @param m M值决定BitMap的大小
|
||||
* @param filters Bloom过滤器列表
|
||||
*/
|
||||
public BitMapBloomFilter(int m, BloomFilter... filters) {
|
||||
this(m);
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加字符串到Filter映射中
|
||||
* @param str 字符串
|
||||
*/
|
||||
@Override
|
||||
public boolean add(String str) {
|
||||
boolean flag = true;
|
||||
for (BloomFilter filter : filters) {
|
||||
flag |= filter.add(str);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可能包含此字符串,此处存在误判
|
||||
* @param str 字符串
|
||||
* @return 是否存在
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(String str) {
|
||||
for (BloomFilter filter : filters) {
|
||||
if (filter.contains(str) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.BitSet;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
/**
|
||||
* BloomFilter实现方式2,此方式使用BitSet存储。<br>
|
||||
* Hash算法的使用使用固定顺序,只需指定个数既可
|
||||
* @author loolly
|
||||
*
|
||||
*/
|
||||
public class BitSetBloomFilter implements BloomFilter{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private BitSet bitSet;
|
||||
private int bitSetSize;
|
||||
private int addedElements;
|
||||
private int hashFunctionNumber;
|
||||
|
||||
/**
|
||||
* 构造一个布隆过滤器,过滤器的容量为c * n 个bit.
|
||||
*
|
||||
* @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍.
|
||||
* @param n 当前过滤器预计所要包含的记录.
|
||||
* @param k 哈希函数的个数,等同每条记录要占用的bit数.
|
||||
*/
|
||||
public BitSetBloomFilter(int c, int n, int k) {
|
||||
this.hashFunctionNumber = k;
|
||||
this.bitSetSize = (int) Math.ceil(c * k);
|
||||
this.addedElements = n;
|
||||
this.bitSet = new BitSet(this.bitSetSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过文件初始化过滤器.
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @param charset 字符集
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public void init(String path, String charset) throws IOException {
|
||||
BufferedReader reader = FileUtil.getReader(path, charset);
|
||||
try {
|
||||
String line;
|
||||
while(true) {
|
||||
line = reader.readLine();
|
||||
if(line == null) {
|
||||
break;
|
||||
}
|
||||
this.add(line);
|
||||
}
|
||||
}finally {
|
||||
IoUtil.close(reader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(String str) {
|
||||
if (contains(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int[] positions = createHashes(str, hashFunctionNumber);
|
||||
for (int i = 0; i < positions.length; i++) {
|
||||
int position = Math.abs(positions[i] % bitSetSize);
|
||||
bitSet.set(position, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定是否包含指定字符串
|
||||
* @param str 字符串
|
||||
* @return 是否包含,存在误差
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(String str) {
|
||||
int[] positions = createHashes(str, hashFunctionNumber);
|
||||
for (int i : positions) {
|
||||
int position = Math.abs(i % bitSetSize);
|
||||
if (!bitSet.get(position)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 得到当前过滤器的错误率.
|
||||
*/
|
||||
public double getFalsePositiveProbability() {
|
||||
// (1 - e^(-k * n / m)) ^ k
|
||||
return Math.pow((1 - Math.exp(-hashFunctionNumber * (double) addedElements / bitSetSize)), hashFunctionNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串的字节表示进行多哈希编码.
|
||||
*
|
||||
* @param str 待添加进过滤器的字符串字节表示.
|
||||
* @param hashNumber 要经过的哈希个数.
|
||||
* @return 各个哈希的结果数组.
|
||||
*/
|
||||
public static int[] createHashes(String str, int hashNumber) {
|
||||
int[] result = new int[hashNumber];
|
||||
for(int i = 0; i < hashNumber; i++) {
|
||||
result[i] = hash(str, i);
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算Hash值
|
||||
* @param str 被计算Hash的字符串
|
||||
* @param k Hash算法序号
|
||||
* @return Hash值
|
||||
*/
|
||||
public static int hash(String str, int k) {
|
||||
switch (k) {
|
||||
case 0:
|
||||
return HashUtil.rsHash(str);
|
||||
case 1:
|
||||
return HashUtil.jsHash(str);
|
||||
case 2:
|
||||
return HashUtil.elfHash(str);
|
||||
case 3:
|
||||
return HashUtil.bkdrHash(str);
|
||||
case 4:
|
||||
return HashUtil.apHash(str);
|
||||
case 5:
|
||||
return HashUtil.djbHash(str);
|
||||
case 6:
|
||||
return HashUtil.sdbmHash(str);
|
||||
case 7:
|
||||
return HashUtil.pjwHash(str);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Bloom filter 是由 Howard Bloom 在 1970 年提出的二进制向量数据结构,它具有很好的空间和时间效率,被用来检测一个元素是不是集合中的一个成员。<br>
|
||||
* 如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中。<br>
|
||||
* 因此Bloom filter具有100%的召回率。这样每个检测请求返回有“在集合内(可能错误)”和“不在集合内(绝对不在集合内)”两种情况。<br>
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public interface BloomFilter extends Serializable{
|
||||
|
||||
/**
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 判断一个字符串是否bitMap中存在
|
||||
*/
|
||||
public boolean contains(String str);
|
||||
|
||||
/**
|
||||
* 在boolean的bitMap中增加一个字符串<br>
|
||||
* 如果存在就返回<code>false</code> .如果不存在.先增加这个字符串.再返回<code>true</code>
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 是否加入成功,如果存在就返回<code>false</code> .如果不存在返回<code>true</code>
|
||||
*/
|
||||
public boolean add(String str);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
/**
|
||||
* 布隆过滤器工具
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public class BloomFilterUtil {
|
||||
|
||||
/**
|
||||
* 创建一个BitSet实现的布隆过滤器,过滤器的容量为c * n 个bit.
|
||||
*
|
||||
* @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍.
|
||||
* @param n 当前过滤器预计所要包含的记录.
|
||||
* @param k 哈希函数的个数,等同每条记录要占用的bit数.
|
||||
* @return BitSetBloomFilter
|
||||
*/
|
||||
public static BitSetBloomFilter createBitSet(int c, int n, int k) {
|
||||
return new BitSetBloomFilter(c, n, k);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建BitMap实现的布隆过滤器
|
||||
*
|
||||
* @param m BitMap的大小
|
||||
* @return BitMapBloomFilter
|
||||
*/
|
||||
public static BitMapBloomFilter createBitMap(int m) {
|
||||
return new BitMapBloomFilter(m);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package cn.hutool.bloomfilter.bitMap;
|
||||
|
||||
/**
|
||||
* BitMap接口,用于将某个int或long值映射到一个数组中,从而判定某个值是否存在
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public interface BitMap{
|
||||
|
||||
public final int MACHINE32 = 32;
|
||||
public final int MACHINE64 = 64;
|
||||
|
||||
/**
|
||||
* 加入值
|
||||
*
|
||||
* @param i 值
|
||||
*/
|
||||
public void add(long i);
|
||||
|
||||
/**
|
||||
* 检查是否包含值
|
||||
*
|
||||
* @param i 值
|
||||
*/
|
||||
public boolean contains(long i);
|
||||
|
||||
/**
|
||||
* 移除值
|
||||
*
|
||||
* @param i 值
|
||||
*/
|
||||
public void remove(long i);
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package cn.hutool.bloomfilter.bitMap;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 过滤器BitMap在32位机器上.这个类能发生更好的效果.一般情况下建议使用此类
|
||||
*
|
||||
* @author loolly
|
||||
*
|
||||
*/
|
||||
public class IntMap implements BitMap, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int[] ints = null;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public IntMap() {
|
||||
ints = new int[93750000];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param size 容量
|
||||
*/
|
||||
public IntMap(int size) {
|
||||
ints = new int[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE32);
|
||||
int c = (int) (i % BitMap.MACHINE32);
|
||||
ints[r] = (int) (ints[r] | (1 << c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE32);
|
||||
int c = (int) (i % BitMap.MACHINE32);
|
||||
if (((int) ((ints[r] >>> c)) & 1) == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE32);
|
||||
int c = (int) (i % BitMap.MACHINE32);
|
||||
ints[r] &= ~(1 << c);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package cn.hutool.bloomfilter.bitMap;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 过滤器BitMap在64位机器上.这个类能发生更好的效果.一般机器不建议使用
|
||||
*
|
||||
* @author loolly
|
||||
*
|
||||
*/
|
||||
public class LongMap implements BitMap, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private long[] longs = null;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public LongMap() {
|
||||
longs = new long[93750000];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param size 容量
|
||||
*/
|
||||
public LongMap(int size) {
|
||||
longs = new long[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE64);
|
||||
long c = i % BitMap.MACHINE64;
|
||||
longs[r] = longs[r] | (1 << c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE64);
|
||||
long c = i % BitMap.MACHINE64;
|
||||
if (((longs[r] >>> c) & 1) == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE64);
|
||||
long c = i % BitMap.MACHINE64;
|
||||
longs[r] &= ~(1 << c);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* BitMap实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.bloomfilter.bitMap;
|
@ -0,0 +1,83 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.bloomfilter.BloomFilter;
|
||||
import cn.hutool.bloomfilter.bitMap.BitMap;
|
||||
import cn.hutool.bloomfilter.bitMap.IntMap;
|
||||
import cn.hutool.bloomfilter.bitMap.LongMap;
|
||||
|
||||
/**
|
||||
* 抽象Bloom过滤器
|
||||
*
|
||||
* @author loolly
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractFilter implements BloomFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private BitMap bm = null;
|
||||
|
||||
protected long size = 0;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param maxValue 最大值
|
||||
* @param machineNum 机器位数
|
||||
*/
|
||||
public AbstractFilter(long maxValue, int machineNum) {
|
||||
init(maxValue, machineNum);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造32位
|
||||
*
|
||||
* @param maxValue 最大值
|
||||
*/
|
||||
public AbstractFilter(long maxValue) {
|
||||
this(maxValue, BitMap.MACHINE32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param maxValue 最大值
|
||||
* @param machineNum 机器位数
|
||||
*/
|
||||
public void init(long maxValue, int machineNum) {
|
||||
this.size = maxValue;
|
||||
switch (machineNum) {
|
||||
case BitMap.MACHINE32:
|
||||
bm = new IntMap((int) (size / machineNum));
|
||||
break;
|
||||
case BitMap.MACHINE64:
|
||||
bm = new LongMap((int) (size / machineNum));
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Error Machine number!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String str) {
|
||||
return bm.contains(Math.abs(hash(str)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(String str) {
|
||||
final long hash = Math.abs(hash(str));
|
||||
if (bm.contains(hash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bm.add(hash);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义Hash方法
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return HashCode
|
||||
*/
|
||||
public abstract long hash(String str);
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
/**
|
||||
* 默认Bloom过滤器,使用Java自带的Hash算法
|
||||
* @author loolly
|
||||
*
|
||||
*/
|
||||
public class DefaultFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DefaultFilter(long maxValue, int MACHINENUM) {
|
||||
super(maxValue, MACHINENUM);
|
||||
}
|
||||
|
||||
public DefaultFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.javaDefaultHash(str) % size;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class ELFFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ELFFilter(long maxValue, int MACHINENUM) {
|
||||
super(maxValue, MACHINENUM);
|
||||
}
|
||||
|
||||
public ELFFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.elfHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class FNVFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public FNVFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public FNVFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.fnvHash(str);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
|
||||
public class HfFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HfFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public HfFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
int length = str.length() ;
|
||||
long hash = 0;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
hash += str.charAt(i) * 3 * i;
|
||||
}
|
||||
|
||||
if (hash < 0) {
|
||||
hash = -hash;
|
||||
}
|
||||
|
||||
return hash % size;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
public class HfIpFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HfIpFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public HfIpFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
int length = str.length();
|
||||
long hash = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
hash += str.charAt(i % 4) ^ str.charAt(i);
|
||||
}
|
||||
return hash % size;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
|
||||
public class JSFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public JSFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public JSFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
int hash = 1315423911;
|
||||
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
hash ^= ((hash << 5) + str.charAt(i) + (hash >> 2));
|
||||
}
|
||||
|
||||
if(hash<0) {
|
||||
hash*=-1 ;
|
||||
}
|
||||
|
||||
return hash % size;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class PJWFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PJWFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public PJWFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.pjwHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class RSFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RSFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public RSFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.rsHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class SDBMFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SDBMFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public SDBMFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.sdbmHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
|
||||
public class TianlFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public TianlFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public TianlFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.tianlHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 各种Hash算法的过滤器实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.bloomfilter.filter;
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 布隆过滤,提供一些Hash算法的布隆过滤
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.bloomfilter;
|
@ -0,0 +1,55 @@
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.bloomfilter.bitMap.IntMap;
|
||||
import cn.hutool.bloomfilter.bitMap.LongMap;
|
||||
|
||||
public class BitMapBloomFilterTest {
|
||||
|
||||
@Test
|
||||
public void filterTest() {
|
||||
BitMapBloomFilter filter = new BitMapBloomFilter(10);
|
||||
filter.add("123");
|
||||
filter.add("abc");
|
||||
filter.add("ddd");
|
||||
|
||||
Assert.assertTrue(filter.contains("abc"));
|
||||
Assert.assertTrue(filter.contains("ddd"));
|
||||
Assert.assertTrue(filter.contains("123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testIntMap(){
|
||||
IntMap intMap = new IntMap();
|
||||
|
||||
for (int i = 0 ; i < 32; i++) {
|
||||
intMap.add(i);
|
||||
}
|
||||
intMap.remove(30);
|
||||
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
System.out.println(i + "是否存在-->" + intMap.contains(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testLongMap(){
|
||||
LongMap longMap = new LongMap();
|
||||
|
||||
for (int i = 0 ; i < 64; i++) {
|
||||
longMap.add(i);
|
||||
}
|
||||
longMap.remove(30);
|
||||
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
System.out.println(i + "是否存在-->" + longMap.contains(i));
|
||||
}
|
||||
}
|
||||
}
|
111
hutool-bom/pom.xml
Normal file
111
hutool-bom/pom.xml
Normal file
@ -0,0 +1,111 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>4.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>提供丰富的Java工具方法,此模块为Hutool所有模块汇总,最终形式为拆分开的多个jar包,可以通过exclude方式排除不需要的模块</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-script</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-system</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
24
hutool-cache/pom.xml
Normal file
24
hutool-cache/pom.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>4.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 缓存</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
149
hutool-cache/src/main/java/cn/hutool/cache/Cache.java
vendored
Normal file
149
hutool-cache/src/main/java/cn/hutool/cache/Cache.java
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
package cn.hutool.cache;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
|
||||
import cn.hutool.cache.impl.CacheObj;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
/**
|
||||
* 缓存接口
|
||||
*
|
||||
* @author Looly,jodd
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public interface Cache<K, V> extends Iterable<V>, Serializable {
|
||||
|
||||
/**
|
||||
* 返回缓存容量,<code>0</code>表示无大小限制
|
||||
*
|
||||
* @return 返回缓存容量,<code>0</code>表示无大小限制
|
||||
*/
|
||||
int capacity();
|
||||
|
||||
/**
|
||||
* 缓存失效时长, <code>0</code> 表示没有设置,单位毫秒
|
||||
*
|
||||
* @return 缓存失效时长, <code>0</code> 表示没有设置,单位毫秒
|
||||
*/
|
||||
long timeout();
|
||||
|
||||
/**
|
||||
* 将对象加入到缓存,使用默认失效时长
|
||||
*
|
||||
* @param key 键
|
||||
* @param object 缓存的对象
|
||||
* @see Cache#put(Object, Object, long)
|
||||
*/
|
||||
void put(K key, V object);
|
||||
|
||||
/**
|
||||
* 将对象加入到缓存,使用指定失效时长<br>
|
||||
* 如果缓存空间满了,{@link #prune()} 将被调用以获得空间来存放新对象
|
||||
*
|
||||
* @param key 键
|
||||
* @param object 缓存的对象
|
||||
* @param timeout 失效时长,单位毫秒
|
||||
* @see Cache#put(Object, Object, long)
|
||||
*/
|
||||
void put(K key, V object, long timeout);
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回<code>null</code>
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
* <p>
|
||||
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
|
||||
*
|
||||
* @param key 键
|
||||
* @return 键对应的对象
|
||||
* @see #get(Object, boolean)
|
||||
*/
|
||||
V get(K key);
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象
|
||||
*
|
||||
* @param key 键
|
||||
* @param supplier 如果不存在回调方法,用于生产值对象
|
||||
* @return 值对象
|
||||
*/
|
||||
V get(K key, Func0<V> supplier);
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回<code>null</code>
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
*
|
||||
* @param key 键
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
|
||||
* @return 键对应的对象
|
||||
*/
|
||||
V get(K key, boolean isUpdateLastAccess);
|
||||
|
||||
/**
|
||||
* 返回缓存迭代器
|
||||
*
|
||||
* @return 缓存迭代器
|
||||
*/
|
||||
@Override
|
||||
Iterator<V> iterator();
|
||||
|
||||
/**
|
||||
* 返回包含键和值得迭代器
|
||||
*
|
||||
* @return 缓存对象迭代器
|
||||
* @since 4.0.10
|
||||
*/
|
||||
Iterator<CacheObj<K, V>> cacheObjIterator();
|
||||
|
||||
/**
|
||||
* 从缓存中清理过期对象,清理策略取决于具体实现
|
||||
*
|
||||
* @return 清理的缓存对象个数
|
||||
*/
|
||||
int prune();
|
||||
|
||||
/**
|
||||
* 缓存是否已满,仅用于有空间限制的缓存对象
|
||||
*
|
||||
* @return 缓存是否已满,仅用于有空间限制的缓存对象
|
||||
*/
|
||||
boolean isFull();
|
||||
|
||||
/**
|
||||
* 从缓存中移除对象
|
||||
*
|
||||
* @param key 键
|
||||
*/
|
||||
void remove(K key);
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* 缓存的对象数量
|
||||
*
|
||||
* @return 缓存的对象数量
|
||||
*/
|
||||
int size();
|
||||
|
||||
/**
|
||||
* 缓存是否为空
|
||||
*
|
||||
* @return 缓存是否为空
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* 是否包含key
|
||||
*
|
||||
* @param key KEY
|
||||
* @return 是否包含key
|
||||
*/
|
||||
boolean containsKey(K key);
|
||||
}
|
129
hutool-cache/src/main/java/cn/hutool/cache/CacheUtil.java
vendored
Normal file
129
hutool-cache/src/main/java/cn/hutool/cache/CacheUtil.java
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
package cn.hutool.cache;
|
||||
|
||||
import cn.hutool.cache.impl.FIFOCache;
|
||||
import cn.hutool.cache.impl.LFUCache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
import cn.hutool.cache.impl.NoCache;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
import cn.hutool.cache.impl.WeakCache;
|
||||
|
||||
/**
|
||||
* 缓存工具类
|
||||
* @author Looly
|
||||
*@since 3.0.1
|
||||
*/
|
||||
public class CacheUtil {
|
||||
|
||||
/**
|
||||
* 创建FIFO(first in first out) 先进先出缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link FIFOCache}
|
||||
*/
|
||||
public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity, long timeout){
|
||||
return new FIFOCache<K, V>(capacity, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建FIFO(first in first out) 先进先出缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @return {@link FIFOCache}
|
||||
*/
|
||||
public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity){
|
||||
return new FIFOCache<K, V>(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建LFU(least frequently used) 最少使用率缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link LFUCache}
|
||||
*/
|
||||
public static <K, V> LFUCache<K, V> newLFUCache(int capacity, long timeout){
|
||||
return new LFUCache<K, V>(capacity, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建LFU(least frequently used) 最少使用率缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @return {@link LFUCache}
|
||||
*/
|
||||
public static <K, V> LFUCache<K, V> newLFUCache(int capacity){
|
||||
return new LFUCache<K, V>(capacity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建LRU (least recently used)最近最久未使用缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link LRUCache}
|
||||
*/
|
||||
public static <K, V> LRUCache<K, V> newLRUCache(int capacity, long timeout){
|
||||
return new LRUCache<K, V>(capacity, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建LRU (least recently used)最近最久未使用缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @return {@link LRUCache}
|
||||
*/
|
||||
public static <K, V> LRUCache<K, V> newLRUCache(int capacity){
|
||||
return new LRUCache<K, V>(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建定时缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link TimedCache}
|
||||
*/
|
||||
public static <K, V> TimedCache<K, V> newTimedCache(long timeout){
|
||||
return new TimedCache<K, V>(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建弱引用缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link WeakCache}
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public static <K, V> WeakCache<K, V> newWeakCache(long timeout){
|
||||
return new WeakCache<K, V>(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建无缓存实现.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @return {@link NoCache}
|
||||
*/
|
||||
public static <K, V> NoCache<K, V> newNoCache(){
|
||||
return new NoCache<K, V>();
|
||||
}
|
||||
|
||||
}
|
83
hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java
vendored
Normal file
83
hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
package cn.hutool.cache;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 全局缓存清理定时器池,用于在需要过期支持的缓存对象中超时任务池
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public enum GlobalPruneTimer {
|
||||
/** 单例对象 */
|
||||
INSTANCE;
|
||||
|
||||
/** 缓存任务计数 */
|
||||
private AtomicInteger cacheTaskNumber = new AtomicInteger(1);
|
||||
|
||||
/** 定时器 */
|
||||
private ScheduledExecutorService pruneTimer;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
private GlobalPruneTimer() {
|
||||
create();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动定时任务
|
||||
*
|
||||
* @param task 任务
|
||||
* @param delay 周期
|
||||
* @return {@link ScheduledFuture}对象,可手动取消此任务
|
||||
*/
|
||||
public ScheduledFuture<?> schedule(Runnable task, long delay) {
|
||||
return this.pruneTimer.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建定时器
|
||||
*/
|
||||
public void create() {
|
||||
if (null != pruneTimer) {
|
||||
shutdownNow();
|
||||
}
|
||||
this.pruneTimer = new ScheduledThreadPoolExecutor(16, new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
return ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁全局定时器
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (null != pruneTimer) {
|
||||
pruneTimer.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁全局定时器
|
||||
*
|
||||
* @return 销毁时未被执行的任务列表
|
||||
*/
|
||||
public List<Runnable> shutdownNow() {
|
||||
if (null != pruneTimer) {
|
||||
return pruneTimer.shutdownNow();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
134
hutool-cache/src/main/java/cn/hutool/cache/file/AbstractFileCache.java
vendored
Normal file
134
hutool-cache/src/main/java/cn/hutool/cache/file/AbstractFileCache.java
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
package cn.hutool.cache.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
|
||||
/**
|
||||
* 文件缓存,以解决频繁读取文件引起的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractFileCache implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 容量 */
|
||||
protected final int capacity;
|
||||
/** 缓存的最大文件大小,文件大于此大小时将不被缓存 */
|
||||
protected final int maxFileSize;
|
||||
/** 默认超时时间,0表示无默认超时 */
|
||||
protected final long timeout;
|
||||
/** 缓存实现 */
|
||||
protected final Cache<File, byte[]> cache;
|
||||
|
||||
/** 已使用缓存空间 */
|
||||
protected int usedSize;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 文件最大大小
|
||||
* @param timeout 默认超时时间,0表示无默认超时
|
||||
*/
|
||||
public AbstractFileCache(int capacity, int maxFileSize, long timeout) {
|
||||
this.capacity = capacity;
|
||||
this.maxFileSize = maxFileSize;
|
||||
this.timeout = timeout;
|
||||
this.cache = initCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 缓存容量(byte数)
|
||||
*/
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 已使用空间大小(byte数)
|
||||
*/
|
||||
public int getUsedSize() {
|
||||
return usedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 允许被缓存文件的最大byte数
|
||||
*/
|
||||
public int maxFileSize() {
|
||||
return maxFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 缓存的文件数
|
||||
*/
|
||||
public int getCachedFilesCount() {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 超时时间
|
||||
*/
|
||||
public long timeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
usedSize = 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- get
|
||||
|
||||
/**
|
||||
* 获得缓存过的文件bytes
|
||||
* @param path 文件路径
|
||||
* @return 缓存过的文件bytes
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public byte[] getFileBytes(String path) throws IORuntimeException {
|
||||
return getFileBytes(new File(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存过的文件bytes
|
||||
* @param file 文件
|
||||
* @return 缓存过的文件bytes
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public byte[] getFileBytes(File file) throws IORuntimeException {
|
||||
byte[] bytes = cache.get(file);
|
||||
if (bytes != null) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// add file
|
||||
bytes = FileUtil.readBytes(file);
|
||||
|
||||
if ((maxFileSize != 0) && (file.length() > maxFileSize)) {
|
||||
//大于缓存空间,不缓存,直接返回
|
||||
return bytes;
|
||||
}
|
||||
|
||||
usedSize += bytes.length;
|
||||
|
||||
//文件放入缓存,如果usedSize > capacity,purge()方法将被调用
|
||||
cache.put(file, bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- protected method start
|
||||
/**
|
||||
* 初始化实现文件缓存的缓存对象
|
||||
* @return {@link Cache}
|
||||
*/
|
||||
protected abstract Cache<File, byte[]> initCache();
|
||||
// ---------------------------------------------------------------- protected method end
|
||||
|
||||
}
|
64
hutool-cache/src/main/java/cn/hutool/cache/file/LFUFileCache.java
vendored
Normal file
64
hutool-cache/src/main/java/cn/hutool/cache/file/LFUFileCache.java
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
package cn.hutool.cache.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.LFUCache;
|
||||
|
||||
/**
|
||||
* 使用LFU缓存文件,以解决频繁读取文件引起的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class LFUFileCache extends AbstractFileCache{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 最大文件大小为缓存容量的一半<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
*/
|
||||
public LFUFileCache(int capacity) {
|
||||
this(capacity, capacity / 2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 最大文件大小
|
||||
*/
|
||||
public LFUFileCache(int capacity, int maxFileSize) {
|
||||
this(capacity, maxFileSize, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 文件最大大小
|
||||
* @param timeout 默认超时时间,0表示无默认超时
|
||||
*/
|
||||
public LFUFileCache(int capacity, int maxFileSize, long timeout) {
|
||||
super(capacity, maxFileSize, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Cache<File, byte[]> initCache() {
|
||||
Cache<File, byte[]> cache = new LFUCache<File, byte[]>(this.capacity, this.timeout) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
return LFUFileCache.this.usedSize > this.capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(File key, byte[] cachedObject) {
|
||||
usedSize -= cachedObject.length;
|
||||
}
|
||||
};
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
64
hutool-cache/src/main/java/cn/hutool/cache/file/LRUFileCache.java
vendored
Normal file
64
hutool-cache/src/main/java/cn/hutool/cache/file/LRUFileCache.java
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
package cn.hutool.cache.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
|
||||
/**
|
||||
* 使用LRU缓存文件,以解决频繁读取文件引起的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class LRUFileCache extends AbstractFileCache{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 最大文件大小为缓存容量的一半<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
*/
|
||||
public LRUFileCache(int capacity) {
|
||||
this(capacity, capacity / 2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 最大文件大小
|
||||
*/
|
||||
public LRUFileCache(int capacity, int maxFileSize) {
|
||||
this(capacity, maxFileSize, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 文件最大大小
|
||||
* @param timeout 默认超时时间,0表示无默认超时
|
||||
*/
|
||||
public LRUFileCache(int capacity, int maxFileSize, long timeout) {
|
||||
super(capacity, maxFileSize, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Cache<File, byte[]> initCache() {
|
||||
Cache<File, byte[]> cache = new LRUCache<File, byte[]>(this.capacity, super.timeout) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
return LRUFileCache.this.usedSize > this.capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(File key, byte[] cachedObject) {
|
||||
usedSize -= cachedObject.length;
|
||||
}
|
||||
};
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
7
hutool-cache/src/main/java/cn/hutool/cache/file/package-info.java
vendored
Normal file
7
hutool-cache/src/main/java/cn/hutool/cache/file/package-info.java
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 提供针对文件的缓存实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cache.file;
|
359
hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java
vendored
Normal file
359
hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java
vendored
Normal file
@ -0,0 +1,359 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.core.collection.CopiedIter;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
/**
|
||||
* 超时和限制大小的缓存的默认实现<br>
|
||||
* 继承此抽象缓存需要:<br>
|
||||
* <ul>
|
||||
* <li>创建一个新的Map</li>
|
||||
* <li>实现 <code>prune</code> 策略</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly,jodd
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected Map<K, CacheObj<K, V>> cacheMap;
|
||||
|
||||
private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
|
||||
private final ReadLock readLock = cacheLock.readLock();
|
||||
private final WriteLock writeLock = cacheLock.writeLock();
|
||||
|
||||
/** 返回缓存容量,<code>0</code>表示无大小限制 */
|
||||
protected int capacity;
|
||||
/** 缓存失效时长, <code>0</code> 表示无限制,单位毫秒 */
|
||||
protected long timeout;
|
||||
|
||||
/** 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 */
|
||||
protected boolean existCustomTimeout;
|
||||
|
||||
/** 命中数 */
|
||||
protected int hitCount;
|
||||
/** 丢失数 */
|
||||
protected int missCount;
|
||||
|
||||
// ---------------------------------------------------------------- put start
|
||||
@Override
|
||||
public void put(K key, V object) {
|
||||
put(key, object, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V object, long timeout) {
|
||||
writeLock.lock();
|
||||
|
||||
try {
|
||||
putWithoutLock(key, object, timeout);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入元素,无锁
|
||||
*
|
||||
* @param key 键
|
||||
* @param object 值
|
||||
* @param timeout 超时时长
|
||||
* @since 4.5.16
|
||||
*/
|
||||
private void putWithoutLock(K key, V object, long timeout) {
|
||||
CacheObj<K, V> co = new CacheObj<K, V>(key, object, timeout);
|
||||
if (timeout != 0) {
|
||||
existCustomTimeout = true;
|
||||
}
|
||||
if (isFull()) {
|
||||
pruneCache();
|
||||
}
|
||||
cacheMap.put(key, co);
|
||||
}
|
||||
// ---------------------------------------------------------------- put end
|
||||
|
||||
// ---------------------------------------------------------------- get start
|
||||
@Override
|
||||
public boolean containsKey(K key) {
|
||||
readLock.lock();
|
||||
|
||||
try {
|
||||
// 不存在或已移除
|
||||
final CacheObj<K, V> co = cacheMap.get(key);
|
||||
if (co == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false == co.isExpired()) {
|
||||
// 命中
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
|
||||
// 过期
|
||||
remove(key, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 命中数
|
||||
*/
|
||||
public int getHitCount() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return hitCount;
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 丢失数
|
||||
*/
|
||||
public int getMissCount() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return missCount;
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key) {
|
||||
return get(key, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key, Func0<V> supplier) {
|
||||
V v = get(key);
|
||||
if (null == v && null != supplier) {
|
||||
writeLock.lock();
|
||||
try {
|
||||
// 双重检查锁
|
||||
final CacheObj<K, V> co = cacheMap.get(key);
|
||||
if(null == co || co.isExpired() || null == co.getValue()) {
|
||||
try {
|
||||
v = supplier.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
putWithoutLock(key, v, this.timeout);
|
||||
} else {
|
||||
v = co.get(true);
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key, boolean isUpdateLastAccess) {
|
||||
readLock.lock();
|
||||
|
||||
try {
|
||||
// 不存在或已移除
|
||||
final CacheObj<K, V> co = cacheMap.get(key);
|
||||
if (co == null) {
|
||||
missCount++;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (false == co.isExpired()) {
|
||||
// 命中
|
||||
hitCount++;
|
||||
return co.get(isUpdateLastAccess);
|
||||
}
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
|
||||
// 过期
|
||||
remove(key, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- get end
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Iterator<V> iterator() {
|
||||
CacheObjIterator<K, V> copiedIterator = (CacheObjIterator<K, V>) this.cacheObjIterator();
|
||||
return new CacheValuesIterator<V>(copiedIterator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<CacheObj<K, V>> cacheObjIterator() {
|
||||
CopiedIter<CacheObj<K, V>> copiedIterator;
|
||||
readLock.lock();
|
||||
try {
|
||||
copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
return new CacheObjIterator<>(copiedIterator);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune start
|
||||
/**
|
||||
* 清理实现
|
||||
*
|
||||
* @return 清理数
|
||||
*/
|
||||
protected abstract int pruneCache();
|
||||
|
||||
@Override
|
||||
public final int prune() {
|
||||
writeLock.lock();
|
||||
try {
|
||||
return pruneCache();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------- prune end
|
||||
|
||||
// ---------------------------------------------------------------- common start
|
||||
@Override
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 默认缓存失效时长。<br>
|
||||
* 每个对象可以单独设置失效时长
|
||||
*/
|
||||
@Override
|
||||
public long timeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只有设置公共缓存失效时长或每个对象单独的失效时长时清理可用
|
||||
*
|
||||
* @return 过期对象清理是否可用,内部使用
|
||||
*/
|
||||
protected boolean isPruneExpiredActive() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return (timeout != 0) || existCustomTimeout;
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return (capacity > 0) && (cacheMap.size() >= capacity);
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(K key) {
|
||||
remove(key, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
writeLock.lock();
|
||||
try {
|
||||
cacheMap.clear();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return cacheMap.size();
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return cacheMap.isEmpty();
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return this.cacheMap.toString();
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------- common end
|
||||
|
||||
/**
|
||||
* 对象移除回调。默认无动作
|
||||
*
|
||||
* @param key 键
|
||||
* @param cachedObject 被缓存的对象
|
||||
*/
|
||||
protected void onRemove(K key, V cachedObject) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除key对应的对象
|
||||
*
|
||||
* @param key 键
|
||||
* @param withMissCount 是否计数丢失数
|
||||
*/
|
||||
private void remove(K key, boolean withMissCount) {
|
||||
writeLock.lock();
|
||||
CacheObj<K, V> co;
|
||||
try {
|
||||
co = removeWithoutLock(key, withMissCount);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
if (null != co) {
|
||||
onRemove(co.key, co.obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除key对应的对象,不加锁
|
||||
*
|
||||
* @param key 键
|
||||
* @param withMissCount 是否计数丢失数
|
||||
* @return 移除的对象,无返回null
|
||||
*/
|
||||
private CacheObj<K, V> removeWithoutLock(K key, boolean withMissCount) {
|
||||
final CacheObj<K, V> co = cacheMap.remove(key);
|
||||
if (withMissCount) {
|
||||
// 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1
|
||||
this.missCount++;
|
||||
}
|
||||
return co;
|
||||
}
|
||||
}
|
92
hutool-cache/src/main/java/cn/hutool/cache/impl/CacheObj.java
vendored
Normal file
92
hutool-cache/src/main/java/cn/hutool/cache/impl/CacheObj.java
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 缓存对象
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
*/
|
||||
public class CacheObj<K, V> implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected final K key;
|
||||
protected final V obj;
|
||||
|
||||
/** 上次访问时间 */
|
||||
private long lastAccess;
|
||||
/** 访问次数 */
|
||||
protected long accessCount;
|
||||
/** 对象存活时长,0表示永久存活*/
|
||||
private long ttl;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param key 键
|
||||
* @param obj 值
|
||||
* @param ttl 超时时长
|
||||
*/
|
||||
protected CacheObj(K key, V obj, long ttl) {
|
||||
this.key = key;
|
||||
this.obj = obj;
|
||||
this.ttl = ttl;
|
||||
this.lastAccess = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否过期
|
||||
*
|
||||
* @return 是否过期
|
||||
*/
|
||||
boolean isExpired() {
|
||||
if(this.ttl > 0) {
|
||||
final long expiredTime = this.lastAccess + this.ttl;
|
||||
if(expiredTime > 0 && expiredTime < System.currentTimeMillis()) {
|
||||
// expiredTime > 0 杜绝Long类型溢出变负数问题,当当前时间超过过期时间,表示过期
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
*
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间
|
||||
* @return 获得对象
|
||||
* @since 4.0.10
|
||||
*/
|
||||
V get(boolean isUpdateLastAccess) {
|
||||
if(isUpdateLastAccess) {
|
||||
lastAccess = System.currentTimeMillis();
|
||||
}
|
||||
accessCount++;
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取键
|
||||
* @return 键
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public K getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
* @return 值
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public V getValue() {
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CacheObj [key=" + key + ", obj=" + obj + ", lastAccess=" + lastAccess + ", accessCount=" + accessCount + ", ttl=" + ttl + "]";
|
||||
}
|
||||
}
|
74
hutool-cache/src/main/java/cn/hutool/cache/impl/CacheObjIterator.java
vendored
Normal file
74
hutool-cache/src/main/java/cn/hutool/cache/impl/CacheObjIterator.java
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* {@link cn.hutool.cache.impl.AbstractCache} 的CacheObj迭代器.
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public class CacheObjIterator<K, V> implements Iterator<CacheObj<K, V>>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Iterator<CacheObj<K, V>> iterator;
|
||||
private CacheObj<K, V> nextValue;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param iterator 原{@link Iterator}
|
||||
* @param readLock 读锁
|
||||
*/
|
||||
CacheObjIterator(Iterator<CacheObj<K, V>> iterator) {
|
||||
this.iterator = iterator;
|
||||
nextValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否有下一个值
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nextValue != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 下一个值
|
||||
*/
|
||||
@Override
|
||||
public CacheObj<K, V> next() {
|
||||
if (false == hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
final CacheObj<K, V> cachedObject = nextValue;
|
||||
nextValue();
|
||||
return cachedObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中移除没有过期的当前值,此方法不支持
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Cache values Iterator is not support to modify.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一个值,当不存在则下一个值为null
|
||||
*/
|
||||
private void nextValue() {
|
||||
while (iterator.hasNext()) {
|
||||
nextValue = iterator.next();
|
||||
if (nextValue.isExpired() == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
nextValue = null;
|
||||
}
|
||||
}
|
49
hutool-cache/src/main/java/cn/hutool/cache/impl/CacheValuesIterator.java
vendored
Normal file
49
hutool-cache/src/main/java/cn/hutool/cache/impl/CacheValuesIterator.java
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* {@link cn.hutool.cache.impl.AbstractCache} 的值迭代器.
|
||||
* @author looly
|
||||
*
|
||||
* @param <V> 迭代对象类型
|
||||
*/
|
||||
public class CacheValuesIterator<V> implements Iterator<V>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final CacheObjIterator<?, V> cacheObjIter;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param iterator 原{@link CacheObjIterator}
|
||||
* @param readLock 读锁
|
||||
*/
|
||||
CacheValuesIterator(CacheObjIterator<?, V> iterator) {
|
||||
this.cacheObjIter = iterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否有下一个值
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.cacheObjIter.hasNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 下一个值
|
||||
*/
|
||||
@Override
|
||||
public V next() {
|
||||
return cacheObjIter.next().getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中移除没有过期的当前值,不支持此方法
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
cacheObjIter.remove();
|
||||
}
|
||||
}
|
78
hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java
vendored
Normal file
78
hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* FIFO(first in first out) 先进先出缓存.
|
||||
*
|
||||
* <p>
|
||||
* 元素不停的加入缓存直到缓存满为止,当缓存满时,清理过期缓存对象,清理后依旧满则删除先入的缓存(链表首部对象)<br>
|
||||
* 优点:简单快速 <br>
|
||||
* 缺点:不灵活,不能保证最常用的对象总是被保留
|
||||
* </p>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class FIFOCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造,默认对象不过期
|
||||
*
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public FIFOCache(int capacity) {
|
||||
this(capacity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长
|
||||
*/
|
||||
public FIFOCache(int capacity, long timeout) {
|
||||
if(Integer.MAX_VALUE == capacity) {
|
||||
capacity -= 1;
|
||||
}
|
||||
|
||||
this.capacity = capacity;
|
||||
this.timeout = timeout;
|
||||
cacheMap = new LinkedHashMap<K, CacheObj<K, V>>(capacity + 1, 1.0f, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 先进先出的清理策略<br>
|
||||
* 先遍历缓存清理过期的缓存对象,如果清理后还是满的,则删除第一个缓存对象
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
int count = 0;
|
||||
CacheObj<K, V> first = null;
|
||||
|
||||
// 清理过期对象并找出链表头部元素(先入元素)
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
while (values.hasNext()) {
|
||||
CacheObj<K, V> co = values.next();
|
||||
if (co.isExpired()) {
|
||||
values.remove();
|
||||
count++;
|
||||
}
|
||||
if (first == null) {
|
||||
first = co;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理结束后依旧是满的,则删除第一个被缓存的对象
|
||||
if (isFull() && null != first) {
|
||||
cacheMap.remove(first.key);
|
||||
onRemove(first.key, first.obj);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
96
hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java
vendored
Normal file
96
hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* LFU(least frequently used) 最少使用率缓存<br>
|
||||
* 根据使用次数来判定对象是否被持续缓存<br>
|
||||
* 使用率是通过访问次数计算的。<br>
|
||||
* 当缓存满时清理过期对象。<br>
|
||||
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
|
||||
*
|
||||
* @author Looly,jodd
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class LFUCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public LFUCache(int capacity) {
|
||||
this(capacity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长
|
||||
*/
|
||||
public LFUCache(int capacity, long timeout) {
|
||||
if(Integer.MAX_VALUE == capacity) {
|
||||
capacity -= 1;
|
||||
}
|
||||
|
||||
this.capacity = capacity;
|
||||
this.timeout = timeout;
|
||||
cacheMap = new HashMap<K, CacheObj<K, V>>(capacity + 1, 1.0f);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune
|
||||
|
||||
/**
|
||||
* 清理过期对象。<br>
|
||||
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
|
||||
*
|
||||
* @return 清理个数
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
int count = 0;
|
||||
CacheObj<K, V> comin = null;
|
||||
|
||||
// 清理过期对象并找出访问最少的对象
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co;
|
||||
while (values.hasNext()) {
|
||||
co = values.next();
|
||||
if (co.isExpired() == true) {
|
||||
values.remove();
|
||||
onRemove(co.key, co.obj);
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
//找出访问最少的对象
|
||||
if (comin == null || co.accessCount < comin.accessCount) {
|
||||
comin = co;
|
||||
}
|
||||
}
|
||||
|
||||
// 减少所有对象访问量,并清除减少后为0的访问对象
|
||||
if (isFull() && comin != null) {
|
||||
long minAccessCount = comin.accessCount;
|
||||
|
||||
values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co1;
|
||||
while (values.hasNext()) {
|
||||
co1 = values.next();
|
||||
co1.accessCount -= minAccessCount;
|
||||
if (co1.accessCount <= 0) {
|
||||
values.remove();
|
||||
onRemove(co1.key, co1.obj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
71
hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java
vendored
Normal file
71
hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import cn.hutool.core.map.FixedLinkedHashMap;
|
||||
|
||||
/**
|
||||
* LRU (least recently used)最近最久未使用缓存<br>
|
||||
* 根据使用时间来判定对象是否被持续缓存<br>
|
||||
* 当对象被访问时放入缓存,当缓存满了,最久未被使用的对象将被移除。<br>
|
||||
* 此缓存基于LinkedHashMap,因此当被缓存的对象每被访问一次,这个对象的key就到链表头部。<br>
|
||||
* 这个算法简单并且非常快,他比FIFO有一个显著优势是经常使用的对象不太可能被移除缓存。<br>
|
||||
* 缺点是当缓存满时,不能被很快的访问。
|
||||
* @author Looly,jodd
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class LRUCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 默认无超时
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public LRUCache(int capacity) {
|
||||
this(capacity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 容量
|
||||
* @param timeout 默认超时时间,单位:毫秒
|
||||
*/
|
||||
public LRUCache(int capacity, long timeout) {
|
||||
if(Integer.MAX_VALUE == capacity) {
|
||||
capacity -= 1;
|
||||
}
|
||||
|
||||
this.capacity = capacity;
|
||||
this.timeout = timeout;
|
||||
|
||||
//链表key按照访问顺序排序,调用get方法后,会将这次访问的元素移至头部
|
||||
cacheMap = new FixedLinkedHashMap<K, CacheObj<K, V>>(capacity);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune
|
||||
|
||||
/**
|
||||
* 只清理超时对象,LRU的实现会交给<code>LinkedHashMap</code>
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
if (isPruneExpiredActive() == false) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co;
|
||||
while (values.hasNext()) {
|
||||
co = values.next();
|
||||
if (co.isExpired()) {
|
||||
values.remove();
|
||||
onRemove(co.key, co.obj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
102
hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java
vendored
Normal file
102
hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
/**
|
||||
* 无缓存实现,用于快速关闭缓存
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author Looly,jodd
|
||||
*/
|
||||
public class NoCache<K, V> implements Cache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long timeout() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V object) {
|
||||
// 跳过
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V object, long timeout) {
|
||||
// 跳过
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(K key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key, boolean isUpdateLastAccess) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key, Func0<V> supplier) {
|
||||
try {
|
||||
return (null == supplier) ? null : supplier.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<CacheObj<K, V>> cacheObjIterator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int prune() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(K key) {
|
||||
// 跳过
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
// 跳过
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
92
hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java
vendored
Normal file
92
hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import cn.hutool.cache.GlobalPruneTimer;
|
||||
|
||||
/**
|
||||
* 定时缓存<br>
|
||||
* 此缓存没有容量限制,对象只有在过期后才会被移除
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class TimedCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 正在执行的定时任务 */
|
||||
private ScheduledFuture<?> pruneJobFuture;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param timeout 超时(过期)时长,单位毫秒
|
||||
*/
|
||||
public TimedCache(long timeout) {
|
||||
this(timeout, new HashMap<K, CacheObj<K, V>>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param timeout 过期时长
|
||||
* @param map 存储缓存对象的map
|
||||
*/
|
||||
public TimedCache(long timeout, Map<K, CacheObj<K, V>> map) {
|
||||
this.capacity = 0;
|
||||
this.timeout = timeout;
|
||||
this.cacheMap = map;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune
|
||||
/**
|
||||
* 清理过期对象
|
||||
*
|
||||
* @return 清理数
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
int count = 0;
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co;
|
||||
while (values.hasNext()) {
|
||||
co = values.next();
|
||||
if (co.isExpired()) {
|
||||
values.remove();
|
||||
onRemove(co.key, co.obj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- auto prune
|
||||
/**
|
||||
* 定时清理
|
||||
*
|
||||
* @param delay 间隔时长,单位毫秒
|
||||
*/
|
||||
public void schedulePrune(long delay) {
|
||||
this.pruneJobFuture = GlobalPruneTimer.INSTANCE.schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
prune();
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消定时清理
|
||||
*/
|
||||
public void cancelPruneSchedule() {
|
||||
if (null != pruneJobFuture) {
|
||||
pruneJobFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
24
hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java
vendored
Normal file
24
hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* 弱引用缓存<br>
|
||||
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br>
|
||||
* 丢弃某个键时,其条目从映射中有效地移除。<br>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> 键
|
||||
* @param <V> 值
|
||||
* @author looly
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public class WeakCache<K, V> extends TimedCache<K, V>{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public WeakCache(long timeout) {
|
||||
super(timeout, new WeakHashMap<K, CacheObj<K, V>>());
|
||||
}
|
||||
|
||||
}
|
7
hutool-cache/src/main/java/cn/hutool/cache/impl/package-info.java
vendored
Normal file
7
hutool-cache/src/main/java/cn/hutool/cache/impl/package-info.java
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 提供各种缓存实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cache.impl;
|
7
hutool-cache/src/main/java/cn/hutool/cache/package-info.java
vendored
Normal file
7
hutool-cache/src/main/java/cn/hutool/cache/package-info.java
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 提供简易的缓存实现,此模块参考了jodd工具中的Cache模块
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cache;
|
101
hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java
vendored
Normal file
101
hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.FIFOCache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
|
||||
/**
|
||||
* 缓存单元测试
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class CacheConcurrentTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void fifoCacheTest() {
|
||||
int threadCount = 4000;
|
||||
final Cache<String, String> cache = new FIFOCache<>(3);
|
||||
|
||||
// 由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
ThreadUtil.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cache.put("key1", "value1", System.currentTimeMillis() * 3);
|
||||
cache.put("key2", "value2", System.currentTimeMillis() * 3);
|
||||
cache.put("key3", "value3", System.currentTimeMillis() * 3);
|
||||
cache.put("key4", "value4", System.currentTimeMillis() * 3);
|
||||
ThreadUtil.sleep(1000);
|
||||
cache.put("key5", "value5", System.currentTimeMillis() * 3);
|
||||
cache.put("key6", "value6", System.currentTimeMillis() * 3);
|
||||
cache.put("key7", "value7", System.currentTimeMillis() * 3);
|
||||
cache.put("key8", "value8", System.currentTimeMillis() * 3);
|
||||
Console.log("put all");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
ThreadUtil.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
show(cache);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
System.out.println("==============================");
|
||||
ThreadUtil.sleep(10000);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void lruCacheTest() {
|
||||
int threadCount = 40000;
|
||||
final Cache<String, String> cache = new LRUCache<>(1000);
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
final int index = i;
|
||||
ThreadUtil.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cache.put("key1"+ index, "value1");
|
||||
cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
|
||||
|
||||
int size = cache.size();
|
||||
int capacity = cache.capacity();
|
||||
if(size > capacity) {
|
||||
Console.log("{} {}", size, capacity);
|
||||
}
|
||||
ThreadUtil.sleep(1000);
|
||||
size = cache.size();
|
||||
capacity = cache.capacity();
|
||||
if(size > capacity) {
|
||||
Console.log("## {} {}", size, capacity);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ThreadUtil.sleep(5000);
|
||||
}
|
||||
|
||||
private void show(Cache<String, String> cache) {
|
||||
Iterator<?> its = cache.iterator();
|
||||
|
||||
while (its.hasNext()) {
|
||||
Object tt = its.next();
|
||||
Console.log(tt);
|
||||
}
|
||||
}
|
||||
}
|
111
hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java
vendored
Normal file
111
hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
|
||||
/**
|
||||
* 缓存测试用例
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CacheTest {
|
||||
|
||||
@Test
|
||||
public void fifoCacheTest(){
|
||||
Cache<String,String> fifoCache = CacheUtil.newFIFOCache(3);
|
||||
fifoCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||
fifoCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||
fifoCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||
fifoCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
|
||||
|
||||
//由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
|
||||
String value1 = fifoCache.get("key1");
|
||||
Assert.assertTrue(null == value1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lfuCacheTest(){
|
||||
Cache<String, String> lfuCache = CacheUtil.newLFUCache(3);
|
||||
lfuCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||
//使用次数+1
|
||||
lfuCache.get("key1");
|
||||
lfuCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||
lfuCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||
lfuCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
|
||||
|
||||
//由于缓存容量只有3,当加入第四个元素的时候,根据LFU规则,最少使用的将被移除(2,3被移除)
|
||||
String value1 = lfuCache.get("key1");
|
||||
String value2 = lfuCache.get("key2");
|
||||
String value3 = lfuCache.get("key3");
|
||||
Assert.assertTrue(null != value1);
|
||||
Assert.assertTrue(null == value2);
|
||||
Assert.assertTrue(null == value3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lruCacheTest(){
|
||||
Cache<String, String> lruCache = CacheUtil.newLRUCache(3);
|
||||
//通过实例化对象创建
|
||||
// LRUCache<String, String> lruCache = new LRUCache<String, String>(3);
|
||||
lruCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||
lruCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||
lruCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||
//使用时间推近
|
||||
lruCache.get("key1");
|
||||
lruCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
|
||||
|
||||
String value1 = lruCache.get("key1");
|
||||
Assert.assertNotNull(value1);
|
||||
//由于缓存容量只有3,当加入第四个元素的时候,根据LRU规则,最少使用的将被移除(2被移除)
|
||||
String value2 = lruCache.get("key2");
|
||||
Assert.assertNull(value2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timedCacheTest(){
|
||||
TimedCache<String, String> timedCache = CacheUtil.newTimedCache(4);
|
||||
// TimedCache<String, String> timedCache = new TimedCache<String, String>(DateUnit.SECOND.getMillis() * 3);
|
||||
timedCache.put("key1", "value1", 1);//1毫秒过期
|
||||
timedCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 5);//5秒过期
|
||||
timedCache.put("key3", "value3");//默认过期(4毫秒)
|
||||
timedCache.put("key4", "value4", Long.MAX_VALUE);//永不过期
|
||||
|
||||
//启动定时任务,每5毫秒秒检查一次过期
|
||||
timedCache.schedulePrune(5);
|
||||
//等待5毫秒
|
||||
ThreadUtil.sleep(5);
|
||||
|
||||
//5毫秒后由于value2设置了5毫秒过期,因此只有value2被保留下来
|
||||
String value1 = timedCache.get("key1");
|
||||
Assert.assertTrue(null == value1);
|
||||
String value2 = timedCache.get("key2");
|
||||
Assert.assertEquals("value2", value2);
|
||||
|
||||
//5毫秒后,由于设置了默认过期,key3只被保留4毫秒,因此为null
|
||||
String value3 = timedCache.get("key3");
|
||||
Assert.assertTrue(null == value3);
|
||||
|
||||
String value3Supplier = timedCache.get("key3", new Func0<String>() {
|
||||
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
return "Default supplier";
|
||||
}
|
||||
});
|
||||
Assert.assertEquals("Default supplier", value3Supplier);
|
||||
|
||||
// 永不过期
|
||||
String value4 = timedCache.get("key4");
|
||||
Assert.assertEquals("value4", value4);
|
||||
|
||||
//取消定时清理
|
||||
timedCache.cancelPruneSchedule();
|
||||
}
|
||||
}
|
19
hutool-cache/src/test/java/cn/hutool/cache/test/FileCacheTest.java
vendored
Normal file
19
hutool-cache/src/test/java/cn/hutool/cache/test/FileCacheTest.java
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.cache.file.LFUFileCache;
|
||||
|
||||
/**
|
||||
* 文件缓存单元测试
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class FileCacheTest {
|
||||
@Test
|
||||
public void lfuFileCacheTest() {
|
||||
LFUFileCache cache = new LFUFileCache(1000, 500, 2000);
|
||||
Assert.assertNotNull(cache);
|
||||
}
|
||||
}
|
24
hutool-captcha/pom.xml
Normal file
24
hutool-captcha/pom.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>4.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 验证码工具</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,226 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
|
||||
/**
|
||||
* 抽象验证码<br>
|
||||
* 抽象验证码实现了验证码字符串的生成、验证,验证码图片的写出<br>
|
||||
* 实现类通过实现{@link #createImage(String)} 方法生成图片对象
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractCaptcha implements ICaptcha {
|
||||
private static final long serialVersionUID = 3180820918087507254L;
|
||||
|
||||
/** 图片的宽度 */
|
||||
protected int width = 100;
|
||||
/** 图片的高度 */
|
||||
protected int height = 37;
|
||||
/** 验证码干扰元素个数 */
|
||||
protected int interfereCount = 15;
|
||||
/** 字体 */
|
||||
protected Font font;
|
||||
/** 验证码 */
|
||||
protected String code;
|
||||
/** 验证码图片 */
|
||||
protected byte[] imageBytes;
|
||||
/** 验证码生成器 */
|
||||
protected CodeGenerator generator;
|
||||
/** 背景色 */
|
||||
protected Color background;
|
||||
/** 文字透明度 */
|
||||
protected AlphaComposite textAlpha;
|
||||
|
||||
/**
|
||||
* 构造,使用随机验证码生成器生成验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param interfereCount 验证码干扰元素个数
|
||||
*/
|
||||
public AbstractCaptcha(int width, int height, int codeCount, int interfereCount) {
|
||||
this(width, height, new RandomGenerator(codeCount), interfereCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param generator 验证码生成器
|
||||
* @param interfereCount 验证码干扰元素个数
|
||||
*/
|
||||
public AbstractCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.generator = generator;
|
||||
this.interfereCount = interfereCount;
|
||||
// 字体高度设为验证码高度-2,留边距
|
||||
this.font = new Font(Font.SANS_SERIF, Font.PLAIN, (int) (this.height * 0.75));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createCode() {
|
||||
generateCode();
|
||||
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ImgUtil.writePng(createImage(this.code), out);
|
||||
this.imageBytes = out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码字符串
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
protected void generateCode() {
|
||||
this.code = generator.generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据生成的code创建验证码图片
|
||||
*
|
||||
* @param code 验证码
|
||||
*/
|
||||
protected abstract Image createImage(String code);
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
if(null == this.code) {
|
||||
createCode();
|
||||
}
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String userInputCode) {
|
||||
return this.generator.verify(getCode(), userInputCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码写出到文件
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void write(String path) throws IORuntimeException {
|
||||
this.write(FileUtil.touch(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码写出到文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void write(File file) throws IORuntimeException {
|
||||
try (OutputStream out = FileUtil.getOutputStream(file)) {
|
||||
this.write(out);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) {
|
||||
IoUtil.write(out, false, getImageBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图形验证码图片bytes
|
||||
*
|
||||
* @return 图形验证码图片bytes
|
||||
* @since 4.5.17
|
||||
*/
|
||||
public byte[] getImageBytes() {
|
||||
if (null == this.imageBytes) {
|
||||
createCode();
|
||||
}
|
||||
return this.imageBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码图
|
||||
*
|
||||
* @return 验证码图
|
||||
*/
|
||||
public BufferedImage getImage() {
|
||||
return ImgUtil.read(IoUtil.toStream(getImageBytes()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得图片的Base64形式
|
||||
*
|
||||
* @return 图片的Base64
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public String getImageBase64() {
|
||||
return Base64.encode(getImageBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义字体
|
||||
*
|
||||
* @param font 字体
|
||||
*/
|
||||
public void setFont(Font font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码生成器
|
||||
*
|
||||
* @return 验证码生成器
|
||||
*/
|
||||
public CodeGenerator getGenerator() {
|
||||
return generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证码生成器
|
||||
*
|
||||
* @param generator 验证码生成器
|
||||
*/
|
||||
public void setGenerator(CodeGenerator generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置背景色
|
||||
*
|
||||
* @param background 背景色
|
||||
* @since 4.1.22
|
||||
*/
|
||||
public void setBackground(Color background) {
|
||||
this.background = background;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文字透明度
|
||||
*
|
||||
* @param textAlpha 文字透明度,取值0~1,1表示不透明
|
||||
* @since 4.5.17
|
||||
*/
|
||||
public void setTextAlpha(float textAlpha) {
|
||||
this.textAlpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, textAlpha);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
/**
|
||||
* 图形验证码工具
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class CaptchaUtil {
|
||||
|
||||
/**
|
||||
* 创建线干扰的验证码,默认5位验证码,150条干扰线
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @return {@link LineCaptcha}
|
||||
*/
|
||||
public static LineCaptcha createLineCaptcha(int width, int height) {
|
||||
return new LineCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建线干扰的验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param lineCount 干扰线条数
|
||||
* @return {@link LineCaptcha}
|
||||
*/
|
||||
public static LineCaptcha createLineCaptcha(int width, int height, int codeCount, int lineCount) {
|
||||
return new LineCaptcha(width, height, codeCount, lineCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建圆圈干扰的验证码,默认5位验证码,15个干扰圈
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @return {@link CircleCaptcha}
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static CircleCaptcha createCircleCaptcha(int width, int height) {
|
||||
return new CircleCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建圆圈干扰的验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param circleCount 干扰圆圈条数
|
||||
* @return {@link CircleCaptcha}
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static CircleCaptcha createCircleCaptcha(int width, int height, int codeCount, int circleCount) {
|
||||
return new CircleCaptcha(width, height, codeCount, circleCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建扭曲干扰的验证码,默认5位验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @return {@link ShearCaptcha}
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static ShearCaptcha createShearCaptcha(int width, int height) {
|
||||
return new ShearCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建扭曲干扰的验证码,默认5位验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param thickness 干扰线宽度
|
||||
* @return {@link ShearCaptcha}
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static ShearCaptcha createShearCaptcha(int width, int height, int codeCount, int thickness) {
|
||||
return new ShearCaptcha(width, height, codeCount, thickness);
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 圆圈干扰验证码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.2.3
|
||||
*
|
||||
*/
|
||||
public class CircleCaptcha extends AbstractCaptcha {
|
||||
private static final long serialVersionUID = -7096627300356535494L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
*/
|
||||
public CircleCaptcha(int width, int height) {
|
||||
this(width, height, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
*/
|
||||
public CircleCaptcha(int width, int height, int codeCount) {
|
||||
this(width, height, codeCount, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param interfereCount 验证码干扰元素个数
|
||||
*/
|
||||
public CircleCaptcha(int width, int height, int codeCount, int interfereCount) {
|
||||
super(width, height, codeCount, interfereCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics2D g = ImgUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
|
||||
// 随机画干扰圈圈
|
||||
drawInterfere(g);
|
||||
|
||||
// 画字符串
|
||||
drawString(g, code);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 指定透明度
|
||||
if (null != this.textAlpha) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 画随机干扰
|
||||
*
|
||||
* @param g {@link Graphics2D}
|
||||
*/
|
||||
private void drawInterfere(Graphics2D g) {
|
||||
final ThreadLocalRandom random = RandomUtil.getRandom();
|
||||
|
||||
for (int i = 0; i < this.interfereCount; i++) {
|
||||
g.setColor(ImgUtil.randomColor(random));
|
||||
g.drawOval(random.nextInt(width), random.nextInt(height), random.nextInt(height >> 1), random.nextInt(height >> 1));
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
40
hutool-captcha/src/main/java/cn/hutool/captcha/ICaptcha.java
Normal file
40
hutool-captcha/src/main/java/cn/hutool/captcha/ICaptcha.java
Normal file
@ -0,0 +1,40 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 验证码接口,提供验证码对象接口定义
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public interface ICaptcha extends Serializable{
|
||||
|
||||
/**
|
||||
* 创建验证码,实现类需同时生成随机验证码字符串和验证码图片
|
||||
*/
|
||||
void createCode();
|
||||
|
||||
/**
|
||||
* 获取验证码的文字内容
|
||||
*
|
||||
* @return 验证码文字内容
|
||||
*/
|
||||
String getCode();
|
||||
|
||||
/**
|
||||
* 验证验证码是否正确,建议忽略大小写
|
||||
*
|
||||
* @param userInputCode 用户输入的验证码
|
||||
* @return 是否与生成的一直
|
||||
*/
|
||||
boolean verify(String userInputCode);
|
||||
|
||||
/**
|
||||
* 将验证码写出到目标流中
|
||||
*
|
||||
* @param out 目标流
|
||||
*/
|
||||
void write(OutputStream out);
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 使用干扰线方式生成的图形验证码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class LineCaptcha extends AbstractCaptcha {
|
||||
private static final long serialVersionUID = 8691294460763091089L;
|
||||
|
||||
// -------------------------------------------------------------------- Constructor start
|
||||
/**
|
||||
* 构造,默认5位验证码,150条干扰线
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
*/
|
||||
public LineCaptcha(int width, int height) {
|
||||
this(width, height, 5, 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param lineCount 干扰线条数
|
||||
*/
|
||||
public LineCaptcha(int width, int height, int codeCount, int lineCount) {
|
||||
super(width, height, codeCount, lineCount);
|
||||
}
|
||||
// -------------------------------------------------------------------- Constructor end
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
// 图像buffer
|
||||
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics2D g = GraphicsUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
|
||||
// 干扰线
|
||||
drawInterfere(g);
|
||||
|
||||
// 字符串
|
||||
drawString(g, code);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 指定透明度
|
||||
if (null != this.textAlpha) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制干扰线
|
||||
*
|
||||
* @param g {@link Graphics2D}画笔
|
||||
*/
|
||||
private void drawInterfere(Graphics2D g) {
|
||||
final ThreadLocalRandom random = RandomUtil.getRandom();
|
||||
// 干扰线
|
||||
for (int i = 0; i < this.interfereCount; i++) {
|
||||
int xs = random.nextInt(width);
|
||||
int ys = random.nextInt(height);
|
||||
int xe = xs + random.nextInt(width / 8);
|
||||
int ye = ys + random.nextInt(height / 8);
|
||||
g.setColor(ImgUtil.randomColor(random));
|
||||
g.drawLine(xs, ys, xe, ye);
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
}
|
201
hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java
Normal file
201
hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java
Normal file
@ -0,0 +1,201 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 扭曲干扰验证码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.2.3
|
||||
*
|
||||
*/
|
||||
public class ShearCaptcha extends AbstractCaptcha {
|
||||
private static final long serialVersionUID = -7096627300356535494L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
*/
|
||||
public ShearCaptcha(int width, int height) {
|
||||
this(width, height, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
*/
|
||||
public ShearCaptcha(int width, int height, int codeCount) {
|
||||
this(width, height, codeCount, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param thickness 干扰线宽度
|
||||
*/
|
||||
public ShearCaptcha(int width, int height, int codeCount, int thickness) {
|
||||
super(width, height, codeCount, thickness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
final BufferedImage image = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics2D g = GraphicsUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
|
||||
// 画字符串
|
||||
drawString(g, code);
|
||||
|
||||
// 扭曲
|
||||
shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
// 画干扰线
|
||||
drawInterfere(g, 0, RandomUtil.randomInt(this.height) + 1, this.width, RandomUtil.randomInt(this.height) + 1, this.interfereCount, ImgUtil.randomColor());
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 指定透明度
|
||||
if (null != this.textAlpha) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 w1
|
||||
* @param h1 h1
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shear(Graphics g, int w1, int h1, Color color) {
|
||||
shearX(g, w1, h1, color);
|
||||
shearY(g, w1, h1, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* X坐标扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 宽
|
||||
* @param h1 高
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shearX(Graphics g, int w1, int h1, Color color) {
|
||||
|
||||
int period = RandomUtil.randomInt(this.width);
|
||||
|
||||
boolean borderGap = true;
|
||||
int frames = 1;
|
||||
int phase = RandomUtil.randomInt(2);
|
||||
|
||||
for (int i = 0; i < h1; i++) {
|
||||
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
||||
g.copyArea(0, i, w1, 1, (int) d, 0);
|
||||
if (borderGap) {
|
||||
g.setColor(color);
|
||||
g.drawLine((int) d, i, 0, i);
|
||||
g.drawLine((int) d + w1, i, w1, i);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Y坐标扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 宽
|
||||
* @param h1 高
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shearY(Graphics g, int w1, int h1, Color color) {
|
||||
|
||||
int period = RandomUtil.randomInt(this.height >> 1);
|
||||
|
||||
int frames = 20;
|
||||
int phase = 7;
|
||||
for (int i = 0; i < w1; i++) {
|
||||
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
||||
g.copyArea(i, 0, 1, h1, 0, (int) d);
|
||||
g.setColor(color);
|
||||
// 擦除原位置的痕迹
|
||||
g.drawLine(i, (int) d, i, 0);
|
||||
g.drawLine(i, (int) d + h1, i, h1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 干扰线
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param x1x1
|
||||
* @param y1 y1
|
||||
* @param x2 x2
|
||||
* @param y2 y2
|
||||
* @param thickness 粗细
|
||||
* @param c 颜色
|
||||
*/
|
||||
private void drawInterfere(Graphics g, int x1, int y1, int x2, int y2, int thickness, Color c) {
|
||||
|
||||
// The thick line is in fact a filled polygon
|
||||
g.setColor(c);
|
||||
int dX = x2 - x1;
|
||||
int dY = y2 - y1;
|
||||
// line length
|
||||
double lineLength = Math.sqrt(dX * dX + dY * dY);
|
||||
|
||||
double scale = (double) (thickness) / (2 * lineLength);
|
||||
|
||||
// The x and y increments from an endpoint needed to create a
|
||||
// rectangle...
|
||||
double ddx = -scale * (double) dY;
|
||||
double ddy = scale * (double) dX;
|
||||
ddx += (ddx > 0) ? 0.5 : -0.5;
|
||||
ddy += (ddy > 0) ? 0.5 : -0.5;
|
||||
int dx = (int) ddx;
|
||||
int dy = (int) ddy;
|
||||
|
||||
// Now we can compute the corner points...
|
||||
int xPoints[] = new int[4];
|
||||
int yPoints[] = new int[4];
|
||||
|
||||
xPoints[0] = x1 + dx;
|
||||
yPoints[0] = y1 + dy;
|
||||
xPoints[1] = x1 - dx;
|
||||
yPoints[1] = y1 - dy;
|
||||
xPoints[2] = x2 - dx;
|
||||
yPoints[2] = y2 - dy;
|
||||
xPoints[3] = x2 + dx;
|
||||
yPoints[3] = y2 + dy;
|
||||
|
||||
g.fillPolygon(xPoints, yPoints, 4);
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 随机字符验证码生成器<br>
|
||||
* 可以通过传入的基础集合和长度随机生成验证码字符
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public abstract class AbstractGenerator implements CodeGenerator {
|
||||
private static final long serialVersionUID = 8685744597154953479L;
|
||||
|
||||
/** 基础字符集合,用于随机获取字符串的字符集合 */
|
||||
protected String baseStr;
|
||||
/** 验证码长度 */
|
||||
protected int length;
|
||||
|
||||
/**
|
||||
* 构造,使用字母+数字做为基础
|
||||
*
|
||||
* @param count 生成验证码长度
|
||||
*/
|
||||
public AbstractGenerator(int count) {
|
||||
this(RandomUtil.BASE_CHAR_NUMBER, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param baseStr 基础字符集合,用于随机获取字符串的字符集合
|
||||
* @param length 生成验证码长度
|
||||
*/
|
||||
public AbstractGenerator(String baseStr, int length) {
|
||||
this.baseStr = baseStr;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取长度验证码
|
||||
*
|
||||
* @return 验证码长度
|
||||
*/
|
||||
public int getLength() {
|
||||
return this.length;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 验证码文字生成器
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public interface CodeGenerator extends Serializable{
|
||||
/**
|
||||
* 生成验证码
|
||||
*
|
||||
* @return 验证码
|
||||
*/
|
||||
public String generate();
|
||||
|
||||
/**
|
||||
* 验证用户输入的字符串是否与生成的验证码匹配<br>
|
||||
* 用户通过实现此方法定义验证码匹配方式
|
||||
*
|
||||
* @param code 生成的随机验证码
|
||||
* @param userInputCode 用户输入的验证码
|
||||
* @return 是否验证通过
|
||||
*/
|
||||
public boolean verify(String code, String userInputCode);
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 数字计算验证码生成器
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public class MathGenerator implements CodeGenerator {
|
||||
private static final long serialVersionUID = -5514819971774091076L;
|
||||
|
||||
private static final String operators = "+-*";
|
||||
|
||||
/** 参与计算数字最大长度 */
|
||||
private int numberLength;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public MathGenerator() {
|
||||
this(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param numberLength 参与计算最大数字位数
|
||||
*/
|
||||
public MathGenerator(int numberLength) {
|
||||
this.numberLength = numberLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
final int limit = getLimit();
|
||||
String number1 = Integer.toString(RandomUtil.randomInt(limit));
|
||||
String number2 = Integer.toString(RandomUtil.randomInt(limit));
|
||||
number1 = StrUtil.padAfter(number1, this.numberLength, CharUtil.SPACE);
|
||||
number2 = StrUtil.padAfter(number2, this.numberLength, CharUtil.SPACE);
|
||||
|
||||
final String code = StrUtil.builder()//
|
||||
.append(number1)//
|
||||
.append(RandomUtil.randomChar(operators))//
|
||||
.append(number2)//
|
||||
.append('=').toString();
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, String userInputCode) {
|
||||
int result;
|
||||
try {
|
||||
result = Integer.parseInt(userInputCode);
|
||||
} catch (NumberFormatException e) {
|
||||
// 用户输入非数字
|
||||
return false;
|
||||
}
|
||||
|
||||
final int a = Integer.parseInt(StrUtil.sub(code, 0, this.numberLength).trim());
|
||||
final char operator = code.charAt(this.numberLength);
|
||||
final int b = Integer.parseInt(StrUtil.sub(code, this.numberLength + 1, this.numberLength + 1 + this.numberLength).trim());
|
||||
|
||||
switch (operator) {
|
||||
case '+':
|
||||
return (a + b) == result;
|
||||
case '-':
|
||||
return (a - b) == result;
|
||||
case '*':
|
||||
return (a * b) == result;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取长度验证码
|
||||
*
|
||||
* @return 验证码长度
|
||||
*/
|
||||
public int getLength() {
|
||||
return this.numberLength * 2 + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据长度获取参与计算数字最大值
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
private int getLimit() {
|
||||
return Integer.parseInt("1" + StrUtil.repeat('0', this.numberLength));
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 随机字符验证码生成器<br>
|
||||
* 可以通过传入的基础集合和长度随机生成验证码字符
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public class RandomGenerator extends AbstractGenerator {
|
||||
private static final long serialVersionUID = -7802758587765561876L;
|
||||
|
||||
/**
|
||||
* 构造,使用字母+数字做为基础
|
||||
*
|
||||
* @param count 生成验证码长度
|
||||
*/
|
||||
public RandomGenerator(int count) {
|
||||
super(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param baseStr 基础字符集合,用于随机获取字符串的字符集合
|
||||
* @param length 生成验证码长度
|
||||
*/
|
||||
public RandomGenerator(String baseStr, int length) {
|
||||
super(baseStr, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
return RandomUtil.randomString(this.baseStr, this.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, String userInputCode) {
|
||||
if (StrUtil.isNotBlank(userInputCode)) {
|
||||
return StrUtil.equalsIgnoreCase(code, userInputCode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 验证码生成策略实现
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
package cn.hutool.captcha.generator;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user