首页
关于
留言
Search
1
红米 AX3000 (AX6) 路由器解锁 SSH 教程
6,676 阅读
2
网盘挂载程序sharelist美化教程
4,216 阅读
3
小米路由器 AX3600 开启SSH教程,官方固件即可安装 ShellClash开启科学上网
2,168 阅读
4
Oracle 甲骨文 ARM VPS 自动抢购脚本
1,819 阅读
5
编译带PassWall和SSR-plus插件的Openwrt系统
1,393 阅读
前端
Vue
React
后端
Java
Python
PHP
数据库
运维
杂谈
小程序
影视资源
登录
Search
标签搜索
Java
Linux
Mysql
IDEA
Debian
Docker
Springboot
CentOS
Cloudflare
Maven
JavaScript
SQL
Wordpress
宝塔
Nginx
Windows
MacBook
JS
CSS
Openwrt
William
累计撰写
144
篇文章
累计收到
702
条评论
首页
栏目
前端
Vue
React
后端
Java
Python
PHP
数据库
运维
杂谈
小程序
影视资源
页面
关于
留言
搜索到
19
篇与
的结果
2022-10-29
如何利用Maven将代码打包成第三方公共jar包?
一、摘要在项目开发过程中,我们经常需要将一些公共方法提取出来,然后单独封装成一个第三方公共jar包,采用普通的方式打包后的jar,依赖的工程执行编译时,却提示找不到对应的依赖包,那么如何将工程打包为可执行jar包呢?下面向大家介绍三种通过maven将工程打包成可执行的打包方式。二、方法实践2.1、assembly插件2.1.1、pom.xml的相关配置文件如下<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.java</groupId> <artifactId>example-frame-fatJar</artifactId> <version>1.0.0</version> ..... <build> <finalName>sso-api</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- maven-assembly-plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.6</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <encoding>UTF-8</encoding> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>2.1.2、运行如下命令,进行打包mvn clean package会在target文件夹里生成一个jar-with-dependencies的jar是可执行的。2.1.3、验证jar是否可执行在带有 jar-with-dependencies 的jar文件下,打开终端,输入如下命令#验证jar是否可执行,如果没有报错,说明没有问题 java -jar xxx-jar-with-dependencies.jar2.2、shade插件2.2.1、pom.xml的相关配置文件如下<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.java</groupId> <artifactId>example-frame-fatJar</artifactId> <version>1.0.0</version> ...... <build> <finalName>sso-api</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- maven-shade-plugin,不同的是shade可以将多个相同的配置文件追加合并 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.xxg.Main</mainClass> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>2.2.2、运行如下命令,进行打包mvn clean package发现生成了2个jar包,一个为:original-XXX.jar,另一个为:XXX.jar,其中original...jar里只包含了工程自己的class文件,而另外的一个jar包则包含了工程本身以及所有依赖的jar包的class文件。我们只需要使用第二个jar包就可以了。2.2.3、验证jar是否可执行在XXX.jar文件下,打开终端,输入如下命令#验证jar是否可执行,如果没有报错,说明没有问题 java -jar XXX.jar不同点:shade可以将多个相同的配置文件追加合并,比如,多个子项目下有相同的配置文件,shade在打包的时候,会将相同的配置文件合并。2.3、Fatjar打包工具(eclipse插件)2.3.1、eclipse在线安装插件1、打开eclipse,打开菜单help > Install New Sofware > Add...name:FatJarURL:http://kurucz-grafika.de/fatjar根据提示下载安装并重启 eclipse!2.3.2、FatJar 使用在使用Eclipse进行导出时,点击项目右键,在弹出的右键菜单中选择 Build Fat Jar, 打开配置Fat Jar弹出框;或者,项目右键,点击Export,然后在打开的Export选择框中选择 Other 下面的 Fat Jar Exporter, 选择需要导出的项目,点击下一步打开配置Fat Jar弹出框。2.3.3、验证jar是否可执行在 XXX.jar 文件下,打开终端,输入如下命令#验证jar是否可执行,如果没有报错,说明没有问题 java -jar XXX.jar
2022年10月29日
11 阅读
0 评论
0 点赞
2022-10-22
如何编写一套多线程的测试用例?
一、摘要很多时候,新开发的功能在上线之前,我们都会进行压力测试,以防上线之后,突然出现性能瓶颈或者出现线程安全问题。那么问题来了,如何进行压力测试呢?实践的手段有很多种,比如采用 jmeter 、fiddler、postman 等第三方工具,可以快速实现性能压力测试。当然除此之外,其实我们也利用 java 的多线程特性,完全可以自行编写一套多线程的压力测试。下面我们以 访问百度首页服务 为例,向大家演示一下,采用 java 的多线程特性,该如何编写并发测试。二、代码实践方案一说到多线程,大家可能想到的就是实例化一个Thread对象,然后启动它,就可以实现异步处理,以模拟100个用户同时请求百度首页为例,代码实践如下:public static void main(String[] args) throws InterruptedException { //模拟100个线程,同时请求百度首页 long start = System.currentTimeMillis(); final int threadNum = 100; final CountDownLatch countDownLatch = new CountDownLatch(threadNum); for (int i = 0; i < threadNum; i++) { final int threadCount = i + 1; new Thread(new Runnable() { @Override public void run() { System.out.println("thread " + threadCount + " start"); //访问百度首页 String url = "https://www.baidu.com"; String rs = HttpUtils.getUrl(url); System.out.println("thread " + threadCount + " run result:" + rs); System.out.println("thread " + threadCount + " final"); //执行完成之后,计数器减一 countDownLatch.countDown(); } }).start(); } //线程同步阻塞 countDownLatch.await(); System.out.println("执行耗时:" + (System.currentTimeMillis() - start) + "ms"); }实践过程非常简单,采用Thread + CountDownLatch组合,进行阻塞测试。但是实际上往往我们进行多线程模拟用户进行访问某个服务的时候,每个用户的请求参数是不一样的,这个时候我们应该如何更加真实的贴近用户实际请求去测试呢?请看下面这个方案!方案二实际上在多线程并发编程中,它还有一个完美搭档,那就是 队列,采用 多线程+队列 组合编程模型,可以实现带任务的异步处理,并且性能高效!下面我们还是以 访问百度首页服务 为例,采用 多线程+队列 组合模式来模拟 100 个用户总共发起了1000次访问百度首页,代码实践如下!public static void main(String[] args) throws InterruptedException { //将每个用户访问百度服务的请求参数,存入阻塞队列BlockingQueue中 BlockingQueue<String> queue = new LinkedBlockingQueue<>(); for (int i = 0; i < 1000; i++) { queue.put("https://www.baidu.com?paramKey=" + i); } //模拟100个线程,执行1000次请求访问百度 long start = System.currentTimeMillis(); final int threadNum = 100; final CountDownLatch countDownLatch = new CountDownLatch(threadNum); for (int i = 0; i < threadNum; i++) { final int threadCount = i + 1; new Thread(new Runnable() { @Override public void run() { System.out.println("thread " + threadCount + " start"); boolean over = false; while (!over) { String url = queue.poll(); if(Objects.nonNull(url)) { //发起请求 String result =HttpUtils.getUrl(url); System.out.println("thread " + threadCount + " run result:" + result); }else { //任务结束 over = true; System.out.println("thread " + threadCount + " final"); countDownLatch.countDown(); } } } }).start(); } countDownLatch.await(); System.out.println("执行耗时:" + (System.currentTimeMillis() - start) + "ms"); }当然,你还可以自由调整线程数,也可以采用 juc 包的线程池来实现多线程编程,改造逻辑如下:public static void main(String[] args) throws InterruptedException { //将每个用户访问百度服务的请求参数,存入阻塞队列BlockingQueue中 BlockingQueue<String> queue = new LinkedBlockingQueue<>(); for (int i = 0; i < 1000; i++) { queue.put("https://www.baidu.com?paramKey=" + i); } //模拟100个线程,执行1000次请求访问百度 long start = System.currentTimeMillis(); final int threadNum = 100; //线程计数器 final CountDownLatch countDownLatch = new CountDownLatch(threadNum); //执行线程池 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threadNum); for (int i = 0; i < threadNum; i++) { final int threadCount = i + 1; fixedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println("thread " + threadCount + " start"); boolean over = false; while (!over) { String url = queue.poll(); if(Objects.nonNull(url)) { //发起请求 String result =HttpUtils.getUrl(url); System.out.println("thread " + threadCount + " run result:" + result); }else { //任务结束 over = true; System.out.println("thread " + threadCount + " final"); countDownLatch.countDown(); } } } }); } countDownLatch.await(); fixedThreadPool.shutdown(); System.out.println("执行耗时:" + (System.currentTimeMillis() - start) + "ms"); }其中 BlockingQueue 阻塞队列,支持线程数据共享,当一个线程把数据取出之后,另一个线程无法再取,最后的运行效果是一样的!
2022年10月22日
27 阅读
0 评论
0 点赞
2022-07-10
Spring Boot 接入支付宝,手把手带你实战!
支付宝推出了新的转账接口 alipay.fund.trans.uni.transfer(升级后安全性更高,功能更加强大) ,老转账接口 alipay.fund.trans.toaccount.transfer 将不再维护,新老接口的一个区别就是新接口采用的证书验签方式。1、将支付宝开放平台里下载的3个证书放在 resources 下面2、写支付宝支付的配置文件 alipay.propertiesalipay.appId=你的应用id alipay.serverUrl=https://openapi.alipay.com/gateway.do alipay.privateKey=你的应用私钥 alipay.format=json alipay.charset=UTF-8 alipay.signType=RSA2 alipay.appCertPath=/cert/appCertPublicKey_2021001164652941.crt alipay.alipayCertPath=/cert/alipayCertPublicKey_RSA2.crt alipay.alipayRootCertPath=/cert/alipayRootCert.crt3、引入pom依赖<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.10.97.ALL</version> </dependency>4、将配置信息注入AliPayBeanimport lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Component @PropertySource("classpath:/production/alipay.properties") @ConfigurationProperties(prefix = "alipay") @Data public class AliPayBean { private String appId; private String privateKey; private String publicKey; private String serverUrl; private String domain; private String format; private String charset; private String signType; private String appCertPath; private String alipayCertPath; private String alipayRootCertPath; }5、写配置类import com.alipay.api.AlipayClient; import com.alipay.api.CertAlipayRequest; import com.alipay.api.DefaultAlipayClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.FileCopyUtils; import java.io.InputStream; @Configuration public class AliConfig { @Value("${custom.http.proxyHost}") private String proxyHost; @Value("${custom.http.proxyPort}") private int proxyPort; @Value("${spring.profiles.active}") private String activeEnv; @Autowired private AliPayBean aliPayBean; @Bean(name = {"alipayClient"}) public AlipayClient alipayClientService() throws Exception{ CertAlipayRequest certAlipayRequest = new CertAlipayRequest(); //设置网关地址 certAlipayRequest.setServerUrl(aliPayBean.getServerUrl()); //设置应用Id certAlipayRequest.setAppId(aliPayBean.getAppId()); //设置应用私钥 certAlipayRequest.setPrivateKey(aliPayBean.getPrivateKey()); //设置请求格式,固定值json certAlipayRequest.setFormat(aliPayBean.getFormat()); //设置字符集 certAlipayRequest.setCharset(aliPayBean.getCharset()); //设置签名类型 certAlipayRequest.setSignType(aliPayBean.getSignType()); //如果是生产环境或者预演环境,则使用代理模式 if ("prod".equals(activeEnv) || "stage".equals(activeEnv) || "test".equals(activeEnv)) { //设置应用公钥证书路径 certAlipayRequest.setCertContent(getCertContentByPath(aliPayBean.getAppCertPath())); //设置支付宝公钥证书路径 certAlipayRequest.setAlipayPublicCertContent(getCertContentByPath(aliPayBean.getAlipayCertPath())); //设置支付宝根证书路径 certAlipayRequest.setRootCertContent(getCertContentByPath(aliPayBean.getAlipayRootCertPath())); certAlipayRequest.setProxyHost(proxyHost); certAlipayRequest.setProxyPort(proxyPort); }else { //local String serverPath = this.getClass().getResource("/").getPath(); //设置应用公钥证书路径 certAlipayRequest.setCertPath(serverPath+aliPayBean.getAppCertPath()); //设置支付宝公钥证书路径 certAlipayRequest.setAlipayPublicCertPath(serverPath+aliPayBean.getAlipayCertPath()); //设置支付宝根证书路径 certAlipayRequest.setRootCertPath(serverPath+aliPayBean.getAlipayRootCertPath()); } return new DefaultAlipayClient(certAlipayRequest); } public String getCertContentByPath(String name){ InputStream inputStream = null; String content = null; try{ inputStream = this.getClass().getClassLoader().getResourceAsStream(name); content = new String(FileCopyUtils.copyToByteArray(inputStream)); }catch (Exception e){ e.printStackTrace(); } return content; } }6、写支付工具类import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.domain.AlipayTradeAppPayModel; import com.alipay.api.domain.AlipayTradeQueryModel; import com.alipay.api.request.AlipayTradeAppPayRequest; import com.alipay.api.request.AlipayTradeQueryRequest; import com.alipay.api.response.AlipayTradeAppPayResponse; import com.alipay.api.response.AlipayTradeQueryResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; /** * @description:支付宝工具类 * @Date:2022-07-10 */ @Slf4j @Service public class AliPayUtils { @Autowired @Qualifier("alipayClient") private AlipayClient alipayClient; /** * 交易查询接口 * @param request * @return * @throws Exception */ public boolean isTradeQuery(AlipayTradeQueryModel model) throws AlipayApiException { AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); request.setBizModel(model); AlipayTradeQueryResponse alipayTradeQueryResponse = alipayClient.certificateExecute(request); if(alipayTradeQueryResponse.isSuccess()){ return true; } else { return false; } } /** * app支付 * @param model * @param notifyUrl * @return * @throws AlipayApiException */ public String startAppPay(AlipayTradeAppPayModel model, String notifyUrl) throws AlipayApiException { AlipayTradeAppPayRequest aliPayRequest = new AlipayTradeAppPayRequest(); model.setProductCode("QUICK_MSECURITY_PAY"); aliPayRequest.setNotifyUrl(notifyUrl); aliPayRequest.setBizModel(model); // 这里和普通的接口调用不同,使用的是sdkExecute AlipayTradeAppPayResponse aliResponse = alipayClient.sdkExecute(aliPayRequest); return aliResponse.getBody(); } /** * 转账接口 * * @param transferParams * @return AlipayFundTransToaccountTransferResponse */ public AlipayFundTransUniTransferResponse doTransferNew(TransferParams transferParams) throws Exception { String title = (StringUtils.isNotBlank(transferParams.getRemark()) ? transferParams .getRemark() : "转账"); //转账请求入参 AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest(); //转账参数 BizContentForUniTransfer bizContent = new BizContentForUniTransfer(); bizContent.setOut_biz_no(transferParams.getOutBizNo()); bizContent.setTrans_amount(MathUtil.changeF2Y(Math.abs(Integer.parseInt(transferParams.getAmount())))); bizContent.setProduct_code("TRANS_ACCOUNT_NO_PWD"); bizContent.setBiz_scene("DIRECT_TRANSFER"); bizContent.setOrder_title(title); Participant participant = new Participant(); participant.setIdentity(transferParams.getPayeeAccount()); participant.setIdentity_type(transferParams.getPayeeType()); participant.setName((StringUtils.isNotBlank(transferParams.getPayeeRealName()) ? transferParams .getPayeeRealName() : StringUtils.EMPTY)); bizContent.setPayee_info(participant); bizContent.setRemark(title); request.setBizContent(JSON.toJSONString(bizContent)); //转账请求返回 AlipayFundTransUniTransferResponse response = null; try { response = alipayClient.certificateExecute(request); } catch (Exception e) { log.info("doTransfer exception,异常信息:{}", e.toString()); log.info("doTransfer exception,支付宝返回信息:{}", JSONObject.toJSONString(response)); } log.info("doTransfer,AlipayFundTransUniTransferResponse:{}", JSONObject.toJSONString(response)); return response; } }Tips:转账用到的类@Data public class TransferParams { /** * 应用编号 */ private Long appId; /** * 创建人id */ private Long createdBy; /** * 转账业务订单号 */ private String outBizNo; /** * 收款方识别方式 */ private String payeeType; /** * 收款方账号,可以是支付宝userId或者支付宝loginId */ private String payeeAccount; /** * 转账金额,单位分 */ private String amount; /** * 付款方名称 */ private String payerShowName; /** * 收款方名称 */ private String payeeRealName; /** * 备注 */ private String remark; /** * 支付宝转账流水号 */ private String orderId; }import lombok.Data; import java.math.BigDecimal; /** * 支付宝转账参数 */ @Data public class BizContentForUniTransfer { /** * 业务订单号 */ private String out_biz_no; /** * 订单总金额,单位为元,精确到小数点后两位, */ private BigDecimal trans_amount; /** * 业务产品码, * 单笔无密转账到支付宝账户固定为:TRANS_ACCOUNT_NO_PWD; * 单笔无密转账到银行卡固定为:TRANS_BANKCARD_NO_PWD; * 收发现金红包固定为:STD_RED_PACKET; */ private String product_code; /** * 描述特定的业务场景,可传的参数如下: * DIRECT_TRANSFER:单笔无密转账到支付宝/银行卡, B2C现金红包; * PERSONAL_COLLECTION:C2C现金红包-领红包 */ private String biz_scene; /** * 转账业务的标题,用于在支付宝用户的账单里显示 */ private String order_title; /** * 原支付宝业务单号。C2C现金红包-红包领取时,传红包支付时返回的支付宝单号; * B2C现金红包、单笔无密转账到支付宝/银行卡不需要该参数。 */ private String original_order_id; /** * 业务备注 */ private String remark; /** * 转账业务请求的扩展参数,支持传入的扩展参数如下: * 1、sub_biz_scene 子业务场景,红包业务必传,取值REDPACKET,C2C现金红包、B2C现金红包均需传入; * 2、withdraw_timeliness为转账到银行卡的预期到账时间,可选(不传入则默认为T1), * 取值T0表示预期T+0到账,取值T1表示预期T+1到账,因到账时效受银行机构处理影响,支付宝无法保证一定是T0或者T1到账; */ private String business_params; /** * 支付收款对象 */ private Participant payee_info; }@Data public class Participant { /** * 参与方的唯一标识 */ private String identity; /** * 参与方的标识类型,目前支持如下类型: * 1、ALIPAY_USER_ID 支付宝的会员ID * 2、ALIPAY_LOGON_ID:支付宝登录号,支持邮箱和手机号格式 */ private String identity_type; /** * 参与方真实姓名,如果非空,将校验收款支付宝账号姓名一致性。 * 当identity_type=ALIPAY_LOGON_ID时,本字段必填。 */ private String name; }
2022年07月10日
14 阅读
0 评论
0 点赞
2022-05-07
如何在MacBook上安装Java JDK并配置环境变量
搞开发的用过 MacBook 的都知道 MacBook 的好,那新入手 MacBook 后要想搞 Java 开发,肯定得重新配置一下 Java 环境呀目前来说大部分企业及开发者都会选择 JDK8,所以这里以安装 JDK8 为例子1、访问Oracle官方的Java JDK网站下载(需要登陆)点击前往官网下载,选择 macOS,M1 芯片的得选择 ARM 版或者其他 JDK 版本2、打开终端检验是否安装成功在打开的终端中输入代码 java -version 输出类似下面的即安装成功:java version "1.8.0_231" Java(TM) SE Runtime Environment (build 1.8.0_231-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)然后因为 MacBook 安装 JDK 的时候是不能配置安装目录的,所以我们需要使用命令行来查看安装目录/usr/libexec/java_home -V下面红色框框内的路径就是Java的安装目录3、配置环境变量同样是使用终端,配置一个叫 bash_profile 的文件,具体路径是 ~/.bash_profile,如果不存在这个文件就自己创建一个文件内加入以下内容(JAVA_HOME要以你自己实际的为准):JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home PATH=$JAVA_HOME/bin:$PATH:. CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:. export JAVA_HOME export PATH export CLASSPATH保存后在终端执行 source ~/.bash_profile这时候终端输入 echo $JAVA_HOME 就会显示出 JAVA 路径
2022年05月07日
15 阅读
0 评论
0 点赞
2022-05-01
Jedis 常用API使用
pom.xml 引入 jedis 依赖<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.2</version> </dependency>1.对String操作的命令set(key, value):给数据库中名称为key的string赋予值valueget(key):返回数据库中名称为key的string的valuegetset(key, value):给名称为key的string赋予上一次的valuemget(key1, key2,…, key N):返回库中多个string(它们的名称为key1,key2…)的valuesetnx(key, value):如果不存在名称为key的string,则向库中添加string,名称为key,值为valuesetex(key, time, value):向库中添加string(名称为key,值为value)同时,设定过期时间timemset(key1, value1, key2, value2,…key N, value N):同时给多个string赋值,名称为key i的string赋值value imsetnx(key1, value1, key2, value2,…key N, value N):如果所有名称为key i的string都不存在,则向库中添加string,名称key i赋值为value iincr(key):名称为key的string增1操作incrby(key, integer):名称为key的string增加integerdecr(key):名称为key的string减1操作decrby(key, integer):名称为key的string减少integerappend(key, value):名称为key的string的值附加valuesubstr(key, start, end):返回名称为key的string的value的子串2.对List操作的命令rpush(key, value):在名称为key的list尾添加一个值为value的元素lpush(key, value):在名称为key的list头添加一个值为value的 元素llen(key):返回名称为key的list的长度lrange(key, start, end):返回名称为key的list中start至end之间的元素(下标从0开始,下同)ltrim(key, start, end):截取名称为key的list,保留start至end之间的元素lindex(key, index):返回名称为key的list中index位置的元素lset(key, index, value):给名称为key的list中index位置的元素赋值为valuelrem(key, count, value):删除count个名称为key的list中值为value的元素。count为0,删除所有值为value的元素,count>0 从头至尾删除count个值为value的元素,count<0从尾到头删除|count|个值为value的元素。lpop(key):返回并删除名称为key的list中的首元素rpop(key):返回并删除名称为key的list中的尾元素blpop(key1, key2,… key N, timeout):lpop 命令的block版本。即当timeout为0时,若遇到名称为key i的list不存在或该list为空,则命令结束。如果 timeout>0,则遇到上述情况时,等待timeout秒,如果问题没有解决,则对key i+1开始的list执行pop操作。brpop(key1, key2,… key N, timeout):rpop的block版本。参考上一命令。rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部3.对Set操作的命令sadd(key, member):向名称为key的set中添加元素membersrem(key, member) :删除名称为key的set中的元素memberspop(key) :随机返回并删除名称为key的set中一个元素smove(srckey, dstkey, member) :将member元素从名称为srckey的集合移到名称为dstkey的集合scard(key) :返回名称为key的set的基数sismember(key, member) :测试member是否是名称为key的set的元素sinter(key1, key2,…key N) :求交集sinterstore(dstkey, key1, key2,…key N) :求交集并将交集保存到dstkey的集合sunion(key1, key2,…key N) :求并集sunionstore(dstkey, key1, key2,…key N) :求并集并将并集保存到dstkey的集合sdiff(key1, key2,…key N) :求差集sdiffstore(dstkey, key1, key2,…key N) :求差集并将差集保存到dstkey的集合smembers(key) :返回名称为key的set的所有元素srandmember(key) :随机返回名称为key的set的一个元素4.对zset(sorted set)操作的命令zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。如果该元素已经存在,则根据score更新该元素的顺序。zrem(key, member) :删除名称为key的zset中的元素memberzincrby(key, increment, member) :如果在名称为key的zset中已经存在元素member,则该元素的score增加increment;否则向集合中添加该元素,其score的值为incrementzrank(key, member) :返回名称为key的zset(元素已按score从小到大排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”zrevrank(key, member) :返回名称为key的zset(元素已按score从大到小排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”zrange(key, start, end):返回名称为key的zset(元素已按score从小到大排序)中的index从start到end的所有元素zrevrange(key, start, end):返回名称为key的zset(元素已按score从大到小排序)中的index从start到end的所有元素zrangebyscore(key, min, max):返回名称为key的zset中score >= min且score <= max的所有元素zcard(key):返回名称为key的zset的基数zscore(key, element):返回名称为key的zset中元素element的scorezremrangebyrank(key, min, max):删除名称为key的zset中rank >= min且rank <= max的所有元素zremrangebyscore(key, min, max) :删除名称为key的zset中score >= min且score <= max的所有元素zunionstore / zinterstore(dstkeyN, key1,…,keyN, WEIGHTS w1,…wN, AGGREGATE SUM|MIN|MAX):对N个zset求并集和交集,并将最后的集合保存在dstkeyN中。对于集合中每一个元素的score,在进行AGGREGATE运算前,都要乘以对于的WEIGHT参数。如果没有提供WEIGHT,默认为1。默认的AGGREGATE是SUM,即结果集合中元素的score是所有集合对应元素进行 SUM运算的值,而MIN和MAX是指,结果集合中元素的score是所有集合对应元素中最小值和最大值。5.对Hash操作的命令hset(key, field, value):向名称为key的hash中添加元素field<—>valuehget(key, field):返回名称为key的hash中field对应的valuehmget(key, field1, …,field N):返回名称为key的hash中field i对应的valuehmset(key, field1, value1,…,field N, value N):向名称为key的hash中添加元素field i<—>value ihincrby(key, field, integer):将名称为key的hash中field的value增加integerhexists(key, field):名称为key的hash中是否存在键为field的域hdel(key, field):删除名称为key的hash中键为field的域hlen(key):返回名称为key的hash中元素个数hkeys(key):返回名称为key的hash中所有键hvals(key):返回名称为key的hash中所有键对应的valuehgetall(key):返回名称为key的hash中所有的键(field)及其对应的value6.对value操作的命令exists(key):确认一个key是否存在del(key):删除一个keytype(key):返回值的类型keys(pattern):返回满足给定pattern的所有keyrandomkey:随机返回key空间的一个keyrename(oldname, newname):将key由oldname重命名为newname,若newname存在则删除newname表示的keydbsize:返回当前数据库中key的数目expire:设定一个key的活动时间(s)ttl:获得一个key的活动时间select(index):按索引查询move(key, dbindex):将当前数据库中的key转移到有dbindex索引的数据库flushdb:删除当前选择数据库中的所有keyflushall:删除所有数据库中的所有key
2022年05月01日
14 阅读
0 评论
0 点赞
2022-04-14
给 JAVA 项目添加 AOP 日志切面
切面介绍面向切面编程是一种编程范式,它作为OOP面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、权限控制、缓存控制、日志打印等等。AOP把软件的功能模块分为两个部分:核心关注点和横切关注点。业务处理的主要功能为核心关注点,而非核心、需要拓展的功能为横切关注点。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点进行分离,使用切面有以下好处:集中处理某一关注点/横切逻辑可以很方便的添加/删除关注点侵入性少,增强代码可读性及可维护性 因此当想打印请求日志时很容易想到切面,对控制层代码0侵入切面的使用【基于注解】@Aspect => 声明该类为一个注解类切点注解:@Pointcut => 定义一个切点,可以简化代码通知注解:@Before => 在切点之前执行代码@After => 在切点之后执行代码@AfterReturning => 切点返回内容后执行代码,可以对切点的返回值进行封装@AfterThrowing => 切点抛出异常后执行@Around => 环绕,在切点前后执行代码写一个请求日志切面使用 @Pointcut 定义切点@Pointcut("execution(* your_package.controller..*(..))") public void requestServer() { }@Pointcut定义了一个切点,因为是请求日志切边,因此切点定义的是Controller包下的所有类下的方法。定义切点以后在通知注解中直接使用requestServer方法名就可以了使用 @Before 再切点前执行@Before("requestServer()") public void doBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); LOGGER.info("===============================Start========================"); LOGGER.info("IP : {}", request.getRemoteAddr()); LOGGER.info("URL : {}", request.getRequestURL().toString()); LOGGER.info("HTTP Method : {}", request.getMethod()); LOGGER.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); }在进入Controller方法前,打印出调用方IP、请求URL、HTTP请求类型、调用的方法名使用 @Around 打印进入控制层的入参@Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); LOGGER.info("Request Params : {}", getRequestParams(proceedingJoinPoint)); LOGGER.info("Result : {}", result); LOGGER.info("Time Cost : {} ms", System.currentTimeMillis() - start); return result; }打印了入参、结果以及耗时getRquestParams 方法:private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) { Map<String, Object> requestParams = new HashMap<>(); //参数名 String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = proceedingJoinPoint.getArgs(); for (int i = 0; i < paramNames.length; i++) { Object value = paramValues[i]; //如果是文件对象 if (value instanceof MultipartFile) { MultipartFile file = (MultipartFile) value; //获取文件名 value = file.getOriginalFilename(); } requestParams.put(paramNames[i], value); } return requestParams; }通过 @PathVariable 以及 @RequestParam 注解传递的参数无法打印出参数名,因此需要手动拼接一下参数名,同时对文件对象进行了特殊处理,只需获取文件名即可@After 方法调用后执行@After("requestServer()") public void doAfter(JoinPoint joinPoint) { LOGGER.info("=============================== End ========================"); }后置环绕这里就没有业务逻辑只是打印了End完整切面代码@Component @Aspect public class RequestLogAspect { private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class); @Pointcut("execution(* your_package.controller..*(..))") public void requestServer() { } @Before("requestServer()") public void doBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); LOGGER.info("===============================Start========================"); LOGGER.info("IP : {}", request.getRemoteAddr()); LOGGER.info("URL : {}", request.getRequestURL().toString()); LOGGER.info("HTTP Method : {}", request.getMethod()); LOGGER.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); } @Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); LOGGER.info("Request Params : {}", getRequestParams(proceedingJoinPoint)); LOGGER.info("Result : {}", result); LOGGER.info("Time Cost : {} ms", System.currentTimeMillis() - start); return result; } @After("requestServer()") public void doAfter(JoinPoint joinPoint) { LOGGER.info("=============================== End ========================"); } /** * 获取入参 * @param proceedingJoinPoint * * @return * */ private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) { Map<String, Object> requestParams = new HashMap<>(); //参数名 String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = proceedingJoinPoint.getArgs(); for (int i = 0; i < paramNames.length; i++) { Object value = paramValues[i]; //如果是文件对象 if (value instanceof MultipartFile) { MultipartFile file = (MultipartFile) value; value = file.getOriginalFilename(); //获取文件名 } requestParams.put(paramNames[i], value); } return requestParams; } }但是,每个信息都打印一行,在高并发请求下确实会出现请求之间打印日志串行的问题,解决日志串行的问题只要将多行打印信息合并为一行就可以了,因此构造一个对象RequestInfo.java@Data public class RequestInfo { private String ip; private String url; private String httpMethod; private String classMethod; private Object requestParams; private Object result; private Long timeCost; }环绕通知方法体@Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.currentTimeMillis(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Object result = proceedingJoinPoint.proceed(); RequestInfo requestInfo = new RequestInfo(); requestInfo.setIp(request.getRemoteAddr()); requestInfo.setUrl(request.getRequestURL().toString()); requestInfo.setHttpMethod(request.getMethod()); requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName())); requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint)); requestInfo.setResult(result); requestInfo.setTimeCost(System.currentTimeMillis() - start); LOGGER.info("Request Info : {}", JSON.toJSONString(requestInfo)); return result; }将 url、http request 这些信息组装成 RequestInfo 对象,再序列化打印对象,打印序列化对象结果而不是直接打印对象是因为序列化有更直观、更清晰,同时可以借助在线解析工具对结果进行解析.就这样在解决高并发下请求串行问题的同时添加了对异常请求信息的打印,通过使用 @AfterThrowing 注解对抛出异常的方法进行处理RequestErrorInfo.java@Data public class RequestErrorInfo { private String ip; private String url; private String httpMethod; private String classMethod; private Object requestParams; private RuntimeException exception; }异常通知环绕体@AfterThrowing(pointcut = "requestServer()", throwing = "e") public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); RequestErrorInfo requestErrorInfo = new RequestErrorInfo(); requestErrorInfo.setIp(request.getRemoteAddr()); requestErrorInfo.setUrl(request.getRequestURL().toString()); requestErrorInfo.setHttpMethod(request.getMethod()); requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName())); requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint)); requestErrorInfo.setException(e); LOGGER.info("Error Request Info : {}", JSON.toJSONString(requestErrorInfo)); }为什么这里要单独搞一个异常环绕通知,因为都运行出问题了,统计运行时间也没什么意义了呀,所以这里不统计耗时,而是添加了异常的打印最后放一下优化后的完整日志请求切面代码:@Component @Aspect public class RequestLogAspect { private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class); @Pointcut("execution(* your_package.controller..*(..))") public void requestServer() { } @Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.currentTimeMillis(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Object result = proceedingJoinPoint.proceed(); RequestInfo requestInfo = new RequestInfo(); requestInfo.setIp(request.getRemoteAddr()); requestInfo.setUrl(request.getRequestURL().toString()); requestInfo.setHttpMethod(request.getMethod()); requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName())); requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint)); requestInfo.setResult(result); requestInfo.setTimeCost(System.currentTimeMillis() - start); LOGGER.info("Request Info : {}", JSON.toJSONString(requestInfo)); return result; } @AfterThrowing(pointcut = "requestServer()", throwing = "e") public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); RequestErrorInfo requestErrorInfo = new RequestErrorInfo(); requestErrorInfo.setIp(request.getRemoteAddr()); requestErrorInfo.setUrl(request.getRequestURL().toString()); requestErrorInfo.setHttpMethod(request.getMethod()); requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName())); requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint)); requestErrorInfo.setException(e); LOGGER.info("Error Request Info : {}", JSON.toJSONString(requestErrorInfo)); } /** * 获取入参 * @param proceedingJoinPoint * * @return * */ private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) { //参数名 String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = proceedingJoinPoint.getArgs(); return buildRequestParam(paramNames, paramValues); } private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) { //参数名 String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = joinPoint.getArgs(); return buildRequestParam(paramNames, paramValues); } private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) { Map<String, Object> requestParams = new HashMap<>(); for (int i = 0; i < paramNames.length; i++) { Object value = paramValues[i]; //如果是文件对象 if (value instanceof MultipartFile) { MultipartFile file = (MultipartFile) value; //获取文件名 value = file.getOriginalFilename(); } requestParams.put(paramNames[i], value); } return requestParams; } @Data public class RequestInfo { private String ip; private String url; private String httpMethod; private String classMethod; private Object requestParams; private Object result; private Long timeCost; } @Data public class RequestErrorInfo { private String ip; private String url; private String httpMethod; private String classMethod; private Object requestParams; private RuntimeException exception; } }
2022年04月14日
19 阅读
0 评论
0 点赞
2021-12-07
Java8+使用 Stream 玩转集合的筛选、归约、分组、聚合
Stream的使用Stream有两种操作,一个是中间操作,每次返回一个新的流,可以有多个;另一个是终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值stream不同于java.io的InputStream和OutputStream,它代表的是任意Java对象的序列。两者对比如下: java.iojava.util.stream存储顺序读写的 byte 或 char顺序输出任意java对象实例用途序列化至文件或网络内存计算、业务逻辑Stream和List也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。 java.util.listjava.util.stream元素已分配并存储在内存可能未分配,实时计算用途操作一组已存在的Java对象惰性计算Stream的特点:它可以“存储”有限个或无限个元素。这里的存储打了个引号,是因为元素有可能已经全部存储在内存中,也有可能是根据需要实时计算出来的。Stream的另一个特点是,一个Stream可以轻易地转换为另一个Stream,而不是修改原Stream本身。最后,真正的计算通常发生在最后结果的获取,也就是惰性计算。惰性计算的特点是:一个Stream转换为另一个Stream时,实际上只存储了转换规则,并没有任何计算发生。1、创建StreamStream.of创建Stream最简单的方式是直接用Stream.of()静态方法,传入可变参数即创建了一个能输出确定元素的Stream:public class Main { public static void main(String[] args) { Stream<String> stream = Stream.of("A", "B", "C", "D"); // forEach()方法相当于内部循环调用, // 可传入符合Consumer接口的void accept(T t)的方法引用: stream.forEach(System.out::println); } }基于数组或Collection第二种创建Stream的方法是基于一个数组或者Collection,这样该Stream输出的元素就是数组或者Collection持有的元素:public class Main { public static void main(String[] args) { Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" }); Stream<String> stream2 = List.of("X", "Y", "Z").stream(); stream1.forEach(System.out::println); stream2.forEach(System.out::println); } }把数组变成Stream使用Arrays.stream()方法。对于Collection(List、Set、Queue等),直接调用stream()方法就可以获得Stream。基于Supplier创建Stream还可以通过Stream.generate()方法,它需要传入一个Supplier对象:Stream<String> s = Stream.generate(Supplier<String> sp);基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列。public class Main { public static void main(String[] args) { Stream<Integer> natual = Stream.generate(new NatualSupplier()); // 注意:无限序列必须先变成有限序列再打印: natual.limit(20).forEach(System.out::println); } } class NatualSupplier implements Supplier<Integer> { int n = 0; public Integer get() { n++; return n; } }对于无限序列,如果直接调用forEach()或者count()这些最终求值操作,会进入死循环,因为永远无法计算完这个序列,所以正确的方法是先把无限序列变成有限序列,例如,用limit()方法可以截取前面若干个元素,这样就变成了一个有限序列,对这个有限序列调用forEach()或者count()操作就没有问题。其他方法创建Stream的第三种方法是通过一些API提供的接口,直接获得Stream。例如,Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) { ... }Java的范型不支持基本类型,所以我们无法用Stream<int>这样的类型,会发生编译错误。为了提高效率,Java标准库提供了IntStream、LongStream和DoubleStream这三种使用基本类型的Stream,它们的使用方法和范型Stream没有大的区别,设计这三个Stream的目的是提高运行效率:// 将int[]数组变为IntStream: IntStream is = Arrays.stream(new int[] { 1, 2, 3 }); // 将Stream<String>转换为LongStream: LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);小结创建Stream的方法有 :通过指定元素、指定数组、指定Collection创建Stream;通过Supplier创建Stream,可以是无限序列;通过其他类的相关方法创建。基本类型的Stream有IntStream、LongStream和DoubleStream。2、使用mapStream.map()是Stream最常用的一个转换方法,它把一个Stream转换为另一个Stream。所谓map操作,就是把一种操作运算,映射到一个序列的每一个元素上。例如,对x计算它的平方,可以使用函数f(x) = x * x。我们把这个函数映射到一个序列1,2,3,4,5上,就得到了另一个序列1,4,9,16,25: f(x) = x * x │ │ ┌───┬───┬───┬───┼───┬───┬───┬───┐ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ [ 1 2 3 4 5 6 7 8 9 ] │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ [ 1 4 9 16 25 36 49 64 81 ]可见,map操作,把一个Stream的每个元素一一对应到应用了目标函数的结果上。JDK9 在 List、Set、Map 等,都提供了 of()方法,表面上看來,它们似乎只是建立 List、Set、Map 实例的便捷方法利用map(),不但能完成数学计算,对于字符串操作,以及任何Java对象都是非常有用的。例如:public class Main { public static void main(String[] args) { List.of(" Apple ", " pear ", " ORANGE", " BaNaNa ") .stream() .map(String::trim) // 去空格 .map(String::toLowerCase) // 变小写 .forEach(System.out::println); // 打印 } } // 输出 apple pear orange banana3、使用 filterStream.filter()是Stream的另一个常用转换方法。所谓filter()操作,就是对一个Stream的所有元素一一进行测试,不满足条件的就被“滤掉”了,剩下的满足条件的元素就构成了一个新的Stream。例如,我们对1,2,3,4,5这个Stream调用filter(),传入的测试函数f(x) = x % 2 != 0用来判断元素是否是奇数,这样就过滤掉偶数,只剩下奇数,因此我们得到了另一个序列1,3,5: f(x) = x % 2 != 0 │ │ ┌───┬───┬───┬───┼───┬───┬───┬───┐ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ [ 1 2 3 4 5 6 7 8 9 ] │ X │ X │ X │ X │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ [ 1 3 5 7 9 ]用IntStream写出上述逻辑,代码如下:public class Main { public static void main(String[] args) { IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9) .filter(n -> n % 2 != 0) .forEach(System.out::println); } }4、使用reducemap()和filter()都是Stream的转换方法,而Stream.reduce()则是Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。 可见,reduce()操作首先初始化结果为指定值(这里是0),紧接着,reduce()对每个元素依次调用(acc, n) -> acc + n,其中,acc是上次计算的结果:import java.util.stream.*; public class Main { public static void main(String[] args) { int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(0, (acc, n) -> acc + n); System.out.println(sum); // 45 } }// 计算过程: acc = 0 // 初始化为指定值 acc = acc + n = 0 + 1 = 1 // n = 1 acc = acc + n = 1 + 2 = 3 // n = 2 acc = acc + n = 3 + 3 = 6 // n = 3 acc = acc + n = 6 + 4 = 10 // n = 4 acc = acc + n = 10 + 5 = 15 // n = 5 acc = acc + n = 15 + 6 = 21 // n = 6 acc = acc + n = 21 + 7 = 28 // n = 7 acc = acc + n = 28 + 8 = 36 // n = 8 acc = acc + n = 36 + 9 = 45 // n = 9利用reduce(),我们可以把求和改成求积,计算求积时,初始值必须设置为1,代码也十分简单:int s = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(1, (acc, n) -> acc * n);5、输出集合Stream的几个常见操作:map()、filter()、reduce()。这些操作对Stream来说可以分为两类,一类是转换操作,即把一个Stream转换为另一个Stream,例如map()和filter(),另一类是聚合操作,即对Stream的每个元素进行计算,得到一个确定的结果,例如reduce()。对于Stream来说,对其进行转换操作并不会触发任何计算!输出为Listcollect(Collectors.toList())可以把Stream的每个元素收集到List中Stream<String> stream = Stream.of("Apple", "", null, "Pear", " ", "Orange"); List<String> list = stream.filter(s -> s != null && !s.isBlank()).collect(Collectors.toList()); System.out.println(list);collect(Collectors.toSet())可以把Stream的每个元素收集到Set中输出为数组只需要调用toArray()方法,并传入数组的“构造方法”:List<String> list = List.of("Apple", "Banana", "Orange"); String[] array = list.stream().toArray(String[]::new);collect(Collectors.toSet())可以把Stream的每个元素收集到Set中输出为Map要把Stream的元素收集到Map中,就稍微麻烦一点。因为对于每个元素,添加到Map时需要key和value,因此,我们要指定两个映射函数,分别把元素映射为key和value:public class Main { public static void main(String[] args) { Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft"); Map<String, String> map = stream .collect(Collectors.toMap( // 把元素s映射为key: s -> s.substring(0, s.indexOf(':')), // 把元素s映射为value: s -> s.substring(s.indexOf(':') + 1))); System.out.println(map); } }分组输出分组输出使用 Collectors.groupingBy(),它需要提供两个函数:一个是分组的key,这里使用s -> s.substring(0, 1),表示只要首字母相同的String分到一组,第二个是分组的value,这里直接使用Collectors.toList(),表示输出为List,上述代码运行结果如下:public class Main { public static void main(String[] args) { List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots"); Map<String, List<String>> groups = list.stream() .collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList())); System.out.println(groups); } }输出{ A=[Apple, Avocado, Apricots], B=[Banana, Blackberry], C=[Coconut, Cherry] }6、其他操作排序对Stream的元素进行排序十分简单,只需调用sorted()方法:public class Main { public static void main(String[] args) { List<String> list = List.of("Orange", "apple", "Banana") .stream() .sorted() .collect(Collectors.toList()); System.out.println(list); } }去重对一个Stream的元素进行去重,没必要先转换为Set,可以直接用distinct():List.of("A", "B", "A", "C", "B", "D") .stream() .distinct() .collect(Collectors.toList()); // [A, B, C, D] 截取截取操作常用于把一个无限的Stream转换成有限的Stream,skip()用于跳过当前Stream的前N个元素,limit()用于截取当前Stream最多前N个元素:List.of("A", "B", "C", "D", "E", "F") .stream() .skip(2) // 跳过A, B .limit(3) // 截取C, D, E .collect(Collectors.toList()); // [C, D, E]合并将两个Stream合并为一个Stream可以使用Stream的静态方法concat():Stream<String> s1 = List.of("A", "B", "C").stream(); Stream<String> s2 = List.of("D", "E").stream(); // 合并: Stream<String> s = Stream.concat(s1, s2); System.out.println(s.collect(Collectors.toList())); // [A, B, C, D, E]flatMap如果Stream的元素的合集是:Stream<List<Integer>> s = Stream.of( Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9));而我们希望把上述Stream转换为Stream<Integer>,就可以使用flatMap():Stream<Integer> i = s.flatMap(list -> list.stream());因此,所谓flatMap(),是指把Stream的每个元素(这里是List)映射为Stream,然后合并成一个新的Stream:┌─────────────┬─────────────┬─────────────┐ │┌───┬───┬───┐│┌───┬───┬───┐│┌───┬───┬───┐│ ││ 1 │ 2 │ 3 │││ 4 │ 5 │ 6 │││ 7 │ 8 │ 9 ││ │└───┴───┴───┘│└───┴───┴───┘│└───┴───┴───┘│ └─────────────┴─────────────┴─────────────┘ │ │flatMap(List -> Stream) │ │ ▼ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ └───┴───┴───┴───┴───┴───┴───┴───┴───┘并行通常情况下,对Stream的元素进行处理是单线程的,即一个一个元素进行处理。但是很多时候,我们希望可以并行处理Stream的元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。把一个普通Stream转换为可以并行处理的Stream非常简单,只需要用parallel()进行转换:Stream<String> s = ... String[] result = s.parallel() // 变成一个可以并行处理的Stream .sorted() // 可以进行并行排序 .toArray(String[]::new);经过parallel()转换后的Stream只要可能,就会对后续操作进行并行处理。我们不需要编写任何多线程代码就可以享受到并行处理带来的执行效率的提升。分组// 将员工按薪资是否高于8000分组 Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000)); // 将员工按性别分组 Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex)); // 将员工先按性别分组,再按地区分组 Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));接合joining 可以将 stream 中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。List<String> list = Arrays.asList("A", "B", "C"); String string = list.stream().collect(Collectors.joining("-")); // 拼接后的字符串:A-B-C排序sorted,中间操作。有两种排序:sorted():自然排序,流中元素需实现 Comparable 接口sorted(Comparator com):Comparator 排序器自定义排序// 按工资升序排序(自然排序) List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName) .collect(Collectors.toList()); // 按工资倒序排序 List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()) .map(Person::getName).collect(Collectors.toList()); // 先按工资再按年龄升序排序 List<String> newList3 = personList.stream() .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName) .collect(Collectors.toList()); // 先按工资再按年龄自定义排序(降序) List<String> newList4 = personList.stream().sorted((p1, p2) -> { if (p1.getSalary() == p2.getSalary()) { return p2.getAge() - p1.getAge(); } else { return p2.getSalary() - p1.getSalary(); } }).map(Person::getName).collect(Collectors.toList());总结Stream提供的常用操作有:转换操作:map(),filter(),sorted(),distinct();合并操作:concat(),flatMap();并行处理:parallel();聚合操作:reduce(),collect(),count(),max(),min(),sum(),average();其他操作:allMatch(), anyMatch(), forEach()。
2021年12月07日
94 阅读
0 评论
0 点赞
2021-10-13
Java8关于日期的处理方法
获取今天的日期Java 8 中的 LocalDate 用于表示当天日期。和 java.util.Date 不同,它只有日期,不包含时间。当你仅需要表示日期时就用这个类。LocalDate today = LocalDate.now(); System.out.println("今天的日期: " + today); // 输出:今天的日期: 2021-10-13获取年、月、日信息LocalDate now = LocalDate.now(); int year = now.getYear(); int month = now.getMonthValue(); int day = now.getDayOfMonth(); System.out.println("year:" + year); System.out.println("month:" + month); System.out.println("day:" + day); // 输出 // year:2021 // month:10 // day:13处理特定日期我们通过静态工厂方法 now() 非常容易地创建了当天日期,你还可以调用另一个有用的工厂方法 LocalDate.of() 创建任意日期, 该方法需要传入年、月、日做参数,返回对应的LocalDate实例。这个方法的好处是没再犯老API的设计错误,比如年度起始于1900,月份是从0开 始等等。LocalDate date = LocalDate.of(2018,2,6); System.out.println("自定义日期:"+date); // 输出 自定义日期:2018-02-06获取当前时间,不含日期LocalTime time = LocalTime.now(); System.out.println("获取当前的时间,不含有日期:" + time); // 输出:获取当前的时间,不含有日期:16:33:06.914现在时间进行加减未来时间:plusHours, plusMinutes, plusSeconds。分别是加小时,加分钟,加秒过去时间:plus替换成minus。对应减操作LocalTime time = LocalTime.now(); LocalTime newTime = time.plusHours(3); System.out.println("现在的时间:" + time); System.out.println("三个小时后的时间为:" + newTime); // 输出: // 现在的时间:16:43:42.642 // 三个小时后的时间为: 19:43:42.642计算一周后的日期和上个例子计算3小时以后的时间类似,这个例子会计算一周后的日期。LocalDate日期不包含时间信息,它的plus()方法用来增加天、周、月,ChronoUnit类声明了这些时间单位。由于LocalDate也是不变类型,返回后一定要用变量赋值。LocalDate today = LocalDate.now(); System.out.println("今天的日期为:" + today); LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); System.out.println("一周后的日期为:" + nextWeek); // 输出 // 今天的日期为:2021-10-13 // 一周后的日期为:2021-10-20可以看到新日期离当天日期是7天,也就是一周。你可以用同样的方法增加1个月、1年、1小时、1分钟甚至一个世纪,ChronoUnit后的WEEKS,换成YEARS,MONTHS,HOURS,MINUTES,DAYS。那要是想计算,减操作呢?就把 today.plus 改成 today.minus判断日期是早于还是晚于另一个日期LocalDate类有两类方法 isBefore() 和 isAfter() 用于比较日期。调用isBefore()方法时,如果给定日期小于当前日期则返回true。LocalDate today = LocalDate.now(); LocalDate tomorrow = LocalDate.of(2021, 12, 6); if (tomorrow.isAfter(today)) { System.out.println("之后的日期:" + tomorrow); } LocalDate yesterday = today.minus(1, ChronoUnit.DAYS); if (yesterday.isBefore(today)) { System.out.println("之前的日期:" + yesterday); } // 输出 // 之后的日期:2021-12-06 // 之前的日期:2021-10-12字符串互转日期类型LocalDateTime date = LocalDateTime.now(); DateTimeFormatter format1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); //日期转字符串 String str = date.format(format1); System.out.println("日期转换为字符串:" + str); DateTimeFormatter format2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); //字符串转日期 LocalDate date2 = LocalDate.parse(str, format2); System.out.println("日期类型:" + date2); // 输出 // 日期转换为字符串:2021-10-13 17:28:51 // 日期类型:2021-10-13
2021年10月13日
17 阅读
0 评论
0 点赞
1
2
3