焦躁六月(2019)

送走了愉快的五月,就来了令人焦躁不已的六月。

不知道是不是温度高了天气热了,总是在这样的季节使得焦躁浑身难受然后发生各种事情。


租房

在外漂没有自己的房子真的是很难受,换工作不在附近得搬,房东不续租说搬也就得搬。

住的地是三室户,这不马上就到期了,一室友不续租了跟女朋友住去结果导致空一间房,找不到人接盘,房东也就不跟我们续租了。也罢,老房子什么设施都是坏的,是得要对自己好一点了,搬吧,那就找房子吧。

找房也还算顺利,虽然是通过中介的,多付了好几千中介费,这也算是找了个新住处,无缝对接。这房子也是个三室,不过面积要稍微大点,环境也好一点,出门就是欧尚超市不远还有个生活广场。原来住的三小伙要搬去浦东刚好也要到期了,房子是真的贵,感觉还合适那就快刀斩乱麻,直接签了得。

过几天得收拾东西准备搬了,其它的倒是还好,就我那个大鱼缸有点费事,也是我要搬之所以头疼的事。说到还是个三室,那还是要准备拉一个人来的,刚好有个新同事过来,先住着,后面就再说吧。也不知道搬过去还能住多久,沪漂什么时候是个头。事情真多,头疼。

淄博

公司以前的一个项目突然就把我派过去考察了,山东淄博潭溪山景区。地方是真的挺偏的,下了高铁,打了个出租一直开了将近两小时,其中山路开了近一个小时,还是盘山上去的。据了解来这的都是自驾的多,不然交通确实是不方便。

在那边呆了差不多四天吧,他们吃粗粮的多。我就入乡随俗,跟着吃了几天的菜饼馒头,一天吃个两顿,还真管饱。

家族企业自建的景区,项目确实是多,玻璃桥建得也很奇,十五根索支持,还是值得去一趟的,就是地方偏了点。有一天中午我从那边跟着人群走山路下来,八百多米啊,走得后来腿都打颤了。现在小腿还酸着呢。在这边我就不吐槽公司。

回来路上开车师傅路边买了点油桃,才两块钱一斤,那边的水果确实是好吃点,跟气候环境关系很大吧。看看他们那边居民生活也挺自在的,景点工作人员早上七点集合一起去山上上班,平时没什么人没什么事就坐在那玩玩手机睡睡觉,等到下午五点就下班回去吃饭了。

回来的火车不知道因为什么事还晚点了半小时左右,我是中转去济南西再到上海的,还好给自己留的时间够长,不然又得要费一番劲了。回到上海晚上八点多,回到住的地方晚上十点多,喝了点稀饭吃了两个家里带的粽子,澡就第二天洗了,弄弄就睡了。

回来公司还有一堆事。

搬搬搬

回头再来说说搬家这个事。

其它东西倒还好,搬的地方也不是很远,还是就那个缸是真的不好弄。思来想去还是叫了辆依维柯,一趟省事,六七十倒也不贵。

周末大雨啊,必是一番雨淋淋。之前一直是直接用的投影,没用上显示器,这下那边房间虽然小但是有桌子,显示器也有地方可以放了,刚好家里有个闲置的显示器,这不得要叫我姐给我寄过来,顺丰21,我最喜欢的数字。家里电脑配置都是七八年前的了,看年底的时候要不要把这边的电脑给他们带回去,自己再重新配一台,要努力赚钱攒钱咯。

这么一搬,房租肯定是上去了,不过还好,还能接受吧。独自在外无人关怀,也不知道这样还要再过上几年啊。


最近发现一个很好听的声音,小C英乐,学英语又有动力了!


走近 Java 世界中的线程

本文摘抄自《Java 多线程编程实战指南》核心篇 第一章小结

