Long Luo's Life Notes

每一天都是奇迹

By Long Luo

最近在学习Java多线程时,遇到了一个下面的笔试题,题目如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
编写一个程序,程序会启动4个线程,向4个文件A,B,C,D里写入数据,每个线程只能写一个值。 
线程A:只写A
线程B:只写B
线程C:只写C
线程D:只写D

4个文件A,B,C,D。

程序运行起来,4个文件的写入结果如下:
A:ABCDABCD...
B:BCDABCDA...
C:CDABCDAB...
D:DABCDABC...

网上搜索了下,好像还是一个Google笔试题,这个问题涉及到的知识点有:多线程, 并发, , 线程间通信

个人分析过程:

  1. 创建4个线程;
  2. 每个线程在输出时都需要加锁;
  3. 操作文件的代码要加锁;
  4. 一个线程完成之后,通知下一个要执行的线程;

根据以上分析,可以写出如下代码:

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package com.imlongluo.Practise;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Practise {

public static void main(String[] args) {

final Task task = new Task();

/** 创建4个线程,同时启动 */
/**
* Thread A
*/
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
task.outputA();
}
}
}, " Thread A").start();

/**
* Thread B
*/
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
task.outputB();
}
}
}, "Thread B").start();

/**
* Thread C
*/
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
task.outputC();
}
}
}, "Thread C").start();

/**
* Thread D
*/
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
task.outputD();
}
}
}, "Thread D").start();
}

/**
任务类
*/
public static class Task {
/**
创建一个Lock锁对象
*/
private Lock lock = new ReentrantLock();

private BufferedWriter bw1 = null;
private BufferedWriter bw2 = null;
private BufferedWriter bw3 = null;
private BufferedWriter bw4 = null;

private int ctl = 0;

/**
* Condition: 线程条件
*/
private Condition cond1 = lock.newCondition();
private Condition cond2 = lock.newCondition();
private Condition cond3 = lock.newCondition();
private Condition cond4 = lock.newCondition();

private boolean[] outputThisRound = { false, true, true, true };

public Task() {
try {
bw1 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(
"/Users/luolong/Code/Android/workspace/MultiThreads/A.txt"))));
bw2 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(
"/Users/luolong/Code/Android/workspace/MultiThreads/B.txt"))));
bw3 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(
"/Users/luolong/Code/Android/workspace/MultiThreads/C.txt"))));
bw4 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(
"/Users/luolong/Code/Android/workspace/MultiThreads/D.txt"))));
} catch (Exception e) {
e.printStackTrace();
}
}

