
大道至简! https://waylau.com/
在2022年11月16日,Juergen Hoeller发布了消息,称 Spring Framework 6将正式发布。Spring 6 是一个重要的版本,距离 Spring 5 发布有四年多了。通过本文的介绍,我们一起来快速了解 Spring 6发行版中的那些令人兴奋的特性。JDK 17+和Jakarta EE 9+基线现在基于Java 17源代码级别的整个框架代码库。Servlet、JPA等从javax迁移到Jakarta 命名空间。运行时与Jakarta EE 9以及Jakarta EE 10 API的兼容性。与最新的Web服务器兼容:Tomcat 10.1,Jetty 11,Undertow 2.3.早期兼容虚拟线程(从JDK 19开始预览)。一般核心修订升级到ASM 9.4和Kotlin 1.7。完整的CGLIB fork,支持捕获CGLIB生成的类。全面的向AOT(Ahead-Of-Time Processing,提前处理)转型。对GraalVM原生映像的一流支持。核心容器默认情况下,无需java.beans.Introspector来确定基本bean属性。在GenericApplicationContext (refreshForAotProcessing)中的支持AOT处理。基于预解析构造函数和工厂方法的Bean定义转换。支持AOP代理和配置类的早期代理类确定。PathMatchingResourcePatternResolver使用NIO和模块路径API进行扫描,分别支持GraalVM本机映像和Java模块路径中的类路径扫描。DefaultFormattingConversionService支持基于ISO的默认java.time类型解析。数据访问和事务支持预定JPA托管类型(用于包含在AOT处理中)。JPA支持Hibernate ORM 6.1(保持与Hibernate ORM 5.6的兼容性)。升级到R2DBC 1.0(包括R2DBC事务定义)。删除JCA CCI支持。Spring消息传递基于@RSocketExchange服务接口的RSocket接口客户端。基于Netty 5 Alpha的Reactor Netty 2的早期支持。支持Jakarta WebSocket 2.1及其标准WebSocket协议升级机制。通用Web修订基于@HttpExchange服务接口的HTTP接口客户端。支持RFC 7807问题详细信息.统一HTTP状态码处理。支持Jackson 2.14。与Servlet 6.0对齐(同时保留与Servlet 5.0的运行时兼容性)。Spring MVC默认情况下使用的PathPatternParser(能够选择进入PathMatcher)。删除过时的Tiles和FreeMarker JSP支持。Spring WebFlux新的PartEvent API用于流式传输多部分表单上传(两者都在客户端和服务器).新的ResponseEntityExceptionHandler用于自定义WebFlux异常并呈现RFC 7807错误响应.非流媒体类型的Flux返回值(写入前不再收集到List)。基于Netty 5 Alpha的Reactor Netty 2的早期支持。JDK HttpClient与WebClient集成。可观察性Micrometer Observation直接可观察性在Spring框架中的部分应用。spring-web模块现在需要io.micrometer:micrometer-observation:1.10+作为编译依赖项。RestTemplate和WebClient被检测为生成HTTP客户端请求观察。Spring MVC可以使用新的org.springframework.web.filter.ServerHttpObservationFilter检测HTTP服务器观察。Spring WebFlux可以使用新的org.springframework.web.filter.reactive.ServerHttpObservationFilter检测HTTP服务器观察。对于Flux和Mono的Micrometer Context Propagation集成,从控制器方法返回值。测试支持在JVM上或GraalVM本机映像中测试AOT处理的应用程序上下文。集成HtmlUnit 2.64 +请求参数处理。Servlet模拟(MockHttpServletRequest、MockHttpSession)现在基于Servlet API 6.0。参考引用https://spring.io/blog/2022/11/16/spring-framework-6-0-goes-gaSpring 5 开发大全(北京大学出版社出版)https://github.com/waylau/spring-5-book原文同步至https://waylau.com/new-features-and-enhancements-in-spring-framework-6/
描述明明生成了N个1到500之间的随机整数。请你删去其中重复的数字,即相同的数字只保留一个,把其余相同的数去掉,然后再把这些数从小到大排序,按照排好的顺序输出。数据范围: 1≤n≤1000 ,输入的数字大小满足1≤val≤500输入描述:第一行先输入随机整数的个数 N 。 接下来的 N 行每行输入一个整数,代表明明生成的随机数。输出描述:输出多行,表示输入数据处理后的结果解法此题考查Set结构的用法以及排序。第一行先输入随机整数的个数 N 。接下来的 N 行每行输入一个整数,代表明明生成的随机数。随机数存入Set结构,实现去重。Set结构转为List,并排序。输出排序后的结果。/** Copyright (c) waylau.com, 2022. All rights reserved.*/package com.waylau.nowcoder.exam.oj.huawei;import java.util.ArrayList;import java.util.Comparator;import java.util.HashMap;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Scanner;import java.util.Set;/*** HJ3 明明的随机数. 描述:明明生成了N个1到500之间的随机整数。请你删去其中重复的数字,* 即相同的数字只保留一个,把其余相同的数去掉,然后再把这些数从小到大排序,按照排好的顺序输出。 数据范围: 1≤n≤1000* ,输入的数字大小满足1≤val≤500 输入描述:第一行先输入随机整数的个数 N 。 接下来的 N 行每行输入一个整数,代表明明生成的随机数。* 输出描述:输出多行,表示输入数据处理后的结果** @author Way Lau* @since 2022-08-05*/public class HJ3RandomNumbers {public static void main(String[] args) {// 输入一行,代表随机整数的个数 N。Scanner sc = new Scanner(System.in);// 全部转为小写int num = sc.nextInt();// 构造一个Set结构,实现数字去重Set<Integer> set = new HashSet<>();// 对输入数字进行遍历,将每遍历一个数字,就将该字符记录到Set中for (int i = 0; i < num; i++) {int randomNumer = sc.nextInt();set.add(randomNumer);}// Set结构转为List,并排序。List<Integer> sortedNumbers = new ArrayList<Integer>(set);sortedNumbers.sort(Comparator.naturalOrder());// 遍历输出每个整数sortedNumbers.forEach(System.out::println);// 关闭资源sc.close();}}解法2去重、排序,这不就是TreeSet的数据结构嘛!因此,可以直接用TreeSet解决。第一行先输入随机整数的个数 N 。接下来的 N 行每行输入一个整数,代表明明生成的随机数。随机数存入TreeSet结构,实现去重、排序。输出排序后的结果。代码如下:/** Copyright (c) waylau.com, 2022. All rights reserved.*/package com.waylau.nowcoder.exam.oj.huawei;import java.util.Scanner;import java.util.TreeSet;/*** HJ3 明明的随机数. 描述:明明生成了N个1到500之间的随机整数。请你删去其中重复的数字,* 即相同的数字只保留一个,把其余相同的数去掉,然后再把这些数从小到大排序,按照排好的顺序输出。 数据范围: 1≤n≤1000* ,输入的数字大小满足1≤val≤500 输入描述:第一行先输入随机整数的个数 N 。 接下来的 N 行每行输入一个整数,代表明明生成的随机数。* 输出描述:输出多行,表示输入数据处理后的结果** @author Way Lau* @since 2022-08-05*/public class HJ3RandomNumbers2 {public static void main(String[] args) {// 输入一行,代表随机整数的个数 N。Scanner sc = new Scanner(System.in);// 全部转为小写int num = sc.nextInt();// 构造一个TreeSet结构,实现数字去重、排序TreeSet<Integer> set = new TreeSet<>();// 对输入数字进行遍历,将每遍历一个数字,就将该字符记录到Set中for (int i = 0; i < num; i++) {int randomNumer = sc.nextInt();set.add(randomNumer);}// 遍历输出每个整数set.forEach(System.out::println);// 关闭资源sc.close();}}运行程序,输出如下:322112参考引用本系列归档至https://github.com/waylau/nowcoder-exam-oj《Java 数据结构及算法实战》:https://github.com/waylau/java-data-structures-and-algorithms-in-action《数据结构和算法基础(Java 语言实现)》(柳伟卫著,北京大学出版社出版):https://item.jd.com/13014179.html
近期,在HarmonyOS官网发布《鸿蒙生态应用开发白皮书》V1.0版本(以下简称《白皮书》)。笔者通读了《鸿蒙生态应用开发白皮书》,总结了读后感。如果熟悉鸿蒙开发,或者熟悉鸿蒙的文档,那么对于《白皮书》的内容应该就不会陌生。《白皮书》主要是从鸿蒙生态应用的背景、核心技术理念、开发能力平台、开发与测试、上架与分发、自由流转与分布式运行环境、运维分析、案例参考等八个方面进行阐述。背景鸿蒙生态应用的背景介绍的是鸿蒙生态应用的产生的背景、面临的机遇和挑战,以及未来的发展趋势。对于开发者而言,熟读这个背景是至关重要的,因为需要你从这个背景判断出你是否需要加入这个鸿蒙生态,未来是否看好这个鸿蒙生态,是否值得投入这个生态的开发。核心技术理念核心技术理念分为三部分来介绍,这其实是就是鸿蒙生态的最为核心的部分了,也是最能吸引开发者的部分:一次开发,多端部署可分可合,自由流转统一生态,原生智能开发能力平台开发能力平台主要是华为公司为了让更多开发者能加容易的进行鸿蒙生态应用的开发,所提供的平台支持。从笔者亲身经历来看,开发能力平台目前已经是非常完善了,可以大大减轻开发者入门的门槛。开发与测试开发与测试就是介绍如何来进行鸿蒙生态应用的开发与测试。从《白皮书》看出,鸿蒙生态应用的开发,将会力挺ArkTS语言。开发者如果之前没有接触ArkTS语言,建议可以关注下。上架与分发上架与分发部分就是介绍如何在开发完成之后,做上架与分发。自由流转与分布式运行自由流转与分布式运行是指鸿蒙生态应用的一个特色。因此,这章主要是介绍这个特色。运维分析运维分析主要是介绍如何来做鸿蒙生态应用的运维,遇到问题应该如何寻求解决。案例参考案例参考主要是给出了一些鸿蒙生态的几个应用场景,这样开发者能够直观了解鸿蒙生态最后能做什么。总结本质上这《白皮书》可以简单理解为是鸿蒙生态开发的一个总览或者是导读,让开发者可以快速去了解鸿蒙生态。“鸿蒙生态”应该是首次在官方文件中出现,这相当于是对“鸿蒙”定了性。之前,在各大媒体中经常能看到“鸿蒙”“OpenHarmony”“HarmonyOS”这些名词存在混用的情况,笔者也在博客《一文搞懂什么是鸿蒙、OpenHarmony、HarmonyOS》中对这些名词做了解释,在该博客中提到“鸿蒙生态”这个名词,不想与《白皮书》不谋而合,也算是一件趣事。最后,衷心祝愿鸿蒙生态越办越好,更多的开发者能进入这个生态,共创美好未来!笔者也提供了开源免费教程《跟老卫学HarmonyOS开发》欢迎指正!参考引用《一文搞懂什么是鸿蒙、OpenHarmony、HarmonyOS》https://waylau.com/what-is-harmonyos/《跟老卫学HarmonyOS开发》https://github.com/waylau/harmonyos-tutorial《鸿蒙HarmonyOS应用开发从入门到精通战》(柳伟卫著,北京大学出版社)https://item.jd.com/13696724.html>、
这是一台我个人DIY的第二代键盘主机(第一代见:https://www.bilibili.com/video/BV16Y4y1p7nv/)。相比与第一代的作品,第二代产品除了USB 3接口、USB 2接口、mini HDMI接口、DC电源接口外,拥有包括扬声器、话筒、3.5毫米耳机接口、TF卡槽等众多功能,因此,我称之为“全功能键盘主机”。主板主板是捡垃圾的 ,N3450低功耗CPU加持,板载6G内存+ 64 G固态,满足办公、轻度娱乐的需求,也适合做下载机。键盘同时,我还淘了个笔记本的键盘。键盘面板是需要切割和打磨成合适的宽度。外壳设计因为每块主板都是独一无二的,因此,外壳必须是量身定制。这边采用CAD软件来设计亚克力外壳。外壳制作我当然没有外壳制作的能力。把CAD图纸发给厂家,厂家做好就给寄过来了。这个外壳在设计时,便考虑了风扇位。安装最后就是将主板、键盘、外壳组装到一起啦。安装过程可以见视频:https://www.bilibili.com/video/BV1PW4y1r7d5/?vd_source=81ee817595c70f6444016cdcd4f62b06测试开机测试,成功点亮!用这个机子看视频,温度在60度左右,在没有开风扇的情况下,被动散热也还行。整体重量341g,跟一般的折叠屏手机差不多重啦。
刚好论坛在举行这个关于技术语言的讨论,我也来聊几句。我掌握的技术语言我掌握的技术语言有C、C++、ActionScript、JavaScript、TypeScript、Flex、Java、SQL、Scala、CAD,当然,这还不算一些具有特殊语言的技术框架,如Vue.js、Angular、Spark、Android、HarmonyOS、Node.js等,如果算上就更多了。纵观编程历史,一个技术语言的诞生必有其意义。比如C语言的出现是为了解决汇编语言可读性低、可重用性差、开发效率低下等问题。C++语言是为了解决C语言面向对象编程的问题。Java语言的出现则是解决C或者C++内存管理的问题。Python、Scala的出现是为了解决Java语言之类的语法繁琐的问题。ActionScript和TypeScript则是为了解决JavaScript类型和面向对象编程的问题。虽说技术语言肯定有生命周期,但有意思的是,大部分语言都长盛不衰,比如C、C++、JavaScript、Java、Python等。技术语言不是简单的技术问题,而是一个生态问题。选择了某个语言,也就变相选择了这个语言的生态。比如Android,从语法上讲Android本质就是Java,但一旦确定了Android的生态,就很难去撼动这个Android的地位。比如后进的鸿蒙,早前的Windows Phone。技术语言也是如此。软件系统原本是有一个生命周期,这个周期必须是会跟某些技术语言相偶合。只要软件系统够长命,技术语言也是可以存活好久。技术语言过时的问题我不太担心技术语言过时的问题。技术语言的使用频率来自于你当前的工作。我近期大部分时间是在使用Java、Scala和SQL语言开发大数据系统。因此,工作决定了我更加熟悉Java、Scala。当我有一天去做了前端的职位,那么那时工作的技术语言肯定又变成了JavaScript、TypeScript。因为对人工智能感兴趣,所以我近期又正在学习Python、FreeCAD。未来新的语言还会层出不穷,老的语言也会不断革新。毕竟Java都已经更新到Java 18了,这是个好事情。我更加担心的是开发者的心态,是否有拥有变化的决心,是否有深入研究技术的耐心。问道一入编程深似海,从此妹子是路人。既入此门,那就问道开发,好好干吧!下面是我整理的开源资料,与君共勉:Apache Shiro 1.2.x 参考手册Jersey 2.x 用户指南Gradle 2 用户指南GitHub 帮助文档Activiti 5.x 用户指南Spring Framework 4.x参考文档Netty 4.x 用户指南REST 案例大全REST 实战Netty 实战(精髓)Java 编码规范Apache MINA 2 用户指南CSS3 教程H2 Database 教程Java Servlet 3.1 规范JSSE 参考指南Apache Cordova 开发指南Java 编程要点分布式 JavaJava 虚拟机规范DB2 教程Apache Isis 教程微服务原理与实践Spring Boot 教程Gradle 3 用户指南Spring Security 教程Thymeleaf 教程NGINX 教程Spring Cloud 教程JDBC 4.2 规范Spring 5 案例大全Cloud Native 案例大全跟老卫学Angular现代Java案例大全跟老卫学IonicNode.js 案例大全Java数据结构及算法实战Java安可认证阶段考试——可信编码实现Java安可认证阶段考试——开发者测试Java安可认证阶段考试——可信代码重构跟老卫学HarmonyOS开发跟老卫学Vue.js开发跟老卫学Apache Spark开发现代C语言编程实战跟老卫学Ehcache开发跟老卫学FreeCAD开发跟老卫学Apache Kafka开发当然,如果你喜欢出版的书籍,这里也有相关的材料:分布式系统常用技术及案例分析Spring Boot 企业级应用开发实战Spring Cloud 微服务架构开发实战Spring 5 开发大全分布式系统常用技术及案例分析(第2版)Cloud Native 分布式架构原理与实践Angular企业级应用开发实战大型互联网应用轻量级架构实战Java核心编程MongoDB+Express+Angular+Node.js全栈开发实战派Node.js企业级应用开发实战還在 LAMP?用最流行的 MEAN 進行全端網頁開發Netty原理解析与开发实战分布式系统开发实战轻量级Java EE企业应用开发实战数据结构和算法基础(Java语言实现)鸿蒙HarmonyOS手机应用开发实战Vue.js 3企业级应用开发实战鸿蒙HarmonyOS应用开发从入门到精通莫愁前路无知己,天下谁人不识君!不知各位妹子是否喜爱这样的程序猿呢?
老版的DevEco Studio只支持layout 资源类型的XML文件的预览。在新版的DevEco Studio 已经能够支持 Ability/AbilitySlice 的Java类文件的预览。新版的DevEco Studio 默认并不启用Java预览器,因此,选中Ability/AbilitySlice文件并切换预览器时,会包如下告警提示:To preview the Ability/AbilitySlice file, go to Settings > DevEco Labs > Enable Java Previewer.提示你需要去启用Java预览器。在DevEco Studio中,勾选“Settings > DevEco Labs > Enable Java Previewer”选项即可。以下是Java预览器的效果参考引用本系列归档至《跟老卫学 HarmonyOS 开发》:https://github.com/waylau/harmonyos-tutorial参考书籍《鸿蒙 HarmonyOS 应用开发从入门到精通》 :https://item.jd.com/13696724.html
在前面的教程中,我们已经认识到了如何来创建一个最为简单的HelloWorld应用,并且通过Car模拟器成功运行了应用。效果如下: 但是使用模拟器运行了应用有一个缺点,那就是启动非常慢。如果我只是调试一个简单的界面,却要等待非常久的时间,那可能就消磨人的耐心了。此时,推荐的方式是使用预览器。 如何安装预览器 在使用预览器查看应用界面的UI效果前,需要确保HarmonyOS SDK > SDK Tools中,已下载Previewer资源。 如何使用预览器打开预览器有两种方式 l 通过菜单栏,点击View>Tool Windows>Previewer,打开预览器。l 在编辑窗口右上角的侧边工具栏,点击Previewer,打开预览器。 显示效果如下图所示。 参考引用本系列归档至《跟老卫学 HarmonyOS 开发》:https://github.com/waylau/harmonyos-tutorial参考书籍《鸿蒙 HarmonyOS 应用开发从入门到精通》 :https://item.jd.com/13696724.html
解决国内环境或者企业内网环境问题,往往访问Maven仓库比较困难,此时可以设置Gradle仓库镜像。1) 在用户目录新建一个 .gradle 文件夹,比如我的机器登录账户是 lwx48xxxx, 那么具体路径为 C:\Users\lwx48xxxx\.gradle;2) 在这个目录下新建一个 init.gradle 文件放入以下内容allprojects { buildscript { repositories { mavenLocal() maven { url 'http://mirrors.tools.huawei.com/maven/' } maven { // 内网JetBrains仓库,下载依赖的IDE和jdk等,用于编译和运行插件 url 'http://artifactory.cde.huawei.com/artifactory/jetbrains-public/' } maven {url "http://artifactory.cde.huawei.com/artifactory/gradle-plugins/"} maven {url "http://artifactory.cde.huawei.com/artifactory/maven-public/"} maven {url "http://artifactory.cde.huawei.com/artifactory/huawei-gradlebuild/"} } dependencies { // gradle-intellij-plugin用于构建JetBrains插件 // 请确保始终升级到最新版本 // http://artifactory.cde.huawei.com/artifactory/maven-public/org/jetbrains/intellij/org.jetbrains.intellij.gradle.plugin/ // classpath "gradle.plugin.org.jetbrains.intellij.plugins:gradle-intellij-plugin" } } repositories { mavenLocal() maven { url 'http://mirrors.tools.huawei.com/maven/' } maven { // 内网JetBrains仓库,下载依赖的IDE和jdk等,用于编译和运行插件 url 'http://artifactory.cde.huawei.com/artifactory/jetbrains-public/' } maven {url "http://artifactory.cde.huawei.com/artifactory/gradle-plugins/"} maven {url "http://artifactory.cde.huawei.com/artifactory/maven-public/"} maven {url "http://artifactory.cde.huawei.com/artifactory/huawei-gradlebuild/"} }}settingsEvaluated { settings -> settings.pluginManagement { plugins { } resolutionStrategy { } repositories { maven {url "http://artifactory.cde.huawei.com/artifactory/gradle-plugins/"} } }}复制参考引用本系列归档至《跟老卫学HarmonyOS开发》:https://github.com/waylau/harmonyos-tutorial参考书籍《鸿蒙HarmonyOS应用开发从入门到精通》 :https://item.jd.com/13696724.html
终于拿到《鸿蒙HarmonyOS应用开发从入门到精通》这本书。迫不及待与大家分享书中的内容。拆书视频也可见B站:https://www.bilibili.com/video/BV1Ja411Y7zE/外观正如之前帖子里面所介绍的(https://developer.huawei.com/consumer/cn/blog/topic/03856987626070027),外观是采用了全黑设计,富有科技感。背面是华为鸿蒙大佬们的推荐语。拆书接下来就是撕开封面,露出本体。内容提要封面勒口部分是作者柳伟卫简介。了解下整本书的大致内容。CPI数据可以看到是今年4月刚出版的。前言了解整本书的背景。这本书有开源本版,见 https://github.com/waylau/harmonyos-tutorial。了解书的大致结构。目录目录部分。正文内容图例丰富,案例新颖,技术前瞻。也有很多综合实战案例。
队列与栈类似,也是一种运算受限的线性表。队列则被限定在表尾进行插入、在表头进行删除,这种数据结构,实现了FIFO(First In First Out,先进先出)或者是LILO(Last In Last Out,后进后出)的方式工作。 下图很形象将队列比作是实现生活中的排队。排在队列前面的总是会最先得到处理,而排在队列后面的总是最后得到处理。 1. 队列的基本概念 在对队列有了基本的认识之后,我们再来看下队列的基本概念。 进行插入操作这一端被称为队尾(tail),相对地,把另一端称为队首(head)。向一个队列插入新元素又称作入队(enqueue)。它是把新元素放到队尾元素的之后,使之成为新的队尾元素。从一个队列中删除元素又称作出队(dequeue)。它是把队首元素删除掉,使其相邻的元素成为新的队首元素。 2. Java对于队列的支持 在Java中,提供了java.util.Queue<E>接口以支持队列。根据实现不同,队列又可以分为以下几种场景。 2.1. 是否阻塞 阻塞是指当队列空时,消费资源是否阻塞;当队列(有界队列)满时,插入数据是否阻塞。 Java提供了java.util.concurrent.BlockingQueue<E>接口以表示阻塞队列。常见的阻塞队列有: l ArrayBlockingQueue l LinkedBlockingQueue l DelayQueue l PriorityBlockingQueue l SynchronousQueue l LinkedBlockingDeque l 等等 2.2. 单端还是双端 单端还是双端,相比于单端队列,双端队列可以从队列的两头分别进行入队和出队操作。 Java提供了java.util.Deque<E>接口以表示双端队列。常见的双端队列有: l ArrayDeque l ConcurrentLinkedDeque l LinkedList l LinkedBlockingDeque l 等等 2.3. 是否有界 判断一个队列是否有界的依据是,队列在初始化时是否设置了容量。以ArrayBlockingQueue为例,ArrayBlockingQueue在初始化时,强制要求以容量作为构造函数的参数之一,这便是有界的。LinkedList是无界的,队列的长度会随着入队的元素的增多而不断增长。 LinkedBlockingQueue比较特殊,在初始化时可以指定容量也可以不指定容量。当初始化LinkedBlockingQueue指定容量时,是有界队列;当初始化LinkedBlockingQueue未指定容量时,其内部会以Integer.MAX_VALUE值作为容量。当然,因为Integer.MAX_VALUE值非常大,近似无限大,因此LinkedBlockingQueue未指定容量时也可以近似认为是无界队列。 在实际项目中,推荐优先选择使用有界队列。因为无界队列可能产生OOM(Out Of Memory,内存溢出)问题,OOM对系统是致命的。 2.4. 内部数据结构 队列的内部数据结构对使用者而言应该是透明的,使用者关注内部数据结构。主要是关注数据结构对业务运行的影响。队列的内部数据结构有数组、链表、堆等,不同数据结构会影响GC、CPU缓存,大长时间运行后,使用链表会产生大量的碎片,对GC造成很大的压力;内存连续的空间,也更有利于CPU缓存的发挥。 根据存储结构的不同,队列还分为以下几种: l 用顺序表实现的队列称为顺序队列 l 用链表实现的队列称为链队列 2.5. 是否无锁 加锁的开锁是巨大的,在高并发场景下,无锁的性能一般有锁的数倍。 2.6. 其他特殊功能 有很多队列都有特殊的功能,来满足特殊的场景。如SynchronousQueue没有缓存用于handoff场景(下文介绍);PriorityQueue、PriorityBlockingQueue支持按优先级入队;DelayedQueue支持延时出队;Disruptor除具有超强的性能外,还支持多消费者复杂的消费模式。 基于上面的场景,整理了下表最常用的10个队列的实现。 表1-1 最常用的10个队列的实现 队列 是否阻塞 单端还是双端 是否有界 内部数据结构 是否无锁 特殊功能 ArrayBlockingQueue 阻塞 单端 是 数组 有锁 NA LinkedBlockingQueue 阻塞 单端 是 链表 有锁 NA SynchronousQueue 阻塞 单端 否 单一元素 有锁 支持HandOff功能; LinkedTransferQueue 阻塞 单端 否 链表 有锁 功能为LinkedBlockingQueue与SynchronousQueue的超集; PriorityBlockingQueue 阻塞 单端 否 堆 有锁 支持优先级队列; DelayQueue 阻塞 单端 否 堆 有锁 支持延时出队; LinkedBlockingDeuqe 阻塞 双端 否 链表 有锁 NA ConcurrentLinkedQueue 非阻塞 单端 否 链表 有锁 NA ConcurrentLinkedDeque 非阻塞 双端 否 链表 有锁 NA Disruptor 阻塞 单端 是 环形数组 无锁 超强性能,支持多种生产者消费者模式; 参考引用 本系列归档至《Java数据结构及算法实战》:https://github.com/waylau/java-data-structures-and-algorithms-in-action《数据结构和算法基础(Java语言实现)》(柳伟卫著,北京大学出版社出版):https://item.jd.com/13014179.html
HarmonyOS初探03——DevEco Studio创建应用问题ERROR Unable to tunnel through proxy. Proxy returns HTTP1.1 403DevEco Studio发表于 2020-12-09 09:27:31659查看问题在内网环境下首次使用DevEco Studio创建应用时,可能会报如下问题:ERROR: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 403 Openproxy_Blocked_URL_list"我们已经是在DevEco Studio中设置了华为内网的网络proxy的。原因产生该问题的原因是,虽然设置了网络proxy,但该网络proxy并未包含Gradle的官方服务器。众所周知,DevEco Studio是采用Gradle来构建的。那么如何来解决?解决方案1:设置Gradle的网络proxy将Gradle的官方服务器纳入到网络proxy中来。方案2:设置Gradle内网服务器地址修改工程中gradle文件夹中的gradle-wrapper.properties文件中的distributionUrl的配置,将默认值https\://services.gradle.org/distributions/gradle-6.3-all.zip修改为其他内网地址,比如http://artifactory.cde.huawei.com/artifactory/gradle-distributions/gradle-6.5-all.zip控制台看到如下字样,说明Gradle已经能成功构建应用了。方案3:设置本地Gradle安装目录如果本地已经安装了Gradle,则可以直接使用本地的Gradle。参考引用本系列归档至《跟老卫学HarmonyOS开发》:https://github.com/waylau/harmonyos-tutorial参考书籍《鸿蒙HarmonyOS应用开发从入门到精通》(柳伟卫著,北京大学出版社出版):https://item.jd.com/13696724.html
虽然Java最新版本已经发展到Java 18了,但市面上大部分的项目还在使用Java 8。由于从Java 8之后,Java API不一定向前兼容,因此很多人都对升级Java版本心存顾虑。Java 11是Java 8的下一个长期支持版本,毫无疑问Java 11比Java 8更加优秀。本文介绍了将代码从 Java 8 转换到 Java 11用到的检查代码工具,还介绍了可能遇到的问题以及解决这些问题的建议。为什么需要Java 11Java 11是Java 8的下一个长期支持版本,这意味着Java 8不再受到官方支持。其外,从 Java 8 到Java 11,Java平台也发生了很大的更改,这些更改都是让Java平台更加优秀。本文重点介绍对性能、诊断和工作效率有影响的更改。模块模块解决在大型应用程序(在 classpath 上运行)中难以管理的配置和封装问题。 模块是 Java 类和接口以及相关资源的自述性集合。有了模块,即可自定义那些仅包含应用程序所需组件的运行时配置。 此自定义产生的内存占用量较小,因此可以使用 jlink 将应用程序静态链接到用于部署的自定义运行时中。 这个较小的内存占用量可能特别适用于微服务体系结构。在内部,JVM 可以通过让类加载更有效的方式利用模块。 结果就是,运行时更小、更轻便且启动速度更快。 JVM 用来改善应用程序性能的优化技术可以更有效,因为模块可以对某个类需要哪些组件进行编码。对程序员来说,模块可以要求显式声明一个模块可以导出哪些包以及它需要哪些组件,并且可以限制反射访问,因此有助于强制实施强封装。 这种级别的封装使应用程序更安全,维护起来更容易。应用程序可以继续使用 classpath,不需转换为作为必备组件的模块即可在 Java 11 上运行。Java 网络流量记录器Java Flight Recorder (JFR) 从正在运行的 Java 应用程序中收集诊断和分析数据。 JFR 对正在运行的 Java 应用程序几乎没有影响。 收集的数据随后可以使用 Java Mission Control (JMC) 和其他工具进行分析。 虽然 JFR 和 JMC 在 Java 8 中都是商业功能,但二者在 Java 11 中都是开放源代码。Java 任务控制java 任务控制 (JMC) 提供 java 网络流量记录器收集的数据的图形显示 (JFR) ,在 java 11 中是开放源代码。除了有关正在运行的应用程序的一般信息外,JMC 还允许用户向下钻取数据。 JFR 和 JMC 可以用来诊断运行时问题,例如内存泄露、GC 开销、热方法、线程瓶颈、阻塞 I/O。统一日志记录Java 11 有一个通用日志记录系统,适合 JVM 的所有组件。 用户可以使用此统一日志记录系统来定义哪些组件需要记录,以及记录到何种级别。 这种精细的日志记录适用于对 JVM 崩溃进行根本原因分析,以及在生产环境中诊断性能问题。低开销堆分析已经向 Java 虚拟机工具接口 (JVMTI) 添加了新的 API,用于对 Java 堆分配采样。 采样的开销低,可以持续启用。 虽然可以使用 Java Flight Recorder (JFR) 监视堆分配,但 JFR 中的采样方法只能用于分配。 JFR 实现也可能未命中分配。 与之形成对比的是,Java 11 中的堆采样可以同时提供活对象和死对象的相关信息。应用程序性能监视 (APM) 供应商开始利用此新功能,Java 工程组正在研究是否有可能将它与 Azure 性能监视工具配合使用。StackWalker进行日志记录时,通常会获取当前线程的堆栈的快照。 问题在于要记录多少堆栈跟踪,以及是否有必要记录堆栈跟踪。 例如,用户可能只想在某个方法出现特定异常时查看堆栈跟踪。 StackWalker 类(在 Java 9 中添加)提供堆栈的快照,并提供方便程序员对堆栈跟踪使用方式进行精细控制的方法。垃圾回收Java 11 提供以下垃圾回收器:Serial、Parallel、Garbage-First 和 Epsilon。 Java 11 中的默认垃圾回收器是 Garbage First 垃圾回收器 (G1GC)。此处提到其他三个回收器为了保持内容完整。 Z 垃圾回收器 (ZGC) 是一个并发、低延迟回收器,它会尝试将暂停时间保持在 10 毫秒以下。 ZGC 在 Java 11 中作为实验性功能提供。 Shenandoah 回收器是一个暂停时间短的回收器,它可以通过正在运行的 Java 程序以并发方式进行更多的垃圾回收,因此缩短了 GC 暂停时间。 Shenandoah 是 Java 12 中的一项实验性功能,但可以后向移植到 Java 11。 Concurrent Mark and Sweep (CMS) 回收器已发布,但自 Java 9 发布后已弃用。对于一般性使用,JVM 会将 GC 用作默认设置。 通常情况下,需根据应用程序的要求对这些设置和其他 GC 设置进行调整,以便优化吞吐量或延迟。 正确调整 GC 需要深入了解 GC,需要 Microsoft Java 工程组提供的专业知识。G1GCJava 11 中的默认垃圾回收器是 G1 垃圾回收器 (G1GC)。 G1GC 的目标是在延迟和吞吐量之间取得平衡。 G1 垃圾回收器尝试在大概率满足暂停时间目标的情况下实现高吞吐量目标。 G1GC 旨在避免整个集合,但当并发回收无法快速回收内存时,将发生回退完全 GC。 完全 GC 使用与初期混合性回收相同的并行工作线程数。并行 GC并行回收器是 Java 8 中的默认回收器。 并行 GC 是一个吞吐量回收器,使用多个线程来加速垃圾回收。EpsilonEpsilon 垃圾回收器负责处理分配,但不回收任何内存。 当堆耗尽时,JVM 会关闭。 Epsilon 适用于生存期短的服务和已知没有垃圾的应用程序。Docker 容器改进在 Java 10 之前,JVM 无法识别在容器上设置的内存和 CPU 约束。 例如,在 Java 8 中,JVM 会将最大堆大小默认设置为基础主机物理内存的四分之一。 从 Java 10 开始,JVM 会使用容器控制组 (cgroups) 设置的约束来设置内存和 CPU 限制(参见下面的说明)。 例如,默认的最大堆大小为容器的内存限制的四分之一(例如,如果内存限制为 2G,则最大堆大小为 500MB)。另外还添加了“JVM 选项”,使 Docker 容器用户可以精细地控制用于 Java 堆的系统内存量。此支持默认启用,仅在基于 Linux 的平台上提供。多版本 jar 文件在 Java 11 中,可以创建一个 jar 文件,其中包含多个特定于 Java 发布版的类文件版本。 有了多发布版 jar 文件,库开发人员就可以支持多个 Java 版本,不需交付多个版本的 jar 文件。 对于这些库的使用者来说,多发布版 jar 文件解决了必须将特定 jar 文件与特定运行时目标匹配的问题。其他性能改进对 JVM 进行以下更改会直接影响性能。JEP 197:分段代码缓存——将代码缓存分割成不同的段。 这种分段可以更好地控制 JVM 内存占用、缩短已编译方法的扫描时间、显著减轻代码缓存的碎片化,从而改进性能。JEP 254: Compact string——将字符串的内部表示形式从每个字符的两个字节更改为每个字符一个或两个字节,具体取决于字符编码。 由于大多数字符串包含 ISO-8859-1/拉丁语-1字符,此更改可以有效地将存储字符串所需的空间量减半。JEP 310:应用程序 Class-Data 共享-Class-Data共享通过允许在运行时进行内存映射来减少启动时间。 应用程序类-数据共享允许将应用程序类置于 CDS 存档中,从而扩展了类-数据共享。 当多个 JVM 共享同一存档文件时,可以节省内存并缩短总体的系统响应时间。JEP 312: Thread-Local 握手——使你能够在无需执行全局 VM safepoint 的情况下在线程上执行回调,这有助于 VM 减少全局 safepoints 的数量,从而实现较低的延迟。延迟分配编译器线程——在分层编译模式下,VM 将启动大量的编译器线程。 在有许多 CPU 的系统上,这是默认模式。 不管可用内存为多少,也不管编译请求有多少个,都会创建这些线程。 线程即使在空闲(几乎所有时间都是如此)的情况下也会耗用内存,这导致资源使用效率不高。 为了解决此问题,我们对实现进行了更改,在启动时每种类型只启动一个编译器线程。 系统会动态处理启动其他线程和关闭未使用线程的操作。对核心库进行以下更改会影响新代码或已修改代码的性能。JEP 193:变量句柄——定义一种标准方法,以调用对象字段和数组元素上的各种 util 和操作的等效操作,这是一组用于精确控制内存排序的标准围栏操作,也是一种标准的可访问性防护操作,以确保引用的对象保持可访问性。JEP 269:集合的便利工厂方法——定义库 api,使你可以轻松地创建包含少量元素的集合和映射的实例。 这是集合接口上的静态工厂方法,用于创建精简且不可修改的集合实例。 这些实例本质上更高效。 这些 API 创建的集合以简洁方式表示,没有包装器类。JEP 285: Spin-Wait 提示——提供 API,该 API 允许 Java 提示运行时系统处于自旋循环中。 某些硬件平台可以利用表明线程正处于“繁忙-等待”状态的软件指示。JEP 321: HTTP 客户端 (标准)——提供一个新的 http 客户端 API,该 API 可实现 Http/2 和 WebSocket,并可替换旧版 HttpURLConnection API。Java 8 转换到 Java 11可能的问题将代码从 Java 8 转换到 Java 11 时,并没有一种适用于所有情况的解决方案。 对于不重要的应用程序来说,从 Java 8 迁移到 Java 11 可能意味着很大的工作量。 潜在问题包括:删除的 API弃用的包内部 API 的使用对类加载程序的更改以及对垃圾回收的更改。通常,解决方法是尝试在不重新编译的情况下在 Java 11 上运行,或者先使用 JDK 11 进行编译。 如果目标是尽快启动并运行应用程序,则通常情况下,最佳方法是直接在 Java 11 上运行。 对于库,目标将是发布使用 JDK 11 编译和测试的项目。迁移到 Java 11 值得付出这样的努力。 自 Java 8 发布以来,已添加了多项新功能并对原有功能进行了强化。 这些功能和增强功能可改进启动、性能和内存使用情况,并提供与容器更好的集成。 此外还对 API 进行了添加和修改,这可以提高开发人员的工作效率。工具箱Java 11 有两个用于探查潜在问题的工具:jdeprscan 和 jdeps。 可以对现有类或 jar 文件运行这两个工具。 无需重新编译即可评估转换工作量。jdeprscan 可查看是否使用了已弃用或已删除的 API。 使用已弃用的 API 不是阻塞性问题,但值得探讨。 是否有更新的 jar 文件? 是否需要记录某个问题才能解决已弃用 API 的使用问题? 使用已删除的 API 是阻塞性问题,必须予以解决,然后才能尝试在 Java 11 上运行应用程序。jdeps,一个 Java 类依赖关系分析器。 与 --jdk-internals 选项一起使用时,jdeps 会告诉你哪个类依赖于哪个内部 API。 可以继续使用 Java 11 中的内部 API,但应优先考虑改变这种使用情况。 OpenJDK Wiki 页面 Java Dependency Analysis Tool(Java 依赖关系分析工具)推荐了某些常用 JDK 内部 API 的替换项。Gradle 和 Maven 都有 jdeps 和 jdeprscan 插件。 建议将以下工具添加到生成脚本中。工具Gradle 插件Maven 插件jdepsjdeps-gradle-pluginApache Maven JDeps 插件jdeprscanjdeprscan-gradle-pluginApache Maven JDeprScan 插件Java 编译器本身 javac 是工具箱中的另一个工具。 从 jdeprscan 和 jdeps 获取的警告和错误来自编译器。 使用 jdeprscan 和 jdeps 的优点是,可以在现有的 jar 和类文件(包括第三方库)上运行这两个工具。jdeprscan 和 jdeps 不能做的是对使用反射来访问封装的 API 进行警告。 反射访问在运行时进行检查。 最终必须在 Java 11 上运行代码才能确切地知道。使用 jdeprscan若要使用 jdeprscan,最简单的方法是为其提供一个来自现有生成的 jar 文件。 还可以为其指定目录(如编译器输出目录)或单个类名。 使用 --release 11 选项可获取已弃用 API 的最完整列表。 若要确定要采用的已弃用 API 的优先级,请将设置回退到 --release 8。 在 Java 8 中弃用的 API 的删除时间可能会早于最近弃用的 API。jdeprscan --release 11 my-application.jar如果无法解析依赖类,jdeprscan 工具会生成错误消息。 例如 error: cannot find class org/apache/logging/log4j/Logger。 建议将依赖类添加到 --class-path 或使用应用程序 class-path,但该工具会在没有它的情况下继续扫描。 参数是 -类路径。 class-path 参数的其他变体将不起作用。jdeprscan --release 11 --class-path log4j-api-2.13.0.jar my-application.jar error: cannot find class sun/misc/BASE64Encoder class com/company/Util uses deprecated method java/lang/Double::<init>(D)V此输出告诉我们,com.company.Util 类在调用 java.lang.Double 类的已弃用构造函数。 javadoc 会建议用来代替已弃用 API 的 API。 无论如何都无法解决“error: cannot find class sun/misc/BASE64Encoder”问题,因为它是已删除的 API。 自 Java 8 发布以来,应使用 java.util.Base64。运行 jdeprscan --release 11 --list 即可了解自 Java 8 后弃用的具体 API。 若要获取已删除 API 的列表,请运行 jdeprscan --release 11 --list --for-removal。使用 jdeps可以使用 jdeps 通过 --jdk-internals 选项来查找 JDK 内部 API 上的依赖项。 此示例需要 --multi-release 11 命令行选项,因为 log4j-core-2.13.0.jar 是多版本 jar 文件。 没有此选项,jdeps 会在找到多版本 jar 文件的情况下发出错误消息。 此选项指定要检查的类文件的版本。jdeps --jdk-internals --multi-release 11 --class-path log4j-core-2.13.0.jar my-application.jar Util.class -> JDK removed internal API Util.class -> jdk.base Util.class -> jdk.unsupported com.company.Util -> sun.misc.BASE64Encoder JDK internal API (JDK removed internal API) com.company.Util -> sun.misc.Unsafe JDK internal API (jdk.unsupported) com.company.Util -> sun.nio.ch.Util JDK internal API (java.base) Warning: JDK internal APIs are unsupported and private to JDK implementation that are subject to be removed or changed incompatibly and could break your application. Please modify your code to eliminate dependence on any JDK internal APIs. For the most recent update on JDK internal API replacements, please check: https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool JDK Internal API Suggested Replacement ---------------- --------------------- sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8 sun.misc.Unsafe See http://openjdk.java.net/jeps/260 输出提供了一些关于避免使用 JDK 内部 API 的好建议! 如果可能,建议使用替换 API。 在括号中提供封装了包的模块的名称。 如果需要显式中断封装,则可将模块名称与 --add-exports 或 --add-opens 配合使用。使用 sun.misc.BASE64Encoder 或 sun.misc.BASE64Decoder 会导致 Java 11 中出现 java.lang.NoClassDefFoundError。 使用这些 API 的代码必须经过修改才能使用 java.util.Base64。尝试不使用来自 jdk.unsupported 模块的任何 API。 此模块中的 API 将引用 JDK 增强方案 (JEP) 260 作为建议的替换方案。 简而言之,JEP 260 指出,在替换 API 可用之前,会一直支持使用内部 API。 虽然你的代码使用的是 JDK 内部 API,但至少在一段时间内它是可以正常运行的。 请看看 JEP 260,因为它指出了某些内部 API 的替换项。 例如,可以使用变量句柄来代替某个 sun.misc.Unsafe API。除了扫描 JDK 内部 API 的使用情况,jdeps 还可以执行其他操作。 它是一项有用的工具,可以用来分析依赖关系和生成模块信息文件。 有关详细信息,请参阅文档。使用 javac如果使用 JDK 11 进行编译,则需要更新才能生成脚本、工具、测试框架和包含的库。 使用 javac 的 -Xlint:unchecked 选项可获取 JDK 内部 API 的使用详情和其他警告。 可能还需要使用 --add-opens 或 --add-reads 向编译器公开封装的包(请参阅 JEP 261)。库可以考虑以多版本 jar 文件形式打包。 多版本 jar 文件允许同时支持同一 jar 文件中的 Java 8 和 Java 11 运行时。 它们增加了生成的复杂性。 如何生成多版本 jar 超出了本文档的讨论范围。在 Java 11 上运行大多数应用程序在不修改的情况下应该可以在 Java 11 上运行。 首先要尝试的是在不重新编译代码的情况下在 Java 11 上运行。 直接运行的目的是查看执行时会出现哪些警告和错误。 此方法可以让应用程序在 Java 11 上更快地运行,因为可以尽量减少那些必须完成的关注事项。你可能会遇到的大多数问题都可以得到解决,无需重新编译代码。 如果需要在代码中修复问题,请进行修复,但继续使用 JDK 8 进行编译。 如果可能,请在使用 JDK 11 进行编译之前,让应用程序使用 java 版本 11 运行。检查命令行选项在 Java 11 上运行之前,请对命令行选项进行快速扫描。 已删除的选项会导致 Java 虚拟机 (JVM) 退出。 如果使用 GC 日志记录选项,则此检查尤其重要,因为它们已明显不同于 Java 8 中的情况。 JaCoLine 工具是一项很好的工具,用于检查命令行选项的问题。检查第三方库你不能控制的第三方库是潜在的问题来源。 可以主动将第三方库更新到较新的版本。 也可查看运行应用程序时哪些库未使用,仅更新那些必需的库。 将所有库更新到最新版本的问题在于,如果应用程序中存在错误,则更难找到根本原因。 发生此错误是因为更新了某个库吗? 或者,此错误是由运行时中的某些更改引起的吗? 仅更新所需内容的问题在于,可能需要多次迭代才能解决问题。此处的建议是尽可能少做更改,将第三方库单独进行更新。 如果更新第三方库,则往往需要与 Java 11 兼容的最新且最好的版本。 根据当前版本的落后程度,你可能需要采取更谨慎的方法,升级到第一个与 Java 9+ 兼容的版本。除了查看发行说明以外,还可以使用 jdeps 和 jdeprscan 来评估 jar 文件。 此外,OpenJDK 质量组还维护一个质量外展 Wiki 页面,其中列出了根据 OpenJDK 版本对多个免费开源软件 (FOSS) 项目进行的测试的状态。显式设置垃圾回收并行垃圾回收器(并行 GC)是 Java 8 中的默认 GC。 如果应用程序使用默认值,则应使用命令行选项 -XX:+UseParallelGC 显式设置 GC。 Java 9 中的默认值已更改为 Garbage First 垃圾回收器 (G1GC)。 若要对 Java 8 与 Java 11 上运行的应用程序进行公平比较,GC 设置必须相同。 应推迟使用 GC 设置进行的试验,直到应用程序在 Java 11 上经过验证。显式设置默认选项如果在作用点 VM 上运行,则设置命令行选项 -XX:+PrintCommandLineFlags 会转储由 VM 设置的选项的值,特别是由 GC 设置的默认值。 在 Java 8 上使用此标志运行,在 Java 11 上运行时使用输出的选项。 大多数情况下,Java 8 到 11 中的默认值是相同的。 但是,使用 Java 8 中的设置可确保奇偶校验。建议设置命令行选项 --illegal-access=warn。 在 Java 11 中,使用反射访问 JDK 内部 API 会生成一个“非法的反射访问”警告。 默认情况下,系统仅对第一次非法访问发出警告。 设置 --illegal-access=warn 会导致系统对每一次非法反射访问发出警告。 如果将选项设置为 warn,则会发现更多非法访问案例。 但是,你也会收到大量冗余警告。在 Java 11 上运行应用程序后,设置 --illegal-access=deny 即可模拟 Java 运行时的未来行为。 从 Java 16 开始,默认设置将为 --illegal-access=deny。ClassLoader 注意事项在 Java 8 中,可以将系统类加载程序强制转换为 URLClassLoader。 这通常由需要在运行时将类注入到 classpath 的应用程序和库完成。 类加载程序层次结构在 Java 11 中已更改。 系统类加载程序(也称为应用程序类加载程序)现在是一个内部类。 强制转换为 URLClassLoader 会在运行时引发 ClassCastException。 Java 11 无法通过 API 在运行时动态增强 classpath,但可以通过反射来实现这一点,它会显示有关如何使用内部 API 的显著警告。在 Java 11 中,启动类加载程序只加载核心模块。 如果创建一个具有 null 父项的类加载程序,则它可能找不到全部平台类。 在 Java 11 中,需要在此类情况下传递 ClassLoader.getPlatformClassLoader() 而不是 null 作为父类加载程序。区域设置数据更改Java 11 中区域设置数据的默认源已通过 JEP 252 更改为 Unicode 联合会的公共区域设置数据存储库。 这可能会影响本地化的格式设置。 如有必要,请将系统属性设置为 java.locale.providers=COMPAT,SPI,还原到 Java 8 区域设置行为。潜在问题下面是可能会遇到的一些常见问题。无法识别的 VM 选项无法识别的选项VM 警告:忽略选项VM 警告:选项 已弃用警告:发生非法的反射访问操作java.lang.reflect.InaccessibleObjectExceptionjava.lang.NoClassDefFoundError-Xbootclasspath/p 不再是受支持的选项java.lang.UnsupportedClassVersionError无法识别的选项如果删除了某个命令行选项,则应用程序会输出 Unrecognized option: 或 Unrecognized VM option,后跟有问题的选项的名称。 无法识别的选项会导致 VM 退出。 已弃用但未删除的选项会生成 VM 警告。通常情况下,已删除的选项没有替换项,唯一办法是从命令行中删除该选项。 垃圾回收日志记录的选项是一个例外。 GC 日志记录已在 Java 9 中重新实现,可以使用统一 JVM 日志记录框架。 请参阅 Java SE 11 工具参考的允许通过 JVM 统一日志记录框架进行日志记录部分中的“表2-2 将旧的垃圾回收日志记录标志映射到 Xlog 配置”。VM 警告使用弃用的选项会生成警告。 当某个选项被替换或不再有用时,即表明它已被弃用。 与使用删除的选项一样,应从命令行中删除这些选项。 “VM Warning: Option was deprecated”警告意味着,该选项仍受支持,但以后可能会取消该支持。 不再受支持的选项会生成“VM Warning: Ignoring option”警告。 不再受支持的选项不影响运行时。Web 页面 VM 选项资源管理器提供了自 JDK 7 以后在 Java 中添加或删除的选项的详尽列表。错误:无法创建 Java 虚拟机当 JVM 遇到无法识别的选项时,会输出此错误消息。警告:发生非法的反射访问操作当 Java 代码使用反射访问 JDK 内部 API 时,运行时会发出“非法的反射访问”警告。WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by my.sample.Main (file:/C:/sample/) to method sun.nio.ch.Util.getTemporaryDirectBuffer(int) WARNING: Please consider reporting this to the maintainers of com.company.Main WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release这意味着,模块未导出通过反射访问的包。 此包在模块中封装,本质上是内部 API。 在 Java 11 上启动并运行应用程序时,第一项操作可能就是忽略此警告。 Java 11 运行时允许反射访问,因此旧代码可以继续运行。若要解决此警告,请查找不使用内部 API 的已更新代码。 如果无法使用更新的代码解决该问题,则可使用 --add-exports 或 --add-opens 命令行选项来启用对包的访问权限。 这些选项允许从一个模块访问另一个模块的未导出类型。--add-exports选项允许目标模块访问源模块的命名包的公共类型。 有时,代码会使用 setAccessible(true) 访问非公共成员和 API。 这称为深度反射。 在这种情况下,请使用 --add-opens,允许代码访问包的非公共成员。 如果不确定是使用 --add-exports 还是 --add-opens,请从 --add-exports 着手。应将 --add-exports 或 --add-opens 选项视为一种权宜解决方案,而不是长期解决方案。 使用这些选项会打破模块系统的封装,该封装是为了防止 JDK 内部 API 被使用。 如果删除或更改内部 API,应用程序会发生故障。 Java 16 会拒绝反射访问,但通过命令行选项(如 --add-opens)启用访问的情况除外。 若要模拟未来行为,请在命令行中设置 --illegal-access=deny。发出上述示例中的警告是因为 sun.nio.ch 包不是由 java.base 模块导出的。 换言之,模块 java.base 的 module-info.java 文件中没有 exports sun.nio.ch;。 这可以通过 --add-exports=java.base/sun.nio.ch=ALL-UNNAMED 来解决。 未在模块中定义的类隐式属于未命名模块,在字面上命名为 ALL-UNNAMED。java.lang.reflect.InaccessibleObjectException此异常指示你尝试在已封装类的字段或方法上调用 setAccessible(true)。 也可能会收到一个“非法的反射访问”警告。 使用 --add-opens 选项可以让代码访问包的非公共成员。 异常消息会告知你,模块未将包打开到试图调用 setAccessible 的模块。 如果模块是“未命名模块”,请使用 UNNAMED-MODULE 作为 --add-opens 选项中的目标模块。java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.ArrayList jdk.internal.loader.URLClassPath.loaders accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @6442b0a6 $ java --add-opens=java.base/jdk.internal.loader=UNNAMED-MODULE example.Mainjava.lang.NoClassDefFoundErrorNoClassDefFoundError 最有可能是由拆分包或引用删除的模块导致的。拆分包导致的 NoClassDefFoundError如果在多个库中找到某个包,则该包为拆分包。 拆分包问题的症状是,你知道某个类会在 class-path 上,但找不到该类。使用 module-path 时才会出现此问题。 Java 模块系统通过将包限制为一个命名的模块来优化类查找。 执行类查找时,运行时会优先处理 module-path 而不是 class-path。 如果包在某个模块和 class-path 之间拆分,则只使用该模块来执行类查找。 这可能导致 NoClassDefFound 错误。若要检查拆分包,一个简单的方法是将模块路径和类路径插入 jdeps,使用应用程序类文件的路径作为 。 如果有拆分包,jdeps 会输出警告:Warning: split package: <package-name> <module-path> <split-path>。可以通过使用 --patch-module <module-name>=<path>[,<path>] 将拆分包添加到命名模块中来解决此问题。使用 Java EE 或 CORBA 模块导致的 NoClassDefFoundError如果应用程序在 Java 8 上运行但却引发 java.lang.NoClassDefFoundError 或 java.lang.ClassNotFoundException,则可能是应用程序在使用 Java EE 或 CORBA 模块中的包。 这些模块在 Java 9 弃用,在 Java 11 中删除。若要解决此问题,请向项目添加运行时依赖项。删除的模块受影响的包建议的依赖项Java API for XML Web Services (JAX-WS)java.xml.wsJAX WS RI 运行时用于 XML 绑定的 Java 体系结构 (JAXB)java.xml.bindJAXB 运行时JavaBeans Activation Framework (JAV)java.activationJavaBeans (TM) Activation Framework常见批注java.xml.ws.annotationJavax 批注 API通用对象请求代理体系结构 (CORBA)java.corbaGlassFish CORBA ORBJava 事务 API (JTA)java.transactionJava 事务 API-Xbootclasspath/p 不再是受支持的选项已删除对 -Xbootclasspath/p 的支持。 请改用 --patch-module。 --patch-module 选项在 JEP 261 中介绍。 查找标为“修补模块内容”的部分。 可以将 --patch-module 与 javac 和 java 配合使用,以便重写或增强模块中的类。实际上,--patch-module 执行的操作是将修补模块插入模块系统的类查找。 模块系统会首先从修补模块获取类。 这与在 Java 8 中预挂起 bootclasspath 的效果相同。UnsupportedClassVersionError此异常表示你尝试在较低版本的 Java 上运行使用较高版本的 Java 编译的代码。 例如,在 Java 11 上运行其 jar 是使用 JDK 13 编译的程序。Java 版本类文件格式版本8529531054115512561357后续步骤在 Java 11 上运行应用程序后,请考虑将库移出 class-path,然后再将其移入 module-path。 查找应用程序所依赖的库的已更新版本。 选择模块库(如果可用)。 尽可能使用 module-path,即使不打算在应用程序中使用模块。 与 class-path 相比,使用module-path 可以获得更好的类加载性能。参考引用原文同步至:https://waylau.com/update-from-java-8-to-java-11https://docs.microsoft.com/zh-cn/java/openjdk/transition-from-java-8-to-java-11更多示例可见《现代Java案例大全》:https://github.com/waylau/modern-java-demos柳伟卫《Java核心编程》:https://search.jd.com/Search?keyword=%E6%9F%B3%E4%BC%9F%E5%8D%AB%20Java%E6%A0%B8%E5%BF%83%E7%BC%96%E7%A8%8B&enc=utf-8&wq=%E6%9F%B3%E4%BC%9F%E5%8D%AB%20Java%E6%A0%B8%E5%BF%83%E7%BC%96%E7%A8%8B&pvid=3f8660921bef4700931a735f536eebfb
《鸿蒙HarmonyOS应用开发从入门到精通》一书由北京大学出版社出版,已经于2022年4月上市。本文希望与读者朋友们分享下这本书里面的大致内容。封面部分首先是介绍封面部分。《鸿蒙HarmonyOS应用开发从入门到精通战》封面部分是采用了全黑设计,富有科技感。中部是个漩涡图样或者是个鸟巢或者是个火花(我猜的。。。),总之寓意着活力或者张力吧。上书青色“鸿蒙”两字,意味着“青涩”?不管怎么样,这个配色还是具有非常高的辨识度的。只是下面的英文“HarmonyOS”中的“r”处理的过于夸张了,反而不容易被人识别出是哪个字母。可以看到,右下角是出版社“北京大学出版社”字样。整体来说,这个封面中规中矩,设计走的一贯的黑色风格。青、黑、白三色搭配还是比好看。漩涡图样建议改为下面的“设备环”可能更加符合鸿蒙“万物互联”之意。封底部分介绍封底部分。封底部分可以看到是两位重量级华为大咖背书,而且都是鸿蒙团队核心人员。这本书归类为计算机/HarmonyOS。全书600多页,定价为119元,也算良心了。极具性价比。内容简介华为自主研发的HarmonyOS(鸿蒙系统)是一款“面向未来”、面向全场景(移动办公、运动健康、社交通信、媒体娱乐等)的分布式操作系统。借助HarmonyOS全场景分布式系统和设备生态,定义全新的硬件、交互和服务体验。本书采用最新的HarmonyOS 2.0版本作为基石,详细介绍了如何基于HarmonyOS来进行应用的开发,包括HarmonyOS架构、DevEco Studio、应用结构、Ability、任务调度、公共事件、通知、剪切板、Java UI、JS UI、多模输入、线程管理、视频、图像、相机、音频、媒体会话管理、媒体数据管理、安全管理、二维码、通用文字识别、蓝牙、WLAN、网络管理、电话服务、设备管理、数据管理等多个主题。本书辅以大量的实战案例,图文并茂,令读者易于理解掌握。同时,案例的选型偏重于解决实际问题,具有很强的前瞻性、应用性、趣味性。加入HarmonyOS生态,让我们一起构建万物互联的新时代!写作背景中国信息产业一直是“缺芯少魂”,其中的“芯”指的是芯片,而“魂”则是指操作系统。而自2019年5月15日起,美国陆续把包括华为在内中国高科技企业列入其所谓的“实体清单”(Entities List),标志着科技再次成为中美博弈的核心领域。随着谷歌暂停与华为的部分合作,包括软件和技术服务的转让。华为在国外市场已经面临着升级Android版本、搭载谷歌服务等方面遇到困境。在这样的背景下,华为顺势推出HarmonyOS,以求在操作系统领域不被受制于人。HarmonyOS是一款“面向未来”、面向全场景(移动办公、运动健康、社交通信、媒体娱乐等)的全新的分布式操作系统。作为操作系统领域的新成员,HarmonyOS势必会面临着bug多、学习资源缺乏等众多困难。为此,笔者在开源社区,以开源方式推出了免费系列学习教程《跟老卫学HarmonyOS开发》(https://github.com/waylau/harmonyos-tutorial),以帮助HarmonyOS爱好者入门。同时,为了让更多的人了解并使用HarmonyOS,笔者将自身工作、学习中遇到的问题、难题进行了总结,形成了本书,以补市场空白。内容介绍全书大致分为了3部分:入门(1-4章):介绍HarmonyOS的背景、开发环境搭建,并创建一个简单的HarmonyOS应用。进阶(5-27章):介绍HarmonyOS的核心功能的开发,内容包括Ability、UI开发、线程管理、视频、图像、相机、音频、媒体会话管理、媒体数据管理、安全管理、二维码、通用文字识别、蓝牙、WLAN、网络管理、电话服务、设备管理、数据管理等。实战(28-31章):演示HarmonyOS在各类场景下的综合实战案例,包括车机、智能穿戴、智慧屏、手机等应用。本书主要面向的是对HarmonyOS应用开发感兴趣的学生、开发人员、架构师。本书特点1.内容全面,技术新颖本书几乎囊括了HarmonyOS所涉及的知识点包括Ability、UI开发、线程管理、视频、图像、相机、音频、媒体会话管理、媒体数据管理、安全管理、二维码、通用文字识别、蓝牙、WLAN、网络管理、电话服务、设备管理、数据管理等方面的内容,并提供了针对各类场景下的综合实战案例,包括车机、智能穿戴、智慧屏、手机等应用。技术前瞻,案例丰富。不管是编程初学者,还是编程高手,都能从本书中获益。本书可作为读者案头的工具书,随手翻阅。2.图文并茂,代码精彩基于最新HarmonyOS 2技术展开,手把手传授从入门到精通的诀窍!在线提供的源代码紧跟版本迭代,目前已经更新到HarmonyOS 3版本。不用担心知识点过时哦。3.案例丰富,实战性强本书提供了丰富的基于HarmonyOS技术点的实例75个,将理论讲解最终落实到代码实现上来。在掌握了基础之后,另外提供了4个综合性实战案例。这些案例从零开始,最终实现了一个完整的企业级应用,内容具有很高的应用价值和参考性。4.附赠资源本书提供了书中涉及的所有实例的源文件。读者可以一边阅读本书,一边参照源文件动手练习,这样不仅提高了学习的效率,而且可以对书中的内容有更加直观的认识,从而逐渐培养自己的编程能力。源代码本书提供的素材和源代码可从以下网址下载: https://github.com/waylau/harmonyos-tutorial勘误和交流本书如有勘误,会在以下网址发布: https://github.com/waylau/harmonyos-tutorial/issues参考引用原文同步至:https://waylau.com/about-harmonyos-application-development-from-zero-to-hero-book京东:https://item.jd.com/13696724.html当当:http://product.dangdang.com/29386650.html
很多语言都有类似于“虚拟线程”的技术,比如Go、C#、Erlang、Lua等,他们称之为“协程”。 不管是虚拟线程还是协程,他们都是轻量级线程,其目的都是为了提高并发能力。 本节详细介绍Java平台的“虚拟线程”的技术——“JEP 425: Virtual Threads (Preview)”。Java平台计划引入虚拟线程,可显著减少编写、维护和观察高吞吐量并发应用程序的工作量。“JEP 425: Virtual Threads (Preview)”目是一个预览性的API。目标使以简单的线程每请求风格编写的服务器应用程序能够以近乎最佳的硬件利用率进行扩展。启用使用java.lang.Thread API的现有代码,以最小的更改采用虚拟线程。使用现有的JDK工具,轻松地对虚拟线程进行故障排除、调试和分析。非目标目标不是删除线程的传统实现,也不是静默迁移现有应用程序以使用虚拟线程。改变Java的基本并发模型并不是目标。在Java语言或Java库中提供新的数据并行结构并不是目标。Stream API仍然是并行处理大型数据集的首选方式。动机近30年来,Java开发人员一直依赖线程作为并发服务器应用程序的构建块。每个方法中的每个语句都在线程内执行,由于Java是多线程的,多个执行线程同时发生。线程是Java的并发单元:一段顺序代码,与其他此类单元同时运行,而且在很大程度上独立于其他此类单元。每个线程都提供一个堆栈来存储局部变量和协调方法调用,以及出错时的上下文:异常被同一线程中的方法抛出和捕获,因此开发人员可以使用线程的堆栈跟踪来查找发生了什么。线程也是工具的核心概念:调试器逐步浏览线程方法中的语句,分析器可视化多个线程的行为,以帮助了解它们的性能。线程每请求样式服务器应用程序通常处理相互独立的并发用户请求,因此应用程序通过在请求的整个持续时间内将线程专用于该请求来处理请求是有意义的。这种线程每请求风格易于理解、易于编程、易于调试和分析,因为它使用平台的并发单位来表示应用程序的并发单位。服务器应用程序的可扩展性遵循利特尔定律(Little's Law),它与延迟、并发和吞吐量有关:对于给定的请求处理持续时间(即延迟),应用程序同时处理的请求数(即,并发)必须与到达速率(即吞吐量)成比例增长。例如,假设平均延迟为50ms的应用程序通过同时处理10个请求,实现每秒200个请求的吞吐量。为了使该应用程序扩展到每秒2000个请求的吞吐量,它需要同时处理100个请求。如果每个请求在请求的持续时间内都在线程中处理,那么,要使应用程序跟上,线程数量必须随着吞吐量的增长而增长。不幸的是,可用线程的数量是有限的,因为JDK将线程作为操作系统(OS)线程的包装器实现。操作系统线程成本高昂,因此我们不能拥有太多线程,这使得实现不适合线程每请求风格。如果每个请求在其持续时间内消耗一个线程,从而消耗一个操作系统线程,那么线程数量通常在其他资源(如CPU或网络连接)耗尽之前很久就成为限制因素。JDK当前的线程实现将应用程序的吞吐量限制在远低于硬件所能支持的水平。即使线程池化,也会发生这种情况,因为池化有助于避免启动新线程的高成本,但不会增加线程总数。使用异步风格提高可扩展性一些希望充分利用硬件的开发人员放弃了每请求线程风格,转到线程共享风格。请求处理代码不是从头到尾处理一个线程上的请求,而是在等待I/O操作完成时将其线程返回到池,以便线程可以为其他请求提供服务。这种细粒度的线程共享--在这种共享中,代码仅在线程执行计算时保留线程,而不是在等待I/O时保留线程--允许大量并发操作,而不会消耗大量线程。虽然它消除了操作系统线程稀缺性对吞吐量的限制,但它的代价很高:它需要所谓的异步编程风格,使用一组单独的I/O方法,这些方法不等待I/O操作完成,而是,稍后,向回调表示它们的完成。如果没有专用线程,开发人员必须将其请求处理逻辑分解为小阶段,通常编写为lambda表达式,然后使用API将它们组合成顺序管道(请参见CompletableFuture,或所谓的“反应性”(reactive)框架。因此,它们放弃了语言的基本顺序组成运算符,如循环和try/catch块。在异步风格中,请求的每个阶段都可能在不同的线程上执行,每个线程以交错的方式运行属于不同请求的阶段。这对理解程序行为具有深刻的影响:堆栈跟踪不提供可用的上下文,调试器无法逐步完成请求处理逻辑,分析器无法将操作的成本与其调用者关联起来。在使用Java时,编写lambda表达式是可以管理的Stream API在短管道中处理数据,但当应用程序中的所有请求处理代码都必须以这种方式编写时,就会有问题。这种编程风格与Java平台不一致,因为应用程序的并发单位--异步管道--不再是平台的并发单位。使用虚拟线程保留线程每请求样式为了使应用程序能够扩展,同时与平台保持和谐,我们应该通过更有效地实现线程来努力保留每个请求的线程风格,这样它们就可以更丰富。操作系统无法更有效地实现操作系统线程,因为不同的语言和运行时以不同的方式使用线程堆栈。但是,Java运行时可以以一种将Java线程与操作系统线程的一对一对应关系分开的方式实现Java线程。就像操作系统通过将大量虚拟地址空间映射到有限数量的物理RAM来给人丰富内存的错觉一样,Java运行时也可以通过将大量虚拟线程映射到少量操作系统线程来给人丰富线程的错觉。虚拟线程是java.lang.Thread的一个实例,它不绑定到特定的操作系统线程。相比之下,平台线程是java.lang.Thread的实例,以传统方式实现,作为操作系统线程周围的精简包装器。线程每请求样式中的应用程序代码可以在虚拟线程中运行整个请求持续时间,但虚拟线程仅在CPU上执行计算时消耗操作系统线程。结果是与异步风格相同的可扩展性,只是它是透明的:当在虚拟线程中运行的代码调用java中的阻塞I/O操作的java.* API时,运行时执行非阻塞操作系统调用,并自动挂起虚拟线程,直到稍后可以恢复。对于Java开发人员来说,虚拟线程只是创建起来很便宜,而且几乎无限丰富的线程。硬件利用率接近最佳,允许高水平的并发性,从而实现高吞吐量,同时应用程序与Java平台及其工具的多线程设计保持和谐。虚拟线程的含义虚拟线程既便宜又丰富,因此永远不应该池化:应该为每个应用程序任务创建一个新的虚拟线程。因此,大多数虚拟线程都是短暂的,并且具有浅调用堆栈,执行的时间只需单个HTTP客户端调用或单个JDBC查询。相比之下,平台线程是重量级和昂贵的,因此通常必须池化。它们往往是长寿命的,具有深度的调用堆栈,并在许多任务中共享。总之,虚拟线程保留了可靠的每请求线程风格,该风格与Java平台的设计和谐,同时优化地利用硬件。使用虚拟线程不需要学习新的概念,尽管它可能需要养成不学习的习惯,以应对当今线程的高成本。虚拟线程不仅将帮助应用程序开发人员,还将帮助框架设计人员提供易于使用的API,这些API与平台的设计兼容,而不影响可扩展性。描述今天,每一个例子 java.lang.Thread在JDK中,是一个平台线程。平台线程在底层操作系统线程上运行Java代码,并在代码的整个生命周期内捕获操作系统线程。平台线程数限制为操作系统线程数。虚拟线程是java.lang.Thread的一个实例,它在基础操作系统线程上运行Java代码,但在代码的整个生命周期内不捕获操作系统线程。这意味着许多虚拟线程可以在同一操作系统线程上运行其Java代码,有效地共享它。虽然平台线程垄断了宝贵的操作系统线程,但虚拟线程却不垄断。虚拟线程的数量可以远大于操作系统线程的数量。虚拟线程是由JDK而不是操作系统提供的线程的轻量级实现。它们是用户模式线程的一种形式,在其他多线程语言中已经成功(例如,Go中的goroutine和Erlang中的进程)。用户态线程甚至被称为“green threads”在Java的早期版本中,当时操作系统线程还不成熟和广泛。然而,Java的green threads都共享一个操作系统线程(M:1调度),平台线程被实现为操作系统线程的包装器(1:1调度)。虚拟线程采用M:N调度,其中大量(M)虚拟线程被调度在较少数量(N)的操作系统线程上运行。使用虚拟线程vs平台线程开发者可以选择使用虚拟线程还是平台线程。这里是一个创建大量虚拟线程的示例程序。程序首先获取一个ExecutorService这将为每个提交的任务创建一个新的虚拟线程。然后,它提交10,000个任务,并等待所有任务完成:try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000).forEach(i -> { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); } // executor.close() is called implicitly, and waits本例中的任务是简单的代码--睡眠一秒钟--现代硬件可以轻松支持10000个虚拟线程同时运行此类代码。在幕后,JDK在少量的操作系统线程上运行代码,也许只有一个线程。如果此程序使用为每个任务创建新的平台线程的ExecutorService,如Executor.newCachedThreadPool(),情况就会大不相同。ExecutorService将尝试创建10000个平台线程,从而创建10000个操作系统线程,并且该程序将在大多数操作系统上崩溃。相反,如果程序使用从池获取平台线程的ExecutorService,如Executor.newFixedThreadPool(200),情况也不会好太多。ExecutorService将创建200个平台线程,供所有10000个任务共享,因此许多任务将顺序运行,而不是并发运行,程序将需要很长时间才能完成。对于此程序,具有200个平台线程的池只能实现每秒200个任务的吞吐量,而虚拟线程的吞吐量则约为每秒10000个任务(在充分预热后)。此外,如果示例程序中的10_000更改为1_000_000,则程序将提交100万个任务,创建100万个同时运行的虚拟线程,并(在充分预热后)实现每秒约100万个任务的吞吐量。如果此程序中的任务执行了一秒钟的计算(例如,排序一个巨大的数组),而不仅仅是睡眠,那么将线程数量增加到处理器核心数量之外都没有帮助,不管它们是虚拟线程还是平台线程。虚拟线程不是更快的线程-它们运行代码的速度并不比平台线程快。它们的存在是为了提供规模(更高的吞吐量),而不是速度(更低的延迟)。它们可能比平台线程多得多,因此根据利特尔定律,它们实现了更高吞吐量所需的更高并发。换一种方式说,虚拟线程可以显著提高应用程序吞吐量,当并发任务数量较高(超过几千个),并且工作负载不绑定CPU,因为在这种情况下,线程数比处理器内核多得多不能提高吞吐量。虚拟线程有助于提高典型服务器应用程序的吞吐量,正是因为此类应用程序由大量并发任务组成,这些任务花费了大量时间等待。虚拟线程可以运行平台线程可以运行的任何代码。特别是,虚拟线程支持线程局部变量和线程中断,就像平台线程一样。这意味着处理请求的现有Java代码将很容易在虚拟线程中运行。许多服务器框架将选择自动执行此操作,为每个传入请求启动一个新的虚拟线程,并在其中运行应用程序的业务逻辑。以下是一个服务器应用程序的示例,它聚合了其他两个服务的结果。假设的服务器框架(未显示)为每个请求创建一个新的虚拟线程,并在该虚拟线程中运行应用程序的句柄代码。应用程序代码反过来创建两个新的虚拟线程,通过与第一个示例相同的ExecutorService并发获取资源:void handle(Request request, Response response) { var url1 = ... var url2 = ... try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { var future1 = executor.submit(() -> fetchURL(url1)); var future2 = executor.submit(() -> fetchURL(url2)); response.send(future1.get() + future2.get()); } catch (ExecutionException | InterruptedException e) { response.fail(e); } } String fetchURL(URL url) throws IOException { try (var in = url.openStream()) { return new String(in.readAllBytes(), StandardCharsets.UTF_8); } }像这样的服务器应用程序,具有简单的阻塞代码,可以很好地扩展,因为它可以使用大量的虚拟线程。Executor.newVirtualThreadPerTaskExecutor()并不是创建虚拟线程的唯一方法。新的java.lang.Thread.BuilderAPI,下面讨论,可以创建和启动虚拟线程。此外,结构化并发提供了一个更强大的API来创建和管理虚拟线程,特别是在类似于此服务器示例的代码中,通过该API,线程之间的关系将被平台及其工具所知道。虚拟线程是一个预览API,默认禁用上面的程序使用Executors.newVirtualThreadPerTaskExecutor()方法,因此要在JDK XX上运行它们,必须按以下方式启用预览API:使用javac --release XX --enable-preview Main.java编译程序,并使用java--enable-preview Main运行程序;或,当使用源代码启动器,使用java --release XX --enable-preview Main.java;运行程序;或,使用时jshell,以jshell --enable-preview开头不池化虚拟线程开发人员通常会将应用程序代码从基于线程池的传统ExecutorService迁移到虚拟线程每任务ExecutorService。线程池和所有资源池一样,旨在共享昂贵的资源,但虚拟线程并不昂贵,而且永远不需要将它们池化。开发人员有时使用线程池来限制对有限资源的并发访问。例如,如果一个服务不能处理超过20个并发请求,则通过提交到大小为20的池的任务执行对该服务的所有访问将确保这一点。由于平台线程的高成本使线程池无处不在,这种成语也变得无处不在,但开发人员不应该被诱惑池虚拟线程以限制并发。专门为此目的设计的构造,如信号量,应用于保护对有限资源的访问。这比线程池更有效和方便,也更安全,因为线程本地数据不会意外从一个任务泄露到另一个任务的风险。观察虚拟线程编写清晰的代码并不是完整的故事。清楚地表示正在运行的程序的状态对于故障排除、维护和优化也是必不可少的,JDK长期以来一直提供调试、分析和监控线程的机制。这些工具应该对虚拟线程执行同样的操作--也许可以对其大量的操作进行一些调节--因为它们毕竟是java.lang.Thread的实例。Java调试器可以逐步浏览虚拟线程、显示调用堆栈和检查堆栈帧中的变量。JDK Flight Recorder (JFR)是JDK的低开销分析和监控机制,可以将应用程序代码中的事件(如对象分配和I/O操作)与正确的虚拟线程关联。这些工具不能为以异步样式编写的应用程序执行这些操作。在这种风格中,任务与线程无关,因此调试器无法显示或操作任务的状态,分析器也无法判断任务等待I/O的时间。线程转储是另一个流行的工具,用于对以线程每请求风格编写的应用程序进行故障排除。不幸的是,JDK的传统线程转储,使用jstack或jcmd获得,提供了一个线程的扁平列表。这适合几十个或数百个平台线程,但不适合数千个或数百万个虚拟线程。因此,我们不会扩展传统的线程转储以包括虚拟线程,而是在jcmd中引入一种新的线程转储,以将虚拟线程与平台线程一起呈现,所有这些线程都以有意义的方式分组。当程序使用时,可以显示出线程之间更丰富的关系结构化并发。由于可视化和分析大量线程可以从工具中受益,jcmd除了纯文本外,还可以以JSON格式发出新的线程转储:$ jcmd <pid> Thread.dump_to_file -format=json <file>新的线程转储格式列出了在网络I/O操作中被阻止的虚拟线程,以及由上面所示的new-thread-per-task(一任务一线程)ExecutorService创建的虚拟线程。它不包括对象地址、锁、JNI统计信息、堆统计信息和传统线程转储中出现的其他信息。此外,由于它可能需要列出大量线程,因此生成新的线程转储不会暂停应用程序。以下是这样的线程转储的示例,取自与上面第二个示例类似的应用程序,在JSON查看器中呈现(见下图):由于虚拟线程是在JDK中实现的,并且不绑定到任何特定的操作系统线程,因此它们对操作系统是不可见的,而操作系统不知道它们的存在。操作系统级监控将观察到JDK进程使用的操作系统线程比虚拟线程少。调度虚拟线程要做有用的工作,需要调度线程,即分配在处理器核心上执行。对于作为操作系统线程实现的平台线程,JDK依赖于操作系统中的调度程序。相比之下,对于虚拟线程,JDK有自己的调度程序。JDK的调度程序将虚拟线程分配给平台线程,而不是直接将虚拟线程分配给处理器(这就是前面提到的虚拟线程的M:N调度)。然后,操作系统将像往常一样调度平台线程。JDK的虚拟线程调度程序是一个窃取工作的工具ForkJoinPool在FIFO模式下工作。调度程序的并行性是可用于调度虚拟线程的平台线程数。默认情况下,它等于可用处理器,但它可以使用系统属性jdk.virtualThreadScheduler.parallelism进行调整。请注意,此ForkJoinPool不同于公共池,例如,它用于并行流的实现,并在后进先出模式下工作。调度程序分配虚拟线程的平台线程称为虚拟线程的载体。虚拟线程可以在其生命周期内在不同的载体上调度;换句话说,调度程序不保持虚拟线程和任何特定平台线程之间的亲和性。但是,从Java代码的角度来看,正在运行的虚拟线程在逻辑上独立于其当前载体:虚拟线程无法使用运营商的身份。Thread.currentThread()返回的值始终是虚拟线程本身。载体和虚拟线程的堆栈跟踪是分开的。虚拟线程中抛出的异常将不包括载体的堆栈帧。线程转储不会在虚拟线程的堆栈中显示载体的堆栈帧,反之亦然。载体的线程局部变量对虚拟线程不可用,反之亦然。此外,从Java代码的角度来看,虚拟线程及其载体暂时共享操作系统线程的事实是不可见的。相比之下,从本机代码的角度来看,虚拟线程和它的载体都运行在同一个本机线程上。因此,在同一虚拟线程上多次调用的本机代码可能会在每次调用时观察到不同的操作系统线程标识符。调度程序当前不为虚拟线程实现分时。分时是对占用分配数量的CPU时间的线程的强制抢占。虽然分时使用几百个平台线程可以有效,但目前还不清楚分时使用一百万个虚拟线程是否会那么有效。执行虚拟线程要利用虚拟线程,没有必要重写程序。虚拟线程不要求或期望应用程序代码显式地将控制权交回调度程序;换句话说,虚拟线程是不合作的。用户代码不得假设虚拟线程如何或何时分配给平台线程,就如假设平台线程如何或何时分配给处理器内核一样。要在虚拟线程中运行代码,JDK的虚拟线程调度程序通过将虚拟线程挂载在平台线程上,分配虚拟线程在平台线程上执行。这使得平台线程成为虚拟线程的载体。稍后,在运行一些代码后,虚拟线程可以从其载体上卸载。在这一点上,平台线程是空闲的,因此调度程序可以在其上装载不同的虚拟线程,从而使其再次成为载体。通常,当虚拟线程阻塞I/O或JDK中的其他阻塞操作时,虚拟线程将卸载,如BlockingQueue.take()。当阻塞操作准备完成时(例如,已在套接字上接收字节),它将虚拟线程提交回调度程序,调度程序将在载体上装载虚拟线程以恢复执行。虚拟线程的装载和卸载频繁且透明地进行,并且不会阻止任何操作系统线程。例如,前面显示的服务器应用程序包括以下代码行,其中包含对阻止操作的调用:response.send(future1.get() + future2.get());这些操作将导致虚拟线程多次装载和卸载,通常每次调用get()一次,在send(...)中执行I/O过程中可能多次。JDK中的绝大多数阻塞操作将卸载虚拟线程,释放其载体和底层操作系统线程来承担新的工作。但是,JDK中的一些阻塞操作不会卸载虚拟线程,因此会阻塞其载体和底层操作系统线程。这是因为操作系统级别(例如,许多文件系统操作)或JDK级别(例如,Object.wait())。这些阻塞操作的实现将通过临时扩展调度程序的并行性来补偿操作系统线程的捕获。因此,调度程序的ForkJoinPool中的平台线程数量可能暂时超过可用处理器的数量。调度程序可用的最大平台线程数可以使用系统属性jdk.virtualThreadScheduler.maxPoolSize进行调整。在两种情况下,虚拟线程在阻塞操作期间无法卸载,因为它被固定在其载体上:当它在同步块或方法中执行代码时,或当它执行本机方法或外来函数.固定不会使应用程序不正确,但可能会妨碍其可扩展性。如果虚拟线程在被固定时执行阻塞操作,如I/O或BlockingQueue.take(),则在操作期间,其载体和基础操作系统线程将被阻塞。长时间频繁固定可能会通过捕获运营商而损害应用程序的可扩展性。调度程序不会通过扩展其并行度来补偿固定。相反,通过修改频繁运行的同步块或方法,并保护要使用的潜在长I/O操作,避免频繁和长期的固定java.util.concurrent.locks.ReentrantLock相反,不需要替换不经常使用的同步块和方法(例如,仅在启动时执行)或保护内存操作。和往常一样,努力保持锁定策略简单明了。新的诊断有助于将代码迁移到虚拟线程,并评估是否应将同步的特定使用替换为java.util.concurrent锁:当线程在被固定时阻塞时,将发出JDK Flight Recorder (JFR) 事件.系统属性jdk.tracePinnedThreads在线程被固定时阻塞时触发堆栈跟踪。使用-Djdk.tracePinnedThreads=full运行时,当线程在固定时阻塞时,打印完整的堆栈跟踪,本机帧和持有监视器的帧高亮显示。使用-Djdk.tracePinnedThreads=short将输出限制为仅有问题的帧。我们也许能够在未来的版本中删除上面的第一个限制。第二个限制是与本机代码正确交互所必需的。内存使用和与垃圾回收的交互虚拟线程的堆栈作为堆栈块对象存储在Java的垃圾收集堆中。堆栈随着应用程序运行而增长和收缩,既是为了提高内存效率,又是为了容纳任意深度的堆栈(最高可达JVM配置的平台线程堆栈大小)。这种效率使大量虚拟线程得以实现,从而使服务器应用程序中线程每请求风格的持续生存能力得以实现。在上面的第二个示例中,请记住,假设框架通过创建新的虚拟线程并调用句柄方法来处理每个请求;即使它在深度调用堆栈的末尾调用句柄(在身份验证、事务等之后),句柄本身会生成多个仅执行短期任务的虚拟线程。因此,对于每个具有深调用栈的虚拟线程,将有多个具有浅调用栈的虚拟线程消耗很少的内存。一般来说,虚拟线程所需的堆空间和垃圾收集器活动量很难与异步代码相比。处理请求的应用程序代码通常必须跨I/O操作维护数据;线程每请求代码可以将数据保留在局部变量中,局部变量存储在堆中的虚拟线程堆栈上。而异步代码必须在堆对象中保留相同数据,这些数据从管道的一个阶段传递到下一个阶段。一方面,虚拟线程所需的堆栈帧布局比紧凑对象更浪费;另一方面,虚拟线程在许多情况下(取决于低级GC交互)可以变异和重用其堆栈,而异步管道总是需要分配新对象。总体而言,线程每请求与异步代码的堆消耗和垃圾收集器活动应该大致相似。随着时间的推移,我们希望使虚拟线程堆栈的内部表示更加紧凑。与平台线程栈不同,虚拟线程栈不是GC根,因此其中包含的引用不会被执行并发扫描的垃圾收集器在停止世界暂停中遍历。这也意味着,如果虚拟线程在上被阻塞,例如 BlockingQueue.take(),并且没有其他线程可以获得对虚拟线程或队列的引用,那么该线程可以被垃圾收集-这很好,因为虚拟线程永远不会被中断或解除阻塞。当然,如果虚拟线程正在运行,或者如果它被阻止,并且可能会被取消阻止,则它不会被垃圾收集。虚拟线程的当前限制是G1 GC不支持巨大的堆栈块对象。如果虚拟线程的堆栈达到区域大小的一半(通常为512KB),则可能会引发StackOverflowError。详细更改其余小节详细介绍了在Java平台及其实现中提出的更改:java.lang.ThreadThread-local variablesjava.util.concurrentNetworkingjava.ioJava Native Interface (JNI)Debugging (JVM TI, JDWP, and JDI)JDK Flight Recorder (JFR)Java Management Extensions (JMX)java.lang.ThreadGroupjava.lang.Thread更新 java.lang.Thread API如下:Thread.Builder、Thread.ofVirtual()和Thread.ofPlatform()是用于创建虚拟线程和平台线程的新API。例如,Thread thread = Thread.ofVirtual().name("duke").unstarted(runnable);创建一个名为“duke”的新未启动虚拟线程。Thread.startVirtualThread(Runnable)是创建和启动虚拟线程的方便方法。Thread.Builder可以创建一个线程,也可以创建一个ThreadFactory,然后可以创建具有相同属性的多个线程。Thread.isVirtual()测试线程是否为虚拟线程。Thread.join 和 Thread.sleep接受等待和睡眠时间作为java.time.Duration.新的最终方法Thread.threadId() 返回线程的标识符。现有非最终方法 Thread.getId() 现在已弃用。Thread.getAllStackTraces() 现在返回所有平台线程而不是所有线程的映射。java.lang.Thread API在其他方面保持不变。线程类定义的构造函数创建平台线程,与前面一样。没有新的公共构造函数。虚拟线程和平台线程之间的主要API区别是:公共线程构造函数无法创建虚拟线程。虚拟线程始终是守护线程。Thread.setDaemon(boolean) 方法无法将虚拟线程更改为非守护线程。虚拟线程的固定优先级为 Thread.NORM_PRIORITY。 Thread.setPriority(int) 方法对虚拟线程没有影响。此限制可能会在未来的版本中重新讨论。虚拟线程不是线程组的活动成员。在虚拟线程上调用时,Thread.getThreadGroup() 返回名为“VirtualThreads”的占位线程组。Thread.Builder API没有定义设置虚拟线程线程的线程组的方法。使用SecurityManager集运行时,虚拟线程没有权限。虚拟线程不支持 stop()、suspend()、 resume()方法。这些方法在虚拟线程上调用时引发异常。线程局部变量虚拟线程支持线程局部变量(ThreadLocal)和可继承的线程局部变量(InheritableThreadLocal),就像平台线程一样,因此它们可以运行使用线程本地程序的现有代码。但是,由于虚拟线程可能非常多,请在仔细考虑后使用线程本地。特别是,不要使用线程本地在线程池中共享同一线程的多个任务之间汇集昂贵的资源。虚拟线程不应池化,因为每个线程在其生命周期内只运行一个任务。我们已经从java.base模块中删除了线程本地的许多使用,以准备虚拟线程,以减少在使用数百万线程运行时的内存占用。此外:Thread.Builder API定义了创建线程时选择退出线程本地的方法。它还定义了选择退出继承可继承线程局部的初始值的方法。当从不支持线程本地的线程调用时,ThreadLocal.get() 返回初始值和线程本地集(T)抛出异常。遗产上下文类加载器现在指定为像本地可继承线程一样工作。如果Thread.setContextClassLoader(ClassLoader)在不支持线程本地的线程上调用,然后引发异常。作用域-局部变量对于某些用例,可能会被证明是线程本地的更好替代方案。java.util.concurrent支持锁定的原始API。java.util.concurrent.LockSupport,现在支持虚拟线程:驻留虚拟线程释放基础载体线程以执行其他工作,取消驻留虚拟线程计划它继续。对LockSupport的此更改使所有使用它的API(锁、信号量、阻塞队列等)在虚拟线程中调用时都能优雅地停放。此外:Executors.newThreadPerTaskExecutor(ThreadFactory) 和 Executors.newVirtualThreadPerTaskExecutor() 创建一个ExecutorService,为每个任务创建一个新线程。这些方法支持迁移和与使用线程池和ExecutorService的现有代码的互操作性。执行器服务现在延伸可自动关闭,因此允许此API与上面示例中所示的try-with-resource构造一起使用。Future现在定义了获取已完成任务的结果或异常以及获取任务状态的方法。结合起来,这些添加使我们可以很容易地将Future对象用作流的元素,过滤Future流以查找已完成的任务,然后映射以获得结果流。这些方法也将有助于为结构化并发。网络java.net和java.nio.channels包中的网络API的实现现在与虚拟线程一起工作:对虚拟线程的操作,该操作阻止建立网络连接或从套接字读取,释放基础载体线程以执行其他工作。为了允许中断和取消,阻塞I/O方法定义为java.net.Socket、ServerSocket和DatagramSocket现在指定为可中断的在虚拟线程中调用时:中断套接字上阻塞的虚拟线程将取消锁定线程并关闭套接字。阻止这些类型套接字上的I/O操作,当从 InterruptibleChannel一直是可中断的,因此此更改将这些API在创建时的行为与从通道获取时的行为对齐。java.iojava.io包提供字节流和字符流的API。这些API的实现是高度同步的,需要进行更改,以避免在虚拟线程中使用它们时固定。作为背景,面向字节的输入/输出流没有被指定为线程安全,也没有指定当线程在读或写方法中被阻塞时调用close()时的预期行为。在大多数情况下,使用来自多个并发线程的特定输入或输出流是没有意义的。面向字符的读取器/写入器也没有被指定为线程安全的,但它们确实公开了子类的锁对象。除了固定之外,这些类中的同步是有问题的和不一致的;例如,InputStreamReader和OutputStreamWriter使用的流解码器和编码器在流对象上同步,而不是在锁对象上同步。为防止固定,实现现在的工作方式如下:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter、PrintStream和PrintWriter现在在直接使用时使用显式锁而不是监视器。这些类在子类时与以前一样同步。InputStreamReader和OutputStreamWriter使用的流解码器和编码器现在使用与封闭的InputStreamReader或OutputStreamWriter相同的锁。更进一步,消除所有这些通常不必要的锁定超出了本JEP的范围。此外,BufferedOutputStream、BufferedWriter和OutputStreamWriter的流编码器使用的缓冲区的初始大小现在更小,以便在堆中有许多流或写入程序时减少内存使用-如果有是一百万个虚拟线程,每个线程在套接字连接上都有一个缓冲流。Java Native Interface (JNI)JNI定义了一个新函数IsVirtualThread,用于测试对象是否为虚拟线程。JNI规范在其他方面保持不变。调试调试体系结构由三个接口组成:JVM Tool Interface (JVM TI)、Java Debug Wire Protocol (JDWP)和Java Debug Interface (JDI)。现在,所有三个接口都支持虚拟线程。更新到JVM TI是:大多数用jthread 调用的函数(即,对线程对象的JNI引用)都可以用对虚拟线程的引用调用。虚拟线程不支持少量函数,即PopFrame、ForceEarlyReturn、StopThread、AgentStartFunction和GetThreadCpuTime。SetLocal*函数仅限于在断点或单步事件挂起的虚拟线程的最顶层帧中设置局部变量。现在,GetAllThreads和GetAllStackTraces函数被指定为返回所有平台线程,而不是所有线程。除在早期VM启动或堆迭代期间发布的事件外,所有事件都可以在虚拟线程的上下文中调用事件回调。挂起/恢复实现允许调试器挂起和恢复虚拟线程,并允许在挂起虚拟线程时挂起载体线程。一项新功能can_support_virtual_threads,使代理可以更精细地控制虚拟线程的线程开始和结束事件。新函数支持虚拟线程的批量挂起和恢复;这些函数需要can_support_virtual_threads功能。现有的JVM TI代理将与以前一样工作,但如果它们调用虚拟线程不支持的函数,则可能会遇到错误。当不知道虚拟线程的代理与使用虚拟线程的应用程序一起使用时,就会出现这些情况。对某些代理来说,更改GetAllThreads以返回仅包含平台线程的数组可能是一个问题。启用ThreadStart和ThreadEnd事件的现有代理可能会遇到性能问题,因为它们无法将这些事件限制在平台线程上。更新到JDWP是:一个新命令允许调试器测试线程是否为虚拟线程。EventRequest命令上的新修饰符允许调试器将线程开始和结束事件限制在平台线程上。更新到JDI是:一种新的方法com.sun.jdi.ThreadReference测试线程是否为虚拟线程。中的新方法com.sun.jdi.request.ThreadStartRequest和com.sun.jdi.request.ThreadDeathRequest将为请求生成的事件限制为平台线程。如上所述,虚拟线程不被视为线程组中的活动线程。因此,JVM TI函数GetThreadGroupCalt、JDWP命令ThreadGroupReference/Children和JDI方法 com.sun.jdi.ThreadGroupReference.threads() 返回的线程列表仅包括平台线程。JDK Flight Recorder (JFR)JFR支持具有多个新事件的虚拟线程:jdk.VirtualThreadStart和jdk.VirtualThreadEnd表示虚拟线程的开始和结束。默认情况下,这些事件是禁用的。jdk.VirtualThreadPinned表示虚拟线程在被固定时被驻留,即,没有释放其载体线程(参见限制)。此事件默认启用,阈值为20ms。jdk.VirtualThreadSubmitFailed表示启动或取消驻留虚拟线程失败,可能是由于资源问题。默认情况下,此事件处于启用状态。Java Management Extensions (JMX)一种新的方法com.sun.management.HotSpotDiagnosticsMXBean 生成描述的新式线程转储以上。该方法也可以通过平台间接调用 MBeanServer从本地或远程JMX工具。java.lang.management.ThreadMXBean 仅支持平台线程的监控和管理。java.lang.ThreadGroupjava.lang.ThreadGroup是一个用于分组线程的旧API,在现代应用程序中很少使用,不适合分组虚拟线程。我们现在不推荐并降级它,并希望在未来引入一个新的线程组织结构,作为结构化并发。作为背景,ThreadGroup API可以追溯到Java 1.0。它最初旨在提供作业控制操作,如停止组中的所有线程。现代代码更倾向于使用java.util.concurrent的线程池API(在Java 5中引入)。ThreadGroup在早期Java版本中支持小程序的隔离,但Java安全体系结构在Java 1.2中发生了显著的发展,ThreadGroup不再发挥重要作用。ThreadGroup也旨在用于诊断目的,但该角色已被Java 5中引入的监控和管理功能所取代,包括java.lang.管理API.除了现在基本上不相关之外,ThreadGroup API和实现还存在许多重大问题:销毁线程组的API和机制存在缺陷。API要求实现引用组中的所有活动线程。这将为线程创建、线程启动和线程终止增加同步和竞争开销。API定义了 enumerate() 方法,这些方法本身是有效的。API定义了suspend()、resume()和stop()方法,这些方法本身就容易死锁和不安全。现在指定、不建议使用和降级ThreadGroup ,如下所示:删除了显式销毁线程组的能力:最终不建议使用的destroy()方法什么都不做。守护程序线程组的概念已删除:守护程序状态设置并由最终弃用的setDaemon(boolean) 和 isDaemon() 方法被忽略。该实现不再保留对子组的强引用。当线程组中没有活动线程,并且没有其他任何其他线程保持线程组活动时,线程组现在有资格被垃圾收集。最终不建议使用的suspend()、resume()和stop()方法总是抛出异常。参考引用原文同步至:https://waylau.com/jep-425-virtual-threads-preview更多Java最新特性可见柳伟卫所著《Java核心编程》:https://github.com/waylau/modern-java-demoshttps://openjdk.java.net/jeps/425
JDK 18概述JDK 18是Java SE平台版本18的开源参考实现,如JSR 393在Java社区进程中。 JDK 18在2022年3月22日正式发布。特性400: 默认UTF-8408: 简单Web服务器413: Java API文档中的代码段416: 使用方法句柄重新实现核心反射417: Vector API(第三个孵化器)418: 互联网地址解析SPI419: 外部函数和内存API(第二个孵化器)420: switch的模式匹配(第二次预览)421: 不建议删除最终确定JEP 400:默认为UTF-8总结指定UTF-8作为标准Java API的默认字符集。通过此更改,依赖于默认字符集的API将在所有实现、操作系统、区域设置和配置中一致地行为。目标当Java程序的代码依赖于默认字符集时,使其更可预测和可移植。澄清标准Java API使用默认字符集的位置。在整个标准Java API中标准化UTF-8,控制台I/O除外。 非目标定义新的标准Java API或受支持的JDK API并不是目标,尽管这项努力可能会发现新的便利方法可能会使现有API更平易近人或更易于使用的机会。不建议使用或删除依赖于默认字符集而不是采用显式字符集参数的标准Java API。 动机用于读取和写入文件以及处理文本的标准Java API允许将字符集作为参数传递。字符集控制Java编程语言的原始字节和16位字符值之间的转换。支持的字符集包括,例如,US-ASCII、UTF-8和ISO-8859-1。 如果未传递字符集参数,则标准Java API通常使用默认字符集。JDK在启动时根据运行时环境选择默认字符集:操作系统、用户的区域设置和其他因素。 由于默认字符集在任何地方都不一样,使用默认字符集的API会带来许多不明显的危险,即使对有经验的开发人员也是如此。 考虑一个应用程序,它创建了java.io.FileWriter,而不传递字符集,然后使用它向文件写入一些文本。生成的文件将包含使用运行应用程序的JDK的默认字符集编码的字节序列。第二个应用程序在不同的计算机上运行,或由同一计算机上的不同用户运行,在不传递字符集的情况下创建java.io.FileReader,并使用它读取该文件中的字节。生成的文本包含使用运行第二个应用程序的JDK的默认字符集解码的字符序列。如果第一个应用程序的JDK和第二个应用程序的JDK之间的默认字符集不同,则生成的文本可能会被静默损坏或不完整,因为FileReader无法判断它使用了相对于FileWriter的错误字符集解码文本。以下是这种危险的一个示例,在macOS上以UTF-8编码的日语文本文件在Windows上以美国英语或日语语言环境读取时已损坏:java.io.FileReader(“hello.txt”) -> “こんにちは” (macOS)java.io.FileReader(“hello.txt”) -> “ã?“ã‚“ã?«ã?¡ã? ” (Windows (en-US))java.io.FileReader(“hello.txt”) -> “縺ォ縺。縺ッ” (Windows (ja-JP)熟悉此类危害的开发人员可以使用显式接受字符集参数的方法和构造函数。但是,必须传递参数可以防止方法和构造函数通过流管道中的方法引用(::)使用。 开发人员有时会尝试通过在命令行上设置系统属性file.encode(即java-Dfile.encode=...)来配置默认字符集,但这从未得到支持。此外,在Java运行时启动后,尝试以编程方式设置属性(即System.setProperty(...))是不起作用的。 并非所有标准Java API都服从JDK的默认字符集选择。例如,java.nio.file.Files中读取或写入没有Charset参数的文件的方法被指定为始终使用UTF-8。较新的API默认使用UTF-8,而较旧的API默认使用默认字符集,这对于使用混合API的应用程序来说是一个危险。 如果默认字符集在任何地方都被指定为相同,整个Java生态系统都将受益。不关心可移植性的应用程序将不会受到什么影响,而通过传递字符集参数来支持可移植性的应用程序将不会受到影响。UTF-8有长期以来一直是万维网上最常见的字符集,UTF-8是由大量Java程序处理的XML和JSON文件的标准,Java自己的API越来越青睐UTF-8,例如NIO接口以及属性文件因此,将UTF-8指定为所有Java API的默认字符集是有意义的。 我们认识到,此更改可能会对迁移到JDK 18的程序产生广泛的兼容性影响。因此,始终可以恢复JDK 18之前的行为,其中默认字符集取决于环境。说明在JDK 17和更早版本中,默认字符集是在Java运行时启动时确定的。在macOS上,它是UTF-8,POSIX C区域设置除外。在其他操作系统上,它取决于用户的区域设置和默认编码,例如,在Windows上,它是基于代码页的字符集,如windows-1252或windows-31j。方法java.nio.charsets.Charset.defaultCharset()返回默认字符集。查看当前JDK默认字符集的快速方法是使用以下命令:java -XshowSettings:properties -version 2>&1 | grep file.encoding几个标准Java API使用默认字符集,包括: 在http://java.io包中,InputStreamReader、FileReader、OutputStreamWriter、FileWriter和PrintStream定义构造函数,以创建使用默认字符集编码或解码的读取器、写入器和打印流。 在java.util包中,Formatter和Scanner定义了构造函数,其结果使用默认字符集。 在http://java.net包中,URLEncoder和URLDecoder定义了使用默认字符集的过时方法。 我们建议更改Charset.defaultCharset()的规范,以说明默认字符集是UTF-8除非通过特定于实现的手段另有配置。(有关如何配置JDK,请参见下文。)UTF-8字符集由RFC 2279;它所依据的转换格式在ISO 10646-1修正案2中规定,并在Unicode标准.不要与修改后的UTF-8. 我们将更新所有使用默认字符集交叉引用Charset.defaultCharset()的标准Java API的规范。这些API包括上面列出的API,但不包括System.out和System.err,它们的字符集将由 Console.charset()定义。file.encoding和native.encoding系统属性正如Charset.defaultCharset()规范所设想的那样,JDK将允许将默认字符集配置为UTF-8以外的其他东西。我们将修改系统属性file.encoding的处理,以便在命令行上设置它是配置默认字符集的支持方法。我们将在实施说明中具体说明这一点 System.getProperties()如下: 如果file.encoding设置为“COMPAT”(即java -Dfile.encoding=COMPAT),则默认字符集将是JDK 17及更早版本中算法选择的字符集,基于用户的操作系统、区域设置和其他因素。file.encode的值将设置为该字符集的名称。 如果file.encoding设置为“UTF-8”(即java -Dfile.encoding=UTF-8),则默认字符集将为UTF-8。定义此no-op值是为了保留现有命令行的行为。 未指定“Compat”和“UTF-8”以外的值的处理。它们不受支持,但如果这样的值在JDK 17中有效,那么它很可能会在JDK 18中继续有效。JDK 17引入了native.encoding 系统属性作为程序获取JDK算法选择的字符集的标准方式,无论默认字符集是否实际配置为该字符集。在JDK 18中,如果在命令行上将file.encoding设置为COMPAT,则file.encoding的运行时值将与 native.encoding的运行时值相同;如果在命令行上将file.encoding设置为UTF-8,则file.encoding的运行时值可能与native.encoding的运行时值不同。 在下面的风险和假设中,我们讨论了如何减轻此更改对file.encoding可能产生的不兼容问题,以及native.encoding系统属性和应用程序建议。 JDK内部使用了三个与字符集相关的系统属性。它们仍然未指明和不支持,但为了完整性,请在此处记录: sun.stdout.encode和sun.stderr.encode-标准输出流(System.out)和标准错误流(System.err)以及java.io.Console API中使用的字符集的名称。 sun.jnu.encode-java.nio.file实现在编码或解码文件名路径时使用的字符集的名称,而不是文件内容。在macOS上,它的值是“UTF-8”;在其他平台上,它通常是默认字符集。源文件编码Java语言允许源代码在UTF-16编码,这不受默认字符集选择UTF-8的影响。但是,javac编译器会受到影响,因为它假定.java源文件是使用默认字符集编码的,除非-encoding 另有配置选项。如果源文件是用非UTF-8编码保存的,并使用较早的JDK编译的,则在JDK 18或更高版本上重新编译可能会导致问题。例如,如果非UTF-8源文件具有包含非ASCII字符的字符串文字,则除非使用-encoding ,否则JDK 18或更高版本中的javac可能会误解这些文字。旧版默认字符集在JDK 17和更早版本中,名称默认值被识别为US-ASCII字符集的别名。也就是说,Charset.forName("default")产生的结果与Charset.forName("US-ASCII")相同。默认别名是在JDK 1.5中引入的,以确保使用http://sun.io converters的旧代码可以迁移到JDK 1.4中引入的java.nio.charset框架。 当默认字符集指定为UTF-8时,JDK 18将默认值保留为US-ASCII的别名将非常混乱。当用户通过在命令行上设置 -Dfile.encoding=COMPAT将默认字符集配置为其JDK 18之前的值时,默认值的含义也会令人困惑。将默认值重新定义为不是US-ASCII的别名,而是默认字符集(无论是UTF-8还是用户配置的)的别名,将导致调用 Charset.forName("default")的(少数)程序中的微妙行为更改。 我们认为,继续承认JDK 18的违约将延长一个糟糕的决定。它不是由Java SE平台定义的,也不是由IANA识别为任何字符集的名称或别名。事实上,对于基于ASCII的网络协议,IANA鼓励使用规范名称US-ASCII,而不仅仅是ASCII或模糊别名,如ANSI_X3.4-1968,使用特定于JDK的别名默认值与该建议背道而驰。Java程序可以使用枚举常量StandardCharsets.US_ASCII来明确其意图,而不是将字符串传递给Charset.forName(...)。 因此,在JDK 18中,Charset.forName("default")将引发不支持的UnsupportedCharsetException。这将使开发人员有机会检测使用该成语,并迁移到US-ASCII或Charset.defaultCharset()的结果。替代方案 保持现状-这并不能消除上述危险。 不建议在Java API中使用默认字符集的所有方法-这将鼓励开发人员使用采用字符集参数的构造函数和方法,但生成的代码将更加详细。 指定UTF-8作为默认字符集,而不提供任何更改它的方法-此更改的兼容性影响将太高。参考引用更多示例可见《Java核心编程》:GitHub - waylau/modern-java-demos: Modern Java: From Java 8 to Java 14. 现代Java案例大全/《Java核心编程》源码
本文是对《Vue.js 3企业级应用开发实战》这本书做了拆箱和导读。介绍本书的写作背景、特点等。整个拆书过程的视频也可见B站:https://www.bilibili.com/video/BV1SL411w7ke/封面部分首先是介绍封面部分。《Vue.js 3企业级应用开发实战》封面部分是采用了Vue的LOGO作为的整体的设计风格。具有非常高的辨识度。可以看到,是由电子工业出版社博文视点出品。右下角可以看到该书的四个特点。理论落实到代码实现买南非提供实例源码知识面广、适合新手商业性、应用性强封底部分介绍封底部分。封底部分可以看到是由电子工业出版社博文视点出品的其他书籍的介绍。这本书归类为前端开发Vue.js。定价为109元。勒口部分勒口部分是作者的简介。一句话概括:经验丰富、著作等身。内容简介本书基于最新的Vue.js 3技术栈展开,全面讲解了Vue.js 3在企业级领域的应用。本书也是一本面向Vue.js 3的“大而全”的教程,力图涉及Vue.js 3的所有知识点,包括应用实例、组件、模板、计算属性、监听器、指令、表单、事件、数据绑定、路由、依赖注入、自定义样式、动画、渲染函数、测试、响应式编程等方面内容,同时还介绍了Vue CLI、TypeScript、Animate.css、Mocha、Vue Router、Naive UI、vue-axios等Vue.js周边生态方面的内容。特别是 Vue.js 3完全支持TypeScript,使编程更加强调采用类、面向对象的方式去组织代码。最后,本书会手把手带领读者一起从零开始实现一个完整的企业级新闻头条客户端应用。本书技术前瞻、面向实战、案例丰富。本书主要面向的是对Vue.js感兴趣的学生、前端工程师、系统架构师。该书是2022年1月份出版的。写作背景Vue.js是近些年广受关注的前端框架。Vue.js已经具备了商业项目开发的必备条件,如语法精炼、优雅而简洁、代码的可读性高、成熟的组件模块化等等,当然,还有商业项目开发最为看重的与第三方控件的结合能力。正是这些能力,确保了“后浪”Vue.js能够与React、Angular等老牌前端开发框架并驾齐驱,在开发者当中占据越来越重要的位置。和React、Angular相比,Vue.js在可读性、可维护性和趣味性之间做到了很好的平衡,非常使用中小型项目的快速开发。随着Vue.js 3的推出,Vue.js具备了进军大型项目开发的可能性。市面上关于Vue.js 1.x和2.x版本的介绍资料比较多,但由于Vue.js 3是近期才发布,因此,市面上有关ue.js 3的资料比较匮乏。于是,笔者在GitHub上,以开源方式撰写了《跟老卫学Vue.js开发》开源书[1],介绍Vue.js 3的使用。同时,考虑到能让更多的人学习到Vue.js,于是笔者以该开源书为蓝本,对Vue.js 3的知识点做了完整的梳理和扩展,补充了实战案例,出版了《Vue.js 3企业级应用开发实战》一书以补空白。希望读者通过本书的学习,掌握Vue.js 3企业级应用开发实战的能力。内容介绍本书大致分为以下几个部分:准备(第1-2章):了解Vue.js基础概念,并带领读者快速创建一个Vue.js应用,使读者对Vue.js有一个初步的印象。入门(第3-11章):了解TypeScript基础、Vue.js应用实例、组件基础、模板、计算属性与监听器、样式、表达式、事件、表单等概念。通过这几章的学习,读者可以了解到 Vue.js常用的知识点。进阶(第12-18章):深入讲解Vue.js高级知识点。实战(第19-22章):手把手带领读者一起从零开始实现一个完整的企业级新闻头条客户端应用,使读者具备Vue.js企业级应用开发的完整的能力。本书特点1.可与笔者在线上交流本书提供线上交流网址:https://github.com/waylau/vuejs-enterprise-application-development/issues读者有任何技术上的问题,都可以向笔者提问。2.提供了基于知识点的77实例和6个综合性实战案例本书提供了丰富的基于Vue.js 3技术点的实例77个,将理论讲解最终落实到代码实现上来。在掌握了基础之后,另外提供了6个综合性实战案例。这些案例从零开始,最终实现了一个完整的企业级应用,内容具有很高的应用价值和参考性。3.免费提供书中实例的源文件本书提供了书中涉及的所有实例的源文件。读者可以一边阅读本书,一边参照源文件动手练习,这样不仅提高了学习的效率,而且可以对书中的内容有更加直观的认识,从而逐渐培养自己的编程能力。4.覆盖的知识面广本书几乎囊括了Vue.js 3所涉及的包括应用实例、组件、模板、计算属性、监听器、指令、表单、事件、数据绑定、路由、依赖注入、自定义样式、动画、渲染函数、测试、响应式编程等方面内容,同时介绍了Vue CLI、TypeScript、Animate.css、Mocha、Vue Router、Naive UI、vue-axios等Vue.js周边生态方面的内容,技术前瞻,案例丰富。不管是编程初学者,还是编程高手,都能从本书中获益。本书可作为读者案头的工具书,随手翻阅。5.采用短段、短句,便于流畅阅读本书采用结构化的层次,并采用短小的段落和语句,让读者读来有顺水行舟的轻快感。6.案例的商业性、应用性强本书提供的案例多数来源于真正的商业项目,具有高度的参考价值。有些代码甚至可以直接移植到自己的项目中,进行重复使用,使从“学”到“用”这个过程变得更加直接。7.印刷精良其实除了上述列出的6点外,我认为还有其他特点,就是这本书的印刷。比如这本书本身是双色版的,黑色和绿色,方便辨识正文和标题、代码和注释。还有一点,这本书的纸张是米黄色的,非常的护眼。对于爱学习的你来说,可以更好的保护自己的哦。源代码本书提供的素材和源代码可从以下网址下载:https://github.com/waylau/vuejs-enterprise-application-development勘误和交流本书如有勘误,会在以下网址发布:https://github.com/waylau/vuejs-enterprise-application-development/issues读者通过以下方式与笔者联系。博客:https://waylau.com邮箱:waylau521@gmail.com微博:http://weibo.com/waylau521GitHub:https://github.com/waylau
随着应用规模的增长,越来越多的开发者认识到静态语言的好处。静态类型系统可以帮助防止许多潜在的运行时错误,这就是为什么Vue 3是用TypeScript编写的。这意味着在Vue应用开发中,使用TypeScript进行开发不需要任何其他工具。TypeScript在Vue 3世界中是一等公民。有两种方式,可以实现在Vue 3应用中支持使用TypeScript。1. 基于Vue 3 Preview创建的项目如果是选择Vue 3模板“Vue 3 Preview”进行项目创建,正如前文hello-world应用那样,则可以采用如下的步骤实现对TypeScript的支持。在应用的根目录下执行如下命令:vue add typescript此时,在命令行会出现提示框,根据提示选择即可。这里都是选“Y”即可,看到如下输出内容,则证明已经完成。2. Manually select features(手动选择)方式如果是采用Manually select features(手动选择)方式创建应用,则直接可以选择TypeScript作为支持选项。具体步骤如下。选择“TypeScript”,而后回车。选择“3.x (Preview)”,而后回车。 3. TypeScript应用的差异相比于JavaScript的应用而言,TypeScript的应用的目录结构如下。 l 多了TypeScript语言的配置文件tsconfig.jsonl package.json和package-lock.json中多了对TypeScript等依赖的描述l main.js改为了main.tsl 多了shims-vue.d.ts文件l 所有在Vue组件中使用JavaScript的地方,都改为了TypeScript参考引用本系列归档至《跟老卫学Vue.js开发》:GitHub - waylau/vuejs-enterprise-application-development: Vue.js Enterprise Application Development. 《跟老卫学Vue.js开发》/《Vue.js 3企业级应用开发实战》源码参考书籍《Vue.js 3企业级应用开发实战》:《Vue.js 3企业级应用开发实战(双色版)(博文视点出品)》(柳伟卫)【摘要 书评 试读】- 京东图书
在上一节完成Vue 3应用创建之后,我们来探索下应用的文件结构。1. 整体结构2. 根目录文件根目录文件包括l .gitignore:用于配置哪些文件不受git管理。l babel.config.js:Babel中的配置文件。Babel一款JavaScript的编译器。l package.json、package-lock.json:npm包管理器的配置文件。npm install读取package.json创建依赖项列表,并使用package-lock.json来通知要安装这些依赖项的哪个版本。如果某个依赖项在package.json中,但是不在package-lock.json中,运行npm install会将这个依赖项的确定版本更新到package-lock.json中,不会更新其它依赖项的版本。l README.md:项目的说明文件。一般会详细说明项目作用、怎么构建、怎么求助等内容。3. node_modules安装node后用来存放用包管理工具下载安装的包的文件夹。打开改目录,可以看到项目所依赖的包非常的多。各个包的含义,这里不再赘述。 4. public目录 public目录在下列情况下使用: l 需要在构建输出中指定一个文件的名字。l 有上千个图片,需要动态引用它们的路径。l 有些库可能和 webpack 不兼容,这些库放到这个目录下,而后将其用一个独立的 5. src目录src目录就是放项目源码的目录。l assets目录:用于放置静态文件,比如一些图片,JSON数据等l components目录:用于放置Vue公共组件。目前,该目录下,仅有一个HelloWorld.vue组件。l App.vue:页面入口文件也是根组件(整个应用只有一个),可以引用其他Vue组件。l main.js:程序入口文件,主要作用是初始化Vue实例并使用需要的插件。5.1. main.js想看下main.js的源码:import { createApp } from 'vue'import App from './App.vue' createApp(App).mount('#app') 上述代码比较简答,就是初始化了Vue的应用实例。应用实例来自App.vue组件。 5.2. App.vueApp.vue是根组件,整个应用只有一个。源码如下: import HelloWorld from './components/HelloWorld.vue' export default { name: 'App', components: { HelloWorld }} app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px;}整体看主要分为三部分组成:、其中,又引用了一个子组件HelloWorld。该HelloWorld组件是通过5.3. HelloWorld.vueHelloWorld.vue子组件是整个应用的核心了。源码如下: {{ msg }} For a guide and recipes on how to configure / customize this project, check out the vue-cli documentation. Installed CLI Plugins babel eslint Essential Links Core Docs Forum Community Chat Twitter News Ecosystem vue-router vuex vue-devtools vue-loader awesome-vue export default { name: 'HelloWorld', props: { msg: String }} h3 { margin: 40px 0 0;}ul { list-style-type: none; padding: 0;}li { display: inline-block; margin: 0 10px;}a { color: #42b983;} HelloWorld.vue子组件的结构与App.vue组件是一样的,也是包含了三部分。那么“msg”属性变量的值到底是什么呢?我们回到App.vue组件的源码: 可以看到HelloWorld组件的msg属性值是“Welcome to Your Vue.js App”。这意味着,子组件HelloWorld.vue可以接收由父组件App.vue组件的传值。msg属性值在页面实际渲染的效果如下图所示。参考引用本系列归档至《跟老卫学Vue.js开发》:https://github.com/waylau/vuejs-enterprise-application-development参考书籍《Vue.js 3企业级应用开发实战》:https://item.jd.com/13624356.html
很多人对鸿蒙、OpenHarmony、HarmonyOS这些术语傻傻的分不清楚,那么本文就做一些解答。6月4日,华为发布了《关于规范HarmonyOS沟通口径的通知》(以下简称《通知》),原文在网上都能搜到,这里就不贴了。本人对该通知做一些解读如下。1. 《通知》背景《通知》开篇就说“关于‘鸿蒙操作系统’,由于缺乏规范的表述和统一的口径,导致内部理解不一致、对外说法不一致,容易引起混淆”所以,大家不要不好意思啊,不光你不懂,很多人都傻傻的分不清。不懂不要怕,咱们继续往下看。2. 鸿蒙操作系统“鸿蒙操作系统”特指华为智能终端操作系统。“鸿蒙操作系统”具有以下特征:一是一套操作系统可以满足大大小小设备需求,实现统一OS,弹性部署;二是搭载该操作系统的设备在系统层面融为一体、形成超级终端,让设备的硬件能力可以弹性扩展,实现设备之间硬件互助,资源共享;三是面向开发者,实现一次开发,多端部署。3. OpenHarmonyOpenHarmony是一个开源项目,由开放原子开源基金会(https://www.openatom.org/)进行管理。开放原子开源基金会由华为、阿里、腾讯、百度、浪潮、招商银行、360等十家互联网企业共同发起组建。OpenHarmony暂时还没有中文名字,名字还在申请中。项目地址为:https://gitee.com/openharmonyOpenHarmony开源项目主要包括两部分:一是华为捐献的“鸿蒙操作系统”的基础能力;二是其他参与者的贡献。因此,OpenHarmony是“鸿蒙操作系统”的底座。4. HarmonyOSHarmonyOS就是“鸿蒙操作系统”,或者简称为“鸿蒙OS”是基于 OpenHarmony、AOSP等开源项目的商用版本。这里需要注意:一是HarmonyOS不是开源项目,而是商用版本。二是HarmonyOS手机和平板之所以能运行Android,是因为HarmonyOS 实现了现有Android生态应用(即AOSP)的运行。5. 鸿蒙生态鸿蒙生态包括 OpenHarmony 和 HarmonyOS,当然还包括开发工具以周边的一些开发库。当我们在说“鸿蒙”的时候,也许就是指鸿蒙生态。6. 何时选择OpenHarmony或是HarmonyOS?如果你只是一个应用开发工程师,专注于终端设备的应用开发,那么选择HarmonyOS。这里也有免费开源教程《跟老卫学HarmonyOS开发》https://github.com/waylau/harmonyos-tutorial如果你对HarmonyOS底层的技术感兴趣,想了解或者想对HarmonyOS做贡献,那么选择OpenHarmony。当然,如果想更进一步,做一款属于自己的操作系统,基于OpenHarmony开源项目做二次开发也是不错的选择哦。以上就是本人对于鸿蒙相关术语的一些解读,看我理解的对不对呢?欢迎大家讨论。
《Vue.js 3企业级应用开发实战》一书由电子工业出版社出版,已经于2022年1月出版上市。近日拿到了样书,第一时间希望与读者朋友们分享下这本书里面的内容。这本书的背景近期拿到了样书,迫不及待的对新书做了浏览。同时也做了拆书与导读。聊下为啥要写这本书。Vue.js是近些年广受关注的前端框架。Vue.js已经具备了商业项目开发的必备条件,如语法精炼、优雅而简洁、代码的可读性高、成熟的组件模块化等等,当然,还有商业项目开发最为看重的与第三方控件的结合能力。正是这些能力,确保了“后浪”Vue.js能够与React、Angular等老牌前端开发框架并驾齐驱,在开发者当中占据越来越重要的位置。和React、Angular相比,Vue.js在可读性、可维护性和趣味性之间做到了很好的平衡,非常使用中小型项目的快速开发。随着Vue.js 3的推出,Vue.js具备了进军大型项目开发的可能性。市面上关于Vue.js 1.x和2.x版本的介绍资料比较多,但由于Vue.js 3是近期才发布,因此,市面上有关ue.js 3的资料比较匮乏。于是,笔者在GitHub上,以开源方式撰写了《跟老卫学Vue.js开发》开源书(https://github.com/waylau/vuejs-enterprise-application-development),介绍Vue.js 3的使用。同时,考虑到能让更多的人学习到Vue.js,于是笔者以该开源书为蓝本,对Vue.js 3的知识点做了完整的梳理和扩展,补充了实战案例,出版了《Vue.js 3企业级应用开发实战》一书以补空白。希望读者通过本书的学习,掌握Vue.js 3企业级应用开发实战的能力。这本书的内容本书大致分为以下几个部分:准备(第1-2章):了解Vue.js基础概念,并带领读者快速创建一个Vue.js应用,使读者对Vue.js有一个初步的印象。入门(第3-11章):了解TypeScript基础、Vue.js应用实例、组件基础、模板、计算属性与监听器、样式、表达式、事件、表单等概念。通过这几章的学习,读者可以了解到 Vue.js常用的知识点。进阶(第12-18章):深入讲解Vue.js高级知识点。实战(第19-22章):手把手带领读者一起从零开始实现一个完整的企业级新闻头条客户端应用,使读者具备Vue.js企业级应用开发的完整的能力。这本书的特点概况起来,这本《Vue.js 3企业级应用开发实战》主要有以下几个特点。1.可与笔者在线上交流本书提供线上交流网址:https://github.com/waylau/vuejs-enterprise-application-development/issues读者有任何技术上的问题,都可以向笔者提问。2.提供了基于知识点的77实例和6个综合性实战案例本书提供了丰富的基于Vue.js 3技术点的实例77个,将理论讲解最终落实到代码实现上来。在掌握了基础之后,另外提供了6个综合性实战案例。这些案例从零开始,最终实现了一个完整的企业级应用,内容具有很高的应用价值和参考性。3.免费提供书中实例的源文件本书提供了书中涉及的所有实例的源文件。读者可以一边阅读本书,一边参照源文件动手练习,这样不仅提高了学习的效率,而且可以对书中的内容有更加直观的认识,从而逐渐培养自己的编程能力。源码见:https://github.com/waylau/vuejs-enterprise-application-development/4.覆盖的知识面广本书几乎囊括了Vue.js 3所涉及的包括应用实例、组件、模板、计算属性、监听器、指令、表单、事件、数据绑定、路由、依赖注入、自定义样式、动画、渲染函数、测试、响应式编程等方面内容,同时介绍了Vue CLI、TypeScript、Animate.css、Mocha、Vue Router、Naive UI、vue-axios等Vue.js周边生态方面的内容,技术前瞻,案例丰富。不管是编程初学者,还是编程高手,都能从本书中获益。本书可作为读者案头的工具书,随手翻阅。5.采用短段、短句,便于流畅阅读本书采用结构化的层次,并采用短小的段落和语句,让读者读来有顺水行舟的轻快感。6.案例的商业性、应用性强本书提供的案例多数来源于真正的商业项目,具有高度的参考价值。有些代码甚至可以直接移植到自己的项目中,进行重复使用,使从“学”到“用”这个过程变得更加直接。参考引用原本同步至:https://waylau.com/vuejs-enterprise-application-development/京东有售:https://item.jd.com/13624356.html当当有售:http://product.dangdang.com/29373978.html
1. 安装Node.js2. 安装Vue CLI执行如下命令npm install -g @vue/cli看到如下输出,则证明安装成功3. 创建项目vue create hello-world选择Vue 3模板“Vue 3 Preview”,并回车。看到如下内容,则证明hello-world项目已经创建完成。4. 运行项目当前目录下,执行cd hello-worldnpm run serve看到如下输出,则证明项目已经启动。访问http://localhost:8080/可以看到项目的主页,如下。至此,已经完成了第一个Vue 3应用的创建!参考引用本系列归档至《跟老卫学Vue.js开发》:https://github.com/waylau/vuejs-enterprise-application-development参考书籍《Vue.js 3企业级应用开发实战》:https://item.jd.com/13624356.html
《鸿蒙HarmonyOS手机应用开发实战》一书由清华大学出版社出版,已经于2022年1月上市。拿到了样书,第一时间希望与读者朋友们分享下这本书里面的内容。这本书的背景近期拿到了样书,迫不及待的对新书做了浏览。同时也做了拆书与导读,可以在B站找到:https://www.bilibili.com/video/BV1gL411c7hg/聊下为啥要写这本书。中国信息产业一直是“缺芯少魂”,其中的“芯”指的是芯片,而“魂”则是指操作系统。而自2019年5月15日起,美国陆续把包括华为在内的中国高科技企业列入其所谓的“实体清单”(Entities List),标志着科技再次成为中美博弈的核心领域。随着谷歌暂停与华为的部分合作,包括软件和技术服务的转让,华为在国外市场已经面临着升级Android版本、搭载谷歌服务等方面的困境。在这样的背景下,华为顺势推出HarmonyOS,以求在操作系统领域不受制于人。HarmonyOS是一款面向未来、面向全场景(移动办公、运动健康、社交通信、媒体娱乐等)的全新的分布式操作系统。作为操作系统领域的新成员,HarmonyOS势必会面临bug多、学习资源缺乏等众多困难。为此,笔者在开源社区以开源方式推出了免费系列学习教程《跟老卫学HarmonyOS开发》 ,以帮助HarmonyOS爱好者入门。同时,为了让更多的人了解并使用HarmonyOS,笔者将自身工作、学习中遇到的问题、难题进行了总结,形成了本书,以补市场空白。几个特点概况起来,这本《鸿蒙HarmonyOS手机应用开发实战》主要有三大特点。B站也有相关介绍:https://www.bilibili.com/video/BV1gL411c7hg/1、涉及面非常广那么涉及广的话可以体现在哪里呢?可以看这本书的内容简介部分。本书采用新的HarmonyOS 2版本作为基石,详细介绍如何基于HarmonyOS进行手机应用的开发,内容涵盖HarmonyOS架构、DevEco Studio、应用结构、Ability、任务调度、公共事件、通知、剪切板、Java UI、JS UI、多模输入、线程管理、视频、图像、相机、音频、媒体会话管理、媒体数据管理、安全管理、二维码、通用文字识别、蓝牙、WLAN、网络管理、电话服务、设备管理、数据管理、原子化服务、流转等多个主题。本书辅以大量解决实际问题的案例,具有很强的前瞻性、应用性、趣味性。可以说,HarmonyOS常见的知识点这本书都已经涉及了,毕竟713页的篇幅不是盖的。更难能可贵的是,这本书也对当前比较新的特新诸如原子化服务、流转也做了讲解。2、图例非常丰富这本书是他这个图例非常丰富,从基本的IDE安装,到复杂的数据结构的演示,都有丰富的图例。通过图例的配套讲解演示,可以方便读者理解。3、详细的接口说明每个用到的API的用法都给你描述的详详细细的。4、代码量非常大第三个特点的话就是它里面的实战案例非常丰富。实战案例体现在,这本书的每一章每个知识点基本上会配套一个实战案例,代码量是非常大的。每行代码这个重点代码它都有一些注释给你写得明明白白。这本书呢是不单只是简单的讲一些理论,它还有会手把手的教你写代码。理论联系实际。5、本书开源本书也是有配套的开源书籍,如果不想花钱,也是有免费版本的哦,见《跟老卫学HarmonyOS开发》https://github.com/waylau/harmonyos-tutorial学习本书,一起构建万物互联的时代!参考引用原本同步至:https://waylau.com/about-harmonyos-mobile-application-development-book/京东有售:https://item.jd.com/13568130.html————————————————版权声明:本文为CSDN博主「_waylau」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/kkkloveyou/article/details/122484759
本节是《Java数据结构及算法实战》系列的第6节,主要介绍算法复杂度等级及其分析的方法。在前一节,我们介绍了程序的性能,也介绍了评估性能的方式。那么,我们是否就能测算出算法需要运行的时间呢?在上一节,我们了解算法复杂度的度量规则,接下来我们将学会如何对各个具体算法的复杂度进行分析。按照渐进复杂度的思想,可以将算法的复杂度按照高低划分为若干典型的级别。这种分类方法,也被称为函数的界或者函数的阶。1. 常数的时间复杂度O(1)首先来看这样一个“取非极端元素”问题:给定整数子集S,+∞ > |S| = n ≥ 3,从中找出一个元素a∈S,使得a ≠ max(S)且a ≠ min(S)。也就是说,在最大、最小者之外,取出任意一个数。这一问题,可以用以下伪代码描述的算法解决:x = S[0] y = S[1] z = S[2] list = sort(x, y, z) output list[1]针对上述问题,我们注意到,既然S是有限集,故其中的最大、最小元素各有且仅有一个。因此,无论S的规模有多大,在前三个元素S[0]、S[1]和S[2]中,必包含至少一个非极端元素。于是,我们可以取x = S[0]、y = S[1]和z = S[2],这只需执行三次基本操作,耗费O(3)时间。接下来,为了确定这三个元素的大小次序,我们最多需要做三次比较,也是O(3)时间。最后,输出居中的那个元素只需O(1)时间。综合起来,上述问题的运行时间为:T(n) = O(3) + O(3) + O(1) = O(7) = O(1) 也就是说,上述问题的算法具有常数的时间复杂度。2. 对数的时间复杂度O(logn)考虑如下“进制转换”问题:给定任一十进制整数,将其转换为三进制表示。比如23(10) = 212(3) 101(10) = 10202(3)这一问题,可以以下伪代码描述的算法解决:while(n != 0) n mod 3 // 取模 n = n/3 // 整除以101(10)为例。第一轮循环,输出101 mod 3 = 2 n = 100/3 = 33第二轮循环,输出33 mod 3 = 0 n = 33/3 = 11第三轮循环,输出11 mod 3 = 2 n = 11/3 = 3第四轮循环,输出3 mod 3 = 0 n = 3/3 = 1第五轮循环,输出1 mod 3 = 1 n = 1/3 = 0至此算法结束。请注意,以上各个数位是按照从低到高的次序输出的,所以转换后的结果应该是10202(3)。我们以整数n的大小作为输入规模,来分析上述算法的运行时间。该算法由若干次循环构成,每一轮循环内部,都只需进行两次基本操作(取模、整除)。为了确定需要进行的循环轮数,我们可以注意到以下事实:每经过一轮循环,n都至少减少至1/3。于是,至多经过1+log3^n次循环,即可减小至0。也可以从另一个角度来解释这一结果。该算法的任务是依次给出三进制表示的各个数位,其中的每一轮循环,都恰好给出其中的一个数位。因此,总共需要进行的循环轮数,应该恰好等于n的三进制表示的位数,即1+log3^n。因此,该算法需要运行的时间为:O(2×(1+log3^n)) = O(log3n)鉴于大O记号的性质,通常会忽略对数函数的常底数。比如这里的底数为常数3,故通常将上述复杂度记作O(logn)。此时,我们称这类算法具有对数的时间复杂度。3. 线性的时间复杂度O(n)考虑如下“数组求和”问题:给定n个整数,计算它们的总和。这一问题,可以以下伪代码描述的算法解决:input(S)) s = 0 for a in S // 遍历数据S中的元素a s += a output s上述算法,对s的初始化需要O(1)时间。算法的主体部分是一个循环,每一轮循环中只需进行一次累加运算,这属于基本操作,可以在O(1)时间内完成。每经过一轮循环,都对一个元素进行累加,故总共需要做n轮循环。因此,上述算法一的运行时间为:O(1) + O(1)×n = O(n+1) = O(n) 我们称这类算法具有线性的时间复杂度。4. 平方的时间复杂度O(n^2)我们⎯⎯起看下一个经典的排序问题:将n个整数排成一个非降序列。排序算法种类繁多,这里我们采用“冒泡排序”。冒泡排序算法又称为交换排序法,是从观察水中气泡变化构思而成。其原理是从第一个元素开始,比较相邻元素的大小,若大小顺序有误,则对调后再进行下一个元素的比较,就仿佛气泡从水底逐渐冒升到水面一样。如此扫描一次之后,就可以确保最后一个元素是位于正确的顺序。接着再逐步进行第二次扫描,直接完成所有元素的排序关系为止。以下伪代码描述的冒泡排序算法:input(S) for (i=0; i>0; i--) // 扫描次数,比较n个值 for (j=0; j<i; j++) // 比较、交换次数 if S[j] > S[j+1] // 比较,如果前面的数比后面的数大,则发生交换 temp = S[j] S[j] = S[j+1] S[j+1] = temp // 后面的数和前面的发生交换 output S为了对n个整数排序,冒泡排序必须执行n-1次扫描,最坏情况和平均情况均比较次数如下:(n-1) + (n-2) + (n-3) + ... + 3 + 2 + 1 = n(n-1)/2 执行次数为n(n-1)/2,鉴于大O记号的特性,低次项可以忽略,常系数可以简化为1,故时间复杂度为:T(n) = O(n^2)这类算法我们称为具有平方时间复杂度。对于其它一些算法,n的次数可能更高,但只要其次数为常数,我们都统称之为多项式时间复杂度。5. 指数的时间复杂度O(2^n)再来考虑幂函数的计算问题:给定非负整数n,计算2^n。为了解决这一问题,可以用以下伪代码描述该算法:input(n) power = 1 while (0 < n--) power = power * 2 output power上述算法总共需要做n次迭代,每次迭代只涉及常数次基本操作,故总共需要运行O(n)时间。按照如上定义,问题的输入规模为n,故有O(n) = O(2^n)。我们称这样的算法具有指数的时间复杂度。从常数、对数、线性到平方时间复杂度,算法的效率不断下降,但就实际应用而言,这类算法的效率还在允许的范围内。然而,在多项式时间复杂度与指数时间复杂度之间,却有着一道巨大的鸿沟,通常我们都认为,指数复杂度的算法无法应用于实际问题之中,它们不是有效的算法,甚至不能称作算法。因此,在实际项目中,应该避免设计出指数时间复杂度的算法。6. 算法复杂度总结算法复杂度总常见的表示形式为常数级O(1)、对数级O(logn)、线性级度O(n)、平方级O(n^2)、指数级O(2^n),其运算时间的典型函数增长情况如图1-5所示。简单来说,当n足够大时,复杂度与时间效率有如下关系(c是一个常量):c < log2^n < n < nlog2^n < n^2 < n^3 < 2^n < 3^n参考引用原本同步至:https://waylau.com/algorithm-complexity-level/本系列归档至《Java数据结构及算法实战》:https://github.com/waylau/java-data-structures-and-algorithms-in-action《数据结构和算法基础(Java语言实现)》:https://item.jd.com/13014179.html
本节是《Java数据结构及算法实战》系列的第5节,主要介绍分析算法和数据结构的重要工具——渐近记法。在前一节,我们介绍了程序的性能,也介绍了评估性能的方式。那么,我们是否就能测算出算法需要运行的时间呢?1.3.1 大O标记法直接回答上述问题并非易事,原因在于,即使是同一算法,针对不同的输入运行的时间并不相同。以排序问题为例,输入序列的规模、组成和次序都不是确定的,这些因素都会影响到排序算法的运行时间。在所有这些因素中,输入的规模是最重要的一个。假设我们要对学生按照成绩排序,那么显然,当学生的规模很少时(比如50个)所耗费的排序时间,肯定是要比当学生的规模很大时(比如50万个)所耗费的排序时间短。因此,在实际分析算法的时间复杂度时,通常只考虑输入规模这一主要因素。如果将某一算法为了处理规模为n的问题所需的时间记作T(n),那么随着问题规模n的增长,运行时间T(n)我们将称之为算法的时间复杂度。由于小规模的问题所需的处理时间相对更少,不同算法在效率方面的差异并不明显,而只有在处理大规模的问题时,这方面的差异才有质的区别。因此,在评价算法的运行时间时,我们往往可以忽略其在处理小规模问题时的性能,转而关注其在处理足够大规模问题时的性能,即所谓的渐进复杂度(Asmpototic Complexity)。另外,通常我们也不需要知道T(n)的确切大小,而只需要对其上界作出估计。比如说,如果存在正常数a、N和一个函数f(n),使得对于任何n > N,都有T(n) < a × f(n) 我们就可以认为在n足够大之后,f(n)给出了T(n)的一个上界。对于这种情况,我们记之为T(n) = O(f(n))这里的O称作“大O记号(Big-O notation)”,是希腊字母omicron的大写形式。从上述例子可以看出,大O记号实质上是对算法执行效率的一种保守估计⎯⎯对于规模为n的任意输入,算法的运行时间都不会超过O(f(n))。换言之,大O记号是对算法执行效率最差情况的估算。大O记号是渐进记法的一种。渐进记法一直就是人们用于分析算法和数据结构的重要工具。其核心思想是:提供一种资源表示形式,主要用于分析某项功能在应对一定规模参数时需要的资源(通常是时间,有时候也会是内存)。常用的渐进记法还包括大Θ记号、大Ω记号。1.3.2 大Ω标记法如果存在正常数a、N和一个函数g(n),使得对于任何n>N,都有T(n) > a × g(n) 我们就可以认为在n足够大之后,g(n)给出了T(n)的一个下界。对于这种情况,我们记之为T(n) = Ω(g(n)) 这里的Ω称作“大Ω记号(Big-Ω notation)”,是希腊字母omega的大写形式。大Ω记号与大O记号正好相反,它是对算法执行效率的一种乐观估计⎯⎯对于规模为n的任意输入,算法的运行时间都不会低于Ω(g(n))。换言之,大O记号是对算法执行效率最好情况的估算。1.3.3 大Θ标记法如果存在正常数a<b、N和一个函数h(n),使得对于任何n > N,都有a × h(n) < T(n) < b × h(n) 我们就可以认为在n足够大之后,h(n)给出了T(n)的一个确界。对于这种情况,我们记之为T(n) = Θ(h(n)) 这里的Θ称作“大Θ记号(Big-Θ notation)”,是希腊字母theta的大写形式。大Θ记号是对算法执行效率的一种准确估计⎯⎯对于规模为n的任意输入,算法的运行时间都与Θ(h(n))同阶。1.3.4 渐近记法总结总结而言,渐近记法的含义如下表1-2所示。表1-2 渐近记法含义符号含义O渐进小于或等于Ω渐进大于或等于Θ渐进等于在上面度量算法复杂度的三种记号中,大O记号是最基本的,也是最常用到的。本书后续的算法复杂度也主要采用按照大O记号来表示。参考引用原本同步至:https://waylau.com/asymptotic-notation/本系列归档至《Java数据结构及算法实战》:https://github.com/waylau/java-data-structures-and-algorithms-in-action《数据结构和算法基础(Java语言实现)》:https://item.jd.com/13014179.html
本节是《Java数据结构及算法实战》系列的第2节,主要介绍描述算法的常用的4种方式。要定义一个算法,我们可以用自然语言、流程图、伪代码的方式描述解决某个问题的过程或是编写一段程序来实现这个过程。比如,在前面所举的“学生信息管理系统”例子中,我们希望实现添加用户、删除用户、查询用户三个算法。1. 自然语言描述算法可以采用自然语言的方式来描述添加用户、删除用户、查询用户三个算法:添加用户:将用户信息添加到系统中。如果已经添加了过该用户的信息,则提示用户。否则将用户信息添加到系统中,并给出提示。删除用户:将用户信息从系统中删除。如果用户信息不存在于系统中,则提示用户。否则将用户信息从系统中删除,并给出提示。查询用户:将系统中所有的用户信息查询出来。如果系统中不存在用户,则提示用户。否则将用户信息查询出来返回,并将用户信息打印出来。使用自然语言描述的好处是任何人都能看懂。当然相比于伪代码或者程序语言而言,使用自然语言描述有时会显得繁琐。2. 流程图描述算法流程图(Flow Diagram)是一种通用的图形符号表示法是一种非正式的,可以清楚描述步骤和判断。图1-2展示的是用流程图的方式来描述添加用户、删除用户、查询用户三个算法。相比较自然语言而言,通过流程图的描述,可以很清楚的看到操作的流向及经过的步骤。但需要注意的是,流程图应该只描述核心的操作步骤以及关键的节点判断,而不是事无巨细的把所有的操作都描述出来,否则只会让整个图看上去复杂难以理解。3. 伪代码描述算法伪代码(Pseudocode)是一种非正式的,类似于英语结构的,用于描述模块结构图的语言。可以采用伪代码的方式来描述添加用户、删除用户、查询用户三个算法。添加用户的伪代码如下:input(student) if student in studentList print "Student exsit" else add student in studentList print "Add student success"删除用户的伪代码如下:input(student) if student in studentList remove student from studentList print "Remove student success" else print "Student not exsit"查询用户的伪代码如下:if student in studentList output studentList else print "No student exsit"伪代码结构清晰、代码简单、可读性好,并且类似自然语言。介于自然语言与编程语言之间。以编程语言的书写形式指明算法职能。使用伪代码,不用拘泥于具体实现。相比程序语言(例如Java、C++、C等等)它更类似自然语言。它虽然不是标准的语言,却可以将整个算法运行过程的结构用接近自然语言的形式(可以使用任何一种你熟悉的文字,关键是把程序的意思表达出来)描述出来。4. 程序语言描述算法程序语言描述算法,实际上就是用程序语言实现算法。不同的编程语言其语法不尽相同。以下是采用Java语言的方式来描述添加用户、删除用户、查询用户三个算法。import java.util.ArrayList; import java.util.List; public class StudentInfoManageSystem { private List<Student> studentList = new ArrayList<>(); public void addStudent(Student student) { // 如果已经添加了过该用户的信息,则提示用户。 // 否则将用户信息添加到系统中,并给出提示。 if (studentList.contains(student)) { System.out.println("Student exsit"); } else { studentList.add(student); System.out.println("Add student success"); } } public void removeStudent(Student student) { // 如果用户信息不存在于系统中,则提示用户。 // 否则将用户信息从系统中删除,并给出提示。 if (studentList.contains(student)) { studentList.remove(student); System.out.println("Remove student success"); } else { System.out.println("Student not exsit"); } } public List<Student> getStudentList() { // 如果系统中不存在用户,则提示用户。 // 否则将用户信息查询出来返回,并将用户信息打印出来。 if (studentList.isEmpty()) { System.out.println("No student exsit"); } else { for (Student s : studentList) { System.out.format("Student info: name %s, age %d, phone %s, address %s%n", s.getName(), s.getAge(), s.getPhoneNumer(), s.getAddress()); } } return studentList; } }为了演示上述算法,还需要一个应用入口。我们用StudentInfoManageSystemDemo类来表示应用主程序,代码如下:import java.util.ArrayList; import java.util.List; public class StudentInfoManageSystemDemo { public static void main(String[] args) { // 初始化系统 StudentInfoManageSystem system = new StudentInfoManageSystem(); // 初始化学生信息 Student student = new Student(32, "Way Lau", "17088888888", "Shenzhen"); // 添加学生 system.addStudent(student); // Add student success // 再次添加学生 system.addStudent(student); // Student exsit // 第一次查询所有学生 List<Student> studentList = system.getStudentList(); // 删除学生 system.removeStudent(student); // Remove student success // 再次删除学生 system.removeStudent(student); // Student not exsit // 查询所有学生 studentList = system.getStudentList(); // No student exsit } }运行上述程序,可以看到控制台输出内容如下:Add student success Student exsit Student info: name Way Lau, age 32, phone 17088888888, address Shenzhen Remove student success Student not exsit No student exsit程序语言描述算法一步到位,写出的算法可直接交予计算机处理。对于懂得这类程序语言的开发者而言,通过运行程序可以马上验证算法的正确性。当然其缺点也较为明显:不便于体现自顶向下、逐步求解的思想;程序语言包含很多细节内容,会淹没算法的主要思想。因此,在描述某个算法时,往往通过几种描述方式结合起来使用。参考引用原本同步至:https://waylau.com/description-of-algorithms/本系列归档:https://github.com/waylau/java-data-structures-and-algorithms-inaction数据结构和算法基础(Java语言实现):https://item.jd.com/13014179.html
本节是《Java数据结构及算法实战》系列的第1节,主要介绍数据结构和算法概念。对于接触过计算机基础知识的读者而言,对于下面这个公式应该不会陌生:算法 + 数据结构 = 程序提出这一公式并以此作为其一本专著书名[1]的瑞士计算机科学家Niklaus Wirth于1984年获得了图灵奖。程序(Program)是由数据结构(Data Structure)和算法(Algorithm)组成,这意味着的程序的好快是直接由程序所采用的数据结构和算法决定的。什么是数据结构数据结构可以简单理解为是承载数据元素的容器,这个容器中的数据元素之间存在一种或者多种特性关系。比如在Java中,Map和List就是非常常见的数据结构,他们提供了非常方便的方法用于将数据元素添加到这类容器中,同时也提供了在容器中查找数据的方法。举一个实际的例子,假设我们有一个“学生信息管理系统”需要管理学生的信息,在Java中,我们可以将学生的信息存储在List<Student>这个结构中,代码如下:List<Student> studentList = new ArrayList<>();学生这个类型Student代码如下:public class Student { private Integer age; // 年龄 private String name; // 姓名 private String phoneNumer; // 电话号码 private String address; // 地址 public Student(Integer age, String name, String phoneNumer, String address) { super(); this.age = age; this.name = name; this.phoneNumer = phoneNumer; this.address = address; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhoneNumer() { return phoneNumer; } public void setPhoneNumer(String phoneNumer) { this.phoneNumer = phoneNumer; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }理论上,学生的所有信息,都可以有在Student类型中做映射,但实际上,在设计计算机系统,我们往往只会记录对该系统相关的信息,比如学生的年龄、姓名、电话号码、地址等。我们没有记录诸如学生的爱好、偶像等信息,因为这些信息对于“学生信息管理系统”而言毫无用处。研究数据结构时,主要是从三个方面入手,这三个方面称为数据结构的三要素:数据的物理结构数据的逻辑结构数据的操作(即算法)1. 物理结构数据的物理结构,是指数据在计算机中的存储形式。因此,物理结构又叫存储结构。物理结构可分为四种:顺序存储结构、链式存储结构、索引结构、散列结构。其优缺点总结如下表1-1所示。表1-1各种物理结构的优缺点物理结构特征优点缺点顺序存储结构一段连续的内存空间能随机访问插入删除效率低,大小固定链式存储结构不连续的内存空间大小动态扩展,插入删除效率高不能随机访问索引存储结构整体无序,但索引块之间有序,需要额外空间,存储索引表对顺序查找的一种改进,查找效率高需额外空间存储索引散列存储结构数据元素的存储位置与散列值之间建立确定对应关系查找基于数据本身即可找到,查找效率高,存取效率高存取随机,不便于顺序查找2. 逻辑结构逻辑结构分为四种类型:集合结构、线性结构、树形结构和图形结构,如下图所示1-1所示。集合结构:就是数据元素同属一个集合,单个数据元素之间没有任何关系。线性结构:类似于线性关系,也就是说,线性结构中的数据元素之间是一对一的关系。线性结构也称为线性表。树形结构:树形结构中的数据元素之间存在一对多的关系。图形结构:数据元素之间是多对多的关系。因此,数据的逻辑结构通常可以采用一个二元组来表示:Data_Structure = (D,R)其中,D是数据元素的有限集,R是D上关系的有限集。在上述数据的逻辑结构分类的基础上,还可以进一步细化,衍生出多少种常见的抽象数据类型,包括数组、链表、矩阵、栈、队列、跳表、散列、树、图等。同时,在书中也会给出上述抽象数据类型的Java实现[2]。什么是算法算法就是解决问题的步骤。比如,要将大象装进冰箱,需要分为三个步骤:第一步,把冰箱门打开第二步,把大象放进去第三步,把冰箱门关上在计算机科学领域,算法这个词来描述一种有限、确定、有效的并适合用计算机程序来实现的解决问题的方法[3]。算法是计算机科学的基础,是这个领域研究的核心。那么如何来理解算法的有限性、确定性和有效性呢?有限性:算法在有限的执行步骤之后,一定会结束,不会产生无限循环。确定性:算法的每一个指令和步骤都是简洁明确的。有效性:算法的步骤是清晰可行的,换言之,即便用户是用纸笔计算也能求解出答案。[1]该书名为Algorithms + Data Structures = Programs,在1975年由Prentice Hall出版社出版[2]本书不会对Java语言本身做过多的介绍。如果读者想深入了解Java语言,可以参阅笔者所著的《Java核心编程》。该书在2020年由清华大学出版社出版[3]出自Robert Sedgewick和Kevin Wayne所著的《算法》一书。该书第4版在2012年由人民邮电出版社出版参考引用原本同步至:https://waylau.com/what-are-data-structures-and-algorithms/本系列归档:https://github.com/waylau/java-data-structures-and-algorithms-inaction数据结构和算法基础(Java语言实现):https://item.jd.com/13014179.html
《数据结构和算法基础(Java语言实现)》一书由北京大学出版社出版,已经于近日上市。拿到了样书,第一时间希望与读者朋友们分享下这本书里面的内容。为啥要写这本书12月6日拿到了样书,迫不及待的对新书做了浏览。同时也做了拆书与导读,可以在B站找到:https://www.bilibili.com/video/BV1fY411s7Kr/聊下为啥要写这本书。其实,这本是我所编写过的书目(https://waylau.com/books/)里面,算是最为“低级”的课题了吧,毕竟谁不知道“数据结构和算法”呢?这个课题太基础了。但是“数据结构和算法”却又是非常重要的课程。算法和数据结构是程序的灵魂,在计算机类培训课程中属于必开的课程。虽然实际工作中大多数人并不是专业的算法工程师,不以算法为深,但不可否认算法在工作中的重要性,初级工程师与高级工程师的差距也许就在对于算法的理解上。理解算法,运用合理的数据结构,可以让程序更加高效。随着云计算、大数据、人工智能、虚拟现实等应用的兴起,企业对于开发人员的算法技术要求也越来越高。不会算法或不精通算法,也许就会错过很多就业良机。另外,在求职时,算法是面试的必考类型。鉴于算法和数据结构在编程中的重要性,笔者迫不及待地希望将工作中常用的算法介绍给大家。因此,笔者陆续在个人开源网站https://github.com/waylau/java-data-structures-and-algorithms-inaction>上发表了众多关于算法的技术博客。2020年年底,笔者将之前算法相关的个人博客整理成册,遂有了本书。三大特点概况起来,这本《数据结构和算法基础(Java语言实现)》主要有三大特点。B站也有相关介绍:https://www.bilibili.com/video/BV1Lg411P7LP/1、涉及面非常广那么涉及广的话可以体现在哪里呢?可以看这本书的内容简介部分。该书分为以下几部分:第一部分 预备知识(第1-2章):介绍数据结构和算法的基本概念,并演示如何搭建开发环境、编写测试用例。第二部分 数据结构(第3-13章):介绍常见的数据结构,包括数组、链表、矩阵、栈、队列、跳表、散列、树、图等。第三部分 常用算法(第14-19章):介绍常用的算法,包括分而治之、动态规划、贪心算法、回溯、遗传算法、蚂蚁算法等。第四部分 商业实战(第20章):介绍汉诺塔游戏的实现。可以说,基本上你常见的一些是业务上还是技术常用的一些数据结构和算法,这本书都已经涉及了。更难能可贵的是,这本书也对当前非常火爆的诸如AI、机器学习等算法也做了讲解。2、图例非常丰富这本书是他这个图例非常丰富,从基本的IDE安装,到复杂的数据结构的演示,都有丰富的图例。那么在讲这种数据结构或者算法理论的时候,通过图例的配套讲解演示,可以方便读者理解。3、代码量非常大第三个特点的话就是它里面的实战案例非常丰富。实战案例体现在,这本书的每一章每个知识点基本上会配套一个实战案例,代码量是非常大的。每行代码这个重点代码它都有一些注释给你写得明明白白。这本书呢是不单只是简单的讲一些理论,它还有会手把手的教你写代码。理论联系实际。学习本书,一起手撕算法!参考引用原本同步至:https://waylau.com/java-data-structures-and-algorithms-in-action-book-three-features/京东有售:https://item.jd.com/13014179.html
JDK 17已经于2021年3月16日如期发布。本文介绍JDK 17新特性。JDK 17于2021年9月14日正式发布(General-Availability Release)。JDK 17将是大多数供应商的长期支持(LMS)版本。上一个LTS版本是JDK 11。本文总结了JDK 17发布的新特性。发布版本说明根据发布的规划,这次发布的 JDK 17 将是一个长期支持版(LTS 版)。LTS 版每 3 年发布一个,上一次长期支持版是 18 年 9 月发布的 JDK 11。 JDK 17是Java SE平台版本17的开源参考实现,由JSR 392在JCP(Java Community Process)指定。安装包下载主要分为OpenJDK版本和Oracle版本,下载地址如下:OpenJDK版本:https://jdk.java.net/16/Oracle版本:https://www.oracle.com/java/technologies/downloads/上述版本,如果是个人学习用途,则差异不大。但如果是用于商业用途,则需要仔细看好相关的授权。Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。 更多有关Java的基本知识,可以参阅《Java核心编程》这本书,描述的非常详细。JDK 17 新特性说明JEP 406:switch的模式匹配(预览)(JDK-8213076)specification通过switch表达式和语句的模式匹配,以及模式语言的扩展,增强Java编程语言。将模式匹配扩展到switch允许对表达式进行测试,每个模式都有特定的操作,以便可以简洁而安全地表达复杂的面向数据的查询。有关更多详细信息,请参见JEP 406JEP 409:密封类(JDK-8260514)specification密封类(Sealed Class)已添加到Java语言中。密封类和接口限制了哪些其他类或接口可以扩展或实现它们。密封类由JEP 360并在JDK 15中作为预览功能交付。它们再次被提出,并进行了改进,由JEP 397并在JDK 16中作为预览功能提供。现在,在JDK 17中,密封类正在最终确定,与JDK 16没有任何更改。有关更多详细信息,请参见JEP 409JEP 382:新的macOS渲染管道(JDK-8238361)client-libs/2dSwing API用于渲染的Java 2D API现在可以使用新的Apple Metal加速渲染API 给macOS。目前默认情况下,这是禁用的,因此渲染仍然使用OpenGL API,这些API被Apple弃用,但仍然可用和支持。要启用金属,应用程序应通过设置系统属性指定其使用:-Dsun.java2d.metal=trueMetal或OpenGL的使用对应用程序是透明的,因为这是内部实现的区别,对Java API没有影响。Metal管道需要macOS 10.14.x或更高版本。在早期版本上设置它的尝试将被忽略。有关更多详细信息,请参见[JEP 382(https://openjdk.java.net/jeps/382)大图标访问新API(JDK-8182043)client-libs/javax.swingJDK 17中提供了一个新的方法javax.swing.filechooser.FileSystemView.getSystemIcon(File, int, int),该方法允许在可能的情况下访问更高质量的图标。它已为Windows平台完全实施;但是,其他平台上的结果可能会有所不同,稍后将增强。例如,通过使用以下代码:FileSystemView fsv = FileSystemView.getFileSystemView(); Icon icon = fsv.getSystemIcon(new File("application.exe"), 64, 64); JLabel label = new JLabel(icon);用户可以为“application.exe”文件获得更高质量的图标。此图标适用于创建可以在HighDPI环境中更好地扩展的标签。DatagramSocket可以直接加入多播组(JDK-8237352)core-libs/java.netjava.net.DatagramSocket在此版本中已更新,以添加对加入多播组(multicast group)的支持。它现在定义了加入和离开多播组的加入组和离开组方法。java.net.DatagramSocket的类级API文档已更新,以解释如何配置普通DatagramSocket并用于加入和离开多播组。此更改意味着DatagramSocket API可以用于组播应用程序,而无需使用旧的java.net.MulticastSocket API。MulticastSocket API的工作原理和以前一样,尽管它的大多数方法都被弃用了。有关此变更理由的更多信息,请查看CSRJDK-8260667JEP 356:增强型伪随机数生成器(JDK-8193209)core-libs/java.util为伪随机数生成器(PRNG)提供新的接口类型和实现,包括可跳转的PRNG和一类额外的可拆分PRNG算法(LXM)。有关更多详细信息,请参见JEP 356Ideal Graph Visualizer的现代化(JDK-8254145)hotspot/compilerIdeal Graph Visualizer(IGV)是一个可视化和交互式地探索HotSpot VM C2即时(JIT)编译器中使用的中间表示的工具,已经现代化。增强功能包括:支持在最多JDK 15上运行IGV(IGV底层NetBeans平台支持的最新版本)更快的、基于Maven的IGV构建系统块形成、组删除和节点跟踪的稳定默认过滤器中更直观的着色和节点分类具有更自然默认行为的排名快速节点搜索现代化的IGV部分兼容从早期JDK版本生成的图形。它支持基本功能,如图形加载和可视化,但辅助功能,如节点聚类和着色可能会受到影响。有关构建和运行IGV的详细信息,请参见https://github.com/openjdk/jdk17/tree/master/src/utils/IdealGraphVisualizer。“New API”的新页面和改进的“Deprecated”页(JDK-8263468)tools/javadoc(tool)JavaDoc现在可以生成一个页面,总结API中最近的更改。要包括的最近版本的列表是使用 --since命令行选项指定的。这些值用于查找具有匹配@since的声明,因为要包含在新页面上的标记。--since-label命令行选项提供了要在“New API”页面标题中使用的文本。在总结已弃用项目的页面上,您可以查看按已弃用项目的版本分组的项目。错误消息中的源详细信息(JDK-8267126)tools/javadoc(tool)当JavaDoc报告输入源文件中的问题时,它将以类似编译器(javac)诊断消息的方式显示问题的源行,以及包含指向该行位置的插入符号(^)的行。此外,日志记录和其他“信息”消息现在写入标准错误流,留下标准输出流用于命令行选项特别请求的输出,如命令行帮助。JEP 412:外部函数和内存API(孵化)(JDK-8265033)core-libs引入一个API,Java程序可以通过该API与Java运行时之外的代码和数据互操作。通过有效地调用外部函数(即JVM外部的代码),并通过安全地访问外部内存(即不由JVM管理的内存),该API使Java程序能够调用本机库并处理本机数据,而不会有JNI的脆弱性和危险。有关更多详细信息,请参阅JEP 412控制台字符集API(JDK-8264208)core-libsjava.io.Console已更新,以定义一个新方法,该方法返回控制台的Charset。返回的Charset可能与Charset.defaultCharset()方法返回的Charset不同。例如,它返回IBM437,而Charset.defaultCharset()在Windows (en-US)上返回windows-1252。请参阅https://bugs.openjdk.java.net/browse/JDK-8264209了解更多详细信息。用于反序列化的JDK Flight Recorder事件(JDK-8261160)core-libs/java.io:serialization现在可以使用JDK Flight Recorder (JFR)监控对象的反序列化。当启用JFR且JFR配置包括反序列化事件时,每当运行程序尝试反序列化对象时,JFR将发出事件。反序列化事件名为jfr.Derialization,默认情况下禁用。反序列化事件包含序列化筛选器机制使用的信息;请参阅对象输入筛选器规范。此外,如果启用了过滤器,JFR事件指示过滤器是接受还是拒绝对象的反序列化。有关如何使用JFR反序列化事件的更多信息,请参阅文章监控反序列化提高应用安全性。有关使用和配置JFR的参考信息,请参阅JFR运行时指南和JFR命令参考JDK任务控制文件的章节。JEP 415:实现特定于上下文的反序列化过滤器(JDK-8264859)core-libs/java.io:serializationJEP 415:特定于上下文的反序列化过滤器允许应用程序通过JVM范围的过滤器工厂配置特定于上下文的和动态选择的反序列化过滤器,该工厂被调用以为每个单独的反序列化操作选择过滤器。用于序列化过滤的Java核心库开发人员指南介绍了用例,并提供了示例。本机字符编码名称的系统属性(JDK-8265989)core-libs/java.lang引入了一个新的系统属性本机.encode。此系统属性提供基础主机环境的字符编码名称。例如,它通常在Linux和macOS平台中具有UTF-8,在Windows (en-US)中具有Cp1252。请参阅https://bugs.openjdk.java.net/browse/JDK-8266075了解更多详细信息。添加java.time.InstantSource (JDK-8266846)core-libs/java.time引入了一个新的接口java.time.InstantSource。此接口是java.time.Clock的抽象,只关注当前时刻,不引用时区。十六进制格式和解析实用程序(JDK-8251989)core-libs/java.utiljava.util.HexFormat为基元类型和字节数组提供十六进制和十六进制之间的转换。分隔符、前缀、后缀和大写或小写的选项由返回HexFormat实例的工厂方法提供。实验Compiler Blackholes支持(JDK-8259316)hotspot/compiler增加了对Compiler Blackholes的实验支持。这些对于低级基准测试非常有用,以避免关键路径上的死代码消除,而不影响基准性能。当前的支持以CompileCommand的形式实现,可访问为-XX:CompileCommand=blackhole,,并计划最终将其毕业到公共API。JMH已经能够在指示/可用时自动检测和使用此设施。有关后续步骤,请查阅JMH文档。HotSpot JVM中的新类层次结构分析实现(JDK-8266074)hotspot/compilerHotSpot JVM中引入了一个新的类层次结构分析实现。它的特点是对抽象和默认方法的增强处理,从而改进了JIT编译器所做的内联决策。新实现将取代原始实现,并在默认情况下打开。为了帮助诊断与新实现相关的可能问题,可以通过指定 -XX:+UnlockDiagnosticVMOptions -XX:-UseVtableBasedCHA命令行标志来打开原始实现。原始实现可能会在未来的版本中删除。JEP 391: macOS/AArch64端口(JDK-8251280)hotspot/compilermacOS 11.0现在支持AArch64体系结构。此JEP在JDK中实现了对macos-aarch64平台的支持。添加的功能之一是支持W^X(write xor execute)内存。它仅对macos-aarch64启用,并可以在某些时候扩展到其他平台。JDK可以在英特尔计算机上交叉编译,也可以在基于Apple M1的计算机上编译。有关更多详细信息,请参见JEP 391统一日志支持异步日志刷新(JDK-8229517)hotspot/runtime为了避免使用统一日志记录的线程中出现不希望的延迟,用户现在可以请求统一日志记录系统在异步模式下运行。这可以通过传递命令行选项-Xlog:async来完成。在异步日志记录模式下,日志站点将所有日志记录消息入队到缓冲区。独立线程负责将它们刷新到相应的输出。中间缓冲区是有界的。缓冲区耗尽时,入队消息将被丢弃。用户可以使用命令行选项-XX:AsyncLogBufferSize=.来控制中间缓冲区的大小。ARM上的macOS早期访问可用(JDK-8266858)infrastructure/build新的macOS现在可用于ARM系统。ARM端口的行为应与英特尔端口类似。没有已知的功能差异。在macOS上报告问题时,请指定是使用ARM还是x64。支持在Keytool -genkeypair命令中指定签名者(JDK-8260693)security-libs/java.security-signer和-signerkeypass选项已添加到keytool实用程序的-genkey对命令中。-signer选项指定签名者的私钥条目的密钥库别名,-signerkeypass选项指定用于保护签名者私钥的密码。这些选项允许keytool -genkey对使用签名者的私钥对证书进行签名。这对于生成具有密钥协商算法作为公钥算法的证书特别有用。SunJCE提供程序通过AES密码支持KW和KWP模式(JDK-8248268)security-libs/javax.cryptoSunJCE提供程序已得到增强,以支持AES密钥换行算法(RFC 3394)和带填充算法的AES密钥换行算法(RFC 5649)。在早期版本中,SunJCE提供程序在“AESWrap”密码算法下支持RFC 3394,该算法只能用于包装和解包装密钥。通过此增强,增加了两种分组密码模式,KW和KWP,支持使用AES进行数据加密/解密和密钥包装/解包装。有关更多详细信息,请查看“JDK提供程序文档”指南的“SunJCE提供程序”部分。新SunPKCS11配置属性(JDK-8240256)security-libs/javax.crypto:pkcs11SunPKCS11提供程序添加了新的提供程序配置属性,以更好地控制本机资源的使用。SunPKCS11提供程序使用本机资源以便与本机PKCS11库一起工作。为了管理和更好地控制本机资源,添加了额外的配置属性,以控制清除本机引用的频率,以及是否在注销后销毁基础PKCS11令牌。SunPKCS11提供程序配置文件的3个新属性是:destroyTokenAfterLogout (布尔值,默认值为false)如果设置为true,则在SunPKCS11提供程序实例上调用java.security.AuthProvider.logout() 时,基础令牌对象将被销毁,资源将被释放。这基本上会在logout() 调用后使SunPKCS11提供程序实例不可用。请注意,不应将此属性设置为true的PKCS11提供程序添加到系统提供程序列表中,因为提供程序对象在logout() 方法调用后不可用。cleaner.shortInterval(整数,默认值为2000,以毫秒为单位)这定义了在繁忙期间清除本机引用的频率,即cleaner线程应多久处理队列中不再需要的本机引用以释放本机内存。请注意,cleaner线程将在200次失败尝试后切换到“longInterval”频率,即在队列中找不到引用时。cleaner.longInterval(整数,默认值为60000,以毫秒为单位)这定义了在非繁忙期间检查本机引用的频率,即cleaner线程应检查队列中的本机引用的频率。请注意,如果检测到用于清理的本机PKCS11引用,cleaner线程将切换回“短间隔”值。具有系统属性的可配置扩展(JDK-8217633)security-libs/javax.net.ssl已添加两个新的系统属性。系统属性jdk.tls.client.disableExts用于禁用客户端中使用的TLS扩展。系统属性jdk.tls.server.disableExts用于禁用服务器中使用的TLS扩展。如果禁用了扩展,则在握手消息中既不会生成也不会处理扩展。属性字符串是在IANA文档中注册的逗号分隔的标准TLS扩展名称列表(例如,server_name、status_request和签名_algorithms_cert)。请注意,扩展名区分大小写。未知、不支持、拼写错误和重复的TLS扩展名称令牌将被忽略。请注意,阻止TLS扩展的影响是复杂的。例如,如果禁用了强制扩展,则可能无法建立TLS连接。请不要禁用强制扩展,除非您清楚地了解其影响,否则不要使用此功能。包摘要页面上的“Related Packages”(JDK-8260388)tools/javadoc(tool)软件包的摘要页面现在包括一个列出任何“Related Packages”的部分。Related Packages(相关软件包)是根据常见命名约定启发式确定的,可能包括以下内容:“parent”包(即,包是子包的包)同级包(即具有相同父包的其他包)任何子包相关软件包不一定都在同一个模块中。参考引用本文同步至: https://waylau.com/jdk-17-released/https://waylau.com/jdk-16-released/https://waylau.com/jdk-15-released/https://waylau.com/jdk-14-released/《Java核心编程》开源项目“现代Java案例大全” https://github.com/waylau/modern-java-demos
随着业务数据的增加,原有的数据库性能瓶颈凸显,以此就需要对数据库进行分库分表操作。为啥需要分库分表随着业务数据的增加,原有的数据库性能瓶颈凸显,主要体现在以下两个方面。IO瓶颈IO瓶颈主要有以下几种情况:第一种:磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的IO,降低查询速度。这种情况适合采用分库和垂直分表。第二种:网络IO瓶颈,请求的数据太多,网络带宽不够。这种情况适合采用分库。CPU瓶颈CPU瓶颈主要有以下几种情况:第一种:SQL问题,如SQL中包含join,group by,order by,非索引字段条件查询等,增加CPU运算的操作。这种情况适合采用SQL优化,建立合适的索引,或者把一些SQL操作移到在业务层中台代码中去做业务计算。第二种:单表数据量太大,查询时扫描的行太多,SQL效率低,CPU率先出现瓶颈这种情况适合采用水平分表。综上,大多数情况下,需要使用数据库的分库分表方案来解决性能瓶颈。理解分库分表“分库分表”本质就是把数据分到不同的数据库或者分到不同的数据表上,以减轻单库或者单表的数据量,从而降低访问单库或者单表时的数据压力。在理解了分库分表的重要性之后,那么来理解下分库分表的实现原理。水平分库水平分库是指,以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中。比如以下的例子。对用户表进行水平分库,分库的策略是对user_id字段进行取模。如果取模结果是0,则放入数据库01;如果取模结果是1,则放入数据库02。水平分库的结果是:每个库的结构都一样;每个库的数据都不一样,没有交集;所有库的并集是全量数据。水平分库适用的场景是,系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库。水平分表水平分表是指,以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中。比如以下的例子。对用户表user_t进行水平分表,分库的策略是对user_id字段进行取模。如果取模结果是0,则放入user_t_01表;如果取模结果是1,则放入user_t_02表。水平分表的结果是:每个表的结构都一样;每个表的数据都不一样,没有交集;所有表的并集是全量数据。水平分表适用的场景是,系统绝对并发量并没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。垂直分库垂直分库是指,以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。比如以下的例子。用户业务相关的表放入到01库,订单业务相关的表放入到02库。垂直分库的结果是:每个库的结构都不一样;每个库的数据也不一样,没有交集;所有库的并集是全量数据。垂直分库适用的场景是,系统绝对并发量上来了,并且可以抽象出单独的业务模块。到这一步,基本上就可以服务化了。例如,随着业务的发展一些公用的配置表、字典表等越来越多,这时可以将这些表拆到单独的库中,甚至可以服务化。再有,随着业务的发展孵化出了一套业务模式,这时可以将相关的表拆到单独的库中,甚至可以服务化或者微服务化。垂直分表垂直分表是指,以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。垂直分表的结果是:每个表的结构都不一样;每个表的数据也不一样,一般来说,每个表的字段至少有一列交集,一般是主键,用于关联数据;所有表的并集是全量数据。垂直分表适用的场景是,系统绝对并发量并没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈。比如以下“新闻头条”应用的例子,“新闻头条”分为了新闻列表页和新闻详情页。垂直分表的拆分原则是将热点数据(比如新闻的标题)放在一起作为主表(news_t),非热点数据(新闻的内容)放在一起作为扩展表(news_ext_t)。这样更多的热点数据就能被缓存下来,进而减少了随机读IO。拆了之后,要想获得全部数据就需要关联两个表来取数据。需要注意的是,垂直分表关联两个表查询的时候,避免使用join,因为join不仅会增加CPU负担并且会讲两个表耦合在一起(必须在一个数据库实例上)。关联数据,尽量是放在业务层中台来做。分库分表的几种分配策略hash取模比如,对用户表user_t进行水平分表,分库的策略是对user_id字段进行取模。如果取模结果是0,则放入user_t_01表;如果取模结果是1,则放入user_t_02表。范围分片(range)比如,user_id从1到10000作为一个分片,从10001到20000作为另一个分片。地理位置分片华南区一个分片,华北一个分片。时间分片按月、季度、年分片等等,可以做到冷热数据。比如,今年内的数据一般就是热数据,而往年的数据就是冷数据。那么可以分为 user_t_2021、user_t_2020等表,user_t_2021是热数据,user_t_2020为冷数据。参考引用本文同步至: https://waylau.com/database-sharding/https://shardingsphere.apache.org/document/current/en/overview/
本文介绍了MyBatis的${}和#{}的用法区别,以及针对$可能带来的风险提供一种简易的SQL防注入的方法。#{}用法select语句是MyBatis中最常用的元素之一,例如:<select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>此语句称为selectPerson,采用int(或Integer)类型的参数,并将查询结果封装为HashMap作为返回。语句中#{id}这告诉MyBatis创建一个PreparedStatement参数。对于JDBC而言,这样的参数类似于PreparedStatement语句中的“?”标识,如下:// JDBC代码 String selectPerson = "SELECT * FROM PERSON WHERE ID=?"; PreparedStatement ps = conn.prepareStatement(selectPerson); ps.setInt(1,id);${}用法默认情况下,使用#{}语法将导致MyBatis生成PreparedStatement属性,并根据PreparedStatement参数安全地设置值(例如“?”标识)。虽然这更安全、更快,而且几乎总是首选,但有时只是想直接将未修改的字符串注入SQL语句。例如,对于订单排序,可以使用类似的内容:ORDER BY ${columnName}在上面的用法中,MyBatis不会修改或转义字符串。但需要注意的是: ${}用法如果是直接接受用户的输入,将未经修改的语句注入到程序是不安全的。这将导致潜在的SQL注入攻击风险。针对${}的SQL防注入器${}用法存在SQL注入的风险,因此需要对用户的输入内容进行校验。这里提供一个简易的SQL防注入的方法。import java.util.regex.Matcher; import java.util.regex.Pattern; /** * SQL注入防护器 * * @author waylau.com * @since 2021-04-18 */ public class SqlInjectionProtector { private static final String SPECIAL_CHAR = "\\W"; /** * 校验SQL语句是否合法 * 如果非法,则抛出异常 * * @param statement 语句 * @return 是否合法 * @throws IllegalArgumentException 校验非法则抛出此异常 */ public static boolean verifySqLStatement(String statement) { if (!StringUtility.isEmpty(statement)) { if (isSpecialChar(statement)) { throw new IllegalArgumentException("illegal statement: " + statement); } } return Boolean.TRUE; } /** * 判断是否含有特殊字符 * * @param str 校验的字符串 * @return 是否特殊字符 */ public static boolean isSpecialChar(String str) { Pattern pattern = Pattern.compile(SPECIAL_CHAR); Matcher matcher = pattern.matcher(str); return matcher.find(); } }上述代码核心思想是,通过正则表达式的方式,来检测出特殊字符。有特殊字符,就抛出异常,中断程序继续往下运行。何为特殊字符?针对ORDER BY ${columnName} 这个例子而言,字段名的所使用的字符是有一定限制的,限制只能使用[a-z0-9A-Z_]这个范围内的字符。因此超出这个范围内的所有字符,即为特殊字符。在正则表达式里面,非[a-z0-9A-Z_]范围内的字符,可以用“\W”表示。以下是测试用例import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; /** * SqlInjectionProtector Test * * @author waylau.com * @since 2021-04-18 */ public class SqlInjectionProtectorTest { @Test public void testIsSpecialChar() { assertFalse(SqlInjectionProtector.isSpecialChar("user_name")); assertTrue(SqlInjectionProtector.isSpecialChar("user_name!")); assertTrue(SqlInjectionProtector.isSpecialChar("user_name@")); assertTrue(SqlInjectionProtector.isSpecialChar("user_name^")); assertTrue(SqlInjectionProtector.isSpecialChar("user_name ")); // 空格 assertTrue(SqlInjectionProtector.isSpecialChar("insert\ninto\nuser_t")); // 换行符 assertTrue(SqlInjectionProtector.isSpecialChar("user_name|user_t")); } @Test public void testVerifySqLStatement() { assertTrue(SqlInjectionProtector.verifySqLStatement("user_name")); } }参考引用本文同步至: https://waylau.com/mybatis-parameters-and-sql-inject-protector/《轻量级Java EE企业应用开发实战》(https://item.jd.com/12817685.html)《大型互联网应用轻量级架构实战》(https://item.jd.com/12629095.html)
JDK 16已经于2021年3月16日如期发布。本文介绍JDK 16新特性。发布版本说明根据发布的规划,这次发布的 JDK 16 将是一个短期的过度版。下一个长期支持版(LTS 版)会在今年的 9 月份候发布(Java 17),LTS 版每 3 年发布一个,上一次长期支持版是 18 年 9 月发布的 JDK 11。 JDK 16是Java SE平台版本16的开源参考实现,由JSR 390在JCP(Java Community Process)指定。安装包下载主要分为OpenJDK版本和Oracle版本,下载地址如下:OpenJDK版本:https://jdk.java.net/16/Oracle版本:https://www.oracle.com/java/technologies/javase-jdk16-downloads.html上述版本,如果是个人学习用途,则差异不大。但如果是用于商业用途,则需要仔细看好相关的授权。Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。 更多有关Java的基本知识,可以参阅《Java核心编程》这本书,描述的非常详细。JDK 16 新特性说明JDK 16 为用户提供了17项主要的增强/更改,包括全新的 Java 语言改进,工具和内存管理,以及还有一些孵化和预览特性,有了这些新功能,Java 会进一步提高开发人员的生产力。值得关注的变化是,JDK 14 中提供的预览特性:模式匹配和记录(Records),经过一年的社区反馈和实际应用,终于在 JDK 16 中完成最终落地了。另外,Oracle 还为 Java SE 订阅服务中免费提供 GraalVM 企业版服务,GraalVM 可以帮助提高应用程序的性能并减少资源消耗,尤其是在微服务和云原生架构中。1. 338: Vector API (孵化)这个不是集合中的Vector,而是一个新的初始迭代孵化器模块jdk.incubator.vector,用于表示在运行时可靠地编译到支持的 CPU 架构上的最佳矢量硬件指令的矢量计算。2. 347: Enable C++14 Language Features允许在 JDK 底层的C++源代码中使用C++14的新语言特性,并且提供了在HotSpot虚拟机代码中,哪些代码使用了这些新特性的指南。3. 357: Migrate from Mercurial to Git将 OpenJDK 社区的源代码存储库从 Mercurial 迁移到 Git。4. 369: Migrate to GitHub在 GitHub 上托管 OpenJDK 社区的 Git 存储库。GitHub 是世界流行的Git代码托管平台。在国内,托管代码推荐Gitee哦。5. 376: ZGC: Concurrent Thread-Stack ProcessingZGC 最早是在 JDK 11 中集成进来的,在 JDK 15 中正式转正。这个版本则是为了让 ZGC 支持并发栈处理,解决了最后一个重大瓶颈,把 ZGC 中的线程栈处理从安全点移到了并发阶段。并且还提供了一种机制,使得其他 HotSpot 子系统可以通过该机制延迟处理线程栈。6. 380: Unix-Domain Socket ChannelsUNIX 域套接字通道,为 java.nio.channels 包中的套接字通道和服务端套接字通道 APIs 增加 Unix 域套接字通道所有特性支持。UNIX 域套接字主要用于同一主机上的进程间通信(IPC),大部分方面与 TCP/IP套接字类似,不同的是 UNIX 域套接字是通过文件系统路径名寻址,而不是通过 IP 地址和端口号。7. 386: Alpine Linux Port在 x64 和 AArch64 平台体系结构上,将 JDK 移植到 Alpine Linux 以及使用 musl 作为其主要 C 语言库的其他 Linux 发行版中。8. 387: Elastic Metaspace弹性的元空间,可以帮助 HotSpot 虚拟机,将元空间中未使用的 class 元数据内存更及时地返回给操作系统,以减少元空间的内存占用空间。另外,还简化了元空间的代码,以降低维护成本。9. 388: Windows/AArch64 Port将 JDK 移植到 Windows/ AArch64 平台系列。10. 389: Foreign Linker API (孵化)引入了一个新的 API,该 API 提供了对本地 native 代码的静态类型访问支持。11. 390: Warnings for Value-Based Classes基于值的类的警告,将基础类型包装类指定为基于值的类,废除其构造函数以进行删除,从而提示新的弃用警告。并且提供了在任何基于值的类的实例上不正常进行同步的警告。12. 392: Packaging Tool提供了 jpackage 打包工具,可用于打包独立的 Java 应用程序。jpackage 打包工具是在 JDK 14 中首次作为孵化工具引入的新特性,到了 JDK 15 它仍然还在孵化中,现在它终于转正了。13. 393: Foreign-Memory Access API (三次孵化)该 API 允许 Java 应用程序安全有效地访问 Java 堆之外的外部内存。这个最早在 JDK 14 中成为孵化特性,JDK 15/ JDK 16 中继续二、三次孵化并对其 API 有了一些更新,这个可以在 JDK 17 中好好期待一下转正。14. 394: Pattern Matching for instanceof模式匹配 for instanceof,相当于是增强的 instanceof,在 JDK 14 中首次成为预览特性,在 JDK 16 中正式转正。模式匹配的到来将使得 instanceof 变得更简洁、更安全,为什么这么说,请看下面的示例。Java 14 之前用法:if (obj instanceof String) { String s = (String) obj; // 使用s }Java 14之后的用法:if (obj instanceof String s) { // 使用s }15. 395: Records简单来说,Records 就是一种新的语法糖,目的还是为了简化代码,在 JDK 14 中首次成为预览特性,在 JDK 16 中正式转正。Records 可以在一定程度上避免低级冗余的代码,比如:constructors, getters, equals(), hashCode(), toString() 方法等,相当于 Lombok 的 @Data 注解,但又不能完全替代。下面来看一个示例:旧写法:class Point { private final int x; private final int y; Point(int x, int y) { this.x = x; this.y = y; } int x() { return x; } int y() { return y; } public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return other.x == x && other.y = y; } public int hashCode() { return Objects.hash(x, y); } public String toString() { return String.format("Point[x=%d, y=%d]", x, y); } }新的Records写法:record Point(int x, int y) { }16. 396: Strongly Encapsulate JDK Internals by DefaultJDK 内部默认强封装,JDK 16 开始对 JDK 内部大部分元素默认进行强封装,sun.misc.Unsafe 之类的关键内部 API 除外,从而限制对它们的访问。此外,用户仍然可以选择自 JDK 9 以来的默认的宽松的强封装,这样可以帮助用户毫不费力地升级到未来的 Java 版本。17. 397: Sealed Classes (二次预览)封闭类(二次预览),可以是封闭类和或者封闭接口,用来增强 Java 编程语言,防止其他类或接口扩展或实现它们。参考引用本文同步至: https://waylau.com/jdk-16-released/https://waylau.com/jdk-15-released/https://waylau.com/jdk-14-released/《Java核心编程》https://github.com/waylau/modern-java-demos
ChannelHandler(管道处理器)其工作模式类似于Java Servlet过滤器,负责对I/O事件或者I/O操作进行拦截处理。采用事件的好处是,ChannelHandler可以选择自己感兴趣的事件进行处理,也可以对不感兴趣的事件进行透传或者终止。ChannelHandler接口基于ChannelHandler接口,用户可以方便实现自己的业务,比如记录日志、编解码、数据过滤等。ChannelHandler接口定义如下:package io.netty.channel; import io.netty.util.Attribute; import io.netty.util.AttributeKey; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public interface ChannelHandler { void handlerAdded(ChannelHandlerContext ctx) throws Exception; void handlerRemoved(ChannelHandlerContext ctx) throws Exception; void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; @Inherited @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Sharable { } }ChannelHandler接口定义比如简单,只有三个方法:handlerAdded方法在ChannelHandler被添加到实际上下文中并准备好处理事件后调用。handlerRemoved方法在ChannelHandler从实际上下文中移除后调用,表明它不再处理事件。exceptionCaught方法会在抛出Throwable类后调用。还有一个Sharable注解,该注解用于表示多个ChannelPipeline可以共享同一个ChannelHandler。正式因为ChannelHandler接口过于简单,我们在实际开发中,不会直接实现ChannelHandler接口,因此,Netty提供了ChannelHandlerAdapter抽象类。ChannelHandlerAdapter抽象类ChannelHandlerAdapter抽象类核心代码如下:package io.netty.channel; import io.netty.util.internal.InternalThreadLocalMap; import java.util.Map; import java.util.WeakHashMap; public abstract class ChannelHandlerAdapter implements ChannelHandler { boolean added; public boolean isSharable() { Class<?> clazz = getClass(); Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache(); Boolean sharable = cache.get(clazz); if (sharable == null) { sharable = clazz.isAnnotationPresent(Sharable.class); cache.put(clazz, sharable); } return sharable; } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // NOOP } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // NOOP } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.fireExceptionCaught(cause); } }ChannelHandlerAdapter对exceptionCaught方法做了实现,并提供了isSharable方法。需要注意的是,ChannelHandlerAdapter是抽象类,用户可以自由的选择是否要覆盖ChannelHandlerAdapter类的实现。如果对某个方法感兴趣,直接覆盖掉这个方法即可,这样代码就变得简单清晰。ChannelHandlerAdapter抽象类提供了两个子类ChannelInboundHandlerAdapter、ChannelOutboundHandlerAdapter用于针对出站事件、入站事件的进行处理。其中ChannelInboundHandlerAdapter实现了ChannelInboundHandler接口,而ChannelOutboundHandlerAdapter实现了ChannelOutboundHandler接口。在实际开发过程中,我们的自定义的ChannelHandler多数是继承自ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter类或者是这两个类的子类。比如在前面章节中所涉及的编解码器ByteToMessageDecoder、MessageToMessageDecoder、MessageToByteEncoder、MessageToMessageEncoder等,就是这两个类的子类。参考引用原文同步至https://waylau.com/netty-channel-handler《Netty原理解析与开发实战》
IntelliJ IDEA一个吸引人的地方在于,他有比较好的反编译工具,这让Eclipse用户牙痒痒。但不要紧,本文介绍如何在Eclipse IDE中使用IntelliJ IDEA的反编译工具Fernflower。 为啥需要反编译 很多jar不提供源码,那么打开class是这个鸟样。 不具备人类可读性。因此需要反编译。 什么是Fernflower 那么我是怎么知道Fernflower的呢?你随便用IntelliJ IDEA打开一个jar中的class文件,可以看到下面的信息,这就是IEDA中的反编译工具Fernflower。 看官网介绍Fernflower(https://github.com/JetBrains/intellij-community/tree/master/plugins/java-decompiler/engine) Fernflower is the first actually working analytical decompiler for Java and probably for a high-level programming language in general “Fernflower是第一个真正为Java工作分析反编译器,通常也适用于一般的高级编程语言” 看介绍是很牛批的样子,当然实际也是。 如何获取Fernflower 非常遗憾的是,Fernflower是IntelliJ IDEA独家所有,那我是怎么搞定的呢? 我先在eclipse市场找了下,没有找到Fernflower,却找到了Enhanced Class Decompiler 看官网介绍(https://marketplace.eclipse.org/content/enhanced-class-decompiler) Enhanced Class Decompiler integrates JD, Jad, FernFlower, CFR, Procyon seamlessly with Eclipse and allows Java developers to debug class files without source code directly. It also integrates with the eclipse class editor, m2e plugin, supports Javadoc, reference search, library source attaching, byte code view and the syntax of JDK8 lambda expression. 简言之,Enhanced Class Decompiler集JD、Jad、FernFlower、CFR、Procyon等各种反编译工具之大成。换言之,FernFlower就是我Enhanced Class Decompiler的一个子集呗。 呵呵,好一招曲线救国。用Enhanced Class Decompiler变相用了FernFlower。 如何在Eclipse IDE中安装Fernflower 1. 在线安装 这是最简单的方式。使用Eclipse的同学都懂。 安装地址是: https://ecd-plugin.github.io/update 2. 离线安装 获取离线安装包zip文件(见附件),在“Add Repository”中指定该zip文件即可。 3. 可选组件 一般就选Core就够用了,不嫌多就全选上。 装完重启Eclipse就能看到这个工具了。 怎么使用Fernflower 右键class文件,使用如何在Eclipse IDE中安装FernFlower打开即可 反编译成功!看到庐山真面目了。 参考引用 原文同步至 https://waylau.com/eclipse-install-fernflower/
正常情况下,在Java中入参是不建议用做返回值的。除了造成代码不易理解、语义不清等问题外,可能还埋下了陷阱等你入坑。 问题背景 比如有这么一段代码: @Named public class AService { private SupplyAssignment localSupply = new SupplyAssignment(); @Inject private BService bervice; public List<Supply> calcSupplyAssignment() List<Supply> supplyList = bService.getLocalSupplyList(this.localSupply); … return supplyList; } } 上面代码,服务A希望调用服务B,以获取supplyList,但同时,服务A又希望修改localSupply的状态值,未能避免修改calcSupplyAssignment接口的(不想改返回的类型),将localSupply作为了入参但同时也用作了返回值。 服务B代码如下: @Named public class BService { public List<Supply> getLocalSupplyList (SupplyAssignment localSupply) SupplyAssignment supplyAssignment = this.getSupplyAssignment(); // 希望localSupply被重新赋值后返回 localSupply = supplyAssignment; … return supplyList; } } 在服务B代码内部,服务A的入参localSupply被传入,希望重新被supplyAssignment赋值而后返回新值。然而,这样做是无效的。 问题原因 先来看下编程语言中关于参数传递的类型: 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 因为Java程序设计语言是采用的值传递,因为Java没有指针的概念。也就是说方法得到的是所有参数值的一个拷贝,方法并不能修改传递给它的任何参数变量的内容。 因此,上述代码中,服务A调用服务B时,服务B的参数localSupply实际上是服务A的localSupply的一个拷贝,当然,这两个都是指向了同一个地址对象supplyAssignment1。 当在服务B内部对参数localSupply进行重新赋值是localSupply = supplyAssignment,实际上,只是对B的参数localSupply做了从新赋值,B的参数localSupply会指向一个新的地址对象supplyAssignment2。 从上图可以清晰看到,因此,服务A的localSupply和B的参数localSupply已经指向了不同的对象了,对B的参数localSupply做任何的修改,都不会影响服务A的localSupply的原值。这就是问题的原因,你希望服务B来修改服务A入参的状态,并将改后的值返回给服务A,但并不奏效。 解决方案 方案1:入参不要用作返回值 当然,这个是最清晰的且易于理解的,但这会导致有的接口的返回类型产生变化。 有时确实想要入参做返回值,那看方案2。 方案2:入参不要赋值新对象 这个方案就是直接在入参的对象上做状态的修改,而不要去赋值新对象。还是这个图: 在这个图中,只要我们是一直在B的参数localSupply修改的是supplyAssignment1的状态值,那结果就能反馈到服务A的localSupply上。如何实现?看下下面代码: @Named public class BService { public List<Supply> getLocalSupplyList (SupplyAssignment localSupply) SupplyAssignment supplyAssignment = this.getSupplyAssignment(); // 针对localSupply不能新建引用,只能重新赋值属性 BeanUtils.copyProperties(supplyAssignment, localSupply); … return supplyList; } } 在上面的方法中,我们用到了Spring的工具类BeanUtils,该类的copyProperties方法的实质是将supplyAssignment的属性值,赋值到了localSupply的属性上。这意味着我们是修改的B的参数localSupply上的属性,而并未新建对象。 参考引用 原文同步至 https://waylau.com/trap-in-java-use-param-for-return/ Java核心编程
今天遇到一个奇怪的Java三元表达式中的空指针异常。特此记录。 代码 代码示意如下: Integer itemVO = null; Integer globleLatenessToleranceUseAlternate = null; Integer latenessToleranceUseAlternate = (itemVO == null ? globleLatenessToleranceUseAlternate : itemVO.intValue()); 从上面代码可以看出:当itemVO不为空时,就取itemVO的值;否则,就取globleLatenessToleranceUseAlternate的值。 原因 但问题就在globleLatenessToleranceUseAlternate。当itemVO为空时,如果取globleLatenessToleranceUseAlternate,并不会得到值null,而是Java会把globleLatenessToleranceUseAlternate进行一个自动开箱拆箱处理。简言之,取得是 globleLatenessToleranceUseAlternate.intValue(),此时,因为globleLatenessToleranceUseAlternate 本身是 null,因此 globleLatenessToleranceUseAlternate.intValue() 导致了空指针因此。 解法 修改如下解决: Integer itemVO = null; Integer globleLatenessToleranceUseAlternate = null; Integer latenessToleranceUseAlternate; if (itemVO != null) { latenessToleranceUseAlternate = itemVO.intValue(); } else { latenessToleranceUseAlternate = globleLatenessToleranceUseAlternate; } 值得注意的是,在新版的JDK和Eclipse中,会做出友好的提示,从而能够有效规避上述问题。提示如下: Null pointer access: This expression of type Integer is null but requires auto-unboxing 参考引用 原文同步至 https://waylau.com/trap-in-java-ternary-expressions/ Java核心编程
JDK 15已经于2020年9月15日如期发布。本文介绍JDK 15新特性。 发布版本说明 根据发布的规划,这次发布的 JDK 15 将是一个短期的过度版,只会被 Oracle 支持(维护)6 个月,直到明年 3 月的 JDK 16 发布此版本将停止维护。而 Oracle 下一个长期支持版(LTS 版)会在明年的 9 月份候发布(Java 17),LTS 版每 3 年发布一个,上一次长期支持版是 18 年 9 月发布的 JDK 11。 下图展示了各个版本的发布历史。 安装包下载 主要分为OpenJDK版本和Oracle版本,下载地址如下: OpenJDK版本:https://jdk.java.net/15/ Oracle版本:http://www.oracle.com/technetwork/java/javase/downloads/index.html 上述版本,如果是个人学习用途,则差异不大。但如果是用于商业用途,则需要仔细看好相关的授权。Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。 安装、验证 本例子以OpenJDK版本为例。解压安装包openjdk-15_windows-x64_bin.zip到任意位置。 设置系统环境变量“JAVA_HOME”,如下图所示。 在用户变量“Path”中,增加“%JAVA_HOME%bin”。 安装完成后,执行下面命令进行验证: >java -version openjdk version "15" 2020-09-15 OpenJDK Runtime Environment (build 15+36-1562) OpenJDK 64-Bit Server VM (build 15+36-1562, mixed mode, sharing) 更多有关Java的基本知识,可以参阅《Java核心编程》这本书,描述的非常详细。 JDK 15 新特性说明 JDK 15 为用户提供了14项主要的增强/更改,包括一个孵化器模块,三个预览功能,两个不推荐使用的功能以及两个删除功能。 1. EdDSA 数字签名算法 新加入 Edwards-Curve 数字签名算法(EdDSA)实现加密签名。在许多其它加密库(如 OpenSSL 和 BoringSSL)中得到支持。与 JDK 中的现有签名方案相比,EdDSA 具有更高的安全性和性能。这是一个新的功能。 使用示例如下: // example: generate a key pair and sign KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519"); KeyPair kp = kpg.generateKeyPair(); // algorithm is pure Ed25519 Signature sig = Signature.getInstance("Ed25519"); sig.initSign(kp.getPrivate()); sig.update(msg); byte[] s = sig.sign(); // example: use KeyFactory to contruct a public key KeyFactory kf = KeyFactory.getInstance("EdDSA"); boolean xOdd = ... BigInteger y = ... NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519"); EdECPublicKeySpec pubSpec = new EdECPublicKeySpec(paramSpec, new EdPoint(xOdd, y)); PublicKey pubKey = kf.generatePublic(pubSpec); 有关EdDSA 数字签名算法的详细内容见RFC 8032规范。 2. 封闭类(预览特性) 可以是封闭类和或者封闭接口,用来增强 Java 编程语言,防止其他类或接口扩展或实现它们。 有了这个特性,意味着以后不是你想继承就继承,想实现就实现了,你得经过允许才行。 示例如下: public abstract sealed class Student permits ZhangSan, LiSi, ZhaoLiu { ... } 类 Student 被 sealed 修饰,说明它是一个封闭类,并且只允许指定的 3 个子类继承。 3. 隐藏类 此功能可帮助需要在运行时生成类的框架。框架生成类需要动态扩展其行为,但是又希望限制对这些类的访问。隐藏类很有用,因为它们只能通过反射访问,而不能从普通字节码访问。此外,隐藏类可以独立于其他类加载,这可以减少框架的内存占用。这是一个新的功能。 4. 移除了 Nashorn JavaScript 脚本引擎 移除了 Nashorn JavaScript 脚本引擎、APIs,以及 jjs 工具。这些早在 JDK 11 中就已经被标记为 deprecated 了,JDK 15 被移除就很正常了。 Nashorn 是 JDK 1.8 引入的一个 JavaScript 脚本引擎,用来取代 Rhino 脚本引擎。Nashorn 是 ECMAScript-262 5.1 的完整实现,增强了 Java 和 JavaScript 的兼容性,并且大大提升了性能。 那么为什么要移除? 官方的解释是主要的:随着 ECMAScript 脚本语言的结构、API 的改编速度越来越快,维护 Nashorn 太有挑战性了,所以……。 5. 重新实现 DatagramSocket API 重新实现旧版 DatagramSocket API,更简单、更现代的实现来代替java.net.DatagramSocket和java.net.MulticastSocketAPI 的基础实现,提高了 JDK 的可维护性和稳定性。 新的底层实现将很容易使用虚拟线程,目前正在 Loom 项目中进行探索。这也是 JEP 353 的后续更新版本,JEP 353 已经重新实现了 Socket API。 6. 准备禁用和废除偏向锁 在 JDK 15 中,默认情况下禁用偏向锁(Biased Locking),并弃用所有相关的命令行选项。 后面再确定是否需要继续支持偏向锁,国为维护这种锁同步优化的成本太高了。 7. 模式匹配(第二次预览) 第一次预览是 JDK 14 中提出来的,点击这里查看我之前写的详细教程。 Java 14 之前用法: if (obj instanceof String) { String s = (String) obj; // 使用s } Java 14之后的用法: if (obj instanceof String s) { // 使用s } Java 15 并没有对此特性进行调整,继续预览特性,只是为了收集更多的用户反馈,可能还不成熟吧。 8. ZGC 功能转正 ZGC是一个可伸缩、低延迟的垃圾回收器。 ZGC 已由JEP 333集成到JDK 11 中,其目标是通过减少 GC 停顿时间来提高性能。借助 JEP 377,JDK 15 将 ZGC 垃圾收集器从预览特性变更为正式特性而已,没错,转正了。 这个 JEP 不会更改默认的 GC,默认仍然是 G1。 9. 文本块功能转正 文本块,是一个多行字符串,它可以避免使用大多数转义符号,自动以可预测的方式格式化字符串,并让开发人员在需要时可以控制格式。 文本块最早准备在 JDK 12 添加的,但最终撤消了,然后在 JDK 13 中作为预览特性进行了添加,然后又在 JDK 14 中再次预览,在 JDK 15 中,文本块终于转正,暂不再做进一步的更改。 Java 13 之前用法,使用one-dimensional的字符串语法: String html = "<html>\n" + " <body>\n" + " <p>Hello, world</p>\n" + " </body>\n" + "</html>\n"; Java 13 之后用法,使用two-dimensional文本块语法: String html = """ <html> <body> <p>Hello, world</p> </body> </html> """; 10. Shenandoah 垃圾回收算法转正 Shenandoah 垃圾回收从实验特性变为产品特性。这是一个从 JDK 12 引入的回收算法,该算法通过与正在运行的 Java 线程同时进行疏散工作来减少 GC 暂停时间。Shenandoah 的暂停时间与堆大小无关,无论堆栈是 200 MB 还是 200 GB,都具有相同的一致暂停时间。 JDK 15 Shenandoah垃圾收集器从预览特性变更为正式特性而已,没错,又是转正了。 11. 移除了 Solaris 和 SPARC 端口。 移除了 Solaris/SPARC、Solaris/x64 和 Linux/SPARC 端口的源代码及构建支持。这些端口在 JDK 14 中就已经被标记为 deprecated 了,JDK 15 被移除也不奇怪。 12. 外部存储器访问 API(二次孵化) 这个最早在 JDK 14 中成为孵化特性,JDK 15 继续二次孵化并对其 API 有了一些更新。 目的是引入一个 API,以允许 Java 程序安全有效地访问 Java 堆之外的外部内存。这同样是 Java 14 的一个预览特性。 13. Records Class(二次预览) Records Class 也是第二次出现的预览功能,它在 JDK 14 中也出现过一次了,使用 Record 可以更方便的创建一个常量类,使用的前后代码对比如下。 旧写法: class Point { private final int x; private final int y; Point(int x, int y) { this.x = x; this.y = y; } int x() { return x; } int y() { return y; } public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return other.x == x && other.y = y; } public int hashCode() { return Objects.hash(x, y); } public String toString() { return String.format("Point[x=%d, y=%d]", x, y); } } 新写法: record Point(int x, int y) { } 也就是说在使用了 record 之后,就可以用一行代码编写出一个常量类,并且这个常量类还包含了构造方法、toString()、equals() 和 hashCode() 等方法。 14. 废除 RMI 激活 废除 RMI 激活,以便在将来进行删除。需要说明的是,RMI 激活是 RMI 中一个过时的组件,自 Java 8 以来一直是可选的。 参考引用 本文同步至: https://waylau.com/jdk-15-released/ https://jdk.java.net/15/release-notes https://openjdk.java.net/projects/jdk/15/ https://openjdk.java.net/projects/jdk/15/spec/ 《Java核心编程》 https://github.com/waylau/modern-java-demos
曾几何时,业界流行使用LAMP架构(Linux、Apache、MySQL和PHP)来快速开发中小网站。LAMP是开放源代码的,而且使用简单、价格廉价,因此LAMP这个组合成为了当时开发中小网站的首选,号称“平民英雄”。而今,随着Node.js的流行,这使得JavaScript终于能够在服务器端拥有了一席之地。JavaScript成为了从前端到后端再到数据库层能够支持全栈开发的语言。而以MongoDB、Express、Angular和Node.js四种开源技术为基础的MEAN架构,除了具备LAMP架构的一切优点外,更能支撑高可用、高并发的大型互联网应用的开发。MEAN架构势必也会成为新的“平民英雄”。 本文介绍了MEAN架构的概念、发展趋势,并阐述了如何学习和使用MEAN架构。 什么是MEAN架构? MEAN架构,是指以MongoDB、Express、Angular和Node.js四种技术为核心的技术栈,广泛应用于全堆栈Web开发。 1. MongoDB MongoDB是强大的非关系型数据库(NoSQL)。与Redis或者HBase等不同,MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富、最像关系数据库的,旨在为Web应用提供可扩展的高性能数据存储解决方案。它支持的数据结构非常松散,是类似JSON的BSON格式,因此可以存储比较复杂的数据类型。MongoDB最大的特点是其支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。自MongoDB 4.0开始,MongoDB开始支持事务管理。 图1-1是最新的数据库排行结果。从图中可以看到,MongoDB是在NoSQL数据库中是排行第一的。该数据来自于DB-Engines(https://db-engines.com/en/ranking) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5FSWZ67e-1591539233123)(https://waylau.com/images/post/20200607-mongodb.png)] 在MEAN架构中,MongoDB承担着数据存储的角色。 2. Express Express是一个简洁而灵活的Node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用。同时,Express也是一款功能非常强大的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。其核心特性包括: 可以设置中间件来响应HTTP请求。 定义了路由表用于执行不同的HTTP请求动作。 可以通过向模板传递参数来动态渲染HTML页面。 在MEAN架构中,Express承担着构建Web服务的角色。 3. Angular 前端组件化开发是目前主流的开发方式,不管是Angular、React还是Vue.js都如此。相比较而言,Angular不管是其开发功能,还是编程思想,在所有前端框架中都是首屈一指的,特别适合大型企业级应用的开发。 Angular不仅仅是一个前端的框架,而更像是一个前端开发平台,试图解决现代Web应用开发各个方面的问题。Angular有着诸多特性,核心功能包括MVC模式、模块化、自动化双向数据绑定、语义化标签、服务、依赖注入等。而这些概念即便对于后端开发人员来说也不陌生。比如,Java开发人员肯定知道MVC模式、模块化、服务、依赖注入等。 在MEAN架构中,Angular承担着UI客户端开发的角色。 4. Node.js Node.js是整个MEAN架构的基石。Node.js采用事件驱动和非阻塞I/O模型,使其变得轻微和高效,非常适合构建运行在分布式设备的数据密集型实时应用。自从有了Node.js,JavaScript不再只是前端开发的小脚色,而是拥有了从前后台到数据数据库完整开发能力的全栈能手。JavaScript和Node.js是相辅相成的,配合流行的JavaScript语言,使得Node.js拥有更广泛的受众。 Node.js能够火爆的另外一个原因是npm。npm可以轻松管理项目依赖,同时也促进了Node.js生态圈的繁荣,因为npm让开发人员分享开源技术变得不再困难。 MEAN架构的优势 MEAN架构的在企业级应用中被广泛采用,总结起来具备以下优势。 1. 开源 正如前两节所述,无论是MongoDB、Express、Angular、Node.js四种核心技术,还是NG-ZORRO、ngx-markdown、NGINX、basic-auth等周边技术,MEAN架构所有的技术栈都是开源的。 开源技术相对与闭源技术而言,有其优势。一方面,开源技术源码是公开的,互联网公司在考察某项技术是否符合自身开发需求时,可以对源码进分析;另一方面,开源技术相对闭源技术而言,商用的成本相对比较低,这对于很多初创的互联网公司而言,可以节省一大笔技术投入。以此,MEAN架构也被称为开发下一代大型互联网应用的“平民英雄”。 当然,开源技术是把双刃剑,你能够看到源码,并不意味着你可以解决所有问题。开源技术在技术支持上不能与闭源技术相提并论,毕竟闭源技术都有成熟的商业模式,会提供完善的商业支持。而开源技术,更多依赖于社区对于开源技术的支持。如果在使用开源技术过程中发现了问题,可以反馈给开源社区,但开源社区不会给你保证什么时候、什么版本能够修复发现的问题。所以,使用开源技术,需要开发团队对开源技术要有深刻的了解。最好能够吃透源码,这样在发现问题时,能够及时解决源码上的问题。 比如,在关系型数据库方面,同属于Oracle公司的MySQL数据库和Oracle数据库,就是开源与闭源技术的两大代表,两者占据了全球数据库的占有率的前两名。MySQL数据库主要是在中小企业或者是云计算供应商中广泛采用,而Oracle数据库则由于其稳定、高性能的特性,深受政府和银行等客户的信赖。 2. 跨平台 跨平台,意味着开发和部署的应用的成本的降低。 试想一下,当今操作系统三足鼎立,分别是Linux、macOS、Windows。如果开发者需要针对不同的操作系统平台,而要开发不同的软件,那么开发成本势必会非常高。而且每个操作系统平台,都有不同的版本、分支,仅仅做不同的版本的适配都需要耗费极大的人力,更别提要针对不同的平台开发软件了。以此,跨平台可以节省开发成本。 同理,由于MEAN架构开发的软件是具有跨平台的,无需担心在部署应用过程中的兼容性问题。开发者在本地开发环境所开发的软件,理论上是可以通过CICD平台直接一键部署到测试环境,甚至是生产环境中,因而可以节省部署的成本。 MEAN架构的跨平台特性,使其非常适合构建Cloud Native应用,特别是在当今容器技术常常作为微服务的宿主,而MEAN架构的应用是支持Docker部署的。 有关Cloud Native方面的内容,可以参阅笔者所著的《Cloud Native 分布式架构原理与实践》。 3. 全栈开发 类似与系统架构师,全栈开发者应该是比一般的软件工程师具有更广的知识面,是拥有全端软件设计思想并掌握多种开发技能的复合型人才,能狗独当一面。相比于Node.js工程师、Angular工程师偏重于某项技能而言,全栈开发意味着必须掌握整个架构的全部细节,要求全栈开发者能够从零开始构建全套完整的企业级应用。 作为一名全栈开发者,在开发时往往会做如下风险的预测,并做好防御。 当前所开发的应用会部署到什么样的服务器、网络环境中? 服务哪里可能会崩?为什么会崩? 是否应该适当的使用云存储? 程序有无具备数据冗余? 是否具备可用性? 界面是否友好? 性能是否能够满足当前的要求? 哪些位置需要加日志,方便日志排查问题? 除上述的思考外,全栈开发者要能够建立合理的、标准的关系模型,包括外键、索引、视图、查找表等。 全栈开发者要熟悉非关系型数据存储,并且知道它们相对关系型存储优势所在。 当然,人的精力毕竟有限,所以想要成为全栈开发者并非易事。所幸MEAN架构让这一切成为了可能。MEAN架构以Node.js为整个技术栈的核心,而Node.js的编程语言是JavaScript,这意味着,开发者只需要掌握JavaScript这一种编程语言,即可以打通所有MEAN架构的技术,这不得不说是全栈开发者的福音。 4. 支持企业级应用 无论是Node.js、Angular还是MongoDB,这些技术在大型互联网公司都被广泛采用。无数应用也证明了MEAN架构是非常适合构建企业级应用的。企业级应用是指那些为商业组织、大型企业而创建并部署的解决方案及应用。这些大型企业级应用的结构复杂,涉及的外部资源众多、事务密集、数据量大、用户数多,有较强的安全性考虑。 MEAN架构用来开发企业级应用,不但具有强大的功能,还能够满足未来业务需求的变化,且易于升级和维护。 更多有关企业级应用开发方面的内容,可以参阅笔者所著的《Spring Boot 企业级应用开发实战》《Angular企业级应用开发实战》《Node.js企业级应用开发实战》等。 5. 支持构建微服务 微服务(Microservices)架构风格就像是把小的服务开发成单一应用的形式,运行在其自己的进程中,并采用轻量级的机制进行通信(一般是HTTP资源API)。这些服务都是围绕业务能力来构建,通过全自动部署工具来实现独立部署。这些服务,其可以使用不同的编程语言和不同的数据存储技术,并保持最小化集中管理。 MEAN架构非常适合构建微服务: Node.js本身提供了跨平台的能力,可以运行在自己的进程中。 Express易于构建Web服务,并支持HTTP的通信。 Node.js+MongoDB支持从前端到后端再到数据库全栈开发能力。 开发人员可以轻易地通过MEAN架构来构建并快速启动一个微服务应用。业界也提供了成熟的微服务解决方案来打造大型微服务架构系统,比如Tars.js、Seneca等。 读者欲了解更多微服务方面的内容,可以参阅笔者所著的《Spring Cloud 微服务架构开发实战》。 6. 业界主流 MEAN架构所涉及的技术都是业界主流,主要体现在以下几方面。 MongoDB是在NoSQL数据库中是排行第一的,而且用户量还在递增。 只要知道JavaScript就必然知道Node.js,而JavaScript是在开源界最流行的开发语言。 前端组件化开发是目前主流的开发方式,不管是Angular、React还是Vue.js都如此。相比较而言,Angular不管是其开发功能,还是编程思想,在所有前端框架中都是首屈一指的,特别适合大型企业级应用的开发。而且,从市场占有率来看,Angular都是首屈一指的。 在大型互联网应用中,经常使用NGINX作为Web服务器。NGINX也是目前使用最广泛的代理服务器。 如何学习MEAN架构? MEAN架构知识点繁多,涉及面广,不是一时可以掌握。关于MEAN架构,笔者撰写了多本开源书籍,方便网友学习。包括: REST 案例大全 REST 实战 CSS3 教程 跟老卫学Ionic Node.js 案例大全 跟老卫学Angular 这些开源书都免费的,可以随时学习哦,附带案例和源码。有任何问题都可以在线在相关的主页留言,有问必答。 当然,笔者也出版了一些的专著,网友们可以按需选择。包括: Angular企业级应用开发实战 大型互联网应用轻量级架构实战 MongoDB+Express+Angular+Node.js全栈开发实战派 上面的案例和源码也都是公开免费的哦。 参考引用 本文同步至: https://waylau.com/mean-architecture-in-action/ Angular企业级应用开发实战(2019年06月出版) 大型互联网应用轻量级架构实战(2019年12月出版) MongoDB+Express+Angular+Node.js全栈开发实战派(2020年06月出版) Spring Boot 企业级应用开发实战(2018年03月出版) Spring Cloud 微服务架构开发实战(2018年06月出版)
本文介绍了Java的发展趋势,并阐述了如何学习Java技术。 Java为啥火爆? 随着互联网应用的发展,各种编程语言层出不穷,比如C#、Golang、TypeScript、ActionScript等,但不管是哪种语言,都无法撼动Java的“霸主”地位。Java语言始终占据着各类编程语言排行榜的榜首,开发者对于Java的热情也是与日俱增。Java已然成为了企业级应用、云计算和Cloud Native应用的首选语言。 图1-1展示的是1985年至2020年TIOBE编程语言排行榜情况(https://www.tiobe.com/tiobe-index/)。从图中可以看出,自Java诞生以来,一直占据排行版前三的位置。 那么为什么Java一致能保持这么火爆呢?究其原因,笔者认为Java能够长盛不衰的最大的秘诀就是能够与时俱进,不断推陈出新。 笔者从事Java开发已经有十几年了,可以说是Java技术发展的见证者和实践者。为了推广Java技术,笔者撰写了包括《分布式系统常用技术及案例分析》、《Spring Boot 企业级应用开发实战》、《Spring Cloud 微服务架构开发实战》、《Spring 5开发大全》、《Cloud Native 分布式架构原理与实践》等在内了几十本Java领域的专著和开源书,期望以个人微薄之力对Java语言有所贡献。由于目前企业所使用的Java,大多是Java 8之前的版本,市面上也缺乏最新Java 12的学习资料,因此笔者才撰写了这本《Java核心编程》一书以补空白。 Java应该怎么学? 那么,Java应该怎么学?参考《Java核心编程》,学习Java分为以下几个层次: 1. 零基础的读者 如果你是没有任何编程经验的技术爱好者,本书可以帮助你打开编程之门。本书案例丰富、思路清晰,可以由浅及深地帮助读者掌握Java。 同时,本书可以帮助读者从一开始就建立正确的编程习惯,逐步树立良好的面向对象设计思维,这对于学习其他语言都是非常有帮助的。 针对这类读者,建议读者在学习过程中,从头至尾详细跟随笔者来理解Java的概念,并编写《Java核心编程》书中的示例。该书附赠从Java 8到Java 14全套案例:https://github.com/waylau/modern-java 2. 有后端开发经验的读者 对于有后端或者是其他面向对象编程的经验的开发而言,理解并掌握Java并非难事。 针对这类读者,适当理解下Java的语法即可,把精力放在动手编写Java示例上面。 3. 有Java开发经验的读者 大多数Java开发人员肯定熟悉Java的语法,那么需要并把精力放在Java新特性上面,根据自身的实际情况,可以选学本书中的知识点,做到查漏补缺。 让我们一起踏上Java的学习之旅吧。 参考引用 本文同步至: https://waylau.com/java-development-is-still-mainstream/ Eclipse IDE支持Java 14: https://waylau.com/eclipse-ide-support-java14/ 现代Java案例大全,从Java 8到Java 14:https://github.com/waylau/modern-java
本文演示了如何如何编写JUnit 5测试用例,在Maven项目中运行JUnit 5测试用例。 编写JUnit 5测试用例 如果你是Java开发者,那么对于JUnit应该就不陌生。JUnit是Java单元测试的基础工具。 JUnit目前最新的版本是JUnit 5.x,但广大的Java开发者估计还停留在JUnit 4.x,因此有必要演示下如何编写JUnit 5测试用例。 引入JUnit 5依赖 相比较JUnit 4而言,JUnit 5一个比较大的改变是JUnit 5拥有与JUnit 4不同的全新的API。JUnit 5分成了三部分: JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage JUnit Platform是在JVM上启动测试框架的基础。 它还定义了TestEngine API,用于开发在平台上运行的测试框架。 此外,该JUnit Platform还提供了一个控制台启动器(用于从命令行启动该平台)和一个基于JUnit 4的运行器,用于在基于JUnit 4的环境中在该平台上运行任何TestEngine。 流行的IDE(IntelliJ IDEA,Eclipse,NetBeans和Visual Studio Code等)和构建工具(Gradle,Maven和Ant等)中也存在对JUnit平台的一流支持。 JUnit Jupiter是新编程模型和扩展模型的组合,用于在JUnit 5中编写测试和扩展。Jupiter子项目提供了一个TestEngine,用于在平台上运行基于Jupiter的测试。 JUnit Vintage提供了一个TestEngine,用于在平台上运行基于JUnit 3和基于JUnit 4的测试。 因此,在Maven中,JUnit 5分模块的,意味着你可以按需引入上面定义的任意模块。这使得引入JUnit 5依赖就有了多个选择。 一般而言,力求省事,就可以通过引入junit-jupiter依赖。junit-jupiter就是常用JUnit 5模块的聚合包。 <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> 编写测试用例 下面是一段简单的Java程序: /** * Welcome to https://waylau.com */ package com.waylau.java.demo; /** * Hello World. * * @since 1.0.0 2020年4月12日 * @author <a href="https://waylau.com">Way Lau</a> */ public class HelloWorld { private String words; public HelloWorld(String words) { this.words = words; } public String getWords() { return words; } } 按照管理,我们会在Maven工程的test目录,创建一个与之对应的单元测试用例: /** * Welcome to https://waylau.com */ package com.waylau.java.demo; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; /** * HelloWorld Test. * * @since 1.0.0 2020年4月12日 * @author <a href="https://waylau.com">Way Lau</a> */ class HelloWorldTests { @Test void testGetWords() { var words = "Hello World"; var hello = new HelloWorld(words); assertEquals(words, hello.getWords()); } } 上述用例非常简单,就是想测试下,HelloWorld的getWords方法,是否与预期的一致。这里需要强调的是JUnit 5和JUnit 4的不同点: JUnit 5使用的API是org.junit.jupiter.api.*包下 测试方法(比如上例testGetWords),可以不加public。 运行JUnit 5测试用例 上如上文所讲,在大多数主流的IDE中,都提供了对JUnit 5的支持。因此可以选择在IDE中运行,也可以通过Maven执行测试。 在IDE中运行 以Eclipse IDE为例,右键类或者方法,选择“Run As -> JUnit Test”即可。如下图所示。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tONJQN3t-1587044607054)(../images/post/20200412-ide.jpg)] 通过Maven执行测试 在Maven中执行测试用例的命令如下: mvn test 如果你执行了上述命令,会得到下面的测试结果 ------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.waylau.java.demo.HelloWorldTests Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec Results : Tests run: 0, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.983 s [INFO] Finished at: 2020-04-12T11:22:16+08:00 [INFO] ------------------------------------------------------------------------ 上面结果没有失败的用例,但同时你也发现了没有成功的用例。因为根本没有执行测试用例。 这是因为,在Maven中并不能直接识别JUnit 5测试用例。如何解决?此时,还需要额外加多Maven Surefire或Maven Failsafe两个插件。 <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>${maven-failsafe-plugin.version}</version> </plugin> </plugins> </build> 在Maven中再次执行测试用例,会得到下面的测试结果: [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.waylau.java.demo.HelloWorldTests [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.045 s - in com.waylau.java.demo.HelloWorldTests [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.116 s [INFO] Finished at: 2020-04-12T11:30:29+08:00 [INFO] ------------------------------------------------------------------------ 可以看到,HelloWorldTests类已经得到了测试执行。 参考引用 本文同步至: https://waylau.com/running-junit5-tests-with-maven/ Eclipse IDE支持Java 14: https://waylau.com/eclipse-ide-support-java14/ 本文示例源码:https://github.com/waylau/java-data-structures-and-algorithms-in-action
随着JDK 14的发布(https://waylau.com/jdk-14-released/),各大Java IDE也开始支持JDK 14。最新版本的Eclipse IDE 2020-03也于2020年3月18日发布,本文介绍如何通过Eclipse IDE来开发Java 14应用。 下载 下载地址https://www.eclipse.org/downloads/packages/ 根据个人的需要,下载Java Developers或者Enterprise Java Developers版本。 设置JDK 下载最新的JDK 14,并在Eclipse IDE中指向该JDK。 设置Maven 可选。如果项目是使用Maven管理,则建议下载最新的Maven,并在Eclipse IDE中指向该Maven。 设置网络代理 为了更快的下载插件,需要设置代理。 设置代码样式 可选。设置符合自己需求的代码样式。 设置字符 建议使用UTF-8。 安装支持Java 14的插件 由于当前的Eclipse还未正式支持Java 14,需要额外安装Java 14 Support for Eclipse 2020-03 (4.15)插件,用以支持Java 14。插件地址: https://marketplace.eclipse.org/content/java-14-support-eclipse-2020-03-415 安装完成之后,就可以选择使用JDK 14的编译器,同时启用预览功能。 编程 终于可以愉快的玩耍了Java 14了。 本文所有源码可见https://github.com/waylau/modern-java-demos。 参考引用 本文同步至: https://waylau.com/eclipse-ide-support-java14/ 现代Java案例大全:https://github.com/waylau/modern-java-demos
初次使用TortorliseGit的小伙伴,怕是很难找到删除分支的菜单。本文介绍如何使用TortorliseGit删除分支。 右键项目,点击“Switch/Checkout”菜单 在点击右侧的三个小点 选中要删除的分支,右键分支,可以看到“Delete branch”按钮,点击该按钮就能删除分支。 点击左侧“remotes”,可以用同样的手法,来删除远端的分支。 参考引用 本文同步至: https://waylau.com/tortorlise-git-delete-branch/
JDK 14已经于2020年3月17日如期发布。本文介绍JDK 14特性。 JEP 305: instanceof的模式匹配(预览) 通过对instanceof运算符进行模式匹配来增强Java编程语言。 模式匹配允许程序中的通用逻辑,即从对象中有条件地提取组件,可以更简洁,更安全地表示。 这是JDK 14中的预览语言功能。 动机 几乎每个程序都包含某种逻辑,这些逻辑结合了对表达式是否具有某种类型或结构的测试,然后有条件地提取其状态的组件以进行进一步处理。例如,以下是在Java程序中常见的instanceof-and-cast用法: if (obj instanceof String) { String s = (String) obj; // 使用s } 上述示例中,为了能够安全地将obj转为我们期望的String类型,需要通过instanceof运算符对obj进行类型判断。这里发生了三件事: 测试obj是否是一个String 将obj转换为String 声明新的局部变量s,以便我们可以使用字符串值。 这种模式很简单,并且所有Java程序员都可以理解,但是由于一些原因,它不是最优的。 语法乏味 同时执行类型检测和类型转换并不是必要的 String类型在程序中出现了3次,这混淆了后面更重要的逻辑 重复的代码容易滋生错误 在JDK 14中,上述代码可以改为下面的方式: if (obj instanceof String s) { // 使用s } 这样整个代码看上去更加简洁。 描述 类型测试模式由指定类型的谓词和单个绑定变量组成。在下面的代码中,短语String是类型测试模式: if (obj instanceof String s) { // 使用s } else { // 不能使用s } 如果obj是String的实例,则将其强制转换为String并分配给绑定变量s。绑定变量在if语句的true块中,而不在if语句的false块中。 与局部变量的范围不同,绑定变量的范围由包含的表达式和语句的语义确定。例如,在此代码中: if (!(obj instanceof String s)) { .. s.contains(..) .. } else { .. s.contains(..) .. } true块中的s表示封闭类中的字段,false块中的s表示由instanceof运算符引入的绑定变量。 当if语句的条件变得比单个instanceof更复杂时,绑定变量的范围也会相应地增长。 例如,在此代码中: if (obj instanceof String s && s.length() > 5) {.. s.contains(..) ..} 绑定变量s在&&运算符右侧以及true块中。仅当instanceof成功并分配给s时,才评估右侧。 另一方面,在此代码中: if (obj instanceof String s || s.length() > 5) {.. s.contains(..) ..} 绑定变量s不在||右侧的范围内运算符,也不在true块的范围内。s指的是封闭类中的一个字段。 Joshua Bloch的经典著作Effective Java中有一段代码示例: @Override public boolean equals(Object o) { return (o instanceof CaseInsensitiveString) && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); } 这段代码可以使用新的语法写成: @Override public boolean equals(Object o) { return (o instanceof CaseInsensitiveString cis) && cis.s.equalsIgnoreCase(s); } 这个特性很有意思,因为它为更为通用的模式匹配打开了大门。模式匹配通过更为简便的语法基于一定的条件来抽取对象的组件,而instanceof刚好是这种情况,它先检查对象类型,然后再调用对象的方法或访问对象的字段。 JEP 343: 打包工具(孵化) 该特性旨在创建一个用于打包独立Java应用程序的工具。 动机 许多Java应用程序需要以一流的方式安装在本机平台上,而不是简单地放置在类路径或模块路径上。对于应用程序开发人员来说,交付简单的JAR文件是不够的。他们必须提供适合本机平台的可安装软件包。这允许以用户熟悉的方式分发,安装和卸载Java应用程序。例如,在Windows上,用户希望能够双击一个软件包来安装他们的软件,然后使用控制面板删除该软件。在macOS上,用户希望能够双击DMG文件并将其应用程序拖到Application文件夹中。 打包工具还可以帮助填补其他技术的空白,例如Java Web Start(已从JDK 11中删除)和pack200(已在JDK 11中弃用,可能在以后的版本中删除)。开发人员可以使用jlink将JDK分解为所需的最小模块集,然后使用打包工具生成一个压缩的、可安装的映像,该映像可以部署到目标计算机。 为了以前满足这些要求,JDK 8分发了一个名为javapackager的打包工具。但是,作为删除JavaFX的一部分,该工具已从JDK 11中删除。 描述 jpackage工具将Java应用程序打包到特定于平台的程序包中,该程序包包含所有必需的依赖项。该应用程序可以作为普通JAR文件的集合或作为模块的集合提供。受支持的特定于平台的软件包格式为: Linux:deb和rpm macOS:pkg和dmg Windows:MSI和EXE 默认情况下,jpackage会以最适合其运行系统的格式生成一个软件包。 以下是基本用法: $ jpackage --name myapp --input lib --main-jar main.jar 用法 1. 基本用法:非模块化应用 假设你有一个由JAR文件组成的应用程序,所有应用程序都位于lib目录下,并且主类在lib/main.jar中。下列命令 $ jpackage --name myapp --input lib --main-jar main.jar 将以本地系统的默认格式打包应用程序,并将生成的打包文件保留在当前目录中。如果main.jar中的MANIFEST.MF文件没有Main-Class属性,我们必须显式地指定主类: $ jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main 打包的名称是myapp。要启动该应用程序,启动器将从输入目录复制的每个JAR文件都放在JVM的类路径上。 如果希望生成默认格式以外的软件安装包,可以使用--type选项。例如要在macOS上生成pkg文件(而不是dmg文件),我们可以使用下面的命令: $ jpackage --name myapp --input lib --main-jar main.jar --type pkg 2. 基本用法:模块化应用 如果你有一个模块化应用程序,该应用程序由lib目录中的模块化JAR文件和/或JMOD文件组成,并且主类位于myapp模块中,则下面的命令 $ jpackage --name myapp --module-path lib -m myapp 能够将其打包。如果myapp模块无法识别主类,则必须明确指定: $ jpackage --name myapp --module-path lib -m myapp/myapp.Main JEP 345: G1的NUMA内存分配优化 通过实现可识别NUMA的内存分配,提高大型计算机上的G1性能。 动机 现代的多插槽计算机越来越多地具有非统一的内存访问(non-uniform memory access,NUMA),即内存与每个插槽或内核之间的距离并不相等。插槽之间的内存访问具有不同的性能特征,对更远的插槽的访问通常具有更大的延迟。 并行收集器中通过启动-XX:+UseParallelGC能够感知NUMA,这个功能已经实现了多年了,这有助于提高跨多插槽运行单个JVM的配置的性能。其他HotSpot收集器没有此功能,这意味着他们无法利用这种垂直多路NUMA缩放功能。大型企业应用程序尤其倾向于在多个多插槽上以大堆配置运行,但是它们希望在单个JVM中运行具有可管理性优势。 使用G1收集器的用户越来越多地遇到这种扩展瓶颈。 描述 G1的堆组织为固定大小区域的集合。一个区域通常是一组物理页面,尽管使用大页面(通过 -XX:+UseLargePages)时,多个区域可能组成一个物理页面。 如果指定了+XX:+UseNUMA选项,则在初始化JVM时,区域将平均分布在可用NUMA节点的总数上。 在开始时固定每个区域的NUMA节点有些不灵活,但是可以通过以下增强来缓解。为了为mutator线程分配新的对象,G1可能需要分配一个新的区域。它将通过从NUMA节点中优先选择一个与当前线程绑定的空闲区域来执行此操作,以便将对象保留在新生代的同一NUMA节点上。如果在为变量分配区域的过程中,同一NUMA节点上没有空闲区域,则G1将触发垃圾回收。要评估的另一种想法是,从距离最近的NUMA节点开始,按距离顺序在其他NUMA节点中搜索自由区域。 该特性不会尝试将对象保留在老年代的同一NUMA节点上。 此分配政策中不包括Humongous区。对于这些区,将不做任何特别的事情。 JEP 349: JFR事件流 公开JDK Flight Recorder数据以进行连续监视。 动机 HotSpot VM通过JFR产生的数据点超过500个,但是使用者只能通过解析日志文件的方法使用它们。 用户要想消费这些数据,必须开始一个记录并停止,将内容转储到磁盘上,然后解析记录文件。这对于应用程序分析非常有效,但是监控数据却十分不方便(例如显示动态更新数据的仪表盘)。 与创建记录相关的开销包括: 发出在创建新记录时必须发生的事件 写入事件元数据(例如字段布局) 写入检查点数据(例如堆栈跟踪) 将数据从磁盘存储复制到单独的记录文件 如果有一种方法,可以在不创建新记录文件的情况下,从磁盘存储库中读取正在记录的数据,就可以避免上述开销。 描述 jdk.jfr模块里的jdk.jfr.consumer包,提供了异步订阅事件的功能。用户可以直接从磁盘存储库读取记录数据,也可以直接从磁盘存储流中读取数据,而无需转储记录文件。可以通过注册处理器(例如lambda函数)与流交互,从而对事件的到达进行响应。 下面的例子打印CPU的总体使用率,并持有锁10毫秒。 try (var rs = new RecordingStream()) { rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1)); rs.enable("jdk.JavaMonitorEnter").withThreshold(Duration.ofMillis(10)); rs.onEvent("jdk.CPULoad", event -> { System.out.println(event.getFloat("machineTotal")); }); rs.onEvent("jdk.JavaMonitorEnter", event -> { System.out.println(event.getClass("monitorClass")); }); rs.start(); } RecordingStream类实现了接口jdk.jfr.consumer.EventStream,该接口提供了一种统一的方式来过滤和使用事件,无论源是实时流还是磁盘上的文件。 public interface EventStream extends AutoCloseable { public static EventStream openRepository(); public static EventStream openRepository(Path directory); public static EventStream openFile(Path file); void setStartTime(Instant startTime); void setEndTime(Instant endTime); void setOrdered(boolean ordered); void setReuse(boolean reuse); void onEvent(Consumer<RecordedEvent> handler); void onEvent(String eventName, Consumer<RecordedEvent handler); void onFlush(Runnable handler); void onClose(Runnable handler); void onError(Runnable handler); void remove(Object handler); void start(); void startAsync(); void awaitTermination(); void awaitTermination(Duration duration); void close(); } 创建流的方法有3种: EventStream::openRepository(Path)从磁盘存储库中构造一个流。这是一种可以直接通过文件系统监视其他进程的方法。磁盘存储库的位置存储在系统属性jdk.jfr.repository中,可以使用API读取到。 EventStream::openRepository()方法执行进程内监控。与RecordingStream不同,它不会开始录制。相反,仅当通过外部方式(例如,使用JCMD或JMX)启动记录时,流才接收事件。 EventStream::openFile(Path)从记录文件中创建流,扩充了已经存在的RecordingFile类。 该接口还可用于设置缓冲的数据量,以及是否应按时间顺序对事件进行排序。为了最大程度地降低分配压力,还可以选择控制是否应为每个事件分配新的事件对象,或者是否可以重用以前的对象。我们可以在当前线程中启动流,也可以异步启动流。 JVM每秒一次将线程本地缓冲区中存储的事件定期刷新到磁盘存储库。 一个单独的线程解析最近的文件,直到写入数据为止,然后将事件推送给订阅者。 为了保持较低的开销,仅从文件中读取活动订阅的事件。 要在刷新完成后收到通知,可以使用EventStream::onFlush(Runnable)方法注册处理程序。 这是在JVM准备下一组事件时将数据聚合或推送到外部系统的机会。 JEP 352: 非易失性映射字节缓冲区 添加新的特定于JDK的文件映射模式,以便可以使用FileChannel API创建引用非易失性内存(non-volatile memory,NVM)的MappedByteBuffer实例。 动机 NVM为应用程序程序员提供了在程序运行过程中创建和更新程序状态的机会,而减少了输出到持久性介质或从持久性介质输入时的成本。这对于事务程序特别重要,在事务程序中,需要定期保持不确定状态以启用崩溃恢复。 现有的C库(例如Intel的libpmem)为C程序提供了对基层NVM的高效访问。他们还以此为基础来支持对各种持久性数据类型的简单管理。当前,由于频繁需要进行系统调用或JNI调用来调用原始操作,从而确保内存更改是持久的,因此即使仅使用Java中的基础库也很昂贵。同样的问题限制了高级库的使用,并且由于C中提供的持久数据类型分配在无法从Java直接访问的内存中这一事实而加剧了这一问题。与C或可以低成本链接到C库的语言相比,这使Java应用程序和中间件(例如Java事务管理器)处于严重的劣势。 该特性试图通过允许映射到ByteBuffer的NVM的有效写回来解决第一个问题。由于Java可以直接访问ByteBuffer映射的内存,因此这可以通过实现与C语言中提供的客户端库等效的客户端库来解决第二个问题,以管理不同持久数据类型的存储。 描述 1. 初步变更 该JEP使用了Java SE API的两个增强功能: 支持implementation-defined的映射模式 MppedByteBuffer::force方法以指定范围 2. 特定于JDK的API更改 通过新模块中的公共API公开新的MapMode枚举值 一个公共扩展枚举ExtendedMapMode将添加到jdk.nio.mapmode程序包: package jdk.nio.mapmode; . . . public class ExtendedMapMode { private ExtendedMapMode() { } public static final MapMode READ_ONLY_SYNC = . . . public static final MapMode READ_WRITE_SYNC = . . . } 在调用FileChannel::map方法创建映射到NVM设备文件上的只读或读写MappedByteBuffer时,可以使用上述的枚举值。如果这些标志在不支持NVM设备文件的平台上传递,程序会抛出UnsupportedOperationException异常。在受支持的平台上,仅当目标FileChannel实例是从通过NVM设备打开的派生文件时,才能传递这些参数。在任何其他情况下,都会抛出IOException异常。 发布BufferPoolMXBean,用于跟踪MappedByteBuffer统计信息 JEP 358: 友好的空指针异常 精确描述哪个变量为null,提高JVM生成的NullPointerException的可用性。 动机 每个Java开发人员都遇到过NullPointerException(NPE)问题。NPE几乎可以出现在程序的任意位置,因此尝试捕获和修复它们是不可能的。下面的代码: a.i = 99; JVM会打印出方法名、文件名和NPE异常的行数: Exception in thread "main" java.lang.NullPointerException at Prog.main(Prog.java:5) 使用这个错误报告,开发人员可以定位到a.i = 99;并推断对象a是null。但是对于更复杂的代码,不使用调试器就无法确定哪个变量为空。假设下面的代码中出现了一个NPE: a.b.c.i = 99; 仅仅使用文件名和行数,并不能精确定位到哪个变量为null,是a、b还是c? 访问数组也会发生类似的问题。假设此代码中出现一个NPE: a[i][j][k] = 99; 文件名和行号不能精确指出哪个数组组件为空。是a还是a[i]或a[i][j]? 一行代码可能包含多个访问路径,每个访问路径都可能是NPE的来源。假设此代码中出现一个NPE: a.i = b.j; 文件名和行号并不能确定哪个对象为空,是a还是b? NPE也可能在方法调用中传递,看下面的代码: x().y().i = 99; 文件名和行号不能指出哪个方法调用返回null。是x()还是y()? 描述 JVM在程序调用空引用的位置抛出NPE异常,通过分析程序的字节码指令,JVM可以精确判断哪个变量为空,并在NPE中描述详细信息(根据源代码)。包含方法名、文件名和行号的null-detail消息将显示在JVM的消息中。 例如a.i = 99;的NPE异常可能是如下格式: Exception in thread "main" java.lang.NullPointerException: Cannot assign field "i" because "a" is null at Prog.main(Prog.java:5) 在更复杂的a.b.c.i = 99;语句中,NPE消息会包含导致空值的完整访问路径: Exception in thread "main" java.lang.NullPointerException: Cannot read field "c" because "a.b" is null at Prog.main(Prog.java:5) 同样,如果数组访问和赋值语句a[i][j][k] = 99;引发NPE: Exception in thread "main" java.lang.NullPointerException: Cannot load from object array because "a[i][j]" is null at Prog.main(Prog.java:5) 类似地,a.i = b.j;会引发NPE: Exception in thread "main" java.lang.NullPointerException: Cannot read field "j" because "b" is null at Prog.main(Prog.java:5) JEP 359: record(预览) 通过record增强Java编程语言。record提供了一种紧凑的语法来声明类,这些类是浅层不可变数据的透明持有者。 动机 我们经常听到这样的抱怨:“Java太冗长”、“Java规则过多”。首当其冲的就是充当简单集合的“数据载体”的类。为了写一个数据类,开发人员必须编写许多低价值、重复且容易出错的代码:构造函数、访问器、equals()、hashCode()和toString()等等。 尽管IDE可以帮助开发人员编写数据载体类的绝大多数编码,但是这些代码仍然冗长。 从表面上看,将Record是为了简化模板编码而生的,但是它还有“远大”的目标:modeling data as data(将数据建模为数据)。record应该更简单、简洁、数据不可变。 描述 record是Java的一种新的类型。同枚举一样,record也是对类的一种限制。record放弃了类通常享有的特性:将API和表示解耦。但是作为回报,record使数据类变得非常简洁。 一个record具有名称和状态描述。状态描述声明了record的组成部分。例如: record Point(int x, int y) { } 因为record在语义上是数据的简单透明持有者,所以记录会自动获取很多标准成员: 状态声明中的每个成员,都有一个 private final的字段; 状态声明中的每个组件的公共读取访问方法,该方法和组件具有相同的名字; 一个公共的构造函数,其签名与状态声明相同; equals和hashCode的实现; toString的实现。 限制 records不能扩展任何类,并且不能声明私有字段以外的实例字段。声明的任何其他字段都必须是静态的。 records类都是隐含的final类,并且不能是抽象类。这些限制使得records的API仅由其状态描述定义,并且以后不能被其他类实现或继承。 在record中额外声明变量 也可以显式声明从状态描述自动派生的任何成员。可以在没有正式参数列表的情况下声明构造函数(这种情况下,假定与状态描述相同),并且在正常构造函数主体正常完成时调用隐式初始化(this.x=x)。这样就可以在显式构造函数中仅执行其参数的验证等逻辑,并省略字段的初始化,例如: record Range(int lo, int hi) { public Range { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); } } JEP 361: Switch Expressions (标准) 扩展switch可以使其应用于语句或表达式。扩展switch使其可以用作语句或表达式,以便两种形式都可以使用传统的“case ... :”标签(带有贯穿)或“... ->”标签(不带有贯穿),还有另一个新语句,用于从switch表达式产生值。这些更改将简化日常编码,并为在交换机中使用模式匹配提供了方法。这些功能在JDK 12和JDK 13中是属于预览语言功能,在JDK 14中正式称为标准。 动机 当我们准备增强Java编程语言以支持模式匹配(JEP 305)时,现有switch语句的一些不规则性(长期以来一直困扰着用户)成为了障碍。下面的代码中,众多的break语句使代码变得冗长,这种“视觉噪声”通常掩盖了更多的错误。 switch (day) { case MONDAY: case FRIDAY: case SUNDAY: System.out.println(6); break; case TUESDAY: System.out.println(7); break; case THURSDAY: case SATURDAY: System.out.println(8); break; case WEDNESDAY: System.out.println(9); break; } 我们建议引入一种新形式的switch标签“case L ->”,以表示如果匹配标签,则只执行标签右边的代码。switch标签允许在每种情况下使用逗号分隔多个常量。现在可以这样编写以前的代码: switch (day) { case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); case TUESDAY -> System.out.println(7); case THURSDAY, SATURDAY -> System.out.println(8); case WEDNESDAY -> System.out.println(9); } switch标签“case L ->”右侧的代码被限制为表达式、代码块或throw语句。这样局部变量的范围在本块之内,而传统的switch语句局部变量的作用域是整个模块! switch (day) { case MONDAY: case TUESDAY: int temp = ... // The scope of 'temp' continues to the } break; case WEDNESDAY: case THURSDAY: int temp2 = ... // Can't call this variable 'temp' break; default: int temp3 = ... // Can't call this variable 'temp' } 许多现有的switch语句实质上是对switch表达式的模拟,其中每个分支要么分配给一个公共目标变量,要么返回一个值: int numLetters; switch (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = 8; break; case WEDNESDAY: numLetters = 9; break; default: throw new IllegalStateException("Wat: " + day); } 上面的表述是复杂、重复且容易出错的。代码设计者的意图是为每天计算numLetters。这段代码可以改写成下面这段形式,更加清晰和安全: int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; }; 描述 1. “case L ->”标签 除了传统的“case L :”标签外,还定义了一种更简洁的形式:“case L ->”标签。如果表达式匹配了某个标签,则仅执行箭头右侧的表达式或语句;否则将不执行任何操作。 static void howMany(int k) { switch (k) { case 1 -> System.out.println("one"); case 2 -> System.out.println("two"); default -> System.out.println("many"); } } 下面的代码: howMany(1); howMany(2); howMany(3); 将会打印: one two many 2. Switch表达式 JDK 14扩展了switch语句,使其可以应用于表达式中。例如上述的howMany方法可以重写为如下形式: static void howMany(int k) { System.out.println( switch (k) { case 1 -> "one"; case 2 -> "two"; default -> "many"; } ); } 在通常情况下,switch表达式如下所示: T result = switch (arg) { case L1 -> e1; case L2 -> e2; default -> e3; }; 3. 通过yield产生值 大多数switch表达式在“case L->”标签的右侧都有一个表达式。如果需要一个完整的块,JDK 14引入了一个新的yield语句来产生一个值,该值成为封闭的switch表达式的值。 int j = switch (day) { case MONDAY -> 0; case TUESDAY -> 1; default -> { int k = day.toString().length(); int result = f(k); yield result; } }; JEP 362: 弃用Solaris和SPARC端口 不建议使用Solaris/SPARC,Solaris/x64和Linux/SPARC端口,以在将来的发行版中删除它们。 动机 放弃对这些端口的支持将使OpenJDK社区中的贡献者能够加速新功能的开发,这些新功能将推动平台向前发展。 JEP 363: 移除CMS垃圾收集器 移除CMS(Concurrent Mark Sweep)垃圾收集器。 动机 在两年多以前的JEP 291中,就已经弃用了CMS收集器,并说明会在以后的发行版中删除,以加快其他垃圾收集器的发展。在这段时间里,我们看到了2个新的垃圾收集器ZGC和Shenandoah的诞生,同时对G1的进一步改进。G1自JDK 6开始便成为CMS的继任者。我们希望以后现有的收集器进一步减少对CMS的需求。 描述 此更改将禁用CMS的编译,删除源代码中gc/cms目录的内容,并删除仅与CMS有关的选项。尝试使用命令-XX:+UseConcMarkSweepGC开启CMS会收到以下警告: Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option UseConcMarkSweepGC; \ support was removed in <version> VM将使用默认收集器继续执行。 JEP 364: macOS系统上的ZGC(实验) 将ZGC垃圾收集器移植到macOS。 动机 尽管我们希望需要ZGC可伸缩性的用户使用基于Linux的环境,但是在部署应用程序之前,开发人员通常会使用Mac进行本地开发和测试。 还有一些用户希望运行桌面应用程序,例如带有ZGC的IDE。 描述 ZGC的macOS实现由两部分组成: 支持macOS上的多映射内存。 ZGC设计大量使用彩色指针,因此在macOS上我们需要一种将多个虚拟地址(在算法中包含不同颜色)映射到同一物理内存的方法。我们将为此使用mach microkernel mach_vm_remap API。堆的物理内存在单独的地址视图中维护,在概念上类似于文件描述符,但位于(主要是)连续的虚拟地址中。该内存被重新映射到内存的各种ZGC视图中,代表了算法的不同指针颜色。 ZGC支持不连续的内存保留。在Linux上,我们在初始化期间保留16TB的虚拟地址空间。我们假设没有共享库将映射到所需的地址空间。在默认的Linux配置上,这是一个安全的假设。但是在macOS上,ASLR机制会侵入我们的地址空间,因此ZGC必须允许堆保留不连续。假设VM实现使用单个连续的内存预留,则共享的VM代码也必须停止。如此一来,is_in_reserved(),reserved_region()和base()之类的GC API将从CollectedHeap中删除。 JEP 365: Windows系统上的ZGC(实验) 将ZGC垃圾收集器移植到Windows系统上。 描述 ZGC的大多数代码库都是平台无关的,不需要Windows特定的更改。现有的x64负载屏障支持与操作系统无关,也可以在Windows上使用。需要移植的特定于平台的代码与如何保留地址空间以及如何将物理内存映射到保留的地址空间有关。用于内存管理的Windows API与POSIX API不同,并且在某些方面不太灵活。 Windows实现的ZGC需要进行以下工作: 支持多映射内存。 ZGC使用彩色指针需要支持堆多重映射,以便可以从进程地址空间中的多个不同位置访问同一物理内存。在Windows上,分页文件支持的内存为物理内存提供了一个标识(句柄),该标识与映射它的虚拟地址无关。使用此标识,ZGC可以将同一物理内存映射到多个位置。 支持将分页文件支持的内存映射到保留的地址空间。 Windows内存管理API不如POSIX的mmap/munmap灵活,尤其是在将文件支持的内存映射到以前保留的地址空间区域中时。为此,ZGC将使用Windows概念的地址空间占位符。 Windows 10和Windows Server版本1803中引入了占位符概念。不会实现对Windows较早版本的ZGC支持。 支持映射和取消映射堆的任意部分。 ZGC的堆布局与其动态调整堆页面大小(以及重新调整大小)相结合,需要支持映射和取消映射任意堆粒子。此要求与Windows地址空间占位符结合使用时,需要特别注意,因为占位符必须由程序显式拆分/合并,而不是由操作系统自动拆分/合并(如在Linux上)。 支持提交和取消提交堆的任意部分。 ZGC可以在Java程序运行时动态地提交和取消提交物理内存。为了支持这些操作,物理内存将被划分为多个分页文件段并由其支持。每个分页文件段都对应一个ZGC堆粒度,并且可以独立于其他段进行提交和取消提交。 JEP 366: 弃用Parallel Scavenge和Serial Old垃圾收集算法的组合 弃用Parallel Scavenge和Serial Old垃圾收集算法的组合。 动机 有一组GC算法的组合很少使用,但是维护起来却需要巨大的工作量:并行年轻代GC(ParallelScavenge)和串行老年代GC(SerialOld)的组合。用户必须使用-XX:+UseParallelGC -XX:-UseParallelOldGC来启用此组合。 这种组合是畸形的,因为它将并行的年轻代GC算法和串行的老年代GC算法组合在一起使用。我们认为这种组合仅在年轻代很多、老年代很少时才有效果。在这种情况下,由于老年代的体积较小,因此完整的收集暂停时间是可以接受的。但是在生产环境中,这种方式是非常冒险的:年轻代的对象容易导致OutOfMemoryException。此组合的唯一优势是总内存使用量略低。我们认为,这种较小的内存占用优势(最多是Java堆大小的约3%)不足以超过维护此GC组合的成本。 描述 除了弃用选项组合-XX:+UseParallelGC -XX:-UseParallelOldGC外,我们还将弃用选项-XX:UseParallelOldGC,因为它唯一的用途是取消选择并行的旧版GC,从而启用串行旧版GC。 因此,任何对UseParallelOldGC选项的明确使用都会显示弃用警告。 JEP 367: 移除Pack200工具和API 删除java.util.jar软件包中的pack200和unpack200工具以及Pack200 API。这些工具和API在Java SE 11中已经被注明为不推荐,并明确打算在将来的版本中删除它们。 动机 Pack200是JSR 200在Java SE 5.0中引入的一种JAR文件压缩方案。其目标是“减少Java应用程序打包,传输和交付的磁盘和带宽需求”。开发人员使用一对工具pack200和unpack200压缩和解压缩其JAR文件。在java.util.jar包中提供了一个API。 删除Pack200的三个原因: 从历史上看,通过56k调制解调器缓慢下载JDK阻碍了Java的采用。 JDK功能的不断增长导致下载量膨胀,进一步阻碍了采用。使用Pack200压缩JDK是缓解此问题的一种方法。但是,时间已经过去了:下载速度得到了提高,并且JDK 9为Java运行时(JEP 220)和用于构建运行时的模块(JMOD)引入了新的压缩方案。因此,JDK 9和更高版本不依赖Pack200。 JDK 8是在构建时用pack200压缩的最新版本,在安装时用unpack200压缩的最新版本。总之,Pack200的主要使用者(JDK本身)不再需要它。 除了JDK,Pack200还可以压缩客户端应用程序,尤其是applet。某些部署技术(例如Oracle的浏览器插件)会自动解压缩applet JAR。但是,客户端应用程序的格局已经改变,并且大多数浏览器都放弃了对插件的支持。因此,Pack200的主要消费者类别(在浏览器中运行的小程序)不再是将Pack200包含在JDK中的驱动程序。 Pack200是一项复杂而精致的技术。它的文件格式与类文件格式和JAR文件格式紧密相关,二者均以JSR 200所无法预料的方式发展。(例如,JEP 309向类文件格式添加了一种新的常量池条目,并且JEP 238在JAR文件格式中添加了版本控制元数据。)JDK中的实现是在Java和本机代码之间划分的,这使得维护变得很困难。 java.util.jar.Pack200中的API不利于Java SE平台的模块化,从而导致在Java SE 9中删除了其四种方法。总的来说,维护Pack200的成本是巨大的,并且超过了其收益。包括在Java SE和JDK中。 描述 JDK最终针对的JDK功能发行版中将删除以前用@Deprecated(forRemoval = true)注解的java.base模块中的三种类型: java.util.jar.Pack200 java.util.jar.Pack200.Packer java.util.jar.Pack200.Unpacker 包含pack200和unpack200工具的jdk.pack模块先前已使用@Deprecated(forRemoval = true)进行了注解,并且还将在此JEP最终针对的JDK功能版本中将其删除。 JEP 368: Text Blocks(二次预览) Java语言增加文本块功能。文本块是多行字符串文字,能避免大多数转义。 动机 在Java中,HTML, XML, SQL, JSON等字符串对象都很难阅读和维护。 1. HTML 使用one-dimensional的字符串语法: String html = "<html>\n" + " <body>\n" + " <p>Hello, world</p>\n" + " </body>\n" + "</html>\n"; 使用two-dimensional文本块语法: String html = """ <html> <body> <p>Hello, world</p> </body> </html> """; 2. SQL 使用one-dimensional的字符串语法: String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" + "WHERE `CITY` = 'INDIANAPOLIS'\n" + "ORDER BY `EMP_ID`, `LAST_NAME`;\n"; 使用two-dimensional文本块语法: String query = """ SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB` WHERE `CITY` = 'INDIANAPOLIS' ORDER BY `EMP_ID`, `LAST_NAME`; """; 3. 多语言示例 使用one-dimensional的字符串语法: ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); Object obj = engine.eval("function hello() {\n" + " print('\"Hello, world\"');\n" + "}\n" + "\n" + "hello();\n"); 使用two-dimensional文本块语法: ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); Object obj = engine.eval(""" function hello() { print('"Hello, world"'); } hello(); """); 描述 文本块是Java语言的新语法,可以用来表示任何字符串,具有更高的表达能力和更少的复杂度。 文本块的开头定界符是由三个双引号字符(""")组成的序列,后面跟0个或多个空格,最后跟一个行终止符。内容从开头定界符的行终止符之后的第一个字符开始。 结束定界符是三个双引号字符的序列。内容在结束定界符的第一个双引号之前的最后一个字符处结束。 与字符串文字中的字符不同,文本块的内容中可以直接包含双引号字符。允许在文本块中使用\",但不是必需的或不建议使用。 与字符串文字中的字符不同,内容可以直接包含行终止符。允许在文本块中使用\n,但不是必需或不建议使用。例如,文本块: """ line 1 line 2 line 3 """ 等效于字符串文字: "line 1\nline 2\nline 3\n" 或字符串文字的串联: "line 1\n" + "line 2\n" + "line 3\n" JEP 370: 外部存储器API(孵化) 引入一个API,以允许Java程序安全有效地访问Java堆之外的外部内存。 动机 许多Java的库都能访问外部存储,例如Ignite、mapDB、memcached及Netty的ByteBuf API。这样可以: 避免垃圾回收相关成本和不可预测性 跨多个进程共享内存 通过将文件映射到内存中来序列化、反序列化内存内容。 但是Java API却没有提供一个令人满意的访问外部内存的解决方案。 Java 1.4中引入的ByteBuffer API允许创建直接字节缓冲区,这些缓冲区是按堆外分配的,并允许用户直接从Java操作堆外内存。但是,直接缓冲区是有限的。 开发人员可以从Java代码访问外部内存的另一种常见途径是使用sun.misc.Unsafe API。Unsafe有许多公开的内存访问操作(例如Unsafe::getInt和putInt)。使用Unsafe访问内存非常高效:所有内存访问操作都定义为JVM内在函数,因此JIT会定期优化内存访问操作。然而根据定义,Unsafe API是不安全的——它允许访问任何内存位置(例如,Unsafe::getInt需要很长的地址)。如果Java程序了访问某些已释放的内存位置,可能会使JVM崩溃。最重要的是,Unsafe API不是受支持的Java API,并且强烈建议不要使用它。 虽然也可以使用JNI访问内存,但是与该解决方案相关的固有成本使其在实践中很少适用。整个开发流程很复杂,因为JNI要求开发人员编写和维护C代码段。 JNI本质上也很慢,因为每次访问都需要Java到native的转换。 在访问外部内存时,开发人员面临一个难题:应该使用安全但受限(可能效率较低)的方法(例如ByteBuffer),还是应该放弃安全保证并接受不受支持和危险的Unsafe API? 该JEP引入了受支持的,安全且有效的外部内存访问API。并且设计时就充分考虑了JIT优化。 描述 外部存储器访问API引入了三个主要的抽象:MemorySegment,MemoryAddress和MemoryLayout。 MemorySegment用于对具有给定空间和时间范围的连续内存区域进行建模。可以将MemoryAddress视为段内的偏移量,MemoryLayout是内存段内容的程序描述。 可以从多种来源创建内存段,例如本机内存缓冲区,Java数组和字节缓冲区(直接或基于堆)。例如,可以如下创建本机内存段: try (MemorySegment segment = MemorySegment.allocateNative(100)) { ... } 上述代码将创建大小为100字节的,与本机内存缓冲区关联的内存段。 内存段在空间上受限制;任何试图使用该段来访问这些界限之外的内存的尝试都会导致异常。正如使用try-with-resource构造所证明的那样,片段在时间上也是有界的。也就是说,它们已创建,使用并在不再使用时关闭。关闭段始终是一个显式操作,并且可能导致其他副作用,例如与该段关联的内存的重新分配。任何访问已关闭的内存段的尝试都将导致异常。空间和时间安全性检查对于确保内存访问API的安全性至关重要。 通过获取内存访问var句柄可以取消引用与段关联的内存。这些特殊的var句柄具有至少一个强制访问坐标,类型为MemoryAddress,即发生取消引用的地址。它们是使用MemoryHandles类中的工厂方法获得的。要设置本机段的元素,我们可以使用如下所示的内存访问var句柄: VarHandle intHandle = MemoryHandles.varHandle(int.class); try (MemorySegment segment = MemorySegment.allocateNative(100)) { MemoryAddress base = segment.baseAddress(); for (int i = 0 ; i < 25 ; i++) { intHandle.set(base.offset(i * 4), i); } } 参考引用 本文同步至: https://waylau.com/jdk-14-released/ https://openjdk.java.net/projects/jdk/14/
在前文,我们介绍来了分布式事务,以及分布式事务的解决方案之一的二阶段提交。本文介绍分布式事务处理方案之一的三阶段提交协议。 分布式事务 分布式事务是指发生在多个数据节点之间的事务,分布式事务比单机事务要复杂的多。在分布式系统中,各个节点之间在是相互独立的,需要通过网络进行沟通和协调。由于存在事务机制,可以保证每个独立节点上的数据操作可以满足ACID。但是,相互独立的节点之间无法准确地知道其他节点的事务执行情况。所以从理论上来讲,两个节点的数据是无法达到一致的状态。如果想让分布式部署的多个节点中的数据保持一致性,那么就要保证在所有节点数据的写操作,要么全部都执行,要么全部都不执行。但是,一台机器在执行本地事务的时候无法知道其他机器中的本地事务的执行结果,所以它也就不知道本次事务到底应该commit还是rollback。所以,常规的解决办法就是引入一个"协调者"的组件来统一调度所有分布式节点的执行。 为了解决这种分布式一致性问题,前人在性能和数据一致性的反反复复权衡过程中总结了许多典型的协议和算法。其中比较著名的有二阶提交协议(Two Phase Commitment Protocol)、三阶提交协议(Three Phase Commitment Protocol)和Paxos算法。针对分布式事务,是X/Open 这个组织定义的一套分布式事务的标准X/Open DTP(X/Open Distributed Transaction Processing ReferenceModel),定义了规范和API接口,可以由各个厂商进行具体的实现。 大部分的关系型数据库通过两阶段提交(Two Phase Commit,2PC)算法来完成分布式事务,比如Oracle中通过dblink方式进行事务处理。下面重点介绍下3PC算法。 下面重点介绍下三阶提交协议算法。 三阶段提交概述 三阶段提交协议可以理解为两阶段提交协议的改良版,是在协调者和参与者中都引入超时机制,并且把两阶段提交协议的第一个阶段分成了两步: 询问,然后再锁资源,最后真正提交。 两阶段提交协议最早是分布式事务的专家Jim Gray在1978年的一篇文章Notes on Database Operating Systems中提及。两阶段提交协议可以保证数据的强一致性,即保证了分布式事务的原子性:所有结点要么全做要么全不做。许多分布式关系型数据管理系统采用此协议来完成分布式事务。它是协调所有分布式原子事务参与者,并决定提交或取消(回滚)的分布式算法。同时也是解决一致性问题的算法。该算法能够解决很多的临时性系统故障(包括进程、网络节点、通信等故障),被广泛地使用。但是,它并不能够通过配置来解决所有的故障,在某些情况下它还需要人为的参与才能解决问题。两阶段提交协议存在的问题是,协调者在某些时刻如果失败了, 整个事务就会阻塞。于是Skeen发布了"NonBlocking Commit Protocols" (1981)这篇论文,论文指出在一个分布式的事务里面, 需要一个三阶段的提交协议来避免在两阶段提交中存在的阻塞问题。 顾名思义,三阶段提交分为以下三个阶段: CanCommit PreCommit DoCommit 在三阶段提交协议中,系统一般包含两类角色: 协调者(Coordinator),通常一个系统中只有一个; 参与者(Participant),一般包含多个,在数据存储系统中可以理解为数据副本的个数。 CanCommit 在CanCommit阶段,协调者协议流程如下: 写本地日志“BEGIN_COMMIT”,并进入WAIT状态; 向所有参与者发送“VOTE_REQUEST”消息; 等待并接收参与者发送的对“VOTE_REQUEST”的响应。参与者响应“VOTE_ABORT”或“VOTE_COMMIT”消息给协调者。 该流程与两阶段提交协议类似。 PreCommit 在PreCommit阶段,,协调者将通知事务参与者准备提交或取消事务,写本地的redo和undo日志,但不提交。 协调者协议流程如下: 若收到任何一个参与者发送的“VOTE_ABORT”消息; 写本地“GLOBAL_ABORT”日志,进入ABORT状态; 向所有的参与者发送“GLOBAL_ABORT”消息; 若收到所有参与者发送的“VOTE_COMMIT”消息; 写本地“PREPARE_COMMIT”日志,进入PRECOMMIT状态; 向所有的参与者发送“PREPARE _COMMIT”消息; 等待并接收参与者发送的对“GLOBAL_ABORT”消息或“PREPARE_COMMIT”消息的确认响应消息。一旦收到所有参与者的“GLOBAL_ABORT”确认消息或者超时没有收到,写本地“END_TRANSACTION”日志流程结束,则不再进入DoCommit阶段。如果收到所有参与者的“PREPARE_COMMIT”确认消息,则进入DoCommit阶段。 该流程与两阶段提交协议相比,多了一个PRECOMMIT状态。 DoCommit 在该阶段, 协调者协议流程如下: 向所有参与者发送的“GLOBAL _COMMIT”消息; 等待并接收参与者发送的对 “GLOBAL_COMMIT”消息的确认响应消息,一旦收到所有参与者的确认消息,写本地“END_TRANSACTION”日志流程结束。 在DoCommit阶段,如果参与者无法及时接收到来自协调者的GLOBAL_COMMIT请求时,会在等待超时之后,会继续进行事务的提交。 三阶段提交状态机 下图为三阶段提交协议中的协调者及参与者的状态机。左侧a为协调者状态机;右侧b为参与者状态机。 三阶段提交的缺陷 相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。 参考引用 本文同步至: https://waylau.com/three-phase-commitment-protocol/ 分布式事务——两阶段提交: https://waylau.com/two-phase-commitment-protocol/ Distributed systems: principles and paradigms Notes on Database Operating Systems NonBlocking Commit Protocols 分布式系统常用技术及案例分析(第二版):https://github.com/waylau/distributed-systems-technologies-and-cases-analysis
在分布式系统中,为了保证数据的高可用,通常会将数据保留多个副本(replica), 这些副本会放置在不同的节点上。这些数据节点可能是物理机器,也可能是虚拟机。为了对用户提供正确的CURD等语意,我们需要保证这些放置在不同节点上的副本是一致的,这就涉及分布式事务的问题。 本文介绍分布式事务处理方案之一的两阶段提交协议。 分布式事务 分布式事务是指发生在多个数据节点之间的事务,分布式事务比单机事务要复杂的多。在分布式系统中,各个节点之间在是相互独立的,需要通过网络进行沟通和协调。由于存在事务机制,可以保证每个独立节点上的数据操作可以满足ACID。但是,相互独立的节点之间无法准确地知道其他节点的事务执行情况。所以从理论上来讲,两个节点的数据是无法达到一致的状态。如果想让分布式部署的多个节点中的数据保持一致性,那么就要保证在所有节点数据的写操作,要么全部都执行,要么全部都不执行。但是,一台机器在执行本地事务的时候无法知道其他机器中的本地事务的执行结果,所以它也就不知道本次事务到底应该commit还是rollback。所以,常规的解决办法就是引入一个"协调者"的组件来统一调度所有分布式节点的执行。 为了解决这种分布式一致性问题,前人在性能和数据一致性的反反复复权衡过程中总结了许多典型的协议和算法。其中比较著名的有二阶提交协议(Two Phase Commitment Protocol)、三阶提交协议(Three Phase Commitment Protocol)和Paxos算法。针对分布式事务,是X/Open 这个组织定义的一套分布式事务的标准X/Open DTP(X/Open Distributed Transaction Processing ReferenceModel),定义了规范和API接口,可以由各个厂商进行具体的实现。 大部分的关系型数据库通过两阶段提交(Two Phase Commit,2PC)算法来完成分布式事务,比如Oracle中通过dblink方式进行事务处理。下面重点介绍下2PC算法。 两阶段提交概述 两阶段提交协议最早是分布式事务的专家Jim Gray在1978年的一篇文章Notes on Database Operating Systems中提及。两阶段提交协议可以保证数据的强一致性,即保证了分布式事务的原子性:所有结点要么全做要么全不做。许多分布式关系型数据管理系统采用此协议来完成分布式事务。它是协调所有分布式原子事务参与者,并决定提交或取消(回滚)的分布式算法。同时也是解决一致性问题的算法。该算法能够解决很多的临时性系统故障(包括进程、网络节点、通信等故障),被广泛地使用。但是,它并不能够通过配置来解决所有的故障,在某些情况下它还需要人为的参与才能解决问题。 顾名思义,两阶段提交分为以下两个阶段: 准备阶段(Prepare Phase) 提交阶段(Commit Phase) 在两阶段提交协议中,系统一般包含两类角色: 协调者(Coordinator),通常一个系统中只有一个; 参与者(Participant),一般包含多个,在数据存储系统中可以理解为数据副本的个数。 准备阶段 在准备阶段,协调者将通知事务参与者准备提交或取消事务,写本地的redo和undo日志,但不提交,然后进入表决过程。在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)。 协调者协议流程如下: 写本地日志“BEGIN_COMMIT”,并进入WAIT状态; 向所有参与者发送“VOTE_REQUEST”消息; 等待并接收参与者发送的对“VOTE_REQUEST”的响应。参与者响应“VOTE_ABORT”或“VOTE_COMMIT”消息给协调者。 提交阶段 在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。参与者在接收到协调者发来的消息后将执行响应的操作。 协调者协议流程如下: 若收到任何一个参与者发送的“VOTE_ABORT”消息; 写本地“GLOBAL_ABORT”日志,进入ABORT状态; 向所有的参与者发送“GLOBAL_ABORT”消息; 若收到所有参与者发送的“VOTE_COMMIT”消息; 写本地“GLOBAL_COMMIT”日志,进入COMMIT状态; 向所有的参与者发送“GLOBAL_COMMIT”消息; 等待并接收参与者发送的对“GLOBAL_ABORT”消息或“GLOBAL_COMMIT”消息的确认响应消息,一旦收到所有参与者的确认消息,写本地“END_TRANSACTION”日志流程结束。 两阶段提交状态机 下图为两阶段提交协议中的协调者及参与者的状态机。左侧a为协调者状态机;右侧b为参与者状态机。 实际案例 MySQL从5.5版本开始支持,SQL Server 2005开始支持,Oracle 7开始支持。 两阶段提交的缺陷 一般情况下,两阶段提交机制都能较好的运行,当在事务进行过程中,有参与者宕机时,重启以后,可以通过询问其他参与者或者协调者,从而知道这个事务到底提交了没有。当然,这一切的前提都是各个参与者在进行每一步操作时,都会事先写入日志。 两阶段提交不能解决的困境如下: 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。 单点故障。由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题) 数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。 参考引用 本文同步至: https://waylau.com/two-phase-commitment-protocol/ Distributed systems: principles and paradigms Notes on Database Operating Systems 分布式系统常用技术及案例分析:https://github.com/waylau/distributed-systems-technologies-and-cases-analysis
Angular CLI 是 Angular 客户端命令行工具,提供非常多的命令来简化 Angular 的开发。今天执行“ng serve”命令时,竟然报找不到模块"@angular-devkit/build-angular"的错误。 问题背景 执行“ng serve”命令时,竟然报找不到模块"@angular-devkit/build-angular"的错误。信息如下: >ng serve An unhandled exception occurred: Could not find module "@angular-devkit/build-angular" from "D:\\workspaceGithub\\mean-news-ui\\mean-news-ui". See "C:\Users\LYF\AppData\Local\Temp\ng-FStMRr\angular-errors.log" for further details. 解决 怀疑是 Angular CLI 与 Angular 应用版本不匹配或者是本地环境有错误引起的。 解决的方案就是卸载 Angular CLI再重新安装,错误就没有了。 1. 卸载老版本 Angular CLI 卸载老版本 Angular CLI,命令如下: >npm uninstall -g @angular/cli removed 244 packages in 20.263s 2. 验证卸载 执行 Angular CLI验证是否已经卸载成功,命令如下: >ng 'ng' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 3. 清理缓存(可选) 建议清理下缓存,该步骤是可选的: >npm cache clean --force npm WARN using --force I sure hope you know what you are doing. 4. 安装新版本 Angular CLI 安装新版本 Angular CLI,命令如下: >npm install -g @angular/cli C:\Users\LYF\AppData\Roaming\npm\ng -> C:\Users\LYF\AppData\Roaming\npm\node_modules\@angular\cli\bin\ng > @angular/cli@8.3.12 postinstall C:\Users\LYF\AppData\Roaming\npm\node_modules\@angular\cli > node ./bin/postinstall/script.js + @angular/cli@8.3.12 added 244 packages from 185 contributors in 63.738s 参考引用 本文同步至: https://waylau.com/angular-could-not-find-module-build-angular/ 完整源码:https://github.com/waylau/angular-enterprise-application-development-samples Angular CLI 常用命令:https://waylau.com/angular-cli-commands/
近期 Java 界好消息频传。先是 Java 13 发布,接着 Eclipse 也发布了新版本表示支持新版本的Java 特性。本文介绍了 Java 13 的新特性并展示了相关的示例。 2019年9月17日,Java 13 正式发布。特性如下。 Java 13 新特性 此版本带来了以下几大新特性: JEP 350,Dynamic CDS Archives:扩展应用程序类-数据共享,以允许在 Java 应用程序执行结束时动态归档类。归档类将包括默认的基础层 CDS(class data-sharing)存档中不存在的所有已加载的应用程序类和库类。 JEP 351,ZGC: Uncommit Unused Memory:增强 ZGC 以将未使用的堆内存返回给操作系统。 JEP 353,Reimplement the Legacy Socket API:使用易于维护和调试的更简单、更现代的实现替换 java.net.Socket 和 java.net.ServerSocket API 使用的底层实现。 JEP 354,Switch Expressions (Preview):可在生产环境中使用的 switch 表达式,JDK 13 中将带来一个 beta 版本实现。switch 表达式扩展了 switch 语句,使其不仅可以作为语句(statement),还可以作为表达式(expression),并且两种写法都可以使用传统的 switch 语法,或者使用简化的“case L ->”模式匹配语法作用于不同范围并控制执行流。这些更改将简化日常编码工作,并为 switch 中的模式匹配(JEP 305)做好准备。 JEP 355,Text Blocks (Preview):将文本块添加到 Java 语言。文本块是一个多行字符串文字,它避免了对大多数转义序列的需要,以可预测的方式自动格式化字符串,并在需要时让开发人员控制格式。 安装 JDK 13 JDK 13下载地址为 https://www.oracle.com/technetwork/java/javase/downloads/index.html。 以Windows环境为例,可通过jdk-13_windows-x64_bin.exe或jdk-13_windows-x64_bin.zip来进行安装。 .exe文件的安装方式较为简单,按照界面提示点击“下一步”即可。 下面演示.zip安装方式。 1. 解压.zip文件到指定位置 将jdk-13_windows-x64_bin.zip文件解压到指定的目录下即可。比如,本例子放置在了D:\Program Files\jdk-13位置。 2. 设置环境变量 创建系统变量“JAVA_HOME”,其值指向了JDK的安装目录。 在用户变量“Path”中,增加“%JAVA_HOME%bin”。 注:JDK13已经无需再安装JRE,设置环境变量时也不用设置CLASSPATH了。 3. 验证安装 执行“java -version”命令进行安装的验证: $ java -version java version "13" 2019-09-17 Java(TM) SE Runtime Environment (build 13+33) Java HotSpot(TM) 64-Bit Server VM (build 13+33, mixed mode, sharing) 如果现实上述信息,则说明JDK已经安装完成。 如果显示的内容还是安装前的老JDK版本,则可按照如下步骤解决。 首先,卸载老版本的JDK 其次,在命令行输入如下指令来设置JAVA_HOM和Path: >SET JAVA_HOME=D:\Program Files\jdk-13 >SET Path=%JAVA_HOME%\bin Eclipse IDE 2019-09 在 Java 13 发布两天后的2019年9月19日,Eclipse IDE 2019-09 发布。Eclipse IDE 2019-09 声称支持Java 13。接下里将演示如何使用Eclipse IDE 2019-09编写 Java 13 的示例。 Eclipse IDE 2019-09 下载地址为 https://www.eclipse.org/downloads/packages/。本例使用的是Eclipse 4.14版本。 编写 Java 13 示例 实战1:Switch表达式的例子 下面是原有的Switch表达式的写法: switch (day) { case MONDAY: case FRIDAY: case SUNDAY: System.out.println(6); break; case TUESDAY: System.out.println(7); break; case THURSDAY: case SATURDAY: System.out.println(8); break; case WEDNESDAY: System.out.println(9); break; } 在Java 12中,Switch表达式可以改为如下写法: switch (day) { case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); case TUESDAY -> System.out.println(7); case THURSDAY, SATURDAY -> System.out.println(8); case WEDNESDAY -> System.out.println(9); } 还能支持在表达式中返回值: int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; }; 在Java 13中,Switch表达式可以改为如下写法: int date = switch (day) { case MONDAY, FRIDAY, SUNDAY : yield 6; case TUESDAY : yield 7; case THURSDAY, SATURDAY : yield 8; case WEDNESDAY : yield 9; default : yield 1; // default条件是必须的 }; System.out.println(date); 需要注意的是,在使用yield时,必须要有default条件。 实战2:文本块 自Java 13开始,支持文本块(Text Blocks)。 以下是Java 13之前的文本块的处理方式的示例: String html = "<html>\n" + " <body>\n" + " <p>Hello, world</p>\n" + " </body>\n" + "</html>\n"; System.out.println(html); 在上述示例中,由于文本块需要换行,所以产生了很多本文的拼接和转义。 以下是Java 13中的文本块示例: String html2 = """ <html> <body> <p>Hello, world</p> </body> </html> """; System.out.println(html2); 在上述示例中,对于文本块的处理变得简洁、自然。 以上两个示例在控制台输出内容都是一样的,效果如下: <html> <body> <p>Hello, world</p> </body> </html> 更多Java示例,可见“现代Java案例大全”。 参考引用 本文同步至: https://waylau.com/java-13-new-features-and-samples/ 完整源码:https://github.com/waylau/modern-java-demos http://openjdk.java.net/projects/jdk/13
本文介绍了如何从 Angular 中的 URL 获取查询参数。 通过注入ActivatedRoute的实例,可以订阅各种可观察对象,包括queryParams和params observable。以下是范例: import { ActivatedRoute } from '@angular/router'; // 用于获取路由参数 import { Component, OnInit } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; // 用于HTML过滤 import { Location } from '@angular/common'; // 用于回退浏览记录 import { NewsDetailService } from '../news-detail.service'; @Component({ selector: 'app-news-detail', templateUrl: './news-detail.component.html', styleUrls: ['./news-detail.component.css'] }) export class NewsDetailComponent implements OnInit { newsDetailData = null; newsUrl = null; constructor(private newsDetailService: NewsDetailService, private domSanitizer: DomSanitizer, private route: ActivatedRoute, private location: Location) { } ngOnInit() { this.showNewsDetailData(); } // 展示新闻详情数据 showNewsDetailData() { this.route.queryParams.subscribe(p => { this.newsUrl = p.newsUrl // 获取参数 this.newsDetailService.getNewsData(this.newsUrl).subscribe( (newsApiData) => this.newsDetailData = this.domSanitizer.bypassSecurityTrustHtml(newsApiData.toString()) //HTML过滤 ); }); } // 返回 goback() { // 浏览器回退浏览记录 this.location.back(); } } 参考引用 本文同步至: https://waylau.com/get-query-params-from-url-in-angular/ 完整源码:https://github.com/waylau/angular-enterprise-application-development-samples 《Angular企业级应用开发实战》
当往MongoDB中插入一条数据时,会自动生成ObjectId作为数据的主键。那么如何通过ObjectId来做数据的唯一查询呢? 在MongoDB中插入一条数据 在MongoDB中插入一条如下结构的数据: { _id: 5d6a32389c825e24106624e4, title: 'GitHub 上有什么好玩的项目', content: '上个月有水友私信问我,GitHub 上有没有比较好玩的项目可以推荐?我跟他说:"有,过两天我整理一下"。\n' + '\n' + '然而,一个月过去了,我把这件事情忘了精光,直至他昨天提醒我才记起2_05.png。\n', creation: 2019-08-31T08:39:20.384Z } 其中,上述_id的值“5d6a32389c825e24106624e4”,是MongoDB自动分配的。 使用 MongoDB 的 ObjectId 作为查询条件 须知,_id的值“5d6a32389c825e24106624e4”并非是字符串,而是ObjectId对象类型。因此,如下查询是行不通的: // 查询指定文档 const findNews = function (db, newsId, callback) { // 获取集合 const news = db.collection('news'); // 查询指定文档 news.findOne({_id: newsId},function (err, result) { if (err) { console.error('error end: ' + err.stack); return; } console.log("查询指定文档,响应结果是:"); console.log(result); callback(result); }); } 需将上述newsId转为 ObjectId对象类型。怎么做呢?做法参考如下: const ObjectId = require('mongodb').ObjectId; // 查询指定文档 const findNews = function (db, newsId, callback) { // 获取集合 const news = db.collection('news'); // 查询指定文档 news.findOne({_id: ObjectId(newsId)},function (err, result) { if (err) { console.error('error end: ' + err.stack); return; } console.log("查询指定文档,响应结果是:"); console.log(result); callback(result); }); } 其中,require('mongodb').ObjectId用于获取ObjectId类,并将字符串newsId转为了 ObjectId 类型。 参考引用 本文同步至: https://waylau.com/node.js-mongodb-objectid/ 完整源码:https://github.com/waylau/mean-book-samples
mysql模块(项目地址为https://github.com/mysqljs/mysql)是一个开源的、JavaScript编写的MySQL驱动,可以在Node.js应用中来操作MySQL。但在使用过程中,出现了“ER_NOT_SUPPORTED_AUTH_MODE”问题。 本文介绍了出现该问题的原因及解决方案。 报错信息 当我试图使用mysql模块来连接MySQL 8时,出现了如下错误信息: D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\index.js:17 throw error; ^ Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client at Handshake.Sequence._packetToError (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\protocol\sequences\Sequence.js:47:14) at Handshake.ErrorPacket (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\protocol\sequences\Handshake.js:123:18) at Protocol._parsePacket (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\protocol\Protocol.js:291:23) at Parser._parsePacket (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\protocol\Parser.js:433:10) at Parser.write (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\protocol\Parser.js:43:10) at Protocol.write (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\protocol\Protocol.js:38:16) at Socket.<anonymous> (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\Connection.js:91:28) at Socket.<anonymous> (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\Connection.js:525:10) at Socket.emit (events.js:196:13) at addChunk (_stream_readable.js:290:12) -------------------- at Protocol._enqueue (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\protocol\Protocol.js:144:48) at Protocol.handshake (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\protocol\Protocol.js:51:23) at Connection.connect (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\node_modules\mysql\lib\Connection.js:119:18) at Object.<anonymous> (D:\workspaceGithub\nodejs-book-samples\samples\mysql-demo\index.js:12:12) at Module._compile (internal/modules/cjs/loader.js:759:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10) at Module.load (internal/modules/cjs/loader.js:628:32) at Function.Module._load (internal/modules/cjs/loader.js:555:12) at Function.Module.runMain (internal/modules/cjs/loader.js:826:10) at internal/main/run_main_module.js:17:11 出错原因 导致这个错误的原因是,目前,最新的mysql模块并未完全支持MySQL 8的“caching_sha2_password”加密方式,而“caching_sha2_password”在MySQL 8中是默认的加密方式。因此,下面的方式命令是默认已经使用了“caching_sha2_password”加密方式,该账号、密码无法在mysql模块中使用。 mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY '123456'; Query OK, 0 rows affected (0.12 sec) 解决方法 解决方法是从新修改用户root的密码,并指定mysql模块能够支持的加密方式: mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456'; Query OK, 0 rows affected (0.12 sec) 上述语句,显示指定了使用“mysql_native_password”的加密方式。这种方式是在mysql模块能够支持。 再此运行应用,可以看到如下的控制台输出信息: $ node index.js The result is: RowDataPacket { user_id: 1, username: '老卫' } 其中,“RowDataPacket { user_id: 1, username: '老卫' }”就是数据库查询的结果。 源码 本节例子可以在https://github.com/waylau/nodejs-book-samples的“mysql-demo”应用中找到。 参考引用 本文同步至: https://waylau.com/node.js-mysql-client-does-not-support-authentication-protocol/ 有关MySQL 8的“caching_sha2_password”加密方式,可见https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html#upgrade-caching-sha2-password
对于聊天室,大家应该都不陌生,笔者也写过很多关于聊天室的例子。 本节,我们将演示如何通过Node.js来实现一个WebSocket聊天服务器的例子。 使用ws创建WebSokcet服务器 Node.js原生API并未提供WebSocket的支持,因此,需要安装第三方包才能使用WebSocket功能。对于WebSocket的支持,在开源社区有非常多的选择,本例子采用的是“ws”框架(项目主页为https://github.com/websockets/ws)。 “ws”顾名思义是一个用于支持WebSocket客户端和服务器的框架。它易于使用,功能强大,且不依赖于其他环境。 想其他Node.js应用一样,使用ws的首选方式是使用npm来管理。以下命令行用于安装ws在应用里面: npm install ws 具备了ws包之后,就可以创建WebSocket服务器了。以下是创建服务器的j简单示例: const WebSocket = require('ws'); const server = new WebSocket.Server({ port: 8080 }); 上述例子服务器启动在8080端口。 聊天服务器的需求 聊天服务器的业务需求比较简单,是一个群聊聊天室。换言之,所有人发送的消息大家都可以见到。 当有新用户连接到服务器时,会以该用户的“IP+端口”作为用户的名称。 服务器的实现 根据前面知识的学习,实现一个聊天服务器比较简单,完整代码如下: const WebSocket = require('ws'); const server = new WebSocket.Server({ port: 8080 }); server.on('open', function open() { console.log('connected'); }); server.on('close', function close() { console.log('disconnected'); }); server.on('connection', function connection(ws, req) { const ip = req.connection.remoteAddress; const port = req.connection.remotePort; const clientName = ip + port; console.log('%s is connected', clientName) // 发送欢迎信息给客户端 ws.send("Welcome " + clientName); ws.on('message', function incoming(message) { console.log('received: %s from %s', message, clientName); // 广播消息给所有客户端 server.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send( clientName + " -> " + message); } }); }); }); 当客户端给服务器发送消息时,服务器会将该客户端的消息转发给所有客户端。 客户端的实现 客户端是通HTML+JavaScript的方式实现的。由于浏览器原生提供了WebSocket的API,所以并不需要ws框架的支持。 客户端client.html文件代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>WebSocket Chat</title> </head> <body> <script type="text/javascript"> var socket; if (!window.WebSocket) { window.WebSocket = window.MozWebSocket; } if (window.WebSocket) { socket = new WebSocket("ws://localhost:8080/ws"); socket.onmessage = function (event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\n' + event.data }; socket.onopen = function (event) { var ta = document.getElementById('responseText'); ta.value = "连接开启!"; }; socket.onclose = function (event) { var ta = document.getElementById('responseText'); ta.value = ta.value + "连接被关闭"; }; } else { alert("你的浏览器不支持 WebSocket!"); } function send(message) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { socket.send(message); } else { alert("连接没有开启."); } } </script> <form onsubmit="return false;"> <h3>WebSocket 聊天室:</h3> <textarea id="responseText" style="width: 500px; height: 300px;"></textarea> <br> <input type="text" name="message" style="width: 300px" value="Welcome to waylau.com"> <input type="button" value="发送消息" onclick="send(this.form.message.value)"> <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空聊天记录"> </form> <br> <br> <a href="https://waylau.com/">更多例子请访问 waylau.com</a> </body> </html> 运行应用 首先启动服务器。执行下面的命令: node index.js 接着用浏览器直接打开client.html文件,可以看到如下的聊天界面。 打开多个聊天窗口,就能模拟多个用户之间的群聊了。 源码 本节例子可以在https://github.com/waylau/nodejs-book-samples的“ws-demo”应用中找到。 本节例子可以在“ws-demo”应用中找到。 参考引用 本文同步至: https://waylau.com/node.js-websocket-chat/ MINA 实现聊天功能: https://waylau.com/mina-chat/ Netty 实现 WebSocket 聊天功能: https://waylau.com/netty-websocket-chat/ Netty 实现聊天功能: https://waylau.com/netty-chat/ WebSocket 和 Golang 实现聊天功能: https://waylau.com/go-websocket-chat/
2023年02月
2022年12月
2022年11月
2022年10月
2022年06月
2022年05月
2022年04月
2022年03月
2022年01月
2021年12月