本章介绍了线程、多线程编程这两个基本概念以及 Java 平台对线程的实现。


  • 进程是程序的运行实例,一个进程可以包含多个线程,这些线程共享其所在进程的资源。

  • 线程是进程中可独立执行的最小单位。Java 标准库类 java.lang.Thread 就是 Java 平台对线程的实现。特定线程总是在执行特定的任务,线程的 run 方法就是线程所要执行任务的处理逻辑的入口方法,该方法由 Java 虚拟机直接调用执行。Java 标准库接口 java.lang.Runnable 就是对任务的抽象,Thread 类就是 Runnable 接口的一个实现类

  • 应用程序负责线程的创建与启动,而线程调度器负责线程的调度和执行。Java 平台中有两种方式创建线程:创建 Thread 的子类和以 Runnable 接口实例为构造器参数直接通过 new 创建 Thread 实例。

  • 在 Java 平台中,任何一段代码总是执行在确定的代码中的。同一段代码可以被不同的线程执行。代码可以通过 Thread.currentThread() 调用来获取其当前执行线程。

  • 为每个线程设置一个简短而含义明确的名称属性有助于多线程程序的调试和问题定位。

  • 一个线程从其创建到运行结束的整个生命周期会经历若干状态。线程执行过程中调用一些对象的方法(如 Thread.sleep(long millis))或者执行特定的操作(如 I/O 操作)往往导致其状态的变更。线程转储是对线程进行监视的重要媒介。操作系统以及 JDK 都提供了一些工具(jvisualvm、jstack 和 Java Mission Control),可以用来获取线程转储。

  • Java 平台是一个多线程的平台,线程的身影在 Java 平台中无处不在。按照线程间的创建关系,我们可以将多个线程间的关系理解为一个层次关系。Java 并无相关 API 用于获取一个线程的父线程或子线程,父线程和子线程之间的生命周期并无必然联系。

  • 线程是多线程编程的基本单位。多线程编程一方面有助于提高系统的吞吐率、提高软件的响应性、充分利用多核处理器资源、最小化对系统资源的使用和简化程序的结构,另一方面面临线程安全问题、线程活性问题、上下文切换和可靠性等问题。因此,多线程编程绝不仅仅是使用多个线程进行编程那么简单,多线程编程有其自身需要解决的问题,而这正是后续章节的主要内容。

本章知识结构图


SpringBoot2 实现邮件发送功能

springboot2 实现邮件发送功能,QQ/Gmail/163/126..

个人博客:DoubleFJ の Blog

效果图如下:springboot 实现邮件发送

技术选型

  • Spring Boot 2.1.3.RELEASE (原本官网推荐 2.1.5.RELEASE,可是搭建途中发现部分注解未生效,故改之)
  • Thymeleaf (用作邮件模板)
  • JDK 1.8

简要讲解

依赖以及配置

这里还是用的 Spring Boot 来整合,自带了模块依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

随着 Spring Boot 热度越来越大,现在从事 Java 的要是不知道 Spring Boot 的存在那就真的很不应该了。本来只是为了取代繁琐的 EJB,一直发展到了如今无所不在的地步。

然后在配置文件中进行对应的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8887

spring:
mail:
host: smtp.163.com
username: ffj0721@163.com
password: xxxx # 授权码
protocol: smtp
properties.mail.smtp.auth: true
properties.mail.smtp.port: 994
properties.mail.display.sendmail: DoubleFJ
properties.mail.display.sendname: Spring Boot Email
properties.mail.smtp.starttls.enable: true
properties.mail.smtp.starttls.required: true
properties.mail.smtp.ssl.enable: true
default-encoding: utf-8
from: ffj0721@163.com

一切就是这样的简单清晰。不同的邮件个别配置数据不同,请自行查阅,这里只用 163 做测试。

配置了之后我们开始操刀敲代码,其实只需要调用 JavaMailSender 接口即可,传参实现,已经给我们封装好了。

常用邮件接口

这里是几个常用的邮件接口:

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
package com.example.springbootmail.service;
import javax.mail.MessagingException;

/**
* 常用邮件接口
*/
public interface IMailService {
/**
* 发送文本邮件
* @param to
* @param subject
* @param content
*/
public void sendSimpleMail(String to, String subject, String content);

public void sendSimpleMail(String to, String subject, String content, String... cc);

/**
* 发送HTML邮件
* @param to
* @param subject
* @param content
* @throws MessagingException
*/
public void sendHtmlMail(String to, String subject, String content) throws MessagingException;

public void sendHtmlMail(String to, String subject, String content, String... cc);

/**
* 发送带附件的邮件
* @param to
* @param subject
* @param content
* @param filePath
* @throws MessagingException
*/
public void sendAttachmentsMail(String to, String subject, String content, String filePath) throws MessagingException;

public void sendAttachmentsMail(String to, String subject, String content, String filePath, String... cc);

/**
* 发送正文中有静态资源的邮件
* @param to
* @param subject
* @param content
* @param rscPath
* @param rscId
* @throws MessagingException
*/
public void sendResourceMail(String to, String subject, String content, String rscPath, String rscId) throws MessagingException;

public void sendResourceMail(String to, String subject, String content, String rscPath, String rscId, String... cc);

}

接口实现类注入 JavaMailSender

实现类分别实现上述接口 注入 JavaMailSender

