Do it now - sometimes LATER becomes NEVER.
post @ 2019-09-08
Read More
post @ 2019-09-01

float,double类型的二进制表示

我们先来看看java中,double类型数据的二进制存储值,和展示的值。

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

/* ieee 754 双精度常量 */
private static final int NOR_LEN_64 = 64;
private static final int M_LEN_64 = 52;
private static final int E_LEN_64 = 11;

/* ieee 754单精度常量*/
private static final int NOR_LEN_32 = 32;
private static final int M_LEN_32 = 23;
private static final int E_LEN_32 = 8;


public static void main(String[] args) {
printBit(2.5);
}

private static void printBit(double d) {
System.out.println("##"+d);
long l = Double.doubleToLongBits(d);
String bits = Long.toBinaryString(l);
int len = bits.length();
System.out.println(bits+"#"+len);

if (len != NOR_LEN_64){// 正数长度为63,最高位默认为0
bits = "0" + bits;
}
System.out.println("S[63]"+bits.charAt(0));
System.out.println("E[62-52]"+bits.substring(1,1+E_LEN_64));
System.out.println("M[51-0]"+bits.substring(NOR_LEN_64-M_LEN_64,NOR_LEN_64));
}
}

输入如下:

Read More
post @ 2019-08-14
  • 参数提示:⌘P
  • 查看类的接口实现:⌥⌘B
  • 查看继承关系(hierarchy):⌃H
  • 返回上次查看的位置:⌥⌘+左右方向键
Read More
post @ 2019-06-13

前言:关于Servlet

历史发展

https://blog.csdn.net/u010297957/article/details/51498018#commentBox

从CGI –> applet –> servlet(java) –> servlet1.1(jsp借鉴asp) –> servlet1.2(mvc思想) –> … –> Servlet3.1(稳定)
–> SpringMVC(本质也是Servlet)

Servlet规范

Servlet官方规范文档
Servlet中文翻译

两个链接分别是Servlet官方文档和中文翻译版本。

狭义的看,Servlet就是一个interface,一个Java的接口,它有五个方法:

1
2
3
4
5
6
7
8
9
10
11
public interface Servlet {
void init(ServletConfig var1) throws ServletException;

ServletConfig getServletConfig();

void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

String getServletInfo();

void destroy();
}

13乎高赞回答,第一、二个回答醍醐灌顶了,爱了爱了。

功能

接受http请求,动态处理并返回结果给客户端。

Servlet与Tomcat

