开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

页面静态化,让你的程序快的飞起来

发表于 2020-12-10

页面静态化,相比很多人都听过,但是我们为什么使用页面静态化和使用页面静态化的好处又有哪些呢?今天这篇文章我们就来详细的讲讲。

什么是页面静态化?

静态化就是指把原本动态生成的html页面变为静态内容保存,用户客户端请求的时候,直接返回静态页面,不用再经过服务渲染,不用访问数据库,可以大大的减小数据库的压力,显著的提高性能。

页面静态化可以解决哪些问题?

在使用页面静态化之前,我们的页面都是通过动态渲染后返回给客户端的,在后台需要大量的查询,而后渲染得到html页面,对数据库的压力比较大,并且请求的响应时间也比较长,并发能力并不高。通常我们解决这样的问题都是缓存技术,把热点数据放入缓存中,但是也不是什么数据都要放入缓存的,比如商品的详情数据,如果商品很多,数据量很大的时候,占用的内存就比较大,此时会给缓存到来很大的压力。如果因为这个导致缓存崩溃,那么就可能直接导致数据库崩溃。

缓存不是万能的,所以我们在一些场景下就选择使用静态化来解决,比如,商品详情页、网站首页、新闻详情这些页面,我们就可以通过静态化,将静态化的页面保存在nginx服务器来部署。

如何实现静态化?

实现静态化页面通常都是通过模板引擎来生成的,常用的模板引擎有:

  • FreeMarker
  • Velocity
  • Thymeleaf

Freemarker

优点:

  1. 不能编写Java代码,可以实现严格的mvc分离。
  2. 在复杂的页面上,性能也很好。
  3. 对jsp标签支持良好。
  4. 内置大量常用功能,使用方便。
  5. 使用表达式语言。
  6. FreeMarker是一个用Java语言编写的模板引擎,基于模板来生成文本输出,与Web容器无关。

缺点:

  1. 不是官方标准。

Velocity

优点:

  1. 不能编写Java代码,可以实现严格的mvc分离。
  2. 性能良好。

缺点:

  1. 不是官方标准。
  2. 对jsp标签支持不够好。
  3. 文件名必须为vm,配置麻烦。

Thymeleaf

优点:

​ 静态html嵌入标签属性,浏览器可以直接打开模板,便于联调,是SpringBoot官方推荐方案。

缺点:

模板必须符合xml规范。

Thymeleaf实现静态化

Thymeleaf除了可以把渲染结果写入Response,也可以写到本地文件实现静态化,先来看看Thymeleaf中的几个概念:

1
2
3
复制代码Context:运行上下文
TemplateResolver:模板解析器
TemplateEngine:模板引擎

Context

上下文:用来保存模型数据,当模板引擎渲染时,可以从Context上下文获取数据用于渲染,当与SpringBoot结合使用时,我们放入Model的数据会被处理到Context,作为模板渲染的数据使用。

TemplateResolver

模板解析器:用来读取模板相关的配置,如:模板存放的位置信息、模板文件名称、模板文件的类型等。与SpringBoot结合使用时,TemplateResolver已由其创建完成,并且各种配置也都有默认值,比如模板存放位置,默认就是:templates,模板文件类型默认就是html

TemplateEngine

模板引擎:用来解析模板的引擎,需要使用到上下文、模板解析器。分别从两者中获取模板中需要的数据,模板文件。然后利用内置的语法规则解析,从而输出解析后的文件。来看下模板引起进行处理的函数

1
arduino复制代码templateEngine.process("模板名", context, writer);

三个参数:

  • 模板名称
  • 上下文:包含了模型数据
  • writer:输出目的地的流.在输出时,我们可以指定输出的目的地,如果目的地是Response的流,那就是网络响应。如果目的地是本地文件,那就实现静态化了。

什么时候创建静态文件?

第一次我们可以通过脚本轮询调用每一个商品的详情页,此时因为还没有生成静态页面,会调用商品服务,生成各个商品的详情页静态页面,并向相应的应用层nginx,下次请求的时候,就不用再调用商品服务。当我们修改商品的详情信息后,会手动删除相应的静态页面,并触发重新生成新的静态页面。

<img src="thymeleaf\未命名文件 (3).jpg" style="zoom:50%;" />

具体实现

application.yml配置生成静态文件保存目录:

1
2
3
yaml复制代码guli:
thymeleaf:
destPath: E:/GuLi/html/item