1
2
3
4
5
6
7
8
9
10
11
/**
* 发送邮件实现类
*/
@Service
public class IMailServiceImpl implements IMailService {

@Autowired
private JavaMailSender mailSender;

@Value("${spring.mail.from}")
private String from;

其中 from 就是配置中我们配置的发送方,直接读取使用。

实现发送文本邮件

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
/**
* 发送文本邮件
*
* @param to
* 邮件接收方
* @param subject
* 邮件标题
* @param content
* 邮件内容
*/
@Override
public void sendSimpleMail(String to, String subject, String content) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to);
message.setSubject(subject);
message.setText(content);
mailSender.send(message);
}

/**
*
* @param to
* 邮件接收方
* @param subject
* 邮件标题
* @param content
* 邮件内容
* @param cc
* 抄送方
*/
@Override
public void sendSimpleMail(String to, String subject, String content, String... cc) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to);
message.setCc(cc);
message.setSubject(subject);
message.setText(content);
mailSender.send(message);
}

如上所示,三个参数的,第一个是你要发邮件的对象,第二个是邮件标题,第三个是邮件发送的内容,第二个 cc 字符串数组就是需要抄送的对象。

实现发送 HTML 邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 发送 HTML 邮件
*
* @param to
* @param subject
* @param content
*/
@Override
public void sendHtmlMail(String to, String subject, String content) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();

// true 表示需要创建一个multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);

mailSender.send(message);
}

官网 MimeMessageHelper 使用介绍。

实现发送带附件邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 发送带附件的邮件
*
* @param to
* @param subject
* @param content
* @param filePath
*/
public void sendAttachmentsMail(String to, String subject, String content, String filePath) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();

MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);

FileSystemResource file = new FileSystemResource(new File(filePath));
// 截取附件名
String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
helper.addAttachment(fileName, file);

mailSender.send(message);
}

这里附件名的截取要按照自己的实际需求来,有人喜欢用 \\,有人喜欢用 /,看具体情况具体分析了。

实现发送正文中有静态资源邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 发送正文中有静态资源(图片)的邮件
*
* @param to
* @param subject
* @param content
* @param rscPath
* @param rscId
*/
public void sendResourceMail(String to, String subject, String content, String rscPath, String rscId) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();

MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);

FileSystemResource res = new FileSystemResource(new File(rscPath));
helper.addInline(rscId, res);

mailSender.send(message);
}

其中 rscId 是资源的唯一 id,rscPath 就是对应资源的路径。具体待会我们看 Controller 调用方法。

实现发送模板邮件

前面说了我们选择使用 Thymeleaf 作为邮件的模板,那就需要在 POM 文件中加入对应依赖。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

然后在 templates 文件夹下新建 mailTemplate.html 页面,我的内容如下:

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
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>spring-boot-mail test</title>
<style>
body {
text-align: center;
margin-left: auto;
margin-right: auto;
}
#welcome {
text-align: center;
}
</style>
</head>
<body>
<div id="welcome">
<h3>Welcome To My Friend!</h3>

GitHub:
<a href="#" th:href="@{${github_url}}" target="_bank">
<strong>GitHub</strong>
</a>
<br />
<br />
个人博客:
<a href="#" th:href="@{${blog_url}}" target="_bank">
<strong>DoubleFJ の Blog</strong>
</a>
<br />
<br />
<img width="258px" height="258px"
src="https://raw.githubusercontent.com/Folgerjun/materials/master/blog/img/WC-GZH.jpg">
<br />微信公众号(诗词鉴赏)
</div>
</body>
</html>

这个模板自己 DIY 即可,不过对应填充参数不可弄错。例如我上面的是 github_urlblog_url

Thymeleaf 官网

调用实现

MailController.java

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
103
104
105
106
107
108
109
package com.example.springbootmail.controller;

import com.example.springbootmail.service.impl.IMailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