Tomcat是Servlet运行的容器。Servlet没有main方法,它受控于另一个Java应用,这个应用就成为容器(Container),而Tomcat就是Servlet的容器。而容器怎么处理用户的请求?我们来复现模拟这个过程:

  1. 客户端发起一个请求。
  2. 容器监听端口发现客户端的请求,创建HttpServletRequestHttpServletResponse两个对象
  3. 容器根据url判断把请求丢给哪个Servlet处理(根据映射规则),为这个请求创建一个线程,并把HttpServletRequestHttpServletResponse`丢给相应Servlet
  4. 线程调用Servlet的service(),service()根据请求调用doGet()或者doPost()方法
  5. Service()处理完成,通过HttpServletResponse把处理结果返回给Tomcat容器,Tomcat继续封装成http响应返回个客户端。

部署一个Servlet

安装Tomcat

本人在阿里云学生机安装Tomcat8,系统是ubuntu16.04,参考资料:
https://www.digitalocean.com/community/tutorials/how-to-install-apache-tomcat-8-on-ubuntu-16-04

编写和编译servlet

  1. /opt/tomcat目录下创建一个myapp目录,接着在myapp下创建images(存放图片)、src(存放java源码)、WEB-INF,在WEB-INF下创建classes(存放字节码文件)和web.xml,目录树如下:

myapp/
├── images
│ └── test.JPG
├── src
│ └── TestingServlet.java
└── WEB-INF
├── classes
│ └── TestingServlet.class
└── web.xml

写一个helloworld版的servlet,使用vim写java你就会发现ide确实方便了开发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

public class TestingServlet extends HttpServlet {

public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

PrintWriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Servlet Testing</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("Welcome to the Servlet Testing Center");
out.println("</BODY>");
out.println("</HTML>");
}
  1. 编译此文件,字节码文件输出到WEB-INFO/classes下:
    javac -d ../WEB-INF/classes/ TestingServlet.java

配置web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="true">
<servlet>
<servlet-name>Testing</servlet-name>
<servlet-class>TestingServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>Testing</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>

</web-app>

当你访问 host:8080/test时,tomcat容器会根据web.xml文件映射,把请求丢给名为Testing的servlet,而Testing会找到/WEB-INFO/classes下的TestingServlet字节码文件,处理完成返回客户端。
https://www.tutorialspoint.com/servlets/servlets-first-example.htm
http://www.informit.com/articles/article.aspx?p=26920&seqNum=4

Read More
post @ 2019-04-20

上一篇SpringBoot依赖注入中依赖注入的例子使用的是JavaConfig显示配置的方法。

回想一下,Config类,他显示地配置了很多的bean,每次都要自己配置不嫌麻烦?能不能省略显示配置的内容,让让Spring自动去发现类—–可以,这便是Spring自动装配。

Spring自动装配

ABC过于抽象,咱们这次换个生动的例子(《Spring实战》),我们为其取名为”音乐播放器和他的CD”。uml类图建模如下图所示:
beans

解释一下:CompactDisc有个play()方法,Fantasy(范特西)实现了CompactDisc接口,CDplayer使用Fantasy播放音乐。代码如下:

1
2
3
public interface CompactDisc {
void play();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Fantasy implements CompactDisc{
private String title = "范特西";
private String artist = "周杰伦";
private List<String> songs;

@Override
public void play() {
System.out.println("正在播放"+artist+"的"+title);
}

public Fantasy(List<String> songs) {
this.songs = songs;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class CDPlayer {
private CompactDisc cd;

public CDPlayer(CompactDisc cd) {
this.cd = cd;
}

public void play(){
cd.play();
}

}

类比一下上一篇的ABC,其实是一样的模子。按照上篇的思维,即显示配置Bean,接下来我们就需要写个Config类,然后在里面显示返回CDPlayer,Fantasy实例对象。你可以停下来想一想。周杰伦14张专辑都实现了CompactDisc接口,你都需要手动写,岂不是很麻烦,时间复杂度为O(n) = =。

好在Spring支持自动配置,解放了我们的双手!那我们该怎么改呢?CompactDisc接口不需要改动;Fantasy只需要在类之前使用@Component注解告诉Spring它是个组件,需要被扫描然后装入容器中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component							// --------------> 改动
public class Fantasy implements CompactDisc{
private String title = "范特西";
private String artist = "周杰伦";
private List<String> songs;

@Override
public void play() {
System.out.println("正在播放"+artist+"的"+title);
}

public Fantasy(List<String> songs) {
this.songs = songs;
}
}

CDPlayer也需要添加@Component注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component							// --------------> 改动
public class CDPlayer {
private CompactDisc cd;

@Autowired // --------------> 改动,自动把cd装入
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}

public void play(){
cd.play();
}

}

@Autowired注解相当于告诉Spring,如果容器中存在CompactDisc的实例,那么在生成CDPlayerBean是,把CompactDisc实例依赖自动装入。最后我们需要一个CDPlayerConfig告诉Spring需要扫描哪些Component

1
2
3
4
@Configuration
@ComponentScan(basePackageClasses = CompactDisc.class)// -------------->扫描CompactDisc.class所在包
public class CDPlayerConfig {
}

你会发现只要在组件上方添加@Component注解,并且使用@ComponentScan告诉Spring哪些组件需要被装入容器,你就能省略上篇冗杂在Config显示配置Bean。

SpringBoot简化操作

如果用过SpringBoot的同鞋可能发现,上述的两步走

  1. 在组件上方添加@Component注解
  2. 使用@ComponentScan告诉Spring哪些组件需要被装入容器

中的第二步是不需要开发人员做的,没错,SpringBoot简化了配置操作,但是不代表它没有做,它只是在幕后帮你完成了第二步。

Read More
post @ 2019-04-20

为什么需要DI

可能DI(依赖注入)这个词本身就不太好理解,首先我们先来看看官方的定义:

依赖注入会将所依赖的关系自动交给目标对象,而不是让对象自己去获取依赖。

比方说,你所要写的A类需要使用到B类的方法,

Read More
post @ 2019-04-14

环境

本地操作系统是Mac OS,使用的Navicat和Termial远程连接MySQL;远程是ALiyun服务器,ubuntu16.04,使用apt安装MySQL社区办作为数据库服务器。

1. 确保MySQL服务启动成功

首先要确保MySQL服务启动成功,可以使用systemctl status mysql或者service mysql status查看数据库运行状态。

2. 阿里云防火墙

确保服务器本地能够登录MySQL命令行之后,在浏览器打开阿里云管理控制台,添加防火墙规则,打开Mysqld默认监听的3306端口,如下图所示:
3306

3. 修改MySQL 配置

MySQL默认只监听本地连接,所以我们必须手动打开。我的系统配置文件路径为/etc/mysql/mysql.conf.d/ mysqld.cnf,不同系统可能不同,不过,前缀路径/etc/mysql应该是一样的。可以使用find / -name mysqld.cnf寻找文件路径。在文件中,我们把bind-address = 127.0.0.1注释掉。

4. 授权

进入MySQL命令行,使用命令授权用户相关权限:

1
grant all privileges on database_name.* to 'username'@'%' identified by 'password';

其中:

  • all 是把所有权限给用户
  • database_name.*是把database_name下的所有tables,当然你也可以把所有数据库连接权利给加上,只需要把database_name改成*即可。
  • username,是连接的用户名,我这里设置成root
  • %是指所有ip地址。你也可以指定特定ip地址。
  • 'password'是用户连接的密码,比方我设置成’123456’。

    然后用flush privileges;命令刷新。

5. 重启MySQL

完成上述所有步骤之后,重启MySQL服务器。

1
systemctl restart mysql

测试连接

  • 本地终端
1
mysql -h ip_of_your_remote_server -P 3306 -u root -p
  • Navicat
    新建连接,连接属性设置如下图:
    Navicat

参考文章

https://www.jianshu.com/p/8fc90e518e2c
https://blog.csdn.net/enweitech/article/details/80677374

Read More
post @ 2019-04-14
  • 索引的最左优先原则
  • 聚集索引 vs 非聚集索引
Read More
post @ 2019-04-08

图的构造

我打算用图存各种人名,人际关系网络的初步实现。然后为了方便判断人名在遍历时是否已经访问过,我索性建立图的最小单位Node,类实现如下:

1
2
3
4
5
6
7
8
9
10
11
public class Node {

private String name; // 存储人名
private boolean marked; // 标记信息

public Node(String name){
this.name = name;
marked = false;
}
/******** getter and setter ********/
}

使用邻接表(Adjacent List)实现对图的表示,而邻接表用Map<Node,List<Node>>实现,map的键是图中的节点,键对应的值是与该节点邻接的所有节点,存放在List<Node>中。图的类属性和构造方法如下所示:

1
2
3
4
5
6
7
8
9
10
11
public class G {
private int vertex; // 定点数
private int egde; // 边数

private Map<Node,List<Node>> adj; // 邻接表
public G(){
vertex = 0;
egde = 0;
adj = new HashMap<>();
}
}

上述代码能够通过构造函数构造没有节点的空图。所以在给他添上 addEdge(Node,Node)方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void addEdge(Node Node_1, Node Node_2){
// 给Node_1添加相邻节点
if (adj.containsKey(Node_1)) {
adj.get(Node_1).add(Node_2);
}
else {
List<Node> l = new LinkedList<>();
l.add(Node_2);
adj.put(Node_1,l);
}
// 给Node_2添加相邻节点
if (adj.containsKey(Node_2)) {
adj.get(Node_2).add(Node_1);
}
else {
List<Node> l = new LinkedList<>();
l.add(Node_1);
adj.put(Node_2,l);
}

vertex = adj.size();
egde ++;
}

例如,在调用addEdge(n1,n2)方法时,会在邻接表的键为n1对应的值中添加n2,同样也会在键为n2对应的值中添加n1。

为了使用BFS或者DFS,图必须还提供一个 返回某个特定节点的所有相邻节点,这个好实现:

1
2
3
public List<Node> getAdj(Node n){
return adj.get(n);
}

好了,图类就写好了。

BFS

BFS的思想就是利用队列存储节点的相邻节点。比方说我有个图如下图所示:
G

如果我们滴起始节点设为’A’,那么该思想就是意味着:把’A’放入队列,当队列不为空时,队列的队首元素(此时是A)出队列,然后将该元素(此时是A)的 相邻节点:’B’、’C’放入队列,重复上述步骤即可或得BFS结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Queue<Node> queue = new LinkedList<>();
queue.offer(A);
A.setMarked(true);

while(!queue.isEmpty()){
Node o = queue.poll();
System.out.print(o.getName()+"\t");

List<Node> list = g.getAdj(o);
for (Node n : list) {
if (!n.isMarked()) {
// 如果没有被访问过
queue.add(n);
// 访问标志位改为true表示已经被访问了
n.setMarked(true);
}
}
}

BFS除了有遍历图的功能,它也是图最短路径的基础。因为,BFS是一层一层遍历的,如图:
BFS

每一圈红色的曲线上的点到A的距离是相同的,就像水面的涟漪一样。但是,举个例子,想要知道A->D的路径经过了哪些节点,那么得在BFS遍历时额外存储的某个节点的上一个节点,如B->A,C->A,D->B,E->C,F->D,这些信息存储起来,那么A->D的路劲怎么求呢?,由于A->D的路劲等同于D->A,而D->A = D->B->A,从而解决了路径问题。

DFS

DFS的思想就是利用栈存储节点的相邻节点,可见,将队列换成,即可实现BFS到DFS的转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Stack<Node> stack = new Stack<>();
stack.push(A); //把A入栈
A.setMarked(true); // 并且把A的访问标记设置为true,防止重复

while(!stack.isEmpty()){
Node o = stack.pop();

System.out.print(o.getName()+"\t");

List<Node> list = g.getAdj(o);
for (Node n : list) {
if (!n.isMarked()) {
// 没有被访问过
stack.push(n);
n.setMarked(true);
}
}
}
Read More
post @ 2019-04-05

事务

‘事务’是具有‘原子性’操作的一组SQL语句,可以看做是一个工作单元。要么里面的SQL语句全部执行,要么里面的SQL语句全部不执行。

我们举个“银行转账”的经典例子: 账户A_account转账到账户B_account 1000CNY,那么转账系统需要至少三步操作:

  1. 检查A_account 的余额是否大于1000CNY
  2. 从A_account 的账户余额中减去 1000CNY
  3. 在 B_account 的账户中加上 1000CNY

将3步打包在一个事物里面,当3步骤中的任何一步出现了错误,就应该回滚所有已完成的操作。我们可以上述3步转换成SQL语句,首先我们,用start transation开启一个事物:

1
2
3
4
start transaction;
select balance from A_account where customer_id = 16058521;
update A_account set balance = balance - 1000 where customer_id = 16058521;
update B_account set balance = balance + 1000 where customer_id = 16058522;

事务的ACID

  • A(Atomicity)原子性
  • C(Consistency)一致性
  • I(Isolation)隔离性
  • D(Durability)持久性

原子性很好理解,要么执行,要么不执行。持久性也好理解,事物一旦提交,那么数据的改变是永久的。而一致性是啥意思呢?下面这句话是原话,各位客官自己理解吧= =

一种一致性状态转移到另一种一致性转态。
隔离性也就是今天的正题了,不同的隔离级别隔离强度不同,咱们下面细讲。

隔离级别

SQL标准定义了4种隔离级别,用来限定事务内外的哪些变化是可见的,哪些是不可见的。为了下面四个隔离级别实现的正常进行,我们先来看看怎么设置这四个级别:

  • 查看隔离级别
1
2
select @@tx_isolation;	// 当前会话隔离级别
select @@global.tx_isolation; // 全局隔离级别

MySQL的InnoDB引擎默认隔离级别是REPEATABLE READ

1
2
3
4
5
6
7
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
  • 设置隔离级别
1
2
set session transaction isolation level READ UNCOMMITTED;	// 设置当前会话隔离级别为 未提交读
set global transaction isolation level READ UNCOMMITTED; // 设置全局隔离级别为 未提交读
  • 取消自动提交事务
1
2
SHOW VARIABLES LIKE 'AUTOCOMMIT';
set autocommit=0; // 取消自动提交事务
  • 创建实验表,最后实验表结果如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mysql> desc customer;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | NO | MUL | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> select * from customer;
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dasx |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | John |
+----+-------------+
5 rows in set (0.00 sec)

我们会使用两个session链接模拟两个用户多数据库操作,分别为session A 和 session B。

READ UNCOMMITTED(未提交读)

首先我们在两个会话的isolation level均设置为READ UNCOMMITTED,或者你也可以在全局设置。
A:

1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> start transaction;

mysql> select * from customer;
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dasx |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | John |
+----+-------------+
5 rows in set (0.00 sec)

然后再B:

1
2
3
mysql> start transaction;

mysql> update customer set name='Johnson' where id=21;

此时,B的事务尚未结束(使用commit提交事务),我们在A中再次查询:

1
2
3
4
5
6
7
8
9
10
mysql> select * from customer;
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dasx |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | Johnson |
+----+-------------+

我们发现,在A中,已经能读取在B中未提交事务的变化。它会有个问题!当B的事务rollback了(John的名字没有修改),那么A读取的数据就是脏数据,也就是出现脏读(Dirty Read)

READ COMMITTED(已提交读)

在此级别,上述脏读问题就没有了,因为B只能读取到A的事务提交了之后数据。但是还是有个问题= =

首先我们将isolation level设置为READ COMMITTED
set session transaction isolation level READ COMMITTED;

A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from customer;
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dasx |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | Johnson |
+----+-------------+
5 rows in set (0.00 sec)

B:

1
2
3
mysql> start transaction;update customer set name='Dashi' where id=15;

mysql> commit;

此时B的事务已经提交,所以当A再去查询时,就会出现与第一次查询不同的结果(在我们看来感觉是合理的= =,B事务都提交之后A再查询当然会看到已经被B修改过的数据 = =),这就是不可重复读
A:

1
2
3
4
5
6
7
8
9
10
mysql> select * from customer;	// Dasx变成了Dashi
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dashi |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | Johnson |
+----+-------------+

这貌似很合乎逻辑啊。但它也会有问题,因为在A两次查询出现的结果是不同的。

REPEATABLE READ(可重复读)

此级别,也是MySQL默认的事务隔离级别,上述不可重复读问题就没有了,但理论上还是会有幻读(Phantom Read)的可能性。什么是幻读(Phantom Read),即:
PhantomRead.png

但是innodb的RR级别,使用GAP锁是解决了幻读的问题的(这个问题超出范围,下次再解释)。

SERIALIZATION(可串行化)

SERIALIZATION是最高的隔离级别,它通过强制事务排序,解决幻读问题。它会在每个读的数据上加排斥锁,让其他事务等待,但是这样做势必对性能造成影响。

同样,我们将A会话的事务隔离级别设置为serializable并开启事务。
A:

1
2
3
set session transaction isolation level serializable;
start transaction;
select * from customer;

B:

1
2
3
4
mysql> begin;insert into customer(id,name) values(25,'Wangxb');
Query OK, 0 rows affected (0.00 sec)

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

很明显,B事务出现了超时,只有当A在timeout时间内提交事务,B才会成功。

Read More
⬆︎TOP