service实现:

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
java复制代码@Service
public class FileService {

@Autowired
private GoodsService goodsService;

@Autowired
private TemplateEngine templateEngine;

@Value("${guli.thymeleaf.destPath}")
private String destPath;

/**
* 创建html页面
* @param id
* @throws Exception
*/
public void createHtml(Long id) throws Exception {
// 创建上下文,
Context context = new Context();
// 把数据加入上下文
context.setVariables(this.goodsService.loadModel(id));

// 创建输出流,关联到一个临时文件
File temp = new File(id + ".html");
// 目标页面文件
File dest = createPath(id);
// 备份原页面文件
File bak = new File(id + "_bak.html");
try (PrintWriter writer = new PrintWriter(temp, "UTF-8")) {
// 利用thymeleaf模板引擎生成 静态页面
templateEngine.process("item", context, writer);

if (dest.exists()) {
// 如果目标文件已经存在,先备份
dest.renameTo(bak);
}
// 将新页面覆盖旧页面
FileCopyUtils.copy(temp,dest);
// 成功后将备份页面删除
bak.delete();
} catch (IOException e) {
// 失败后,将备份页面恢复
bak.renameTo(dest);
// 重新抛出异常,声明页面生成失败
throw new Exception(e);
} finally {
// 删除临时页面
if (temp.exists()) {
temp.delete();
}
}

}

private File createPath(Long id) {
if (id == null) {
return null;
}
File dest = new File(this.destPath);
if (!dest.exists()) {
dest.mkdirs();
}
return new File(dest, id + ".html");
}

/**
* 判断某个商品的页面是否存在
* @param id
* @return
*/
public boolean exists(Long id){
return this.createPath(id).exists();
}

/**
* 异步创建html页面
* @param id
*/
public void syncCreateHtml(Long id){
ThreadUtils.execute(() -> {
try {
createHtml(id);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}

线程工具类:

1
2
3
4
5
6
7
8
java复制代码public class ThreadUtils {

private static final ExecutorService es = Executors.newFixedThreadPool(10);

public static void execute(Runnable runnable) {
es.submit(runnable);
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Boot 最最最常用的注解梳理 一、注解(an

发表于 2020-12-09

荡不羁,一生所爱。Java耕耘者(微信公众号ID:Java耕耘者),欢迎关注。

一、注解(annotations)列表

@SpringBootApplication: 包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解。其中@ComponentScan让spring Boot扫描到Configuration类并把它加入到程序上下文。

@Configuration 等同于spring的XML配置文件;使用Java代码可以检查类型安全。

@EnableAutoConfiguration 自动配置。

@ComponentScan 组件扫描,可自动发现和装配一些Bean。

@Component 可配合CommandLineRunner使用,在程序启动后执行一些基础任务。

@RestController 注解是@Controller和@ResponseBody的合集,表示这是个控制器bean,并且是将函数的返回值直 接填入HTTP响应体中,是REST风格的控制器。

@Autowired 自动导入。

@PathVariable 获取参数。

@JsonBackReference 解决嵌套外链问题。

@RepositoryRestResourcepublic 配合spring-boot-starter-data-rest使用。

二、注解(annotations)详解

@SpringBootApplication: 申明让spring boot自动给程序进行必要的配置,这个配置等同于:@Configuration ,@EnableAutoConfiguration 和 @ComponentScan 三个配置。

1
scss复制代码import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScanpublic class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }}

@ResponseBody: 表示该方法的返回结果直接写入HTTP response body中,一般在异步获取数据时使用,用于构建RESTful的api。在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。

比如异步获取json数据,加上@responsebody后,会直接返回json数据。该注解一般会配合@RequestMapping一起使用。关注公众号程序员小乐回复关键字“offer”获取算法面试题和答案

示例代码:

1
less复制代码@RequestMapping(“/test”)@ResponseBodypublic String test(){    return”ok”;}

@Controller: 用于定义控制器类,在spring 项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层),一般这个注解在类中,通常方法需要配合注解@RequestMapping。

示例代码:

1
less复制代码@Controller@RequestMapping(“/demoInfo”)publicclass DemoController {    @Autowired    private DemoInfoService demoInfoService;    @RequestMapping("/hello")    public String hello(Map<String,Object> map){        System.out.println("DemoController.hello()");        map.put("hello","from TemplateController.helloHtml");        //会使用hello.html或者hello.ftl模板进行渲染显示.        return"/hello";    }}

@RestController: 用于标注控制层组件(如struts中的action),@ResponseBody和@Controller的合集。

示例代码:

1
kotlin复制代码import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping(“/demoInfo2”)publicclass DemoController2 {    @RequestMapping("/test")    public String test(){        return"ok";    }}

@RequestMapping: 提供路由信息,负责URL到Controller中的具体函数的映射。

@EnableAutoConfiguration: Spring Boot自动配置(auto-configuration):尝试根据你添加的jar依赖自动配置你的Spring应用。例如,如果你的classpath下存在HSQLDB,并且你没有手动配置任何数据库连接beans,那么我们将自动配置一个内存型(in-memory)数据库”。

你可以将@EnableAutoConfiguration或者@SpringBootApplication注解添加到一个@Configuration类上来选择自动配置。如果发现应用了你不想要的特定自动配置类,你可以使用@EnableAutoConfiguration注解的排除属性来禁用它们。

@ComponentScan: 表示将该类自动发现扫描组件。个人理解相当于,如果扫描到有@Component、@Controller、@Service等这些注解的类,并注册为Bean,可以自动收集所有的Spring组件,包括@Configuration类。

我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。可以自动收集所有的Spring组件,包括@Configuration类。我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。

如果没有配置的话,Spring Boot会扫描启动类所在包下以及子包下的使用了@Service,@Repository等注解的类。

@Configuration: 相当于传统的xml配置文件,如果有些第三方库需要用到xml文件,建议仍然通过@Configuration类作为项目的配置主类——可以使用@ImportResource注解加载xml配置文件。

@Import: 用来导入其他配置类。

@ImportResource: 用来加载xml配置文件。

@Autowired: 自动导入依赖的bean

@Service: 一般用于修饰service层的组件

@Repository: 使用@Repository注解可以确保DAO或者repositories提供异常转译,这个注解修饰的DAO或者repositories类会被ComponetScan发现并配置,同时也不需要为它们提供XML配置项。

@Bean: 用@Bean标注方法等价于XML中配置的bean。

@Value: 注入Spring boot application.properties配置的属性的值。

示例代码:

1
ini复制代码@Value(value = “#{message}”)private String message;

@Inject: 等价于默认的@Autowired,只是没有required属性;

@Component: 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

@Bean: 相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理。

@AutoWired: 自动导入依赖的bean。byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。当加上(required=false)时,就算找不到bean也不报错。

@Qualifier: 当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用。@Qualifier限定描述符除了能根据名字进行注入,但能进行更细粒度的控制如何选择候选者,具体使用方式如下:

1
less复制代码@Autowired@Qualifier(value = “demoInfoService”)private DemoInfoService demoInfoService;

@Resource(name=”name”,type=”type”): 没有括号内内容的话,默认byName。与@Autowired干类似的事。关注公众号程序员小乐回复关键字“Java”获取大厂面试题和答案

三、JPA注解

@Entity: @Table(name=”“): 表明这是一个实体类。一般用于jpa这两个注解一般一块使用,但是如果表名和实体类名相同的话,@Table可以省略

@MappedSuperClass: 用在确定是父类的entity上。父类的属性子类可以继承。

@NoRepositoryBean: 一般用作父类的repository,有这个注解,spring不会去实例化该repository。

@Column: 如果字段名与列名相同,则可以省略。

@Id: 表示该属性为主键。

@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = “repair_seq”): 表示主键生成策略是sequence(可以为Auto、IDENTITY、native等,Auto表示可在多个数据库间切换),指定sequence的名字是repair_seq。

@SequenceGeneretor(name = “repair_seq”, sequenceName = “seq_repair”, allocationSize = 1): name为sequence的名称,以便使用,sequenceName为数据库的sequence名称,两个名称可以一致。

@Transient: 表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性。如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic。@Basic(fetch=FetchType.LAZY):标记可以指定实体属性的加载方式

@JsonIgnore: 作用是json序列化时将Java bean中的一些属性忽略掉,序列化和反序列化都受影响。

@JoinColumn(name=”loginId”): 一对一:本表中指向另一个表的外键。一对多:另一个表指向本表的外键。

@OneToOne、@OneToMany、@ManyToOne: 对应hibernate配置文件中的一对一,一对多,多对一。

四、springMVC相关注解

@RequestMapping: @RequestMapping(“/path”)表示该控制器处理所有“/path”的UR L请求。RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。该注解有六个属性:

  • params:指定request中必须包含某些参数值是,才让该方法处理。
  • headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。
  • value:指定请求的实际地址,指定的地址可以是URI Template 模式
  • method:指定请求的method类型, GET、POST、PUT、DELETE等
  • consumes:指定处理请求的提交内容类型(Content-Type),如application/json,text/html;
  • produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回

@RequestParam: 用在方法的参数前面。

@RequestParam String a =request.getParameter(“a”)。

@PathVariable: 路径变量。如

1
less复制代码RequestMapping(“user/get/mac/{macAddress}”)public String getByMacAddress(@PathVariable String macAddress){    //do something;}

参数与大括号里的名字一样要相同。

五、全局异常处理

@ControllerAdvice: 包含@Component。可以被扫描到。统一处理异常。

@ExceptionHandler(Exception.class): 用在方法上面表示遇到这个异常就执行以下方法。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Vmare虚拟机网络连接方式桥接模式+桥接模式+主机模式

发表于 2020-12-09

虚拟机网络连接模式

最近在学习虚拟机和计算机网络,在网上看了一些关于虚拟机网络连接方式的介绍

这篇文章写的不错:www.cnblogs.com/luxiaodai/p…
在这里插入图片描述

Content

虚拟机网络连接方式

  1. Bridged(桥接模式)
  2. NAT(桥接模式)
  3. Host-only(主机模式)
    虚拟机NAT模式配置静态IP以及DNS
    虚拟机网络连接方式
    安装好虚拟机以后,在网络连接里面可以看到多了两块网卡:

在这里插入图片描述

其中VMnet1是虚拟机Host-only模式的网络接口,VMnet8是NAT模式的网络接口。

虚拟机常见有三种网络连接方式:如下图:

在这里插入图片描述

1. Bridged(桥接模式)

  在桥接模式下,VMware虚拟出来的操作系统就像是局域网中的一独立的主机,它可以访问网内任何一台机器。不过你需要空闲的IP地址,并且需要手工为虚拟系统配置IP地址、子网掩码,而且还要和宿主机器处于同一网段,这样虚拟系统才能和宿主机器进行通信。

  如果你想利用VMware在局域网内新建一个虚拟服务器,为局域网用户提供网络服务,就应该选择桥接模式。

在这里插入图片描述

2. NAT(网络地址转换模式)

  使用NAT模式,就是让虚拟系统借助NAT(网络地址转换)功能,通过宿主机器所在的网络来访问公网。也就是说,使用NAT模式可以实现在虚拟系统里访问互联网。NAT模式下的虚拟系统的TCP/IP配置信息是由VMnet8(NAT)虚拟网络的DHCP服务器提供的,无法进行手工修改,因此虚拟系统也就无法和本局域网中的其他真实主机进行通讯。采用NAT模式最大的优势是虚拟系统接入互联网非常简单,你不需要进行任何其他的配置,只需要宿主机器能访问互联网即可。

  如果你想利用VMware安装一个新的虚拟系统,在虚拟系统中不用进行任何手工配置就能直接访问互联网,建议你采用NAT模式。(也可以配置静态IP,后面会介绍)

在这里插入图片描述

3. Host-only(主机模式)

  在某些特殊的网络调试环境中,要求将真实环境和虚拟环境隔离开,这时你就可采用Host-only模式。

  在Host-only模式中,所有的虚拟系统是可以相互通信的,但虚拟系统和真实的网络是被隔离开的。可以利用Windows XP里面自带的Internet连接共享(实际上是一个简单的路由NAT)来让虚拟机 通过主机真实的网卡进行外网的访问。虚拟系统的TCP/IP配置信息(如IP地址、网关地址、DNS服务器等),都是由VMnet1(Host-only)虚拟网络的DHCP服务器来动态分配的。如果你想利用VMware创建一个与网内其他机器相隔离的虚拟系统,进行某些特殊的网络调试工作,可以选择Host-only模式。

在这里插入图片描述

虚拟机NAT模式配置静态IP以及DNS
step1:配置虚拟机网络,取消勾选DHCP,点击NAT设置,然后记住网关IP

在这里插入图片描述

step2:配置VMnet8,IP处于同一网段并且配置网关

在这里插入图片描述

step3:linux配置
在/etc/sysconfig/network-scripts目录下设置对应的网口,设置为静态IP并且开机启动,同时设置DNS

IP地址的相关配置信息

1
2
3
4
5
6
7
8
9
10
11
ini复制代码ifconfig 查看(修改)ip地址(重启后无效)
ifconfig eth0 192.168.12.22 修改ip地址
如果想要永久生效
修改 /etc/sysconfig/network-scripts/ifcfg-eth0文件
DEVICE=eth0 #网卡名称
BOOTPROTO=static #获取ip的方式(static/dhcp/bootp/none)
HWADDR=00:0C:29:B5:B2:69 #MAC地址
IPADDR=12.168.177.129 #IP地址
NETMASK=255.255.255.0 #子网掩码
NETWORK=192.168.177.0 #网络地址
BROADCAST=192.168.0.255 #广播地址

在这里插入图片描述

然后重启网络服务,能ping通8.8.8.8,证明已经可以连外网了,能ping通网站,证明DNS也设置好了,如果ping不同,请检查windows和linux的防火墙设置

在这里插入图片描述

设置成功

在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

大厂也在用的 6种 数据脱敏方案,严防泄露数据的 “内鬼”

发表于 2020-12-09

最近连着几天晚上在家总是接到一些奇奇怪怪的电话,“哥,你是 xxx 吧,我们这里是 xxx 高端男士私人会所…”,握草,我先是一愣,然后狠狠的骂了回去。一脸傲娇的转过头,面带微笑稍显谄媚:老婆你听我说,我真的啥也没干,你要相信我!

啪~

搞事情啊

过后揉揉脸细想想,肯定是哪个不道德的网站,又把我的个人信息给卖了,现在的人上网都处于一个裸奔的状态,个人信息已不再属于个人,时下这种事好像也见怪不怪了,不过,出现这种事大多是有内鬼。

停止交易,有内鬼

而作为开发者的我们,能做的就是尽量避免经我们手的用户数据泄露,那今天就来讲讲互联网中内部防止隐私数据泄露的手段-数据脱敏。

什么是数据脱敏

先来看看什么是数据脱敏?数据脱敏也叫数据的去隐私化,在我们给定脱敏规则和策略的情况下,对敏感数据比如 手机号、银行卡号 等信息,进行转换或者修改的一种技术手段,防止敏感数据直接在不可靠的环境下使用。

像政府、医疗行业、金融机构、移动运营商是比较早开始应用数据脱敏的,因为他们所掌握的都是用户最核心的私密数据,如果泄露后果是不可估量的。

数据脱敏的应用在生活中是比较常见的,比如我们在淘宝买东西订单详情中,商家账户信息会被用 * 遮挡,保障了商户隐私不泄露,这就是一种数据脱敏方式。

淘宝详情

数据脱敏又分为静态数据脱敏(SDM)和 动态数据脱敏(DDM):

静态数据脱敏

静态数据脱敏(SDM):适用于将数据抽取出生产环境脱敏后分发至测试、开发、培训、数据分析等场景。

有时我们可能需要将生产环境的数据 copy 到测试、开发库中,以此来排查问题或进行数据分析,但出于安全考虑又不能将敏感数据存储于非生产环境,此时就要把敏感数据从生产环境脱敏完毕之后再在非生产环境使用。

这样脱敏后的数据与生产环境隔离,满足业务需要的同时又保障了生产数据的安全。

数据脱敏过程

如上图所示,将用户的真实 姓名、手机号、身份证、银行卡号 通过 替换、无效化、乱序、对称加密 等方案进行脱敏改造。

动态数据脱敏

动态数据脱敏(DDM):一般用在生产环境,访问敏感数据时实时进行脱敏,因为有时在不同情况下对于同一敏感数据的读取,需要做不同级别的脱敏处理,例如:不同角色、不同权限所执行的脱敏方案会不同。

注意:在抹去数据中的敏感内容同时,也需要保持原有的数据特征、业务规则和数据关联性,保证我们在开发、测试以及数据分析类业务不会受到脱敏的影响,使脱敏前后的数据一致性和有效性。总之一句话:你爱怎么脱就怎么脱,别影响我使用就行。

数据脱敏方案

数据脱敏系统可以按照不同业务场景自行定义和编写脱敏规则,可以针对库表的某个敏感字段,进行数据的不落地脱敏。

脱敏系统

数据脱敏的方式有很多种,接下来以下图数据为准一个一个的演示每种方案。

原始数据

1、无效化

无效化方案在处理待脱敏的数据时,通过对字段数据值进行 截断、加密、隐藏 等方式让敏感数据脱敏,使其不再具有利用价值。一般采用特殊字符(*等)代替真值,这种隐藏敏感数据的方法简单,但缺点是用户无法得知原数据的格式,如果想要获取完整信息,要让用户授权查询。

截断方式

比如我们将身份证号用 * 替换真实数字就变成了 “220724 ****** 3523”,非常简单。

隐藏方式

2、随机值

随机值替换,字母变为随机字母,数字变为随机数字,文字随机替换文字的方式来改变敏感数据,这种方案的优点在于可以在一定程度上保留原有数据的格式,往往这种方法用户不易察觉的。

我们看到 name 和 idnumber 字段进行了随机化脱敏,而名字姓、氏随机化稍有特殊,需要有对应姓氏字典数据支持。

随机值

3、数据替换

数据替换与前边的无效化方式比较相似,不同的是这里不以特殊字符进行遮挡,而是用一个设定的虚拟值替换真值。比如说我们将手机号统一设置成 “13651300000”。

数据替换

4、对称加密

对称加密是一种特殊的可逆脱敏方法,通过加密密钥和算法对敏感数据进行加密,密文格式与原始数据在逻辑规则上一致,通过密钥解密可以恢复原始数据,要注意的就是密钥的安全性。

对称加密

5、平均值

平均值方案经常用在统计场景,针对数值型数据,我们先计算它们的均值,然后使脱敏后的值在均值附近随机分布,从而保持数据的总和不变。

原始数据

对价格字段 price 做平均值处理后,字段总金额不变,但脱敏后的字段值都在均值 60 附近。

平均值

6、偏移和取整

这种方式通过随机移位改变数字数据,偏移取整在保持了数据的安全性的同时保证了范围的大致真实性,比之前几种方案更接近真实数据,在大数据分析场景中意义比较大。

比如下边的日期字段create_time中 2020-12-08 15:12:25 变为 2018-01-02 15:00:00。

取整

数据脱敏规则在实际应用中往往都是多种方案配合使用,以此来达到更高的安全级别。

总结

无论是静态脱敏还是动态脱敏,其最终都是为了防止组织内部对隐私数据的滥用,防止隐私数据在未经脱敏的情况下从组织流出。所以作为一个程序员不泄露数据是最起码的操守。

整理了几百本各类技术电子书,有需要的同学可以,关注同名公号 「程序员内点事」 回复 「 666 」 自取。还有想要加技术群的同学可以加我好友,和大佬侃技术、不定期内推,程序员的内点事这都有。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

使用了这个神器,让我的代码bug少了一半

发表于 2020-12-09

前言

最近一段时间,我们团队在生产环境出现了几次线上问题,有部分比较严重,直接影响用户功能的使用,惹得领导不高兴了,让我想办法提升代码质量,这时候项目工程代码质量检测神器——SonarQube,出现在我们的视线当中。

一 sonarqube是做什么的

SonarQube®是一种自动代码审查工具,用于检测代码中的错误,漏洞和代码味道。它可以与您现有的工作流程集成,以实现跨项目分支和提取请求的连续代码检查。通过插件形式,可以支持包括 java, C#, C/C++, PL/SQL, Cobol, JavaScrip, Groovy 等二十几种编程语言的代码质量管理与检测。sonarqube可以从以下7个维度检测代码质量,而作为开发人员至少需要处理前5种代码质量问题。

1.1 不遵循代码标准
sonarqube可以通过CheckStyle等代码规则检测工具规范代码编写。

1.2 存在的缺陷漏洞
sonarqube可以通过Findbugs等等代码规则检测工具检测出潜在的缺陷。

1.3 糟糕的复杂度分布
文件、类、方法等,如果复杂度过高将难以改变,这会使得开发人员 难以理解它们, 且如果没有自动化的单元测试,对于程序中的任何组件的改变都将可能导致需要全面的回归测试。

1.4 重复
显然程序中包含大量复制粘贴的代码是质量低下的,sonarqube可以展示源码中重复严重的地方。

1.5 注释不足或者过多
没有注释将使代码可读性变差,特别是当不可避免地出现人员变动 时,程序的可读性将大幅下降 而过多的注释又会使得开发人员将精力过多地花费在阅读注释上,亦违背初衷。

1.6 缺乏单元测试
sonarqube可以很方便地统计并展示单元测试覆盖率。

1.7 糟糕的设计
通过sonarqube可以找出循环,展示包与包、类与类之间的相互依赖关系,可以检测自定义的架构规则 通过sonarqube可以管理第三方的jar包,可以利用LCOM4检测单个任务规则的应用情况, 检测耦合。sonarqube可以很方便地统计并展示单元测试覆盖率。

总览:

在典型的开发过程中:

1.开发人员在IDE中开发和合并代码(最好使用SonarLint在编辑器中接收即时反馈),然后将其代码签入ALM。

2.组织的持续集成(CI)工具可以检出,构建和运行单元测试,而集成的SonarQube扫描仪可以分析结果。

3.扫描程序将结果发布到SonarQube服务器,该服务器通过SonarQube界面,电子邮件,IDE内通知(通过SonarLint)以及对拉取或合并请求的修饰(使用Developer Edition及更高版本时)向开发人员提供反馈。

SonarQube实例包含三个组件:

SonarQube服务器运行以下过程:

1.提供SonarQube用户界面的Web服务器。

2.基于Elasticsearch的搜索服务器。

3.计算引擎负责处理代码分析报告并将其保存在SonarQube数据库中。

该数据库存储以下内容:

代码扫描期间生成的代码质量和安全性的度量标准和问题。

SonarQube实例配置。

在构建或连续集成服务器上运行的一台或多台扫描仪可以分析项目。

二 sonarqube如何搭建

官网地址:www.sonarqube.org/,选择“文档”菜单

在出现的文档页面中可以选择版本,目前最新的版本是8.5。笔者尝试过三个版本:

8.5:它是目前最新的版本,需要安装JDK11,并且只支持oracle、sqlserver和PostgreSQL数据库

7.9:它是一个长期支持的版本,非常文档,也需要安装JDK11,并且只支持oracle、sqlserver和PostgreSQL数据库 。

7.6:它是一个老版本,只需安装JDK8,支持oracle、sqlserver和PostgreSQL数据库,以及mysql数据库。

刚开始我们为了省事,安装了 7.6的版本,因为mysql数据库我们已经在用了,无需额外安装其他数据库,并且JDK8也在使用,安装成本最小。但是后来发现,如果需要安装汉化版插件,或者mybatis插件,这些插件要求的SonarQube版本必须在7.9以上,并且需要运行在JDK11以上。经过权衡之后,我们决定安装最新版的。

2.1 安装JDK11和postgreSQL
JDK下载地址:www.oracle.com/java/techno…

JDK的安装比较简单,我在这里就不过多介绍了,网上有很多教程。

PostgreSQL它自己号称自己是世界上最先进的开源数据库,具有许多功能,旨在帮助开发人员构建应用程序,管理员来保护数据完整性和构建容错环境,并帮助您管理数据,无论数据集的大小。除了免费和开源之外,PostgreSQL也是高度可扩展的。例如,您可以定义自己的数据类型,构建自定义函数,甚至可以使用不同的编程语言编写代码,而无需重新编译数据库。

PostgreSQL的安装与使用可以参数:www.jianshu.com/p/7d133efcc…

2.3 从zip文件安装sonarqube

SonarQube无法在root基于Unix的系统上运行,因此,如有必要,请为SonarQube创建专用的用户帐户。

$ SONARQUBE-HOME(下面)指的是SonarQube发行版已解压缩的目录的路径。

设置对数据库的访问
编辑$ SONARQUBE-HOME / conf / sonar.properties以配置数据库设置。模板可用于每个受支持的数据库。只需取消注释并配置所需的模板,然后注释掉专用于H2的行:

1
2
3
ini复制代码sonar.jdbc.username=sonarqube
sonar.jdbc.password=mypassword
sonar.jdbc.url=jdbc:postgresql://localhost/sonarqube

配置Elasticsearch存储路径
默认情况下,Elasticsearch数据存储在$ SONARQUBE-HOME / data中,但不建议将其用于生产实例。相反,您应该将此数据存储在其他位置,最好是在具有快速I / O的专用卷中。除了保持可接受的性能外,这样做还可以简化SonarQube的升级。

编辑$ SONARQUBE-HOME / conf / sonar.properties以配置以下设置:

1
2
ini复制代码sonar.path.data=/var/sonarqube/data
sonar.path.temp=/var/sonarqube/temp

用于启动SonarQube的用户必须具有对这些目录的读写权限。

启动Web服务器
默认端口为“ 9000”,上下文路径为“ /”。这些值可以在$ SONARQUBE-HOME / conf / sonar.properties中进行更改:

1
2
3
ini复制代码sonar.web.host=192.0.0.1
sonar.web.port=80
sonar.web.context=/sonarqube

执行以下脚本来启动服务器:

  • 在Linux上:bin / linux-x86-64 / sonar.sh start
  • 在macOS上:bin / macosx-universal-64 / sonar.sh start
  • 在Windows上:bin / windows-x86-64 / StartSonar.bat

调整Java安装
如果服务器上安装了多个Java版本,则可能需要明确定义使用哪个Java版本。

要更改SonarQube使用的Java JVM,请编辑$ SONARQUBE-HOME / conf / wrapper.conf并更新以下行:

wrapper.java.command=/path/to/my/jdk/bin/java
您现在可以在http:// localhost:9000上浏览SonarQube (默认的系统管理员凭据为admin/ admin)。第一次访问这个地址比较会停留在这个页面一段时间,因为SonarQube会做一些初始化工作,包含往空数据库中建表

初始化成功后运行的页面:

同时会生成多张表:

2.3 安装插件
根据个人需要,可以安装汉化插件,sonarqube默认是英文界面。

github地址:github.com/SonarQubeCo…

将项目下载编译打包后,将jar放到$SONARQUBE-HOME\extensions\plugins

目录下即可,然后执行:./sonar.sh restart命令重启sonarqube服务。

此外,还有mybatis插件

gitee地址:gitee.com/mirrors/son…

我个人用过,觉得作用不大,不过可以基于这个代码扩展自己需要的功能。

三 sonarqube如何使用

3.1 在maven项目中集成sonarqube
先在maven的settings.xml文件中增加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
xml复制代码<pluginGroups>
<pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
</pluginGroups>
<profiles>
<profile>
<id>sonar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!-- Optional URL to server. Default value is http://localhost:9000 -->
<sonar.host.url>
http://localhost:9000
</sonar.host.url>
</properties>
</profile>
</profiles>

然后在pom.xml文件中增加配置:

1
2
3
4
5
xml复制代码<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.3.0.603</version>
</plugin>

在项目目录下运行代码检测命令: mvn clean complie -U -Dmaven.test.skip=true sonar:sonar

看到这几句话,就表示检测成功了

然后在sonar后台查看检测报告

报告里面包含:bug、漏洞、异味、安全热点、覆盖、重复率等,对有问题的代码能够快速定位。

点击某个bug可以查看具体有问题代码:

没有关闭输入流问题:

空指针问题:

错误的用法:

SimpleDateFormat不应该被定义成static的。

检测出的代码问题类型太多,这里就不一一列举了。总之,记住一句话:sonar很牛逼。它不光可以检测出代码问题,还对一些不好的代码写法和用法有更好的建议。

彩蛋

sonarqube非常强大,上面只介绍了它的基本用法。一般情况下,我们可以使用jenkins配置需要代码检测的项目,从gitlab上下载代码,执行maven编译打包代码测试命令,可直接生成报告。jenkins触发执行代码检测的时机是:1.有代码提交,或者指定比如test分支有代码提交,项目数量少可以这样做。2.定时执行,我们公司就是配置在凌晨定时执行,因为jenkins部署的项目太多了,为了不影响正常的项目部署。

此外,我们可以自定义代码检测的执行规则,根据实际的项目需求自己开发插件,比如:我们自己开发了mybatis插件,扫描mapper和xml文件名称不一致的情况。

总之,sonar的功能非常强大,强烈建议大家在项目中使用,真的可以减少很多隐藏的bug,提高代码质量,如果你用过就会发现它的好处。如果想了解更多sonar的用法,可以在公众号中回复:sonar,可以获取更详细的用法。
来源:quellanan.blog.csdn.net/article/det…

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

基础配置篇:初次使用golang,创建项目入口,使用iris

发表于 2020-12-09

每一个项目都需要一个入口,才能让项目运行起来。前面章节我们提到了main目录为入口目录,main()为golang的入口函数。因此,我们在main目录下创建一个main.go文件来作为入口文件,在里面编写main()函数。

小试身手

1
2
3
4
5
6
7
golang复制代码package main

import "fmt"

func main() {
fmt.Println("Hello World")
}

这里简单的输出了Hello World到控制台中。我们先试一下运行它,看能不能得到Hello World的结果。

运行测试是非常简单的,在前期我们做好准备后,在main()函数前会有一个实心绿色三角形,点击它,并选择第一个菜单来运行
运行

运行后,将在控制台中看到输出的执行结果:
运行结果
很好,我们已经成功将go跑起来了,以后,它将作为我们的博客项目入口,从这里开始运行代码。

单单一个hello world 当然是无法满足我们的项目要求的,我们可是有一堆需求等着实现呢。这里,我们重写一下main()函数,让他满足我们的需求。

iris 的使用

iris目前有2个版本,我们选择使用iris12版本。

bootstrap.go

首先,我们在根目录创建一个bootstrap.go文件,将iris的逻辑代码放在这个文件里。

整个文件的代码如下:

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
golang复制代码package irisweb

import (
"context"
"fmt"
"github.com/kataras/iris/v12"
)

type Bootstrap struct {
Application *iris.Application
Port int
LoggerLevel string
}

func New(port int, loggerLevel string) *Bootstrap {
var bootstrap Bootstrap
bootstrap.Application = iris.New()
bootstrap.Port = port
bootstrap.LoggerLevel = loggerLevel

return &bootstrap
}

func (bootstrap *Bootstrap) Serve() {
bootstrap.Application.Logger().SetLevel(bootstrap.LoggerLevel)

bootstrap.Application.Get("/", func(ctx iris.Context) {
ctx.WriteString("Hello World")
})

bootstrap.Application.Run(
iris.Addr(fmt.Sprintf("127.0.0.1:%d", bootstrap.Port)),
iris.WithoutServerError(iris.ErrServerClosed),
iris.WithoutBodyConsumptionOnUnmarshal,
)
}

整个文件比较简单:

  • 一个Bootstrap 的结构体,用来存放iris的对象、tcp端口和日志记录级别信息。
  • 一个New 函数,用来初始化iris,以及端口赋值、日志记录级别赋值等。
  • 还有一个函数是iris启动服务函数,里面开始对iris运行进行初始化信息设置。这些配置信息在由存放在config.json中的信息得来。
1
2
3
golang复制代码  bootstrap.Application.Get("/", func(ctx iris.Context) {
ctx.WriteString("Hello World")
})

这一句定义一个访问路由,匿名函数是访问这个路由的时候,会执行的函数。iris的路由配置有多种形式,支持Get/Post/Put/Delete,还支持Party对路由进行分组。

1
2
3
4
5
golang复制代码  bootstrap.Application.Run(
iris.Addr(fmt.Sprintf("127.0.0.1:%d", bootstrap.Port)),
iris.WithoutServerError(iris.ErrServerClosed),
iris.WithoutBodyConsumptionOnUnmarshal,
)

这一部分的代码是使用iris鉴定指定的端口,端口就是我们在config.json中定义的端口,除了设置端口外,这里还支持增加其他配置项,比如 WithoutServerError 会忽略errors错误。WithoutBodyConsumptionOnUnmarshal 运行iris的body可以多次消费,也就是说,你在中间件中使用了body,在后续的处理函数中,还可以再次使用body。如果不设置这个配置项,body就只能使用一次,再次使用就得不到内容。

main.go

上面已经写好了iris的逻辑代码,我们还需要将它放到main.go 中,才能让代码跑起来。于是,我们将前面写的的hello world删掉,我们将main.go重写下:

1
2
3
4
5
6
7
8
9
10
11
golang复制代码package main

import (
"irisweb"
"irisweb/config"
)

func main() {
b := irisweb.New(config.ServerConfig.Port, config.ServerConfig.LogLevel)
b.Serve()
}

代码如上,这回的内容输出就不是在控制台了,现在的内容输出就变成了在浏览器输出了。来我们一起试一下,点击main.go 函数左边的绿色实心三角形,运行起来看看预期结果。

运行了之后,在浏览器输入 http://127.0.0.1:8001 访问。这里的8001端口是config.json设置的端口,如果你设置的端口不是8001,请改成你设置的端口。如果不出意外,你将看到如下的输出内容:
输出结果

恭喜你,你的配置已经成功。仅仅在浏览器中输出hello world,当然是不能满足我们的需求的,下一节我们将再跟进我们的需求文档,来继续完善我们的博客网站。我们再对bootstrap.go 进行改造。

完整的项目示例代码托管在GitHub上,需要查看完整的项目代码可以到github.com/fesiong/gob… 上查看,也可以直接fork一份来在上面做修改。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

批量查询id的顺序问题

发表于 2020-12-08

批量查询id的顺序问题

简介

批量查询在sql查询中,特别常用。对应的sql模板如

1
sql复制代码SELECT cols FROM table WHERE primary_key in (example1,example2)

一般情况下,我们都会认为我们传入example的顺序和实际mysql的结果是对应的,也就是上看的sql返回的cols顺序也应该是example1_cols,example2_cols,实际上是事与愿违的。

测试

)

根据图上的测试结果,可以得知。传入的id顺序并不能影响返回结果的顺序。Mysql会默认将结果根据主键进行排序。

如何让按照传入顺序返回

通过查询官方文档,我们可以使用 ORDER BY FIELD(col,example…) 来实现按照给定的顺序获取返回结果。

我们先确认一下FIELD()函数在Mysql中的作用:

FIELD() is a function that returns the index position of a comma-delimited list if the value you are searching for exists.

IF id = 1, then FIELD(id,3,2,1,4) returns 3 (position where 1 is in the list)

IF id = 2, then FIELD(id,3,2,1,4) returns 2 (position where 2 is in the list)

IF id = 3, then FIELD(id,3,2,1,4) returns 1 (position where 3 is in the list)

IF id = 4, then FIELD(id,3,2,1,4) returns 4 (position where 4 is in the list)

IF id = anything else, then FIELD(id,3,2,1,4) returns 0 (not in the list)

The ORDER BY values are evaluated by what FIELD() returns

简单来说就是,会根据给定字段对应的值,返回值对应的索引,然后ORDER BY会根据FIELD给定的结果进行排序,从而实现给定参数顺序返回结果。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

逻辑思维题

发表于 2020-12-08

⭐️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。

在计算机面试中,偶尔会遇到逻辑类题目。由于题目花样百出,准备难度较大,题海战术可能不是推荐的策略。同时,我认为回答问题的思路比答案本身更加重要。在这个专栏里,我将精选一些经典的逻辑思维题,希望能帮助你找到解题思路 / 技巧。

本文是逻辑思维系列的第 1 篇文章,完整文章目录请移步到文章末尾~


  1. 题目描述

给定 25 匹马与 5 条赛道,一个赛道只能容纳一匹马,每轮比赛只能得到 5 匹马之间的快慢程度,而不是速度,求决胜 1,2,3 名至少多少轮。


  1. 解题关键

2.1 分治思想

欲求得 25 匹马中的前三名,可以先求得较小规模问题中的前三名,再合并小规模问题的解得出最终解。

2.2 代表元法

在并查集(一种数据结构)中,会使用根节点来代表一个集合,这种方法叫做代表元法。我们可以借鉴这种 “代表元” 的思想,让一组马中跑的最快的一匹来代表整组马。举个例子,给定一组赛马 A1,A2,A3,A4,A5A_1,A_2,A_3,A_4,A_5A1,A2,A3,A4,A5,A1A_1A1为这组马中冠军马,若有 B1>A1B_1>A_1B1>A1,则自然有 B1>AB_1>AB1>A(即:如果 B1B_1B1 比 AAA 组中跑的最快的一匹马还快,自然可以得出 B1B_1B1 比 AAA 组所有马都快的结论)。

提示: 若不了解并查集,请务必阅读我之前写过的一篇文章:《数据结构 | 并查集 & 联合 - 查找算法》


  1. 解决问题

理解了分治和代表元后,现在可以说问题的解法了,一共分为 2 个回合来解决:

3.1 第一回合

首先,我们将 25 匹赛马分为 5 组,让每组马进行组内比赛,得到组内排名,假设结果为 A1>A2>A3>A4>A5A_1>A_2>A_3>A_4>A_5A1>A2>A3>A4>A5(此时进行了 5 轮比赛)。因为组内排名第四与第五名不可能竞争全场前三名,所以排除每一组的第四与第五名。

A组:{A1,A2,A3,A4,A5}A 组:{A_1,A_2,A_3,A_4,A_5}A组:{A1,A2,A3,A4,A5}

B组:{B1,B2,B3,B4,B5}B 组:{B_1,B_2,B_3,B_4,B_5}B组:{B1,B2,B3,B4,B5}

C组:{C1,C2,C3,C4,C5}C 组:{C_1,C_2,C_3,C_4,C_5}C组:{C1,C2,C3,C4,C5}

D组:{D1,D2,D3,D4,D5}D 组:{D_1,D_2,D_3,D_4,D_5}D组:{D1,D2,D3,D4,D5}

E组:{E1,E2,E3,E4,E5}E 组:{E_1,E_2,E_3,E_4,E_5}E组:{E1,E2,E3,E4,E5}

第一回合

3.2 第二回合

其次,每一组跑得最快的一匹马作为代表元参与一轮 “代表赛”,假设比赛结果是:[A1>B1>C1>D1>E1][A_1>B_1>C_1>D_1>E_1][A1>B1>C1>D1>E1],由此可以排除失去竞争资格的赛马:

  • A1A_1A1 是代表赛中最快的,所以 A1A_1A1 一定是全场第一名;
  • B1B_1B1 是代表赛中的第二名,最快情况下 B1B_1B1 同时也是全场的第二名,则 B3B_3B3 前面还有 B2B_2B2,所以 B3B_3B3 失去竞争前三名的资格;
  • C1C_1C1 是代表赛中的第三名,最快情况下 C1C_1C1 同时也是全场的第三名,则 C2、C3{C_2、C_3}C2、C3 失去前三名的竞争资格;
  • D1D_1D1 和 D1D_1D1 是代表赛的四五名,说明 D 组和 E 组都失去了前三名的竞争资格;

第二回合

3.3 第三回合

此时,剩余的未知顺序的赛马正好有 5 匹,加赛一轮就可以得出第二名和第三名的归属。三个回合总共进行了 7 轮比赛,故答案就是 7。

{A2,A3}{A_2,A_3}{A2,A3}

{B1,B2}{B_1,B_2}{B1,B2}

{C1}{C_1}{C1}


推荐阅读

逻辑思维系列往期回顾:

  • #1 25 匹马 5 条赛道,最快需要几轮求出前 3 名?
  • #2 舞会上有多少顶黑帽?
  • #3 至少要几个砝码,可以称出 1g ~ 40g 重量
  • #4 我知道你不知道,我到底知不知道

⭐️ 永远相信美好的事情即将发生,欢迎加入小彭的 Android 交流社群~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

代码优化大盘点:35 个 Java 代码优化魔鬼细节 前言

发表于 2020-12-08

前言

代码 优化 ,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是,吃的小虾米一多之后,鲸鱼就被喂饱了。

代码优化也是一样,如果项目着眼于尽快无BUG上线,那么此时可以抓大放小,代码的细节可以不精打细磨;但是如果有足够的时间开发、维护代码,这时候就必须考虑每个可以优化的细节了,一个一个细小的优化点累积起来,对于代码的运行效率绝对是有提升的。

代码优化的目标是:

减小代码的体积

提高代码运行的效率

代码优化细节

1、尽量指定类、方法的final修饰符

带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String,整个类都是final的。为类指定final修饰符可以让类不可以被继承,为方法指定final修饰符可以让方法不可以被重写。如果指定了一个类为final,则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,具体参见Java运行期优化。此举能够使性能平均提高50% 。

2、尽量重用对象

特别是String对象的使用,出现字符串连接时应该使用StringBuilder/StringBuffer代替。由于Java虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响。

3、尽可能使用局部变量

调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。

4、及时关闭流

Java编程过程中,进行数据库连接、I/O流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。如果条件允许,即JDK7以上,强烈建议使用try-with-resources。

5、尽量减少对变量的重复计算

明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作:

建议替换为:

这样,在list.size很大的时候,就减少了很多的消耗

6、尽量采用懒加载的策略,即在需要的时候才创建*

例如:

建议替换为:

7、慎用异常

异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fillInStackTrace的本地同步方法,fillInStackTrace方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。

8、不要在循环中使用try…catch…,应该把其放在最外层

除非不得已。如果毫无理由地这么写了,只要你的领导资深一点、有强迫症一点,八成就要骂你为什么写出这种垃圾代码来了。

9、如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度

比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder为例:

(1)StringBuilder // 默认分配16个字符的空间

(2)StringBuilder(int size) // 默认分配size个字符的空间

(3)StringBuilder(String str) // 默认分配16个字符+str.length个字符空间

可以通过类(这里指的不仅仅是上面的StringBuilder)的来设定它的初始化容量,这样可以明显地提升性能。比如StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。因为当StringBuilder达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,无论何时只要StringBuilder达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中—-这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放5000个字符而不指定长度,最接近5000的2次幂是4096,每次扩容加的2不管,那么:

(1)在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间;

(2)把原来的4096个字符拷贝到新的的字符数组中去。

这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成new HashMap(128)、new HashMap(256)都可以。

10、当复制大量数据时,使用System.arraycopy命令

11、乘法和除法使用移位操作

例如:

用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的,因此建议修改为:

移位操作虽然快,但是可能会使代码不太好理解,因此最好加上相应的注释。

12、循环内不要不断创建对象引用

例如:

这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:

这样的话,内存中只有一份Object对象引用,每次new Object的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。

13、基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList

14、尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销

15、不要将数组声明为public static final

因为这毫无意义,这样只是定义了引用为static final,数组的内容还是可以随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变。

16、尽量在合适的场合使用单例

使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:

(1)控制资源的使用,通过线程同步来控制资源的并发访问

(2)控制实例的产生,以达到节约资源的目的

(3)控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信

17、尽量避免随意使用静态变量

要知道,当某个对象被定义为static的变量所引用,那么gc通常是不会回收这个对象所占有的堆内存的,如:

此时静态变量b的生命周期与A类相同,如果A类不被卸载,那么引用B指向的B对象会常驻内存,直到程序终止。

18、及时清除不再需要的Session

为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多的会话时,如果内存不足,那么操作系统会把部分数据转移到磁盘,应用服务器也可能根据MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常。如果会话要被转储到磁盘,那么必须要先被序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。因此,当会话不再需要时,应当及时调用HttpSession的invalidate方法清除会话。

19、实现RandomAccess接口的集合比如ArrayList,应当使用最普通的for循环而不是foreach循环来遍历

这是JDK推荐给用户的。JDK API对于RandomAccess接口的解释是:实现RandomAccess接口用来表明其支持快速随机访问,此接口的主要目的是允许一般的算法更改其行为,从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明,实现RandomAccess接口的类实例,假如是随机访问的,使用普通for循环效率将高于使用foreach循环;反过来,如果是顺序访问的,则使用Iterator会效率更高。可以使用类似如下的代码作判断:

foreach循环的底层实现原理就是迭代器Iterator,参见Java语法糖1:可变长度参数以及foreach循环原理。所以后半句”反过来,如果是顺序访问的,则使用Iterator会效率更高”的意思就是顺序访问的那些类实例,使用foreach循环去遍历。

20、使用同步代码块替代同步方法

这点在多线程模块中的synchronized锁方法块一文中已经讲得很清楚了,除非能确定一整个方法都是需要进行同步的,否则尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。

21、将常量声明为static final,并以大写命名

这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量。

22、不要创建一些不使用的对象,不要导入一些不使用的类

这毫无意义,如果代码中出现”The value of the local variable i is not used”、”The import java.util is never used”,那么请删除这些无用的内容

23、程序运行过程中避免使用反射

关于,请参见反射。反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是Method的invoke方法,如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存—-用户只关心和对端交互的时候获取最快的响应速度,并不关心对端的项目启动花多久时间。

24、使用数据库连接池和线程池

这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程。

25、使用带缓冲的输入输出流进行IO操作

带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率。

26、顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList这个,理解ArrayList和LinkedList的原理就知道了。

27、不要让public方法中有太多的形参

public方法即对外提供的方法,如果给这些方法太多形参的话主要有两点坏处:

1、违反了面向对象的编程思想,Java讲求一切都是对象,太多的形参,和面向对象的编程思想并不契合

2、参数太多势必导致方法调用的出错概率增加

至于这个”太多”指的是多少个,3、4个吧。比如我们用JDBC写一个insertStudentInfo方法,有10个学生信息字段要插如Student表中,可以把这10个参数封装在一个实体类中,作为insert方法的形参。

28、字符串变量和字符串常量equals的时候将字符串常量写在前面

这是一个比较常见的小技巧了,如果有以下代码:

建议修改为:

这么做主要是可以避免空指针异常。

29、请知道,在java中if (i == 1)和if (1 == i)是没有区别的,但从阅读习惯上讲,建议使用前者

平时有人问,”if (i == 1)”和”if (1== i)”有没有区别,这就要从C/C++讲起。

在C/C++中,”if (i == 1)”判断条件成立,是以0与非0为基准的,0表示false,非0表示true,如果有这么一段代码:

C/C++判断”i==1″不成立,所以以0表示,即false。但是如果:

万一程序员一个不小心,把”if (i == 1)”写成”if (i = 1)”,这样就有问题了。在if之内将i赋值为1,if判断里面的内容非0,返回的就是true了,但是明明i为2,比较的值是1,应该返回的false。这种情况在C/C++的开发中是很可能发生的并且会导致一些难以理解的错误产生,所以,为了避免开发者在if语句中不正确的赋值操作,建议将if语句写为:

这样,即使开发者不小心写成了”1 = i”,C/C++编译器也可以第一时间检查出来,因为我们可以对一个变量赋值i为1,但是不能对一个常量赋值1为i。

但是,在Java中,C/C++这种”if (i = 1)”的语法是不可能出现的,因为一旦写了这种语法,Java就会编译报错”Type mismatch: cannot convert from int to boolean”。但是,尽管Java的”if (i == 1)”和”if (1 == i)”在语义上没有任何区别,但是从阅读习惯上讲,建议使用前者会更好些。

30、不要对数组使用toString方法

看一下对数组使用toString打印出来的是什么:

结果是:

本意是想打印出数组内容,却有可能因为数组引用is为空而导致空指针异常。不过虽然对数组toString没有意义,但是对集合toString是可以打印出集合里面的内容的,因为集合的父类AbstractCollections重写了Object的toString方法。

31、不要对超出范围的基本数据类型做向下强制转型

这绝不会得到想要的结果:

我们可能期望得到其中的某几位,但是结果却是:

1942892530

解释一下。Java中long是8个字节64位的,所以12345678901234在计算机中的表示应该是:

0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010

一个int型数据是4个字节32位的,从低位取出上面这串二进制数据的前32位是:

0111 0011 1100 1110 0010 1111 1111 0010

这串二进制表示为十进制1942892530,所以就是我们上面的控制台上输出的内容。从这个例子上还能顺便得到两个结论:

1、整型默认的数据类型是int,long l = 12345678901234L,这个数字已经超出了int的范围了,所以最后有一个L,表示这是一个long型数。顺便,浮点型的默认类型是double,所以定义float的时候要写成””float f = 3.5f”

2、接下来再写一句”int ii = l + i;”会报错,因为long + int是一个long,不能赋值给int

32、公用的集合类中不使用的数据一定要及时remove掉

如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去remove掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。

33、把一个基本数据类型转为字符串,基本数据类型.toString是最快的方式、String.valueOf次之、数据+””最慢

把一个基本数据类型转为一般有三种方式,我有一个Integer型数据i,可以使用i.toString、String.valueOf(i)、i+””三种方式,三种方式的效率如何,看一个测试:

运行结果为:

所以以后遇到把一个基本数据类型转为String的时候,优先考虑使用toString方法。至于为什么,很简单:

1、String.valueOf方法底层调用了Integer.toString方法,但是会在调用前做空判断

2、Integer.toString方法就不说了,直接调用了

3、i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString方法获取字符串

三者对比下来,明显是2最快、1次之、3最慢。

34、使用最有效率的方式去遍历Map

遍历Map的方式有很多,通常场景下我们需要的是遍历Map中的Key和Value,那么推荐使用的、效率最高的方式是:

如果你只是想遍历一下这个Map的key值,那用”Set keySet = hm.keySet;”会比较合适一些

35、对资源的close建议分开操作

意思是,比如我有这么一段代码:

建议修改为:

虽然有些麻烦,却能避免资源泄露。我想,如果没有修改过的代码,万一XXX.close抛异常了,那么就进入了cath块中了,YYY.close不会执行,YYY这块资源就不会回收了,一直占用着,这样的代码一多,是可能引起资源句柄泄露的。而改为上面的写法之后,就保证了无论如何XXX和YYY都会被close掉。如果JDK7以上,强烈建议使用try-with-resources。

最后

欢迎关注公众号:程序员白楠楠,领取一份300页pdf文档的Java核心知识点总结!

这些资料的内容都是面试时面试官必问的知识点,篇章包括了很多知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。

大家一起交流,喜欢文章记得关注我点个赞哟,感谢支持!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

多线程的实际应用场景

发表于 2020-12-08

业务场景

我们公司做的是加油业务,用户可以在app上通过当前位置和目标位置来查询地图路线以及路线途径的所有加油站,路线查询会调用高德地图提供的接口,途径油站则根据返回的路线信息进行查询,所以当用户输入起始位置和目标位置点击查询后会做以下几步操作:

  1. 调用高德地图接口获取路线
  2. 根据高德地图返回的路线信息去查询途径的所有油站

问题点

公司发展很快,入驻平台的油站很多,当用户输入的起始地和目标地距离很远时,那么途径油站的数量会很大,单独采取普通查询库的方式会很耗时,并且途径油站的数据必须是实时的,所以无法使用缓存来提高接口响应效率。第一步是调用高德地图API,也会存在一定的延迟。那么我们应该怎么优化呢?

为了降低接口耗时,提高用户体验,我们需要对接口实现进行优化,调用高德API我们无法优化,所以只能优化查询途径油站这部分。

优化思路

当油站过多时,一次查询会很耗时,所以我们可以考虑分批多线程并发的去查询,将一段很长的路线按照路径长度分成若干个条件,比如一段路径长达800km,我们可以将这800km的查询参数拆分成若干个距离较小的参数集合(ps:举例方便大家理解,实际路径规划查询都是根据经纬度、距离等多重参数进行查询的)。比如,{[0,50],[50,100],[100,150]…..[750,800]},这时我们开启多个线程去并发的根据新的查询条件去查询,最后将结果拼接封装返回,从而达到降低查询时间的目的。

虽然思路很容易理解,但是实现起来有两个需要注意的地方,我列出来看看大家有没有考虑到。

  • 根据业务场景,这里不是单纯的异步查询就可以的,而是需要所有的线程都执行完后并且组合查询结果后进行返回,所以这里需要进行同步控制。这里我们使用jdk提供的CountDownLatch同步组件实现。
  • 线程内操作需要有返回值,使用Callable接口以及FutureTask搭配实现。

具体实现

​ 1.通常来说,我们定义线程需要实现Runnable接口,但是对于需要返回值的线程,就需要线程实现Callable接口了。

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
java复制代码@Component
@Slf4j
@Scope("protoType") // 这里需要注意Spring默认注入的Bean都是单例的,当前业务场景下肯定需要多个线程去执行查询操作,所以这里声明组件为protoType模式
public class PathPlanTask implements Callable<List<Object>> {
// 查询参数
private PathPlanPartQuery pathPlanPartQuery;

private CountDownLatch countDownLatch;

@Override
public List<Object> call() throws Exception {
try {
//TODO 业务查询
List<Object> result = queryList(pathPlanPartQuery);
// 返回结果
return result;
}catch (Exception e){
// 错误日志打印
log.error("query PathByGasstation error!");
}finally{
// 类似 i-- 的操作,当减到0的时候,countDownLatch.await()就会放行,否则会一直阻塞。
countDownLatch.countDown();
}
}

public void setPathPlanPartQuery(PathPlanPartQuery pathPlanPartQuery){
this.pathPlanPartQuery = pathPlanPartQuery;
}

public void setCountDownLatch(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}

private List<Object> queryList(PathPlanPartQuery pathPlanPartQuery) {
// TODO 具体查询逻辑,这里省略
return Lists.newArrayList();
}

}

​ 2.Callable通常和FutureTask搭配使用,通过FutureTask的get方法获取到线程的返回值。

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
java复制代码// 通常定义为工具类进行获取
private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(8, 20, 1000,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(50), new ThreadPoolExecutor.AbortPolicy());

// 业务代码
private List<Object> queryGasInfoBaseDtoList(List<PathPlanQueryParam> queryParamList) {
long stMills = System.currentTimeMillis();
// 定义线程池来进行多线程的管理,通过Util获取静态的线程池

// 定义countDownLatch,构造函数传递参数集合的size,该集合具体参数可以参考
// 上面举的例子,{[0,50],[50,100],[100,150]...[750,800]}
CountDownLatch countDownLatch = new CountDownLatch(queryParamList.size());
// 批量查询,定义FutureTask集合
List<FutureTask<List<GasInfoBaseResponseDto>>> futureTaskList = Lists.newArrayList();
try {
// 遍历查询参数集合
for (PathPlanQueryParam queryParam : queryParamList) {
// 这里使用getBean方式获取。
PathPlanTask pathPlanTask =
ApplicationContextProvider.getBean("pathPlanTask", PathPlanTask.class);
// 设置countDownLatch
pathPlanTask.setCountDown(countDownLatch);
// 获取查询参数
PathPlanPartQuery pathPlanPartQuery = getPathPlanPartQuery(queryParam);
pathPlanTask.setPathPlanPartQuery(pathPlanPartQuery);
// 定义FutureTask,将定义好的Callable实现类作为构造参数
FutureTask<List<GasInfoBaseResponseDto>> futureTask = new FutureTask<>(pathPlanTask);
// 交给线程池去执行
poolExecutor.submit(futureTask);
// 添加futureTask集合
futureTaskList.add(futureTask);
}
// 这里会一直进行阻塞,直到countDownLatch.countDown()方法将创建时传递的size参数减为0后放行。
// 这块可以保证多个线程全部执行完后进行最终返回。
countDownLatch.await();

// 多个线程执行完后我们拼接最终结果
List<Object> gasInfoDtoList = Lists.newArrayList();
for (FutureTask<List<Object>> futureTask : futureTaskList) {
// 通过futrueTask的get方法获取返回值,当线程还在执行未返回时执行futureTask.get()会被阻塞
List<Object> baseResponseDtoList = futureTask.get();
if (CollectionUtils.isNotEmpty(baseResponseDtoList)) {
gasInfoDtoList.addAll(baseResponseDtoList);
}
}
return gasInfoDtoList;
} catch (Exception e) {
log.error("queryGasInfoBaseDtoList_err", e);
} finally {
log.info("queryGasInfoBaseDtoList_requestId:{},batchTimes:{},cost:{}", pointRequestOld.getRequestId(),
pointRequestOld.getBatchTimes(), System.currentTimeMillis() - stMills);
}
return null;
}

总结

以上是我在工作中遇到的多线程实际应用的场景,总结下来就是通过线程池开启多个Callable线程去分批查询数据, 引入CountDownLatch组件来控制查询结束时机,而后利用FutureTask的get方法获取最终结果拼装返回。

后面我会更新一些多线程的底层知识,通过源码来介绍AQS、CountDownLatch、线程池等等的工作原理,欢迎大家的关注!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…756757758…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%