@RestController
@RequestMapping("/Mail")
public class MailController {

private static final String SUCC_MAIL = "邮件发送成功!";
private static final String FAIL_MAIL = "邮件发送失败!";

// 图片路径
private static final String IMG_PATH = "C:/Users/zjj/Desktop/github/materials/blog/img/WC-GZH.jpg";
// 发送对象
private static final String MAIL_TO = "folgerjun@gmail.com";

@Autowired
private IMailServiceImpl mailService;
@Autowired
private TemplateEngine templateEngine;

@RequestMapping("/Email")
public String index(){
try {
mailService.sendSimpleMail(MAIL_TO,"这是一封普通的邮件","这是一封普通的SpringBoot测试邮件");
}catch (Exception ex){
ex.printStackTrace();
return FAIL_MAIL;
}
return SUCC_MAIL;
}

@RequestMapping("/htmlEmail")
public String htmlEmail(){
try {
mailService.sendHtmlMail(MAIL_TO,"这是一HTML的邮件","<body>\n" +
"<div id=\"welcome\">\n" +
" <h3>Welcome To My Friend!</h3>\n" +
"\n" +
" GitHub:\n" +
" <a href=\"#\" th:href=\"@{${github_url}}\" target=\"_bank\">\n" +
" <strong>GitHub</strong>\n" +
" </a>\n" +
" <br />\n" +
" <br />\n" +
" 个人博客:\n" +
" <a href=\"#\" th:href=\"@{${blog_url}}\" target=\"_bank\">\n" +
" <strong>DoubleFJ の Blog</strong>\n" +
" </a>\n" +
" <br />\n" +
" <br />\n" +
" <img width=\"258px\" height=\"258px\"\n" +
" src=\"https://raw.githubusercontent.com/Folgerjun/materials/master/blog/img/WC-GZH.jpg\">\n" +
" <br />微信公众号(诗词鉴赏)\n" +
"</div>\n" +
"</body>");
}catch (Exception ex){
ex.printStackTrace();
return FAIL_MAIL;
}
return SUCC_MAIL;
}

@RequestMapping("/attachmentsMail")
public String attachmentsMail(){
try {
mailService.sendAttachmentsMail(MAIL_TO, "这是一封带附件的邮件", "邮件中有附件,请注意查收!", IMG_PATH);
}catch (Exception ex){
ex.printStackTrace();
return FAIL_MAIL;
}
return SUCC_MAIL;
}

@RequestMapping("/resourceMail")
public String resourceMail(){
try {
String rscId = "DoubleFJ";
String content = "<html><body>这是有图片的邮件<br/><img src=\'cid:" + rscId + "\' ></body></html>";
mailService.sendResourceMail(MAIL_TO, "这邮件中含有图片", content, IMG_PATH, rscId);

}catch (Exception ex){
ex.printStackTrace();
return FAIL_MAIL;
}
return SUCC_MAIL;
}

@RequestMapping("/templateMail")
public String templateMail(){
try {
Context context = new Context();
context.setVariable("github_url", "https://github.com/Folgerjun");
context.setVariable("blog_url", "http://putop.top/");
String emailContent = templateEngine.process("mailTemplate", context);

mailService.sendHtmlMail(MAIL_TO, "这是模板邮件", emailContent);
}catch (Exception ex){
ex.printStackTrace();
return FAIL_MAIL;
}
return SUCC_MAIL;
}
}

程序运行后若访问 http://localhost:8887/Mail/Email 页面出现邮件发送成功!字样就说明邮件发送已经实现了。

相关链接


愉快五月(2019)

这个五月过得是真的快啊~

五一回家在家呆了三天,跟父母老姐和姐夫(已订婚)去了趟老家县城附近的自建的一个玻璃栈道景点。过年期间就在朋友圈看到刷屏了,这次趁着空一起去逛了逛。老家小县城本就四面环山,景色相当不错,也有几个国家 4A 级景点,什么仙华山、神丽峡、江南第一家啊……

我老爸恐高啊,我也有点。全程在桥上老爸搭着老妈的肩膀小心翼翼地走在玻璃桥的边缘,事后一直被老妈嘲笑,家里也是很少有这样的机会一家人一起出去玩玩,我很喜欢一家人在一起的感觉。

一定要多跟他们在一起!


五一回来上班一周,又出去耍了!公司旅游投票菲律宾和塞班岛,结果毫无疑问,塞班岛走起~大晚上的飞机四个半小时凌晨飞到那边,一下飞机热浪扑面而来,弄弄行李到达酒店,天就亮了,太快了吧。

进房间洗洗躺下,大太阳就刺进来了,朝东的房间,太丫的亮了。结果躺了起,起了躺,后来终于睡着了,本来是十点钟门口集合导游大巴带我们去景点逛,这一睡也太舒服了,空调打得低。微信群里无人反馈(睡死了都),随着同事破门而入,一阵慌乱中奔向大巴。

岛不大,逛逛一圈也用不了多久。

景色是真的不错,碧海蓝天白云。

(拍了不少帅比照,哈哈)

就是太阳毒了点,回来黑了不少,后背轻微脱皮。本想晒成古天乐的,差点晒了个熊猫眼,下次再晒。在海边一点腥味都没闻到,在这样的海边上住着才是真的享受啊。

不得不说,在老美的地盘花着美金消费就是高,资金不允许啊就没怎么玩多少项目,不过也是体验了拖伞和浮潜。大头都留在那边的赌场了……

逛了一圈那边的礼品店,乍一眼都是从中国的地摊上运过来的,本地也没什么特色,我也就没买东西,哈哈(反正买了也没人送)