public void outputA() {

lock.lock();

try {
while (outputThisRound[0]) {
System.out.println("outputA begin to await!");
cond1.await(); //阻塞A线程
}

if (ctl % 4 == 0) {
bw1.write("A");
bw1.flush();
} else if (ctl % 4 == 1) {
bw4.write("A");
bw4.flush();
} else if (ctl % 4 == 2) {
bw3.write("A");
bw3.flush();
} else if (ctl % 4 == 3) {
bw2.write("A");
bw2.flush();
}

outputThisRound[0] = true;
outputThisRound[1] = false;

System.out.println("outputA signal outputB!");
cond2.signal(); //唤醒B线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}

public void outputB() {

lock.lock();

try {
while (outputThisRound[1]) {
System.out.println("outputB begin to await!");
cond2.await();
}

if (ctl % 4 == 0) {
bw2.write("B");
bw2.flush();
} else if (ctl % 4 == 1) {
bw1.write("B");
bw1.flush();
} else if (ctl % 4 == 2) {
bw4.write("B");
bw4.flush();
} else if (ctl % 4 == 3) {
bw3.write("B");
bw3.flush();
}

outputThisRound[1] = true;
outputThisRound[2] = false;
System.out.println("outputB signal outputC!");
cond3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void outputC() {
lock.lock();
try {
while (outputThisRound[2]) {
System.out.println("outputC begin to await!");
cond3.await();
}

if (ctl % 4 == 0) {
bw3.write("C");
bw3.flush();
} else if (ctl % 4 == 1) {
bw2.write("C");
bw2.flush();
} else if (ctl % 4 == 2) {
bw1.write("C");
bw1.flush();
} else if (ctl % 4 == 3) {
bw4.write("C");
bw4.flush();
}

outputThisRound[2] = true;
outputThisRound[3] = false;
System.out.println("outputC signal outputD!");
cond4.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void outputD() {
lock.lock();

try {
while (outputThisRound[3]) {
System.out.println("outputD begin to await!");
cond4.await();
}

if (ctl % 4 == 0) {
bw4.write("D");
bw4.flush();
} else if (ctl % 4 == 1) {
bw3.write("D");
bw3.flush();
} else if (ctl % 4 == 2) {
bw2.write("D");
bw2.flush();
} else if (ctl % 4 == 3) {
bw1.write("D");
bw1.flush();
}

outputThisRound[3] = true;
outputThisRound[0] = false;
ctl++;
System.out.println("outputD signal outputA!");
cond1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}

以上。

如果大家还有其他更好的方法,欢迎一起讨论:-)

Created by Long Luo at 2015-04-09 22:14:02 @Shenzhen, China. Completed By Long Luo at 2015-04-09 23:46:29 @Shenzhen, China.

By Long Luo

最近去面试时,在一家小公司面试时,公司小BOSS给我出了一道算法题:

一个人爬楼梯,一步可以迈一级,二级,三级台阶,如果楼梯有 \(N\) 级,要求编写程序,求总共有多少种走法。

这个问题应该是一个很老的题目了,用中学数学来说,就是一个排列组合问题。当时拿到这个题目之后,首先想到使用递归的思想去解决这个问题:

N级楼梯问题可以划分为:\(N-1\) 级楼梯,\(N-2\) 级楼梯,\(N-3\) 级楼梯的走法之和。

先计算下0,1,2,3及楼梯有多少种走法:

1
2
3
1 --> 1
2 --> 11 2
3 --> 111 12 21 3

那么,根据以上的分析很容易写出如下代码:

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
public static int countNumber(int stepsNum) {
int sum = 0;

if (stepsNum == 0) {
return 0;
}

if (stepsNum == 1) {
return 1;
} else if (stepsNum == 2) {
return 2;
} else if (stepsNum == 3) {
return 4;
} else if (stepsNum > 3) {
return countNumber(stepsNum - 3) + countNumber(stepsNum - 2)
+ countNumber(stepsNum - 1);
}

return sum;
}

public static void main(String[] args) {

for (int i = 0; i <= 10; i++) {
System.out.println("楼梯台阶数:" + i + ", 走法有:" + countNumber(i));
}
}

再看看输出:

1
2
3
4
5
6
7
8
9
10
楼梯台阶数:0, 走法有:0
楼梯台阶数:1, 走法有:1
楼梯台阶数:2, 走法有:2
楼梯台阶数:3, 走法有:4
楼梯台阶数:4, 走法有:7
楼梯台阶数:5, 走法有:13
楼梯台阶数:6, 走法有:24
楼梯台阶数:7, 走法有:44
楼梯台阶数:8, 走法有:81
楼梯台阶数:9, 走法有:149

如果求解具体全部走法呢?

仅仅算出有多少种走法是很容易的,如果更近一步呢?基于这个基础,如何输出具体的走法呢?

我们可以使用Stack数据结构和递归的思想去完成这个题目:Stack<T>用于保存每一步的走法。

代码如下所示:

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
/**
* 一个人爬楼梯,一步可以迈一级,二级,三级台阶,如果楼梯有N级,编写程序,输出所有走法。
*
* @param args
*/
public static void main(String[] args) {
Stack<Integer> stt = new Stack<Integer>();

buileT(stt, 3);
}

public static void buileT(Stack<Integer> stt, int N) {
if (N >= 1) {
stt.push(1);
buileT(stt, N - 1);
stt.pop();
}
if (N >= 2) {
stt.push(2);
buileT(stt, N - 2);
stt.pop();
}
if (N >= 3) {
stt.push(3);
buileT(stt, N - 3);
stt.pop();
}
if (N == 0) {
for (int i : stt) {
System.out.print("Step:" + i + "-->");
}
System.out.println("完成");
}
}

以上。

———- Updated at 2022.04.21 ———-

这篇文章写于2015年,其实这个题就是 Leetcode 1137. 第 N 个泰波那契数 ,是 70. 爬楼梯 的变体,解决方法可参考 9种求斐波那契数(Fibonacci Numbers)的算法

Created by Long Luo at 2015-04-08 00:40:12 @Shenzhen, China. Completed By Long Luo at 2015-04-08 18:15:38 @Shenzhen, China. Updated By Long Luo at 2022年4月21日 14点51分 @Shenzhen, China.

By LongLuo

一、什么是404页面?

想必大家都有过这样的经历,好不容易看到搜索引擎出现了我们想要找了很久的内容,兴冲冲点进去,然而出现在屏幕上的却是一个大大404?仿佛在逗你玩?WTF?

不用怀疑,你想要的页面丢失了。

404错误信息通常是在目标页面被更改或移除,或客户端输入页面地址错误后显示的页面。

那么,404除了代表你所要浏览的页面丢失外,你是否知道它的产生原理呢?

不仅如此,在庞大的互联网中除了404还有哪些HTTP状态码?它们又分别代表着什么?

正如上面所述,404是一种标准的HTTP返回代码,官方称其为HTTP状态码,用来表示网页服务器HTTP的响应状态

由于网站日志通常会记录下HTTP状态码,因此通过查看网站日志中的HTTP状态码,便可清楚地看到网站服务器与客户端之间的信息交换情况。

1.1 HTTP状态码

下面是常见的HTTP状态码:

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误

1.1.1 HTTP状态码分类

HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。

HTTP状态码共分为5种类型:

分类分类描述
1xx信息,服务器收到请求,需要请求者继续执行操作
2xx成功,操作被成功接收并处理
3xx重定向,需要进一步的操作以完成请求
4xx客户端错误,请求包含语法错误或无法完成请求
5xx服务器错误,服务器在处理请求的过程中发生了错误

HTTP状态码列表:

状态码状态码英文名称中文描述
100Continue继续。客户端应继续其请求
101Switching Protocols切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
200OK请求成功。一般用于GET与POST请求
201Created已创建。成功请求并创建了新的资源
202Accepted已接受。已经接受请求,但未处理完成
203Non-Authoritative Information非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204No Content无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205Reset Content重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206Partial Content部分内容。服务器成功处理了部分GET请求
300Multiple Choices多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301Moved Permanently永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302Found临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303See Other查看其它地址。与301类似。使用GET和POST请求查看
304Not Modified未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305Use Proxy使用代理。所请求的资源必须通过代理访问
306Unused已经被废弃的HTTP状态码
307Temporary Redirect临时重定向。与302类似。使用GET请求重定向
400Bad Request客户端请求的语法错误,服务器无法理解
401Unauthorized请求要求用户的身份认证
402Payment Required保留,将来使用
403Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求
404Not Found服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置”您所请求的资源无法找到”的个性页面
405Method Not Allowed客户端请求中的方法被禁止
406Not Acceptable服务器无法根据客户端请求的内容特性完成请求
407Proxy Authentication Required请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408Request Time-out服务器等待客户端发送的请求时间过长,超时
409Conflict服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突
410Gone客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411Length Required服务器无法处理客户端发送的不带Content-Length的请求信息
412Precondition Failed客户端请求信息的先决条件错误
413Request Entity Too Large由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414Request-URI Too Large请求的URI过长(URI通常为网址),服务器无法处理
415Unsupported Media Type服务器无法处理请求附带的媒体格式
416Requested range not satisfiable客户端请求的范围无效
417Expectation Failed服务器无法满足Expect的请求头信息
500Internal Server Error服务器内部错误,无法完成请求
501Not Implemented服务器不支持请求的功能,无法完成请求
502Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503Service Unavailable由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504Gateway Time-out充当网关或代理的服务器,未及时从远端服务器获取请求
505HTTP Version not supported服务器不支持请求的HTTP协议的版本,无法完成处理
阅读全文 »

By Long Luo

引言

NOKIA 有句著名的广告语:“科技以人为本”。任何技术都是为了满足人的生产生活需要而产生的。具体到小小的一个手机,里面蕴含的技术也是浩如烟海,是几千年来人类科技的结晶,单个人穷其一生也未必能掌握其一角。不过个人一直认为基本的技术和思想是放之四海而皆准的,许多技术未必需要我们从头到尾再研究一遍,我们要做的就是站在巨人的肩膀上,利用其成果来为人们的需求服务。

随着移动互联网时代的大潮,越来越多的 App 不光是需要和网络服务器进行数据传输和交互,也需要和其他 App 进行数据传递。承担 App 与网络来进行传输和存储数据的一般是 XML 或者 JSON 。在移动互联网时代,XML 和 JSON 很重要。

最近一段时间,个人综合了之前对 XML 、JSON的一些了解,参考了相关资料,再结合视频的代码,把自己的一些思考融入了这篇总结文档中,同时尝试用通俗诙谐的语言风格来阐述,期望能给感兴趣的读者带来帮助。

为了不和时代落伍,我们必须要学习 XML 和 JSON ,但同时它们也很容易学习,Let’s start :-)

一、XML

XML 即可扩展标记语言(eXtensible Markup Language)。标记是指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等。如何定义这些标记,既可以选择国际通用的标记语言,比如HTML,也可以使用象XML这样由相关人士自由决定的标记语言,这就是语言的可扩展性。XML 是从 SGML 中简化修改出来的。它主要用到的有XML、XSL 和 XPath 等。

上面这段是对XML的一个基本定义,一个被广泛接受的说明。简单说,XML就是一种数据的描述语言,虽然它是语言,但是通常情况下,它并不具备常见语言的基本功能——被计算机识别并运行。只有依靠另一种语言,来解释它,使它达到你想要的效果或被计算机所接受。

记住以下几点就行了:

  • XML是一种标记语言,很类似HTML
  • XML的设计宗旨是传输数据,而非显示数据
  • XML标签没有被预定义。您需要自行定义标签。
  • XML被设计为具有自我描述性。
  • XML是W3C的推荐标准

总结:

XML 是独立于软件和硬件的信息传输工具。目前,XML 在 Web 中起到的作用不会亚于一直作为 Web 基石的 HTML。

XML无所不在。XML 是各种应用程序之间进行数据传输的最常用的工具,并且在信息存储和描述领域变得越来越流行。

1.1 XML属性

1.1.1 XML与HTML的主要差异

  • XML不是HTML的替代。
  • XML和HTML为不同的目的而设计。
  • XML被设计为传输和存储数据,其焦点是数据的内容。
  • HTML被设计用来显示数据,其焦点是数据的外观。
  • HTML旨在显示信息,而 XML 旨在传输信息

1.1.2 XML是不作为的。

也许这有点难以理解,但是XML不会做任何事情。XML被设计用来结构化、存储以及传输信息

下面是John写给George的便签,存储为XML:

1
2
3
4
5
6
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

上面的这条便签具有自我描述性。它拥有标题以及留言,同时包含了发送者和接受者的信息。但是,这个 XML 文档仍然没有做任何事情。它仅仅是包装在XML标签中的纯粹的信息。我们需要编写软件或者程序,才能传送、接收和显示出这个文档。

1.1.3 XML仅仅是纯文本

XML没什么特别的。它仅仅是纯文本而已。有能力处理纯文本的软件都可以处理XML。 不过,能够读懂 XML 的应用程序可以有针对性地处理 XML 的标签。标签的功能性意义依赖于应用程序的特性。

1.1.4 XML允许自定义标签

上例中的标签没有在任何XML标准中定义过(比如)。这些标签是由文档的创作者发明的。这是因为XML没有预定义的标签。

在HTML中使用的标签(以及HTML的结构)是预定义的。HTML文档只使用在HTML标准中定义过的标签(比如<p><h1> 等等)。

XML允许创作者定义自己的标签和自己的文档结构。

1.1.5 XML不是对HTML的替代

XML是对HTML的补充。

XML不会替代HTML,理解这一点很重要。在大多数 web 应用程序中,XML用于传输数据,而HTML用于格式化并显示数据。

阅读全文 »

By Long Luo

之前一段时间花了很长时间来玩了一个 Mac OS平台下的塔防游戏《iBomber Defense Pacific》,为此浪费了不少时间。

玩到通关之后,躺在床上,我一方面心痛我居然对游戏如此没有免疫力,另外一方面我在思考这款游戏好在哪里,哪些地方设计的很好?

作为一款塔防游戏,这款游戏和其他塔防游戏有什么优点?如何设计一款塔防游戏呢?它需要遵循哪些原则呢?

塔防游戏

Tower Defense,指的是一类通过在地图上建造炮塔或类似建筑物,以阻止游戏中敌人进攻的策略型游戏。比较有名的像《植物大战僵尸》、《Field Runners》、《保卫萝卜》、《Desktop TD》等。

这种游戏多见于flash游戏,手机平台也很多,塔防的操作模式和节奏都比较适合触屏的操作,所以现在的Android 和 iOS平台上有非常多的此类游戏。

现在以玩的<iBomber Defense Pacific>为例,来说明一款好的TD游戏的要素和原则:

1.1 要素之一:美工设计

不同游戏有不同的美术风格,比如《植物大战僵尸》和《保护萝卜》的Q版风格, 《iBomber Defense Pacific》的战争风格。App Store中国区很多塔防游戏都是三国流。

游戏设计的越精美越好,包括人物设计精美度,有辨识性,突出重点。

1.2 要素之二:敌人

敌人也分为几类:

  1. 低HP,移动速度快,低攻击力,在一波中数量众多;
  2. 中等HP,移动速度快,中等攻击力,在一波中数量中等;
  3. 高HP,移动速度慢,高攻击力,在一波中熟练较少。

以上的都是地面unit,我们所摆的阵型只对地面部队起作用,但一款好的TD游戏中都有空军无视你的阵型,所以你还需要考虑防空处理。

1.3 要素之三:塔

塔的属性有以下几点:

  1. 攻击力ATTACK: 决定单位建筑对敌人所能造成的伤害程度
  2. 攻击频率FREQENCY: 决定单位建筑在单位时间内的攻击次数
  3. 攻击范围RANGE:决定单位建筑的攻击有效距离
  4. 造价GOLD: 决定单位建筑的造价。

塔也分为几类,一类是攻击型的,一类是减速型的,一类是加群防群补型的,一款好的游戏应该包含以上所有类型。

1.4 要素之四:地图

地图在一款好的TD游戏是非常重要的,不同的地图需要不同类型的塔才能发挥最大的作用。

再来谈谈我认可的几个原则

1.4.1 原则之一:一个高等级的塔效果要超过2个低等级的塔。

只有这样才能体现策略,才能在地图上摆阵需要思考,考虑到当前的金钱和位置,需要思考哪个位置放置什么类型的塔,这样才能成功。

1.4.2 原则之二:不同类型的塔的组合效果必须大于单一塔的效果。

只依靠一个塔的简单累加就能过关的游戏是失败的。

1.4.3 原则之三:不同敌人组合。

在敌人组合的时候,不是固定的,三类不同敌人进行组合才能对玩家产生最大的压迫效果。

1.4.4 原则之四:关卡设计。

一款游戏一般都设计成以下阶段:新手–>成长–>高手–>专业。不同阶段都需要对玩家产生吸引力,每个阶段的难度设计都需要比较合理。

最后总结:

玩游戏,倒后面对游戏设计师的所有伎俩都清清楚楚,反倒不想打游戏了。

2013年3月28日下午10:09第一版 2013年4月2日下午10:43完稿

Modified By Long Luo at 2014-09-23 23:29:27 @Shenzhen, China. Modified By Long Luo at 2018年9月28日23点39分 @Shenzhen, China.

0%