最近在写一些东西的时候,有一些数据是使用XML来保存的,而其中有一些数据是一段HTML文本,一开始觉得没什么问题,当把这些HTML放到XML的一个结点的时候,才发现数据已经变样了。 原来在XML中有5个预定义的实体引用: < < 小于 > > 大于 & & 和号 ' ' 省略号 " " 引号 参考:http://www.w3school.com.cn/xml/xml_cdata.asp HTML和XML差不多,是由一些预定义的标签组成的,所以包含了大量的"<"、">"符号。 所以有两个方法可以将HTML插入到XML中: 1、将HTML进行一次转换,将所有的"<"、">"等预定义符号转换成相应的实体引用。 2、将HTML包在CDATA中。 很明显,第一种方法比较烦琐,序列化数据的时候和把序列化的时候都需要进行转换。 第二种方法则很简单了。 写了一个方法:
1 2 3 4 5 6 7 |
public class XMLUtil { public static function CDATA(data:String):XML { return new XML( " <![CDATA[ " + data + " ]]\> " ); } } |
可以这样使用:
1 |
var node:XML = < node > {XMLUtil.CDATA( " 字符串 " )} </ node > ; |
参考: http://cookbooks.adobe.com/post_Create_CDATA_tags_between_XML_nodes_using_AS-6142.html 转载于:https://www.cnblogs.com/yili16438/archive/2011/04/13/2015369.html from:https://blog.csdn.net/weixin_30556161/article/details/97479499
View Details缘由 我们在使用spring boot开发的服务中,一般会选择打包成单体的fatjar来发布服务,这在传统的部署方式下是非常方便的,但是当我们选择使用docker这种容器化的方式来部署应用的时候,却有一点点的不便之处,因为这个单体的jar一般都比较大,每次镜像push到仓库和从仓库拉取都需要比较长的时间。 原因是什么了? docker的一大特色就是镜像的存储是分层的,参考下面这张官图 我们在Dockerfile中的每一个指令会对应到镜像的每一层,docker在更新镜像时,只会推送变更过的层,当它计算出来这一层的摘要和之前的版本一致时,会复用上一次打包镜像时的缓存,会极大的提高打包镜像以及镜像push/pull操作的速度。 那么问题来了,当我们springboot打包出来的单体jar的时候,每次编译这个jar都会发生变化,对应的存储层也会发生变化,push和pull操作时都需要重新推送,而且这个jar一般都不小,一个典型的应用会在100M左右,对应用部署和发布的速度会有比较大的影响。 稍作思考,很容易就能发现这个肥大的jar文件里面,大部分其实都是固定不变的各种依赖库,我们真正每次编译会变化的业务代码部分其实很小很小,可能也就只有几百KB,只要能将这两部分分离,变成docker镜像中的两层,一定能极大的提升镜像发布的速度 牛刀小试 首先拿来动手尝试的是一个springboot admin的项目,项目的结构是这样的: 使用最常见的打包方式:
1 2 3 4 |
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> |
会生成一个32M的jar文件,优化之前的Dockerfile非常简单:
1 2 3 4 5 6 7 8 |
FROM openjdk:11.0.5-stretch as builder RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone VOLUME /tmp COPY target/*.jar ./app.jar CMD ["/bin/bash", "-c", "java -jar -server app.jar"] EXPOSE 8080 |
可以看到这种方式在构建v2版本的镜像时,会重新copy整个完整的jar 如果要拆开这个单体的jar,有两种方式,一是修改mvn打包的配置,将lib包放在独立的文件夹下,在这里我们考虑到项目众多,尽量减少修改,选择了在Docker打包镜像时,解压打包出来的jar包,将其中的内容分开来copy,修改后的Dockerfile如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#采用docker的分阶段构建方式,第一阶段负责解压jar包 FROM openjdk:11.0.5-stretch as builder RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone VOLUME /tmp WORKDIR /target ADD target/*.jar app.jar RUN jar xf app.jar #这里分别copy解压后的内容 FROM openjdk:11.0.5-stretch VOLUME /tmp WORKDIR /app COPY --from=builder target/BOOT-INF/lib ./lib COPY --from=builder target/org/ ./org COPY --from=builder target/META-INF/ ./META-INF COPY --from=builder target/BOOT-INF/classes ./classes CMD ["/bin/bash", "-c", "java -cp .:./classes/:./lib/* -server org.springframework.boot.loader.JarLauncher"] EXPOSE 8080 |
看看修改后的效果: 在copy lib目录时,是直接using cache的。来看看push的时候效果对比 首先是优化前的push: 可以看到在push v2的时候还是会push一个33MB的层,虽然其实我们一行代码没有修改。 然后是优化后的: 可以看到这一次仅仅只推送了一个13KB的层,推送的速度快了非常多,同理也可以想象的到,我们在拉取镜像更新版本时速度会快很多。 路遇荆棘 在针对springboot-admin这个最简单的项目的优化取得很好的效果之后,就开始准备照搬到其他的项目中,没想到同样的方式怎么折腾都无效,分离之后的lib目录依然会每次需要全量重新push。出问题的项目结构大概是这样的: 一个常见的多模块mvn项目,有common,domain,rest-client,rest-server 这4个子模块,其中rest-server会依赖common和domain这两个子模块,打包出来的jar是在rest-server这个模块中。 究竟是什么鬼了? 终得正果 苦苦思索一番之后,lib目录既然不能复用上一次的cache,那一定是因为里面的内容有变化,遂将jar包解压,进到lib目录,真凶果然在此: 项目自身的3个子模块在每次编译的时候也会做为jar包放到lib目录下,这3个jar包每次编译都会有变化,所以导致这一层的cache失效。 找到问题之后,解决的思路就很简单了,将这种jar单独copy到一个目录下即可,修改后的Dockerfile如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
FROM openjdk:11.0.5-stretch as builder RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone VOLUME /tmp WORKDIR /target ADD target/*.jar app.jar RUN jar xf app.jar #创建一个snapshot目录,把snapshot的jar复制过去 RUN mkdir BOOT-INF/snapshot RUN mv BOOT-INF/lib/*SNAPSHOT.jar BOOT-INF/snapshot/ FROM openjdk:11.0.5-stretch VOLUME /tmp WORKDIR /app COPY --from=builder target/BOOT-INF/lib ./lib COPY --from=builder target/org/ ./org COPY --from=builder target/META-INF/ ./META-INF #单独复制snapshot这一层 COPY --from=builder target/BOOT-INF/snapshot ./snapshot COPY --from=builder target/BOOT-INF/classes ./classes CMD ["/bin/bash", "-c", "java -cp .:./classes/:./lib/*:./snapshot/* -server org.springframework.boot.loader.JarLauncher"] EXPOSE 8089 |
这样修改之后效果就和上面单模块的项目一样了,至此,基本完成了springboot项目的docker镜像优化,在jenkins的流水线上可以将原来镜像push的时间从1分钟以上优化到10s左右 未来之路 在整个优化的过程中,发现springboot2.3 M1版本已经有针对性的优化方案,增加了LAYERED_JAR的打包格式,未来可期。 具体可参考下文: www.jdon.com/53738 注: 本文中举例的两个项目案例,可在github上找到:github.com/yishh/sprin… 作者:thor_lee 链接:https://juejin.cn/post/6844904119338008583 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
View Details遇到的问题: 公司java项目按老的方式打包出来一个fat jar,100MB, 推送到腾讯云镜像仓库很慢,8分钟。。。走的是公网,专线暂时还没配置好 以前是内网harbor,速度还不明显。 归根究底,一次推送100MB是个不合理的事情 思路: 了解spring boot打包,期望将依赖的libs 和 业务代码拆分 优化dockerfile,充分利用缓存 解决问题: 先修改spring-boot-maven-plugin,只打包业务代码。网上有些是配置exclude,我试了,恶心到了。。那么多包挨个找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <!--<mainClass>cn.qg.clotho.Bootstrap</mainClass>--> <layout>ZIP</layout> <!--<executable>true</executable>--> <includes> <include> <groupId>${project.groupId}</groupId> <artifactId>${project.artifactId}</artifactId> </include> </includes> </configuration> </plugin> |
新增maven-dependency-plugin,将依赖移到libs目录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <type>jar</type> <includeTypes>jar</includeTypes> <includeScope>runtime</includeScope> <outputDirectory>${project.build.directory}/libs</outputDirectory> </configuration> </execution> </executions> </plugin> |
优化dockerfile 参考 https://medium.com/@nieldw/caching-maven-dependencies-in-a-docker-build-dca6ca7ad612 ,牛逼
1 2 3 4 5 6 7 8 9 10 11 12 13 |
FROM mvn3.5 as builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src/ /build/src/ RUN mvn package FROM jdk1.8 EXPOSE 80 CMD exec java -Dloader.path="/home/libs/" -jar /home/app.jar COPY --from=builder /build/target/*.jar /home/app.jar COPY --from=builder /build/target/libs /home/libs/ |
搞定。最终代码变化每次推送也就1MB多 启动命令 java -Dloader.path="libs/" -jar app.jar 作者:小猋_a8f1 链接:https://www.jianshu.com/p/32456eea0488 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
View Details在这容器化的世界里,我们已经很少直接通过文件发布来运行asp.net core程序了。现在大多数情况下,我们都会使用docker来运行程序。在使用docker之前,我们往往需要打包我们的应用程序。asp.net core程序的镜像打包,网上有很多教程,其中大多数是使用sdk这个镜像来直接打包。打出来的包有好几百MB,3.1 SDK打出来的包甚至超过了1GB。那么有什么办法来缩小我们打出来的镜像吗?最小能缩小到多少呢?这篇文章就来介绍下如何缩小asp.net core 打包出来镜像的大小。 新建asp.net core 程序 新建一个asp.net core应用程序,用来演示打包。首先我们演示下如果使用dotnet sdk5.0来打包 docker 镜像。 sdk:5.0
1 2 3 4 5 6 7 8 |
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build WORKDIR /app COPY /. /app RUN dotnet restore -s https://nuget.cdn.azure.cn/v3/index.json WORKDIR /app/CoreDockerImageSizeTest RUN dotnet publish -o ./out -c Release EXPOSE 5000 ENTRYPOINT ["dotnet", "out/CoreDockerImageSizeTest.dll"] |
在项目根目录下新建一个Dockerfile文件,文件内容如上。这个Dockerfile比较简单,使用dotnet sdk:5.0最为底层包来构建,这也是最傻瓜的打包方式。那么看看这个镜像打出来有多大吧。
1 |
docker build . -t coredockerimagesizetest_0.1 |
使用docker build命令进行打包。
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE coredockerimagesizetest_0.1 latest 14aea8e0c1d5 5 seconds ago 643MB |
使用docker images命令来查看镜像列表,我们发现我们打出来的镜像居然有643MB,真的很大。如果是内网还好一点,如果在镜像存在docker hub等第三方仓库,这得下半天。显然这个镜像太大了,接下来看我们如何进行优化。 sdk:5.0-buster-slim 最新的VisualStudio内置了docker工具,可以自动为我们生成Dockerfile文件。我们来看看它生成的镜像文件有多大。 右键解决方案=>添加=>Docker支持=>Linux 。 选择完成后VS会为我们自动添加一个Dockerfile在根目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base WORKDIR /app EXPOSE 5000 FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build WORKDIR /src COPY ["CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj", "CoreDockerImageSizeTest/"] RUN dotnet restore "CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj" COPY . . WORKDIR "/src/CoreDockerImageSizeTest" RUN dotnet build "CoreDockerImageSizeTest.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "CoreDockerImageSizeTest.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "CoreDockerImageSizeTest.dll"] |
这个自动生成的Dockerfile使用了sdk:5.0-buster-slim这个镜像进行build跟publish,使用aspnet:5.0-buster-slim这个runtime级别的镜像做为final底包。从名字来看,很明显slim代表着轻量。让我们试试这个Dockerfile打出来的包有多大。
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE coredockerimagesizetest_0.2 latest 0a24618f6ece 11 seconds ago 210MB |
使用docker build命令进行打包。使用docker images命令查看镜像的大小,这个镜像的大小为210MB。果然比上面的镜像小了很多。那么是否还能继续缩小镜像的大小呢?继续往下看。 5.0-alpine 除了使用buster-slim镜像,我们还可以选择更加小巧的alpine镜像来打包。alpine镜像是继续alpine linux创建的镜像,所以它更加轻量级更加小巧。 关于alpine linux可以查看这篇:Alpine Linux 与 CentOS 有什么区别? 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
FROM mcr.microsoft.com/dotnet/aspnet:5.0-alpine AS base WORKDIR /app EXPOSE 5000 FROM mcr.microsoft.com/dotnet/sdk:5.0-alpine AS build WORKDIR /src COPY ["CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj", "CoreDockerImageSizeTest/"] RUN dotnet restore "CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj" COPY . . WORKDIR "/src/CoreDockerImageSizeTest" RUN dotnet build "CoreDockerImageSizeTest.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "CoreDockerImageSizeTest.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "CoreDockerImageSizeTest.dll"] |
修改Dockerfile使用aspnet:5.0-alpine及sdk:5.0-alpine来构建这个镜像。
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE coredockerimagesizetest_0.3 latest db34d613e21a 12 seconds ago 108MB |
使用docker build命令进行打包。使用docker images命令查看镜像的大小,这个镜像的大小为108MB。现在这个镜像已经比我们第一次打包减少了500多MB了。那么还能更小吗?请往下看。 runtime-deps:5.0-alpine 最新的.net core程序支持自宿主及单文件发布。如果采用以上发布形式,那么我们可以选择使用runtime-deps:5.0-alpine做为最终底包来打包我们的镜像。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
FROM mcr.microsoft.com/dotnet/aspnet:5.0-alpine AS base WORKDIR /app EXPOSE 5000 FROM mcr.microsoft.com/dotnet/sdk:5.0-alpine AS build WORKDIR /src COPY ["CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj", "CoreDockerImageSizeTest/"] RUN dotnet restore "CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj" COPY . . WORKDIR "/src/CoreDockerImageSizeTest" RUN dotnet build "CoreDockerImageSizeTest.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "CoreDockerImageSizeTest.csproj" -c Release -o /app/publish \ --runtime alpine-x64 \ --self-contained true \ /p:PublishTrimmed=true \ /p:PublishSingleFile=true FROM mcr.microsoft.com/dotnet/runtime-deps:5.0-alpine AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["./CoreDockerImageSizeTest"] |
修改Dockerfile,使用/runtime-deps:5.0-alpine做为final镜像。
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE coredockerimagesizetest_0.5 latest dab1289626f9 6 seconds ago 54.6MB |
使用docker build命令进行打包。使用docker images命令查看镜像的大小,这个镜像的大小为54.6MB。 总结 通过以上演示,我们的镜像大小从一开始的600多MB缩小到了54MB。一般生产我主要选择buster-slim这个镜像来打包。如果选择runtime-deps打包,打出来的包是最小的,虽然演示项目是可以运行的,但是本人没有在生产使用过,还请谨慎使用。 代码在这:CoreDockerImageSizeTest from:https://www.cnblogs.com/kklldog/p/netcore-docker-image-size.html
View DetailsGuava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 等等。 Guava 是Java的工具集,提供了一些常用的便利的操作工具类,减少因为 空指针、异步操作等引起的问题BUG,提高开发效率。 本文主要介绍了Guava常用的工具方法,快速入门Guava。 1、基本工具(Base utils) 1. Optional null 值出现在代码中,有如下缺点: 语义模糊,引起歧义。例如,Map.get(key)返回Null时,可能表示map中的值是null,亦或map中没有key对应的值。 在应用层面可能造成混乱,出现令人意外的错误。 为了尽量避免程序中的null值,guava提供了Optional对数据进行封装。如果值为空则立即抛出异常,并且提供了Absent和Present两个子类分别表示值缺失和值存在的情形,来增强null的语义。 常用方法如下: isPresent():如果Optional包含非null的引用(引用存在),返回true get() :如果Optional为NULL将触发异常
1 2 3 4 5 6 7 8 |
public static void test(){ Optional<Integer> possible = Optional.fromNullable(5); //创建允许null值的Optional if(possible.isPresent()){//包含的引用非null的(引用存在),返回true System.out.println(possible.get()); }else{ System.out.println("possible is null"); } } |
or(defaultvalue) :包含的引用缺失(null),返回默认的值,否则返回本身 orNull():包含的引用缺失,返回null asSet():如果引用存在,返回只有单一元素的集合;若为NULl返回空集合 2. 先决条件 Preconditions Preconditions 提供了判断条件是否合法的静态方法,如果不符合要求会抛出异常。类似断言。 方法声明(不包括额外参数) 描述 检查失败时抛出的异常 checkArgument(boolean) 检查boolean是否为true,用来检查传递给方法的参数 IllegalArgumentException checkNotNull(T) 检查value是否为null,该方法直接返回value,因此可以内嵌使用checkNotNull NullPointerException checkState(boolean) 用来检查对象的某些状态。 IllegalStateException checkElementIndex(int index, int size) 检查index作为索引值对某个列表、字符串或数组是否有效。index>=0 && index<size IndexOutOfBoundsException checkPositionIndex(int index, int size) 检查index作为位置值对某个列表、字符串或数组是否有效。index>=0 && index<=size IndexOutOfBoundsException checkPositionIndexes(int start, int end, int size) 检查[start, end]表示的位置范围对某个列表、字符串或数组是否有效 IndexOutOfBoundsException 每个判断方法都有三个多态方法: 没有额外参数:抛出的异常中没有错误消息; 有一个Object对象作为额外参数:抛出的异常使用Object.toString() 作为错误消息; 有一个String对象作为额外参数,并且有一组任意数量的附加Object对象:这个变种处理异常消息的方式有点类似printf,但考虑GWT的兼容性和效率,只支持%s指示符。例如:
1 2 3 |
checkArgument(i >= 0); checkArgument(i >= 0, "Argument was expected nonnegative"); checkArgument(i < j, "Expected i < j, but %s > %s", i, j); |
3. […]
View DetailsGuava是谷歌提供的一个核心Java类库,其中包括新的集合类型、不可变集合、图库,以及用于并发、I/O、Hash、缓存、字符串等的 实用工具。它在谷歌中的大多数Java项目中被广泛使用,也被许多其他公司广泛使用,熟练掌握这些工具类能帮助我们快速的处理日常开发中的一些问题,比如,不可变集合、集合的转换、字符串处理、本地缓存等 最近一段时间,我把Guava中常用到的工具类学了一遍,感觉有些工具类还是挺有用的,至少它帮你封装了很多功能,让你在处理一些逻辑的时候,不用太关注细节,把Guava的工具类直接拿来用就好了。下面我就介绍一下Guava中最常用的一些工具: 1、Guava不可变集合 不可变集合就是集合创建之后元素是不可改变的,主要用途如下: 不可变对象提供给别人使用时是安全的,因为不可变,所有人都无法进行修改,只能读 支持多个线程调用,不存在竞争的问题,天然支持多线程 不可变集合节省内存空间,因为不可变,集合空间在创建时就已经确定好了,不用考虑扩容等问题,内存利用率高 不可变集合可用于常量 Guava针对常用的集合类型List、Set、Map等都提供了不可变类型的集合 详细使用方法,可查看另一篇博客《Guava系列之不可变集合》 2、Guava新的集合类型 Guava提供了几种新的集合类型,补充了JDK中的集合类型 比如我们要统计List中某个元素出现的次数,如果使用JDK中的list就需要使用循环遍历进行统计,但使用了Guava的Multiset就可以直接统计出来元素出现的次数 再比如,我们要通过Map中的key查找value,通过value来查找值,也就是需要一个双向Map,如果使用JDK中的Map,我们需要维护两个Map,一个从key映射到value,另外一个从value映射到key,而且不管是新增还是修改Map中的元素,都要保持两个Map同步修改,维护成本太高了,使用Guava的BiMap可以通过一个Map轻松解决这个问题 更多新集合类型请查看《Guava系列之新的集合类型》 3、Guava超实用的集合工具类 JDK中集合的操作已经提供了很多工具类,比如基本的集合交集、并集、差集这些常用的操作,Guava中提供的工具类是对JDK的补充,在Guava中提供了静态的创建集合的方法,还有集合的很多操作,比如笛卡尔集、list反转、排列组合、Set转Map、Map的各种过滤等 新集合工具类的详细使用,请查看《Guava系列之超实用的集合工具类》 4、Guava本地缓存Cache Guava中的缓存是本地缓存的实现,与ConcurrentMap相似,但不完全一样。最基本的区别就是,ConcurrentMap会一直保存添加进去的元素,除非你主动remove掉。而Guava Cache为了限制内存的使用,通常都会设置自动回收 Guava Cache的使用场景: 以空间换取时间,就是你愿意用内存的消耗来换取读取性能的提升 你已经预测到某些数据会被频繁的查询 缓存中存放的数据不会超过内存空间 Guava Cache的详细使用方法,可查看《Guava系列之Cache》 5、Guava强大的String工具类 String是我们平时开发工作当中使用最频繁的类型, Guava提供了字符串的连接、分隔等操作,特别是字符串的匹配,那是相当强大,比如提取出字符串中的字母、数字、特殊字符等,可以从指定字符串中提取、删除、替换等操作 举个例子,提取“er 3j6o 3k ,)$ wt@ wr4576je ow3453535345irjew jwfel ” 字符串的字母,直接可以调用现成的方法 再比如,你需要将上述字符串中的数字全部移除或替换成其他字符,都有现成的方法,使用起来非常方便,只要你使用好了这些工具类, 可以大大提升你对字符串的处理效率 具体详细用法,请查看《Guava系列之强大的String工具类》 6、Guava限流RateLimiter 在互联网高并发场景下,限流是用来保证系统稳定性的一种手段,当系统遭遇瞬时流量激增时,可能会由于系统资源耗尽导致宕机。而限流可以把一小部分流量拒绝掉,保证大部分流量可以正常访问,从而保证系统只接收承受范围以内的请求,多余的请求给拒绝掉 我们常用的限流算法有:漏桶算法、令牌桶算法 Guava中的限流使用的是令牌桶算法,RateLimiter提供了两种限流实现 平滑突发限流(SmoothBursty) 平滑预热限流(SmoothWarmingUp) Guava RateLimiter的详细用法,请查看《Guava系列之限流RateLimiter》 7、Guava发布/订阅EventBus EventBus是Guava中实现的用于发布/订阅模式的事件处理组件,它是设计模式中观察者模式的优雅实现 EventBus是消息总线,它会根据消息的类型发送到指定的消息订阅者,当有消息没有订阅者接收时,会将消息发送给DeadEvent 关于EventBus的详细用法,请查看《Guava系列之EventBus》 以上是对最近学习Guava类库的一个总结,它包括了我们平常开发中最常用的一些组件工具类,熟练掌握这些工具类的使用方法,必然会让你的工作如虎添翼~ from:https://www.pianshen.com/article/57281487560/
View Details概述 工具类 就是封装平常用的方法,不需要你重复造轮子,节省开发人员时间,提高工作效率。谷歌作为大公司,当然会从日常的工作中提取中很多高效率的方法出来。所以就诞生了guava。 guava的优点: 高效设计良好的API,被Google的开发者设计,实现和使用 遵循高效的java语法实践 使代码更刻度,简洁,简单 节约时间,资源,提高生产力 Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如: 集合 [collections] 缓存 [caching] 原生类型支持 [primitives support] 并发库 [concurrency libraries] 通用注解 [common annotations] 字符串处理 [string processing] I/O 等等。 使用 引入gradle依赖(引入Jar包)
1 |
compile 'com.google.guava:guava:26.0-jre' |
1.集合的创建
1 2 3 4 5 6 7 8 9 |
// 普通Collection的创建 List<String> list = Lists.newArrayList(); Set<String> set = Sets.newHashSet(); Map<String, String> map = Maps.newHashMap(); // 不变Collection的创建 ImmutableList<String> iList = ImmutableList.of("a", "b", "c"); ImmutableSet<String> iSet = ImmutableSet.of("e1", "e2"); ImmutableMap<String, String> iMap = ImmutableMap.of("k1", "v1", "k2", "v2"); |
创建不可变集合 先理解什么是immutable(不可变)对象 在多线程操作下,是线程安全的 所有不可变集合会比可变集合更有效的利用资源 中途不可改变
1 |
ImmutableList<String> immutableList = ImmutableList.of("1","2","3","4"); |
这声明了一个不可变的List集合,List中有数据1,2,3,4。类中的 操作集合的方法(譬如add, set, sort, replace等)都被声明过期,并且抛出异常。 而没用guava之前是需要声明并且加各种包裹集合才能实现这个功能
1 2 3 4 5 |
// add 方法 @Deprecated @Override public final void add(int index, E element) { throw new UnsupportedOperationException(); } |
当我们需要一个map中包含key为String类型,value为List类型的时候,以前我们是这样写的
1 2 3 4 5 6 |
Map<String,List<Integer>> map = new HashMap<String,List<Integer>>(); List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); map.put("aa", list); System.out.println(map.get("aa"));//[1, 2] |
而现在
1 2 3 4 |
Multimap<String,Integer> map = ArrayListMultimap.create(); map.put("aa", 1); map.put("aa", 2); System.out.println(map.get("aa")); //[1, 2] |
其他的黑科技集合
1 2 3 4 5 6 7 8 9 10 11 12 13 |
MultiSet: 无序+可重复 count()方法获取单词的次数 增强了可读性+操作简单 创建方式: Multiset<String> set = HashMultiset.create(); Multimap: key-value key可以重复 创建方式: Multimap<String, String> teachers = ArrayListMultimap.create(); BiMap: 双向Map(Bidirectional Map) 键与值都不能重复 创建方式: BiMap<String, String> biMap = HashBiMap.create(); Table: 双键的Map Map--> Table-->rowKey+columnKey+value //和sql中的联合主键有点像 创建方式: Table<String, String, Integer> tables = HashBasedTable.create(); ...等等(guava中还有很多java里面没有给出的集合类型) |
2.将集合转换为特定规则的字符串 以前我们将list转换为特定规则的字符串是这样写的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//use java List<String> list = new ArrayList<String>(); list.add("aa"); list.add("bb"); list.add("cc"); String str = ""; for(int i=0; i<list.size(); i++){ str = str + "-" +list.get(i); } //str 为-aa-bb-cc //use guava List<String> list = new ArrayList<String>(); list.add("aa"); list.add("bb"); list.add("cc"); String result = Joiner.on("-").join(list); //result为 aa-bb-cc |
把map集合转换为特定规则的字符串
1 2 3 4 5 |
Map<String, Integer> map = Maps.newHashMap(); map.put("xiaoming", 12); map.put("xiaohong",13); String result = Joiner.on(",").withKeyValueSeparator("=").join(map); // result为 xiaoming=12,xiaohong=13 |
3.将String转换为特定的集合
1 2 3 4 5 6 7 8 9 10 11 12 |
//use java List<String> list = new ArrayList<String>(); String a = "1-2-3-4-5-6"; String[] strs = a.split("-"); for(int i=0; i<strs.length; i++){ list.add(strs[i]); } //use guava String str = "1-2-3-4-5-6"; List<String> list = Splitter.on("-").splitToList(str); //list为 [1, 2, 3, 4, 5, 6] |
如果
1 |
str="1-2-3-4- 5- 6 "; |
guava还可以使用 omitEmptyStrings().trimResults() 去除空串与空格
1 2 3 |
String str = "1-2-3-4- 5- 6 "; List<String> list = Splitter.on("-").omitEmptyStrings().trimResults().splitToList(str); System.out.println(list); |
将String转换为map
1 2 |
String str = "xiaoming=11,xiaohong=23"; Map<String,String> map = Splitter.on(",").withKeyValueSeparator("=").split(str); |
4.guava还支持多个字符切割,或者特定的正则分隔
1 2 |
String input = "aa.dd,,ff,,."; List<String> result = Splitter.onPattern("[.|,]").omitEmptyStrings().splitToList(input); |
关于字符串的操作 都是在Splitter这个类上进行的
1 2 3 4 5 6 7 8 9 10 |
// 判断匹配结果 boolean result = CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z')).matches('K'); //true // 保留数字文本 CharMatcher.digit() 已过时 retain 保留 //String s1 = CharMatcher.digit().retainFrom("abc 123 efg"); //123 String s1 = CharMatcher.inRange('0', '9').retainFrom("abc 123 efg"); // 123 // 删除数字文本 remove 删除 // String s2 = CharMatcher.digit().removeFrom("abc 123 efg"); //abc efg String s2 = CharMatcher.inRange('0', '9').removeFrom("abc 123 efg"); // abc efg |
[…]
View Details转自:http://www.codeceo.com/article/apache-commons-tools.html Apache Commons包含了很多开源的工具,用于解决平时编程经常会遇到的问题,减少重复劳动。我选了一些比较常用的项目做简单介绍。文中用了很多网上现成的东西,我只是做了一个汇总整理。 一、Commons BeanUtils http://jakarta.apache.org/commons/beanutils/index.html 说明:针对Bean的一个工具集。由于Bean往往是有一堆get和set组成,所以BeanUtils也是在此基础上进行一些包装。 使用示例:功能有很多,网站上有详细介绍。一个比较常用的功能是Bean Copy,也就是copy bean的属性。如果做分层架构开发的话就会用到,比如从PO(Persistent Object)拷贝数据到VO(Value Object)。 传统方法如下: //得到TeacherForm TeacherForm teacherForm=(TeacherForm)form; //构造Teacher对象 Teacher teacher=new Teacher(); //赋值 teacher.setName(teacherForm.getName()); teacher.setAge(teacherForm.getAge()); teacher.setGender(teacherForm.getGender()); teacher.setMajor(teacherForm.getMajor()); teacher.setDepartment(teacherForm.getDepartment()); //持久化Teacher对象到数据库 HibernateDAO= ; HibernateDAO.save(teacher); 使用BeanUtils后,代码就大大改观了,如下所示: //得到TeacherForm TeacherForm teacherForm=(TeacherForm)form; //构造Teacher对象 Teacher teacher=new Teacher(); //赋值 BeanUtils.copyProperties(teacher,teacherForm); //持久化Teacher对象到数据库 HibernateDAO= ; HibernateDAO.save(teacher); 二、Commons CLI http://jakarta.apache.org/commons/cli/index.html 说明:这是一个处理命令的工具。比如main方法输入的string[]需要解析。你可以预先定义好参数的规则,然后就可以调用CLI来解析。 使用示例: // create Options object Options options = new Options(); // add t option, option is the command parameter, false indicates that // this parameter is not required. options.addOption(“t”, false, “display current time”); options.addOption("c", true, "country code"); CommandLineParser parser = new PosixParser(); CommandLine cmd […]
View Details为什么使用雪花ID 在以前的项目中,最常见的两种主键类型是自增Id和UUID,在比较这两种ID之前首先要搞明白一个问题,就是为什么主键有序比无序查询效率要快,因为自增Id和UUID之间最大的不同点就在于有序性。 我们都知道,当我们定义了主键时,数据库会选择表的主键作为聚集索引(B+Tree),mysql 在底层是以数据页为单位来存储数据的。 也就是说如果主键为自增 id 的话,mysql 在写满一个数据页的时候,直接申请另一个新数据页接着写就可以了。如果一个数据页存满了,mysql 就会去申请一个新的数据页来存储数据。如果主键是UUID,为了确保索引有序,mysql 就需要将每次插入的数据都放到合适的位置上。这就造成了页分裂,这个大量移动数据的过程是会严重影响插入效率的。 一句话总结就是,InnoDB表的数据写入顺序能和B+树索引的叶子节点顺序一致的话,这时候存取效率是最高的。 但是为什么很多情况又不用自增id作为主键呢? 容易导致主键重复。比如导入旧数据时,线上又有新的数据新增,这时就有可能在导入时发生主键重复的异常。为了避免导入数据时出现主键重复的情况,要选择在应用停业后导入旧数据,导入完成后再启动应用。显然这样会造成不必要的麻烦。而UUID作为主键就不用担心这种情况。 不利于数据库的扩展。当采用自增id时,分库分表也会有主键重复的问题。UUID则不用担心这种问题。 那么问题就来了,自增id会担心主键重复,UUID不能保证有序性,有没有一种ID既是有序的,又是唯一的呢? 当然有,就是雪花ID。 什么是雪花ID snowflake是Twitter开源的分布式ID生成算法,结果是64bit的Long类型的ID,有着全局唯一和有序递增的特点。 最高位是符号位,因为生成的 ID 总是正数,始终为0,不可用。 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。 10位的机器标识,10位的长度最多支持部署1024个节点。 12位的计数序列号,序列号即一系列的自增ID,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。 缺点也是有的,就是强依赖机器时钟,如果机器上时钟回拨,有可能会导致主键重复的问题。 Java实现雪花ID 下面是用Java实现雪花ID的代码,供大家参考一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
public class SnowflakeIdWorker { /** * 开始时间:2020-01-01 00:00:00 */ private final long beginTs = 1577808000000L; private final long workerIdBits = 10; /** * 2^10 - 1 = 1023 */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); private final long sequenceBits = 12; /** * 2^12 - 1 = 4095 */ private final long maxSequence = -1L ^ (-1L << sequenceBits); /** * 时间戳左移22位 */ private final long timestampLeftOffset = workerIdBits + sequenceBits; /** * 业务ID左移12位 */ private final long workerIdLeftOffset = sequenceBits; /** * 合并了机器ID和数据标示ID,统称业务ID,10位 */ private long workerId; /** * 毫秒内序列,12位,2^12 = 4096个数字 */ private long sequence = 0L; /** * 上一次生成的ID的时间戳,同一个worker中 */ private long lastTimestamp = -1L; public SnowflakeIdWorker(long workerId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("WorkerId必须大于或等于0且小于或等于%d", maxWorkerId)); } this.workerId = workerId; } public synchronized long nextId() { long ts = System.currentTimeMillis(); if (ts < lastTimestamp) { throw new RuntimeException(String.format("系统时钟回退了%d毫秒", (lastTimestamp - ts))); } // 同一时间内,则计算序列号 if (ts == lastTimestamp) { // 序列号溢出 if (++sequence > maxSequence) { ts = tilNextMillis(lastTimestamp); sequence = 0L; } } else { // 时间戳改变,重置序列号 sequence = 0L; } lastTimestamp = ts; // 0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000000 00 - 00000000 0000 // 左移后,低位补0,进行按位或运算相当于二进制拼接 // 本来高位还有个0<<63,0与任何数字按位或都是本身,所以写不写效果一样 return (ts - beginTs) << timestampLeftOffset | workerId << workerIdLeftOffset | sequence; } /** * 阻塞到下一个毫秒 * * @param lastTimestamp * @return */ private long tilNextMillis(long lastTimestamp) { long ts = System.currentTimeMillis(); while (ts <= lastTimestamp) { ts = System.currentTimeMillis(); } return ts; } public static void main(String[] args) { SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(7); for (int i = 0; i < 10; i++) { long id = snowflakeIdWorker.nextId(); System.out.println(id); } } } |
main方法,测试结果如下:
1 2 3 4 5 6 7 8 9 10 |
184309536616640512 184309536616640513 184309536616640514 184309536616640515 184309536616640516 184309536616640517 184309536616640518 184309536616640519 184309536616640520 184309536616640521 |
总结 在大部分公司的开发项目中里,雪花ID是主流的ID生成策略,除了自己实现之外,目前市场上也有很多开源的实现,比如: 美团开源的Leaf 百度开源的UidGenerator 有兴趣的可以自行观摩一下,那么这篇文章就写到这里了,感谢大家的阅读。 作者:java技术爱好者 链接:https://juejin.cn/post/6965510420387856398 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
View DetailsStringBuilder转化为String
1 2 |
String str = "abcdefghijklmnopqrs"; StringBuilder stb = new StringBuilder(str); |
整型数组转化为字符串
1 2 3 4 5 |
StringBuilder s = new StringBuilder(); for(i=1;i<=n;i++) { s.append(String.valueOf(a[i])); } String str = ""+s; |
字符串转化为整形数组
1 2 3 4 5 |
String str="123456"; int[] a = new int[str.length()]; for(int i=0;i<str.length();i++) { a[i] = str.charAt(i)-'0'; } |
字符串转化为字符数组
1 2 3 |
String str="123456"; char[] c = str.toCharArray() ; System.out.println(c); |
字符数组转化为字符串
1 2 3 |
char[] c = {'a','s','d','4','5',}; String str = new String(c); System.out.println(str); |
字符数组转化为整型数组
1 2 3 4 5 6 |
char[] c = { '1', '2', '3', '4', '5', }; int[] a = new int[c.length]; for (int i = 0; i < 5; i++) { a[i] = c[i] - '0'; System.out.println(a[i]); } |
整型数组转化为字符数组
1 2 3 4 5 6 |
int[] a = {1,2,3,4,5}; char[] c = new char[a.length]; for (int i = 0; i < 5; i++) { c[i] = (char) (a[i]+'0'); System.out.println(c[i]); } |
整型数转化为字符串
1 2 3 |
1.String str = Integer.toString(i); 2.String s = String.valueOf(i); 3.String s = "" + i; |
字符串转化为整型数
1 |
int i = Integer.valueOf(str).intValue(); |
java类型转换 Integer String Long Float Double Date 1如何将字串 String 转换成整数 int? A. 有两个方法:
1 2 3 4 5 6 7 |
1). int i = Integer.parseInt([String]); 或 i = Integer.parseInt([String],[int radix]); 2). int i = Integer.valueOf(my_str).intValue(); 注: 字串转成 Double, Float, Long 的方法大同小异. |
2 如何将整数 int 转换成字串 String ? A. 有叁种方法:
1 2 3 4 5 6 7 |
1.) String s = String.valueOf(i); 2.) String s = Integer.toString(i); 3.) String s = "" + i; 注: Double, Float, Long 转成字串的方法大同小异. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package cn.com.lwkj.erts.register; import java.sql.Date; public class TypeChange { public TypeChange() { } //change the string type to the int type public static int stringToInt(String intstr) { Integer integer; integer = Integer.valueOf(intstr); return integer.intValue(); } //change int type to the string type public static String intToString(int value) { Integer integer = new Integer(value); return integer.toString(); } //change the string type to the float type public static float stringToFloat(String floatstr) { Float floatee; floatee = Float.valueOf(floatstr); return floatee.floatValue(); } //change the float type to the string type public static String floatToString(float value) { Float floatee = new Float(value); return floatee.toString(); } //change the string type to the sqlDate type public static java.sql.Date stringToDate(String dateStr) { return java.sql.Date.valueOf(dateStr); } //change the sqlDate type to the string type public static String dateToString(java.sql.Date datee) { return datee.toString(); } public static void main(String[] args) { java.sql.Date day ; day = TypeChange.stringToDate("2003-11-3"); String strday = TypeChange.dateToString(day); System.out.println(strday); } } /* 我们 www.jb51.net */ |
JAVA中常用数据类型转换函数 虽然都能在JAVA API中找到,整理一下做个备份。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
string->byte Byte static byte parseByte(String s) byte->string Byte static String toString(byte b) char->string Character static String to String (char c) string->Short Short static Short parseShort(String s) Short->String Short static String toString(Short s) String->Integer Integer static int parseInt(String s) Integer->String Integer static String tostring(int i) String->Long Long static long parseLong(String s) Long->String Long static String toString(Long i) String->Float Float static float parseFloat(String s) Float->String Float static String toString(float f) String->Double Double static double parseDouble(String s) Double->String Double static String toString(Double) ++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
数据类型 基本类型有以下四种: int长度数据类型有:byte(8bits)、short(16bits)、int(32bits)、long(64bits)、 float长度数据类型有:单精度(32bits float)、双精度(64bits double) boolean类型变量的取值有:ture、false char数据类型有:unicode字符,16位 对应的类类型:Integer、Float、Boolean、Character、Double、Short、Byte、Long 转换原则 从低精度向高精度转换 byte 、short、int、long、float、double、char 注:两个char型运算时,自动转换为int型;当char与别的类型运算时,也会先自动转换为int型的,再做其它类型的自动转换 基本类型向类类型转换 正向转换:通过类包装器来new出一个新的类类型的变量 Integer a= new Integer(2); 反向转换:通过类包装器来转换 int b=a.intValue(); 类类型向字符串转换 正向转换:因为每个类都是object类的子类,而所有的object类都有一个toString()函数,所以通过toString()函数来转换即可 反向转换:通过类包装器new出一个新的类类型的变量 eg1: int i=Integer.valueOf(“123”).intValue() 说明:上例是将一个字符串转化成一个Integer对象,然后再调用这个对象的intValue()方法返回其对应的int数值。 eg2: float f=Float.valueOf(“123”).floatValue() 说明:上例是将一个字符串转化成一个Float对象,然后再调用这个对象的floatValue()方法返回其对应的float数值。 eg3: boolean b=Boolean.valueOf(“123”).booleanValue() 说明:上例是将一个字符串转化成一个Boolean对象,然后再调用这个对象的booleanValue()方法返回其对应的boolean数值。 eg4:double d=Double.valueOf(“123”).doublue() […]
View Details