玩着耍着,六天五晚的旅程就结束了,要回来上班咯。

再上两三个星期就端午了,再之后就只有到中秋国庆了,能回家还是要回家的啊。


刚从塞班岛回来就又去了临港出差,开了个房还没走进去就听到老大声的“老公老公,啊啊啊嗯……”,真特么刺激,真好。

晚上就在他们的欢声笑语中入睡。


生活都不易,且活且珍惜。

做最好的自己。


惊险四月(2019)

就在昨天,公司自己刚搭没多久的小服务器中了 ETH 勒索病毒,还好刚起步数据量不大,真可怕!

把环境又给装了一遍,轻车熟路了。

专业事情还是需要专业人士来做,网络安全也是一个相当大的方面,只要被不法分子盯上就很有可能是一笔不小的损失了。

虽然说现在的技术科技发展很快,但是安全方面却不是那么跟得上脚步。中午跟同事聊起来,有些公司还是用最初的刻盘来保存数据,银行系统还有很多是 XP,回到本初,最原始的即最安全也是不无道理,整得再花再虚,我最需要的还是安全实用。

从这些角度继续往下想,有时候我们会埋怨某些系统太过于古老,技术太过于落后,这也是前几年我看到一些古老项目的想法。技术的世界日新月异,新轮子不断出现,内涵包裹越来越深,过于追求抽象化。

当我们选择项目中要用的技术栈时,肯定会优先选择那些稳定的、有人长期维护的、社区较活跃的,这些现象也正说明了这个技术关注度高,较为成熟。不然找偏门技术栈的可能出现了 Bug,其创始人都不知道为什么会出现该怎么解决,那不就废了吗。现在开源社区大多都是拿来即用,也很少会有人去专门研究源码,这就是技术选型。

当你接手了一个老项目,你了解了其作用与每个地方的功能后,发现市面上完全有成熟的技术可以取代之,且换架构后项目会更加清晰易理解。这个时候你就可以向上申报或者自己闲暇时间尝试给它换一个壳(当然是因人而异,也有可能是费力不讨好之事,若对自身有益且有闲时,可一做)。当完成这个“手术”之后,往往自己会对这个项目有了一个更深的理解,很可能还会找到几处优化的地方。改造成功一个项目,看着它,就像是你赋予了它生命一般,后期需要改动或是加功能打补丁都会更得心应手。

最近在看《深入理解计算机系统》,本是一本基础教材。明白了“万变不离其宗”的道理之后,深知基础的重要性。别人问我:你们搞计算机的要记的东西不少,感觉很难啊。我说:只要你脑子不笨不呆,灵活会转会用心记就行。

调调几个 API 谁不会呢,我才不管你是怎么实现的,我只要拿到我想要的数据得到我想要的结果就行了,不就是一堆数据传过来传过去。现在框架轮子多得死,一拼一凑就是一个“项目”。

可不是嘛,平时要写的就是一大坨的业务逻辑处理代码,正常的有几个会拎不清呢?当然也是有的,所以这层就开始出现了能力的差别。越往细小的领域,这差距就会越大。

业务逻辑写个几年,天花板就到头了,若是脑子不活络无管理能力的,那就是真的“废”了。虽说饿不死,至少以后找个公司维护维护老项目都能养活自己。这就导致了以后没有了 竞争力,年级越大越无奈,这一阶段的人可以说是到处都是,市场完全不会缺。

前一段时间招了个实习生(上海理工研二),自动化专业想要来写代码(跨专业,基础确实是差),说实话已是一片红海,若是像我上面说的那样还要去那个阶段一起游泳,那我觉得真是有点不值了。


前几天智齿长出来了!右腮帮就像被人挥了一拳一样,感觉是真的不爽!本命年才会长智齿,真的是准啊,长大了长大了。

同事搬过来变成了新室友,好家伙现在天天晚上都要粥煮起来吃,不吃睡不着,这吃下去再不长肉就真不科学了,再加上每天下午去楼下全家买两个包子或是两个馒头。搞了张尊享会员卡,也开始了没事喝喝咖啡的日子了(送的不喝那我就是有病了),血压低,多喝喝。

哈哈,室友将他那辆二手永久车送我了!我自己去补了个胎,就花了二十换了辆自行车,怎么都不亏啊哈哈。刚好摩拜季卡到期了,共享单车又集体涨价,这辆三手车(可能不止三手了……)至少能骑个半年咯。

等夏天到了,早上戴着墨镜骑着车听听音乐去上班,然后一身汗,湿透。


有名气有底蕴的大学是真的不一样,看了附近同济、复旦的校区后,自己的母校是何等的简陋。。。以后还要多去其它学校看看,欣赏欣赏别人大学的风景。

生活在于折腾,前提是还能经得起折腾。年纪轻轻就不要想那么多,想要的想做的就尽量去满足自己,一旦念头消散可能就再无机会了。

至少还能有可以愿意去回想的东西,还有内心觉得美好的日子,还有至今无悔的事情。

也许现在的生活并不是那么尽人意,但若是有可以回去的机会我还是会选择那样过,还是想要经历那些经历过的事情,虽然也许无果但无悔、无愧,青春,知足。


我这个人啊,耳根子软,心也软又善,还重情义。你若对我一丝好,我便会含泪感动入睡。

不过这不代表一向如此,上述只是我大多状态。人啊,久了都会不懂自己。

最近在补权游,Valar Morghulis.


LeetCode 之总持续时间可被 60 整除的歌曲(Pairs of Songs With Total Durations Divisible by 60)

题目虽然有点长,不过可以化简为同一个类型的,就是两两配对其和是某个数的倍数。

原题描述如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在歌曲列表中,第 i 首歌曲的持续时间为 time[i] 秒。

返回其总持续时间(以秒为单位)可被 60 整除的歌曲对的数量。形式上,我们希望索引的数字 i < j 且有 (time[i] + time[j]) % 60 == 0。

示例 1:
输入:[30,20,150,100,40]
输出:3
解释:这三对的总持续时间可被 60 整数:
(time[0] = 30, time[2] = 150): 总持续时间 180
(time[1] = 20, time[3] = 100): 总持续时间 120
(time[1] = 20, time[4] = 40): 总持续时间 60

示例 2:
输入:[60,60,60]
输出:3
解释:所有三对的总持续时间都是 120,可以被 60 整数。

提示:

1 <= time.length <= 60000
1 <= time[i] <= 500

初看题目,脑子都不要动,我相信大多人也是和我一样,两个 for 搞定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 超时
*
* @param time
* @return
*/
public int numPairsDivisibleBy60(int[] time) {
int result = 0;
for (int i = 0; i < time.length - 1; i++) {
for (int j = i + 1; j < time.length; j++) {
int value = (time[i] + time[j]) % 60;
if (value == 0)
result++;
}
}
return result;
}

果然不意外,提交显示超时了。

我们静下心来想想,两数之和是某个数的倍数。既然这样循环查找过于繁琐,那有什么方式可以快速精准查找呢?

哐…当然有了。

首先我们会想到数组,还有键值对形式存储的 Map

这里我使用的是数组,那要怎么使用它呢,再细想。

数组精准查找,那得要根据下标,两数之和为某数,自然,那就将数字作为下标存储到对应块中,值就是其对应的个数。

思路有了,代码如行云流水,请看:

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
public int numPairsDivisibleBy601(int[] time) {
// 每个元素取60余
time = Arrays.stream(time).map(x -> x % 60).toArray();
int[] arrCount = new int[60];
// 统计对应下标数
Arrays.stream(time).forEach(x -> {
arrCount[x]++;
});
// 余数是 0 或 30 的两两配对
int result = qiuhe(arrCount[0]) + qiuhe(arrCount[30]);
// 其余互相配对
for (int i = 1; i < 30; i++) {
int count1 = arrCount[i];
int count2 = arrCount[60 - i];
result += count1 * count2;
}
return result;
}

/**
* 求两两配对数
*
* @param num
* @return
*/
private int qiuhe(int num) {
if (num < 2)
return 0;
// (num-1)!
return num * (num - 1) / 2;
}

很好理解,不过速度还是慢,通过阅读其它人的代码后发现了另一种 O(n) 解法,cool~

1
2
3
4
5
6
7
8
9
10
11
12
13
public int numPairsDivisibleBy602(int[] time) {
int result = 0;
int[] arrCount = new int[60];
for (int t : time) {
// 对应的下标
int index = t == 0 ? 0 : 60 - t % 60;
// 与已经统计的数进行匹配 可防止两两重复匹配
result += arrCount[index];
// 对应数字加一
arrCount[t % 60]++;
}
return result;
}

一个 for 完美解决问题,想必这就是算法的魅力吧。


忙碌三月(2019)

三月头到三月底算是在这公司至今最忙的一段时间了

从三月初去昆明之后回来又去临港来回三趟,之后又是世博文化中心项目急得要死,加班赶项目还花了两周末。

还不错,今年公司五月去海岛旅游,该加紧锻炼了!


昆明

说来惭愧,昆明这趟是我的“处女飞”,去的时候坐的是吉祥航空经济舱,体验是真特么差,便宜啊便宜。回来坐的是昆明航空经济舱,贵是贵了,体验也确实好很多,不知什么时候能体验下头等舱的魅力。

去了昆明长水机场,那确实是偏了点,坐了两个多小时才到了目的地。之后在昆明医科大学吃了顿饭,附近找了个宾馆,乖乖,全程微信沟通,就过来送了个发票。

昆明路上拍摄的黄金龙头

你说这是“凤凰”?没差,龙凤呈祥!

去的时候那边温度比上海要高个 10℃,宾馆中都不带空调的,听说是四季如春啊,蓝天白云真不错。虽然没有大都市那么繁华,也很安谧,宜居。

晚上没事干搜了附近,去了家汗蒸店,结果一进去发现全是女的,尴尬。附近学校多嘛,女同学经常会来汗蒸放松放松,我就找了个角落闭目凝神静气。大概蒸了一个半小时,全身真的蒸透了,很舒服。

临港

临港创新科技城,规模不小设计造型独特,几年后估计又是一片天地。临港现在没有像市区那么繁华,街道上也几乎没什么人,打个车也没那么方便,不过看这发展的样子,未来也是个不错的地方,如果生活工作都在临港,那绝对幸福感不会低,看了下,均价三万一平吧。

去临港三月去了三趟,过去要 8 号线 -> 2 号线 -> 16 号线,将近三小时的路程。不过还好,每次都待了个几天不然一天来回估计脑袋够呛。

公司接的项目都这么高级,着实厉害。

原先我们都是现场用 485 串口服务器直连现场服务器进行数据采集,包括这个临港项目也是。之后准备用 485 转 GPRS 透传功能直接与公司外网 IP 暴露出去的端口进行数据传输,这样就不用在现场布置服务器了,直接在公司服务器部署采集即可。不过这样必然对现场工人们的技术要求提高些,不光要保证传感器的完好还要保证采集设备的完好还要过程中接线的正确性,由于现场技术人员不在就需要他们要有一定的问题排查能力。有利有弊吧,不过总体是方便了不少。

世博

上海世博文化中心项目最近要收尾了,公司干活人数有限,之前催死了。这项目还是用的现场部署服务器进行数据采集,不过用的是 4G 路由器,无固定外网 IP,所以现场能访问公司服务器,而外部访问不了,这就需要之前我总结过的使用 ssh 反向隧道穿透 NAT 访问 Linux 内网主机。然后还有一个问题就是数据需要同步到公司服务器,不然无法给业主实时查看。针对业务需求,通过一系列的资料查找,最终选择阿里开源的项目 otter,具体可看记录 otter 数据同步

加了几天班,终于也是告一段落了。

还没缓过来,松江云廊屋盖项目也要来了,今年真是项目都堆积一起了。


期待五月的海岛游(听说是塞班岛,俺土鳖,哪都成!)


纵欲二月(2019)

欲望使我痛苦不堪。

春节来到,饮食不当,肠胃坏掉,噩梦来撩。

去年没赚什么钱,自然没多少物质回馈父母,所以我得在家多呆几天,陪陪他们。

可是啊可是,我就一周的假期,不上班我就没有钱,没有钱我怎么有脸在家鬼混啊。

假期结束,我就又回到大上海开始两点一线的生活了。


聚会

年前参加了初中同学聚会,距离初中毕业已然过了九个春夏。上一次聚会我还在读大学呢,这次都工作一年多了。

大多同学许多年未见,一些更是自毕业后再无接触。人生能再有几个九年,不过这个九年大家似乎外貌变化不太大,一些女生就例外了,女大十八变啊。

大家各行各业的都有,交谈起来不免觉得自己同学都好优秀,我怎么混成了这样,哭唧唧。同学中有护士、医生、律师、海龟、警察等等。还有自己开舞蹈工作室的,有在清迈开民宿的,有自己开厂当老板的,我的天啊。还有北航读书的、北邮读研的,卧槽还有清华大学本硕的,真是个女学霸,厉害!

当然,也找到了几个同行。顿时觉得,我要是在老家发展,那这资源岂不丰富!不行啊,我还得赚钱买房娶媳妇啊。

这么聚聚挺好的,再过几年都成家后怕是抽不出空来咯。

聚餐后 KTV 喝酒喝多了,一个个真特么能喝!

过年

年三十一家人吃了年夜饭,后又去爬了个小山消食,一家人走走挺好的。

接着就是去亲戚家拜年咯,一家一天,吃吃喝喝,基本就没有米饭什么事了。

我家顶楼是个阳光棚,买了烧烤架以及一些简单的东西,在自家楼上也可以吃吃烧烤,看看阴天,或者是听听滴水声。

春节在中国这么重要的节日,我竟然不知道要写些什么,光在家嗑核桃嗑傻了吧。

说到嗑核桃,核桃是不是吃多上火?嘴角后来长了好大一个疮,都多久没长过了,现在都还有印子。

痛苦

太痛苦了,一回到上海肠胃就出问题了,不知道是冻着了还是之前吃狠了,急性肠胃炎啊。

真是吃什么拉什么,都到了什么程度你晓得不,我晚上回去怕自己虚脱,直接拿细盐冲开水往嘴里怼。

后来喝了一星期的藿香正气水,吃了一星期的消炎药,早上一杯燕麦,中午基本不吃,晚上吃水煮小馄饨,慢慢好转。

为什么我不去医院?去医院不得要花大钱啊,哭唧唧。我相信自己的身体,它能抗!

抗完肠胃抗流感。

什么情况啊,真是屋漏偏逢连夜雨!

对不起可爱的室友们了,空调我已经偷摸着开了好几个晚上了,哈哈。

船到桥头自然直 彩虹总在风雨后


JVM 常用调优参数

记录下 JVM 常用的一些调优参数。

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
// 常见参数
-Xms1024m 初始堆大小
-Xmx1024m 最大堆大小 一般将 Xms 和 Xmx 设置为相同大小,防止堆扩展,影响性能。
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值.如:为 3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代年老代和的 1/4
-XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的比值.注意 Survivor 区有两个.如: 3,表示 Eden:Survivor=3:2,一个 Survivor 区占整个年轻代的 1/5
-XX:MaxPermSize=n:设置持久代大小
-XX:+HeapDumpOnOutOfMemoryError OOM 时自动保存堆文件,可以用 visualvm 分析堆文件

// 收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器

// 垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename

// 并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比.公式为 1/(1+n)

// 并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式.适用于单 CPU 情况.
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的 CPU 数.并行收集线程数.

LeetCode 之反转链表(Reverse Linked List)

前言

反转链表也是常见的面试算法题了。

何为链表?

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

正文

我们先来看下题目描述:

1
2
3
4
5
6
7
8
反转一个单链表。

示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

小时候都玩过玩具蛇吧,那种可以一节一节拼接的。

我们可以想象下现在面前就有这么一条“蛇”,我们试着把它重新组装一遍,我们简单地 边拆边装

先把它尾巴拆了放一边,再接着拆它的倒数第二块同时把它安装到拆下来的尾巴那,以此下去……

到最后把“蛇头”也给装好,就完事了。

这道题的解题思路也就是这样,边拆边装

你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

贴出两种解决方案代码:

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
public class ReverseLinkedList {

public class ListNode {
int val;
ListNode next;

ListNode(int x) {
val = x;
}
}

/**
* 递归
*
* @param head
* @return
*/
public ListNode reverseList(ListNode head) {
ListNode reverseList = null;
return helper(head, reverseList);
}

private ListNode helper(ListNode head, ListNode reverseList) {
if (head == null) // 反转结束
return reverseList;
// 节点指针变换
ListNode tempNode = head.next;
head.next = reverseList;
return helper(tempNode, head);
}

/**
* 迭代
*
* @param head
* @return
*/
public ListNode reverseList1(ListNode head) {

ListNode newHead = null;
while (head != null) { // 遍历
ListNode next = head.next;
head.next = newHead;
newHead = head;
head = next;
}
return newHead;
}
}

两者都是先用一个空链表然后再进行一步步得组装。

就是指针指来指去,有点绕,借助实物理解起来会容易很多。

还有一个进阶版本的反转链表 II,看题:

1
2
3
4
5
6
7
8
9
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明:
1 ≤ m ≤ n ≤ 链表长度。

示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL

区别就是这里不是反转所有的结点了,只需要反转指定位置之间的结点了,重点就是确认反转的指针位置。然后反转的操作还是与上面一样。

代码如下:

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
public class ReverseLinkedListII {

public class ListNode {
int val;
ListNode next;

ListNode(int x) {
val = x;
}
}

public ListNode reverseBetween(ListNode head, int m, int n) {
if (head == null)
return null;
// 新建一个节点并指向 head
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
// pre 为需要反转的前节点
for (int i = 0; i < m - 1; i++)
pre = pre.next;

// 需要反转的节点 双指针
ListNode start = pre.next;
ListNode then = start.next;

// 反转节点
for (int i = 0; i < n - m; i++) {
start.next = then.next;
then.next = pre.next;
pre.next = then;
then = start.next;
}
return dummy.next;
}
}

注释齐全,一目了然。