on the way

亦余心之所向兮,虽九死其犹未悔!


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

对自身学习方面的反思

发表于 2018-12-17 | 分类于 reading | | 阅读次数:

对自身学习方面的反思


可能出于一种浮躁的心态,在前几天购买了极客时间平台左耳听风的课程,这是我在该平台上购买的第二个课程,上一门课程是关于Go语言的教程。花了249大洋着实心疼了半天,不过经过两天的阅读发现物超所值,阅读后确实能够解答不少现阶段的诸多迷惑。

快餐式生活的时代,保持平稳的心态尤其重要

记着从16年毕业开始,由于是跨专业转行,一直以来都认识到自己的各种不足,从毕业开始就疲于学习各种技术。

从最开始的Java基础,到后来的Spring全家桶,再到JavaScript以及层出不穷的Web前端框架,越是学习越觉得CS系统方面的真是无穷无尽,越学越多。

技术之路遥遥无尽,越学发现自己懂得越少,不知不觉整个人也变得焦虑起来了…学习效率自是一般,所幸自己一直没松懈下来,多多少少总算是有点收获…

深处现在这个信息爆炸的社会,不知不觉也变得愈发心浮气躁,什么事情都急于求成,零零散散的信息碎片充斥在周围,什么都想得到,到头来什么也没得到。

不过很多东西都是要一步一步来的,着急不得,千里之行始于足下。耗子叔最近几篇文章解答了我好久以来的迷惑,也让我对自己的学习方向有了更清晰地规划。

最近读的几篇文章比较深刻的印象是主动学习这个概念。我们从小到大以来接受的都是填鸭式的教育,这种被动学习的方式效率很差,是别人硬塞给你的,始终不是你自己的。

就算毕业后我自己去阅读书籍,好像也带着股被动学习的味道,是作者把知识硬塞给你的,其实好像完全可以化被动为主动,主动实践各种,与书中结果进行验证,接下来要调整自己的
这方面学习方式咯。

初步的学习计划

根据自身实际情况初步指定以下学习计划

  • Java高级特性(本职语言,要一直抓)
  • Go语言(扩展语言,比较看好前景)
  • 英语(查资料必备)
  • 计算机基础拾遗(CSAPP,数据结构与算法,网络)

路漫漫其修远兮,吾将上下而求索…

Redis API 入门

发表于 2018-01-26 | 分类于 redis | | 阅读次数:

基本

  • windows 连接redis: redis-cli.exe -h 127.0.0.1 -p 6379
  • 查看所有的key: keys *
  • 删除所有key value : flushall
  • 查看当前数据库键的数量: dbsize
  • 切换数据库 : select [index]
  • 获取键值的数据类型:type key

字符串(string)

  • 插入键: set key value
  • 获取值: get key
  • 多重插入键:mset key value
  • 多重获取值: mget key
  • 判断键是否存在:exists key
  • 删除键:del key (删除不支持通配符,不过支持多键参数:redis-cli DEL ‘redis-cli KEYS “user:*”‘)
  • 递增数字: incr key
  • 增加指定整数: incrby key increment
  • 减少指定整数: decrby key increment
  • 增加指定浮点数: incrbyfloat key increment
  • 追加value: append key value
  • 获取字符串长度: strlen key

散列(hash)

  • 插入键值: hset key field value
  • 获取值: hget key field
  • 获取全部键值: hgetall key
  • 多重插入键:hmset key field1 value1 field2 value2*
  • 多重获取值: hmget key field1 field1
  • 判断键是否存在:hexists key field
  • 插入键值(原子,键值存在则不插入) : hsetnx key field
  • 增加指定整数: hincrby key field increment
  • 删除键值: hdel key field
  • 只获取字段名:hkeys key
  • 只获取字段值:hvals key
  • 获取字段数量: hlen key

列表(list)

  • 向列表左边添加元素(创建元素): lpush key value
  • 向列表右边添加元素(创建元素): rpush key value
  • 从左边弹出一个元素:lpop key
  • 从右边弹出一个元素:rpop key
  • 获取列表中元素数量:llen key
  • 获取列表中片段: lrange key 0 2
  • 删除元素: lrem key count value
  • 当count>0,从左边删除count个value值
  • 当count<0,从右边删除|count|个value值
  • 当count=0,整个列表中删除value值
  • 根据index获取元素:lindex key index
  • 指定index位置的的元素:lset key index value
  • 修剪列表: ltrim key start end
  • 插入元素:linsert key before|after pivot value
  • 将元素从一个列表转向另一个列表:rpoplpush source destination

集合(set)

  • 添加元素:sadd key value[…]
  • 删除元素:srem key value[…]
  • 获取全部集合元素: smembers key
  • 判断元素是否存在: sismember key value
  • 获取集合元素个数:scard key
  • 随机弹出元素: spop
  • 随机获取:srandmember key count
  • 当count>0,从集合获取count个不重复值,count>集合元素数目,获取全部
  • 当count<0,从集合获取|count|个值,元素有可能重复
  • 差:sdiff setA setB[…]
  • 交集 : sinner setA setB[…]
  • 并集 : sunion setA setB[…]
  • 差:sdiffstore destination setA setB[…]
  • 交集:sinner destination setA setB[…]
  • 并集:sunion destination setA setB[…]

有序集合(sorted set)

有序集合和列表相似,不过二者使用场景还是有很多不同的。
1.列表是通过链表实现,获取靠近两端数据极快,比较适合“新鲜事”、“日志”这样比较少访问中间元素的应用。
2.有序集合是使用散列表和跳跃表来实现,即时读取中间位置元素,速度也很快(时间复杂度O(log(N)))。
3.列表不能简单地调整元素位置,有序集合可以(更改分数)。
4.有序集合比列表更耗费内存。

  • 添加元素:zadd key score member […]
  • 获得元素分数:zscore key member
  • 获取集合中片段(分数由小到大排序): zrange key start stop[withscores]
  • 获取集合中片段(分数由大到小排序): zrevrange key start stop[withscores]
  • 根据分数范围获取列表中片段(分数由小到大排序):zrangebyscore key min max [withscores ] [limit offset count]
  • 根据分数范围获取集合中片段(分数由大到小排序):zrevrangebyscore key max min [withscores ] [limit offset count]
  • +inf -inf 代表正负无穷 1 (5 代表数学上的[1 5)
  • 改变元素分数:zincrby key increment member
  • 获取集合中元素数量: zcard key
  • 获取指定分数范围内元素数目: zcount key min max
  • 删除元素: zrem key member[…]
  • 按照排名rank删除元素: zremrangebyrank key start stop
  • 按照分数删除元素: zremrangebyrank key min max
  • 获取元素排名(正序,分数最小的排名0): zrank key member
  • 获取元素排名(倒序,分数最大的排名0):zrevrank key member
  • 计算有序集合的交集(并集同理):zinterstore destination numkeys key[…] [aggregate :sum|min|max]
    其中numkeys是交集集合元素数目,aggregate选项是交集集合score的具体来源方式,例如aggregate=sum,此时交集集合当前元素的score就是所有被交集的元素score的和。

附上jedis相关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
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
/**
* test redis api
*/
public class testRedisApi {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost");
//testString(jedis);
//testHash(jedis);
//testList(jedis);
//testSet(jedis);
testZset(jedis);
}
/**
* 测试String类型
* @param jedis
*/
private static void testString (Jedis jedis) {
if (!jedis.exists("color:0")) {
print("插入 string data...",true);
// 新增key value
jedis.set("color:0","red");
}
print("测试String类型...");
String key = "color:0";
// 判断key是否存在
print("判断key是否存在" + jedis.exists(key));
// 根据key获取value
print("初始值 = " + jedis.get(key));
// 根据key追加value
jedis.append(key,"|blue");
print("追加blue后的值: "+ jedis.get(key));
// 获取string长度
print("获取string长度: "+ jedis.strlen(key));
// 删除
jedis.del(key);
print("删除key后的值: "+ jedis.get(key));
// 递增数字键
print("test递增数字键: "+ jedis.incr("id"));
print("test递增数字键: "+ jedis.incr("id"));
jedis.set("color:0","red");
}
/**
* 测试Hash类型
* @param jedis
*/
private static void testHash (Jedis jedis) {
if (jedis.hlen("car0") == 0) {
print("插入 hash data...",true);
// 新增hash数据
Map<String,String> hashMap = new HashMap<>();
hashMap.put("brand","BMW");
hashMap.put("color","black");
hashMap.put("price","888888");
jedis.hmset("car0",hashMap);
}
print("测试Hash类型...");
String key = "car0";
// 根据key获取field value
print("获取field值:" + jedis.hget(key,"brand"));
// 获取全部field value (Map)
print("获取全部field值:" + jedis.hgetAll(key));
// 判断filed是否存在
print("判断filed是否存在:" + jedis.hexists(key,"brand"));
// 获取全部field
print("获取全部field:" + jedis.hkeys(key));
// 获取全部value
print("获取全部value: " + jedis.hvals(key));
// 删除指定field
print("删除指定field: " + jedis.hdel(key,"color","price","brand"));
// 获取field数量
print("获取field数量:" +jedis.hlen(key));
}
/**
* 测试List类型
* @param jedis
*/
private static void testList (Jedis jedis) {
if (jedis.llen("girls") == 0) {
print("插入 list data...",true);
// 新增list数据
jedis.lpush("girls","yuyu");
jedis.rpush("girls","jingjing");
jedis.rpush("girls","qiqi");
}
print("测试List类型...");
String key = "girls";
//获取list中片段(-1代表最后一个)
print(jedis.lrange(key,0,-1).toString());
// 从左边pop一个元素(右边rpop)
print(jedis.lpop(key));
// 获取list中元素数量
print("list中元素数量" +jedis.llen(key));
// 根据index获取元素
print("根据index获取元素:" + jedis.lindex(key,0));
// 根据index设置元素(index必须是在当前数组域中,否则报ERR index out of range)
print("根据index设置元素:" + jedis.lset(key,0,"huhu"));
// 删除元素
// 当count>0,从左边删除count个value值
// 当count<0,从右边删除|count|个value值
// 当count=0,整个列表中删除value值
print("删除元素:" + jedis.lrem(key,0,"huhu"));
// 插入元素到指定位置
print("插入元素到指定位置:"+ jedis.linsert(key, BinaryClient.LIST_POSITION.AFTER,"yuyu","hxhx"));
// 将元素从一个列表转向另一个列表
print("元素从一个列表转向另一个列表:" + jedis.rpoplpush(key,"test"));
// 修剪list列表
print("修剪list列表:" + jedis.ltrim("test",jedis.llen("test"),jedis.llen("test")));
}
/**
* 测试Set类型
* @param jedis
*/
private static void testSet (Jedis jedis) {
if (jedis.scard("languages") == 0) {
print("插入 Set data...",true);
// 新增set数据
jedis.sadd("languages","java","java","javascript","python","golang");
}
print("测试Set类型...");
String key = "languages";
// 获取全部集合元素 (Set 无序)
print("获取全部集合元素:" + jedis.smembers(key));
// 判断元素是否存在
print("判断元素是否存在:" + jedis.sismember(key,"java"));
// 随机pop元素
print("随机pop元素:" + jedis.spop(key));
// 随机获取
// 当count>0,从集合获取count个不重复值,count>集合元素数目,获取全部
// 当count<0,从集合获取|count|个值,元素有可能重复
print("随机获取元素:" + jedis.srandmember(key,1));
// 移除集合指定成员
print("移除集合指定成员:" + jedis.srem(key,"java","javascript"));
jedis.sadd("set1","1","2","3");
jedis.sadd("set2","1","4","5");
// 差集
print("差集:" + jedis.sdiff("set1","set2"));
// 交集
print("交集:" + jedis.sinter("set1","set2"));
// 并集
print("并集:"+ jedis.sunion("set1","set2"));
}
/**
* 测试Zset类型
* @param jedis
*/
private static void testZset (Jedis jedis) {
if (jedis.zcard("phones") == 0) {
print("插入 Zset data...",true);
// 新增zset数据
Map<String,Double> zMap = new HashMap<>();
zMap.put("xiaomi",1.0);
zMap.put("huawei",1.0);
zMap.put("oneplus",2.0);
zMap.put("apple",3.0);
jedis.zadd("phones",zMap);
}
print("测试Zset类型...");
String key = "phones";
// 根据index获取集合片段(socre由小到大排序,zrevrange是相反的)
print("根据index获取集合片段:"+ jedis.zrange(key,0,-1));
// 获取元素score
print("获取元素score:" + jedis.zscore(key,"apple"));
// 根据score范围获取集合片段(四个参数分别为:MAX socre,MIN score, offset[偏移量] ,count[数目])
print("根据score获取集合片段:" + jedis.zrevrangeByScore(key,4,0,1,1));
// 获取指定score范围内元素数目
print("获取指定score范围元素数目:" + jedis.zcount(key,0,2));
// 改变元素score
print("改变元素socre:" + jedis.zincrby(key,4,"huawei"));
// 获取元素排名index(正序)
print("获取元素排名:" + jedis.zrank(key,"huawei"));
}
/**
* 打印消息(不切行)
* @param str
*/
private static void print (String str) {
print(str,false);
}
/**
* 打印消息(切行)
* @param str
*/
private static void print (String str,boolean isNextLine) {
if (isNextLine) {
System.out.println(str+"\n");
return;
}
System.out.println(str);
}
}

ArrayList源码

发表于 2018-01-09 | 分类于 java | | 阅读次数:
ArrayList
  • 本质是数组
  • 初始容量DEFAULT_CAPACITY = 10
  • get , set操作比较有效率,是直接根据索引来操作
  • add,remove操作涉及到index时,需要对数组拷贝,消耗大

代码块

成员变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 默认数组容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 有参构造器初始容纳数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 无参构造器初始容纳数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存放数据的缓冲区
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 容纳数据数
*/
private int size;

构造器:

三种构造方式:无参数,int参数,Collection参数

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
/**
* 无参构造器,默认以初始容量DEFAULT_CAPACITY = 10 构造
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 参数为int的构造器
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) { //initialCapacity参数大于0,直接构建对象
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//initialCapacity参数等于0,用EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 参数为Collection的构造器
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//将Collection参数转数组
if ((size = elementData.length) != 0) {//Collection参数中存在数据时(非空Collection)
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

辅助方法:

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
/**
* 增加Array容量
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)// 要求的容量大于此时数组容量,触发grow()
grow(minCapacity);
}
/**
* 最大容量
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//当前数组容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//新数组容量(右移一位代表除2^1位,新容量是旧容量的1.5倍)
if (newCapacity - minCapacity < 0)//新容量仍然不满足要求的容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//新容量大于最大容量
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow ????为什么会出现小于0情况
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? //要求的容量大于最大容量时用Integer.MAX_VALUE尝试 ,否则用MAX_ARRAY_SIZE(也就是不可以按1.5倍来扩容了,数组实在太大)
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

常用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
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
/**
* 正序返回index
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 倒序返回index
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 根据index获取数据元素,对数组直接操作,比较高效
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* 根据index放入数据元素,对数组直接操作,比较高效
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
/**
* 不要求index次序的添加,对数组直接操作,也比较高效
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 要求index次序的添加,需要拷贝数组,比较耗费资源
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
/**
* 移除指定位置元素,需要拷贝数组,比较耗费资源
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);//被移除的元素
int numMoved = size - index - 1;//index后的元素数目
if (numMoved > 0)//index后存在元素(也就是被移除的这个元素不是最后一个)
System.arraycopy(elementData, index+1, elementData, index, numMoved);//index后存在元素(也就是被移除的这个元素不是最后一个)将index后的元素复制过来
elementData[--size] = null; // (最后一个数组位置将其变成null,因为复制完后面的元素均前移一步,最后位置此时无值)clear to let GC do its work
return oldValue;
}
/**
* 移除指定的元素,需要拷贝数组,比较耗费资源
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/**
* 与public remove(int index)方法差不多,只不过没有返回值
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
/**
* 清除所有元素
*/
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
/**
* 将Collection添加进ArrayList中,需要拷贝数组,比较耗费资源
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
/**
* 根据index来添加Collection,需要拷贝数组,比较耗费资源
*/
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}

三种循环效率

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
public class testArrayList {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i <10000 ; i++) {
list.add(i);
}
testArrayList.iterateByForEach(list);
testArrayList.iterateByiterator(list);
testArrayList.iterateByRandomAccess(list);
}
private static void iterateByRandomAccess (List list) {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
for (int i = 0; i <list.size() ; i++) {
list.get(i);
}
endTime = System.currentTimeMillis();
System.out.println("iterateByRandomAccess Time is :" + (endTime - startTime));
}
private static void iterateByForEach (List list) {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
for (Object obj : list) {
}
endTime = System.currentTimeMillis();
System.out.println("iterateByForEach Time is :" + (endTime - startTime));
}
private static void iterateByiterator (List list) {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
for (Iterator iterator = list.iterator(); iterator.hasNext();) {
iterator.next();
}
endTime = System.currentTimeMillis();
System.out.println("iterateByiterator Time is :" + (endTime - startTime));
}
}
  • 随机访问 iterateByRandomAccess 最快
  • iforEach循环 中等速度
  • 迭代器 iterateByiterator 最慢

适配器模式

发表于 2017-12-19 | 分类于 设计模式 | | 阅读次数:

适配器模式


概念

有时候在程序中需要对接口进行适配,也就是已有的接口(实现类的方法)在某个业务上不能满足我们的需要,需要我们改写下已有的接口,使之符合我们的需要。但是已有的接口不能动(因为可能项目中其他地方已经在用这个接口),这时候就需要适配器来解决这个问题了。

  • 定义: 适配器模式(Adapter Pattern):将一个类的接口转换成客户期望的另一个接口,让原本不兼容的接口可以合作无间。

实例

举个例子吧,中国电流标准电压是220v,笔记本电脑需要的电压是20v,这时就需要个电源适配器来让笔记本正常工作了。

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 interface Electric {
public void display();
}
//国标电流
public class GBElectric implements Electric {
@Override
public void display() {
System.out.println("220V电流");
}
}
//笔记本电脑
public class Computer {
Electric electric;
public Computer (Electric electric) {
this.electric = electric;
}
public void work () {
electric.display();
}
}
//客户端
public class Client {
public static void main(String[] args) {
Electric elec ;
elec = new GBElectric();
Computer computer = new Computer(elec);
computer.work();
}
}
运行结果:
220V电流

运行结果很显然不对,这时就需要我们用适配器来更改下了。

1
2
3
4
5
6
7
//适配器
public class ElectricAdapter extends GBElectric {
@Override
public void display() {
System.out.println("20V电流");
}
}

客户端代码变为:

1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
Electric elec ;
elec = new ElectricAdapter();
Computer computer = new Computer(elec);
computer.work();
}
}
运行结果:
20V电流

可以看到我们只是扩展了原本的GBElectric类与改了下客户端代码,原本接口(GBElectric )与后端(Computer)代码不变,完全符合开闭原则。

总结

缺点
  • java不支持多继承,一次只能适配一个接口
适用场景
  • 现有接口不满足业务需求时,需要更改

建造者模式

发表于 2017-12-13 | 分类于 设计模式 | | 阅读次数:

建造者模式


概念

设计系统时有时会需要实现相对复杂对象的构建,建造者模式就是为了解决这个问题的。建造者模式返回的是由多个部件组成的复杂产品,它专注于一个复杂对象的构建。

  • 定义: 建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 结构图如下:
    建造者模式

实例

建造者模式主要由产品类,抽象建造者,具体建造者,指挥者组成,其中指挥者可根据不同需求进行更改。

以常见的成员信息统计为例子,产品类如下:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 产品角色
*/
public class MemberInfo {
private String name;
private int age;
private String sex;
private String phone;
private String addr;
//get set 省略....
}

抽象建造者和具体建造者分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 建造抽象
*/
public abstract class MemberInfoBuilder {
protected MemberInfo memberInfo = new MemberInfo();
public abstract void buildName ();
public abstract void buildAge ();
public abstract void buildSex ();
public abstract void buildPhone ();
public abstract void buildAddr ();
public MemberInfo createMemmerInfo(){
return this.memberInfo;
}
}


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
/**
* 具体建造者
*/
public class XiaoHuaMemberInoBuilder extends MemberInfoBuilder{
@Override
public void buildName() {
memberInfo.setName("小花");
}
@Override
public void buildAge() {
memberInfo.setAge(22);
}
@Override
public void buildSex() {
memberInfo.setSex("女");
}
@Override
public void buildPhone() {
memberInfo.setPhone("11111111111");
}
@Override
public void buildAddr() {
memberInfo.setAddr("美国洛杉矶");
}
}

指挥者代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 指挥者
*/
public class MemberInfoController {
public MemberInfo construct (MemberInfoBuilder builder) {
MemberInfo memberInfo;
builder.buildName();
builder.buildSex();
builder.buildAge();
builder.buildAddr();
builder.buildPhone();
memberInfo = builder.createMemmerInfo();
return memberInfo;
}
public static void main(String[] args) {
MemberInfoController memberInfoController = new MemberInfoController();
MemberInfo memberInfo = memberInfoController.construct(new XiaoHuaMemberInoBuilder());
System.out.println(memberInfo.getName());//小花
}
}

上诉代码关键部分在于抽象建造者里的 protected MemberInfo memberInfo = new MemberInfo() ,将 MemBerInfo对象的访问权限设置为 protected,以便于子类可以在被覆写的抽象方法里直接对其进行操作。

总结

指挥者类的功能可以在抽象建造者里面直接实现,这样可以减少类的数目,不过违背了“开闭原则”,以后若要进行建造顺序等更改,必须修改源代码。也可以在抽象建造者里面增加钩子方法,控制具体建造过程。

缺点
  • 使用范围受到限制,适合一系列相似的复杂产品的创建
  • 加大系统复杂度,增加理解难度与运行成本
适用场景
  • 隔离复杂对象的创建与使用
  • 对象属性互相依赖,例如控制属性生成的顺序

原型模式

发表于 2017-12-12 | 分类于 设计模式 | | 阅读次数:

原型模式


概念

在设计系统过程中,有时候可能需要一些相同的或相似的对象,这时可以采用原型模式。

  • 定义: 原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

实例

java中的原型模式很简单,也就是Object对象的clone()方法,不过只是浅拷贝,也就是采用clone方法获得的拷贝对象不会将引用类型也拷贝下来,只会拷贝基本类型。需要注意的是:正确拷贝的对象与原来的对象不在一个内存地址上,也就是它们是两个互相独立的对象,互不影响,只是内容看起来一样而已。

下面以文件复制过程来演示下浅拷贝:

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
/**
* ShallowClone
*/
public class File implements Cloneable{
private String name;
private String content;
private Date date;
private List<String> list;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getDate() {
return date;
}
public void setDate(Date data) {
this.date = data;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
@Override
public File clone () {
Object obj = null;
try {
obj = super.clone();
return (File) obj;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}

客户端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Client {
public static void main(String[] args) {
File file0 = new File();
file0.setName("记事本0");
file0.setContent("呜呜呜呜呜呜呜呜呜");
file0.setDate(new Date());
ArrayList<String> list = new ArrayList<String>(3);
list.add("1");
list.add("2");
list.add("3");
file0.setList(list);
File file1 = file0.clone();
file1.setName("记事本1");
System.out.println(file0 == file1);//false
System.out.println(file0.getClass() == file1.getClass()); //true
System.out.println(file0.getDate() == file1.getDate());//true
System.out.println(file0.getList() == file1.getList());//true
}
}

输出结果似乎没问题,不过我们可以看到file0.getList() == file1.getList()结果竟然是true。对于引用类型来说,用 ==比较符比较的应该是内存地址。结果为true说明file0与file1都指向同一个对象,file1只是把“指针”给复制过来了。将来如果对file0中的List进行修改,file1中的List也会被改变。这就是所谓的浅拷贝了。

如果要实现深拷贝需要我们自己实现编码,被拷贝的对象必须实现Serializable接口。下面随便建立一个带List成员的类演示下实现深拷贝:

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
/**
* DeepClone
*/
public class DeepClone implements Serializable{
private List<String> list;
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public DeepClone deepClone () throws IOException, ClassNotFoundException {
//将对象写入流
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(this);
//将对象读出流
ByteArrayInputStream bai = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bai);
return (DeepClone)ois.readObject();
}
}

客户端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Client {
public static void main(String[] args) {
DeepClone dp0 = new DeepClone();
ArrayList<String> list = new ArrayList<String>(3);
list.add("1");
list.add("2");
list.add("3");
dp0.setList(list);
try {
DeepClone dp1 = dp0.deepClone();
System.out.println(dp0 == dp1); //false
System.out.println(dp0.getList() == dp1.getList()); //false
} catch (Exception e) {
e.printStackTrace();
}
}
}

可以看到dp0.getList() == dp1.getList()结果为false,说明List拷贝成功。

总结

缺点
  • 需要为每个类提供Clone方法,比较麻烦。
  • 实现深克隆比较麻烦,尤其是当对象存在多重嵌套时。
适用场景
  • 创建对象成本较大时,可考虑用克隆模式来复制,减少系统资源占用。

单例模式

发表于 2017-12-09 | 分类于 设计模式 | | 阅读次数:

单例模式


概念

有时对于系统中某些类而言,不需要多个实体。一方面需要保证这个实体的“原子性”,另一方面也可以节省系统资源。比如对于游戏账号这个类来说,账号里某些信息是恒定不变的,用户上线下线登陆的还是那个账号,这时可以考虑单例模式。

  • 定义: 单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。 结构图如下:
     单例模式

实例

对于单例模式而言,要求我们在系统中只能获得一个实例。很容易想到以下写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 有线程安全问题
*/
public class SimpleSingleton {
private static SimpleSingleton instance = null;
private SimpleSingleton () {
}
public static SimpleSingleton getInstance () {
if (instance == null) {
instance = new SimpleSingleton();
}
return instance;
}
}

首先将此类构造器设置为访问权限设置为private,禁止外部创建此对象。然后建立个私有静态成员变量instance存储实例,最后建立共公有静态成员方法,返回实例。

但是在高并发访问环境下,还是可能会出现多个实例的情况。原因是当第一次调用getInstance()时并触发instance=null时,程序执行instance = new SimpleSingleton(),如果SimpleSingleton对象初始化时间足够长,并且外部又出现第二次调用getInstance(),此时由于SimpleSingleton对象还在初始化中,还是会触发instance=null,进入循环体第二次执行instance = new SimpleSingleton()。

下面介绍三种单例模式都可以从各方面来解决这个问题。

  • 恶汉式单例类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 饿汉单例
    */
    public class EagerSinleton {
    private final static EagerSinleton instance = new EagerSinleton();
    private EagerSinleton () {
    }
    public static EagerSinleton getInstance() {
    return instance;
    }
    }

恶汉单例模式主要是通过final关键字来实现的,当类EagerSinleton加载时静态变量instance执行初始化,final可以保证instance的指向不会改变。缺点是类加载时实例就会创建,可能会对系统资源开销造成影响。

  • 懒汉式单例类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 懒汉单例
*/
public class LazySinleton {
private volatile static LazySinleton instance = null;
private LazySinleton () {
}
public static LazySinleton getInstance () {
if (instance == null) {
synchronized (LazySinleton.class) {
if (instance == null) {
instance = new LazySinleton();
}
}
}
return instance;
}
}

可以看到懒汉单例模式就是上述SimpleSingleton的线程安全版本。当需要此对象时对象才会被创建,也就是所谓的懒加载(LazyLoad)。

在getInstance方法中用synchronize关键字对LazySinleton.Class对象加锁,防止该对象同时被两个线程访问。instance采用volatile关键字修饰,保证此变量的对其他的线程可见性。多加了一层判断保证程序稳定性。缺点是必须处理线程锁,初次加载时可能消耗性能。

  • Initialization Demand Holder (IoDH)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * Initialization Demand Holder (IoDH)
    */
    public class Singleton {
    private Singleton () {
    }
    private static class HolderClass {
    private final static Singleton instance = new Singleton();
    }
    public static Singleton getInstance () {
    return HolderClass.instance;
    }
    }

上述单例采用静态内部类的方式来实现,依赖于java语言特性,将恶汉,懒汉特性结合。

总结

缺点
  • 职责过重,在一定程度上违背单一职责原则。
  • 对于带GC回收机制的语言来说,长时间不用的对象有可能被回收,可能导致单例对象状态丢失。
适用场景
  • 系统只需要一个实例

抽象工厂模式

发表于 2017-12-03 | 分类于 设计模式 | | 阅读次数:

抽象工厂模式


概念

工厂方法模式为了解决工厂类责任过重的问题,引入了工厂等级结构。但是工厂方法模式一个Factory类只生产一个产品,这样势必会造成类数目暴涨问题,带来额外系统开销。可以用抽象工厂模式来解决这个问题。

  • 定义: 抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式为创建一组对象提供了一种解决方案。 结构图如下:

抽象工厂模式

实例

上面的定义和结构图看着可能云里雾里的,直接看代码好了。以我喜欢吃的水果为例子。水果通常分果皮、果肉、果核。吃水果之前我们一般需要判断下哪部分可以吃…

首先建立Factory与Product接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//水果工厂
public interface FruitFactory {
public Peel createPeel();
public Flesh createFlesh();
public Kernel createKernel();
}
//果皮
public interface Peel {
public void isEat();
}
//果肉
public interface Flesh {
public void isEat();
}
//果核
public interface Kernel {
public void isEat();
}

接着建立ConcreteFactory 与ConcreteProcduct

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
public class AppleFactory implements FruitFactory {
@Override
public Peel createPeel() {
return new ApplePeel();
}
@Override
public Flesh createFlesh() {
return new AppleFlesh();
}
@Override
public Kernel createKernel() {
return new AppleKernel();
}
}
public class ApplePeel implements Peel {
@Override
public void isEat() {
System.out.println("苹果皮可以吃!");
}
}
public class AppleFlesh implements Flesh {
@Override
public void isEat() {
System.out.println("苹果果肉可以吃!");
}
}
public class AppleKernel implements Kernel {
@Override
public void isEat() {
System.out.println("苹果核不可以吃!");
}
}

看下运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
public class EatFruit {
public static void main(String[] args) {
FruitFactory fruit = new AppleFactory();
fruit.createPeel().isEat();
fruit.createFlesh().isEat();
fruit.createKernel().isEat();
}
}
运行结果:
苹果皮可以吃!
苹果果肉可以吃!
苹果核不可以吃!

总结

可以看到抽象工厂模式是将一系列有共同特征的对象封装在一起(使用一个工厂来创建),将来系统扩展时也比较方便,有效地减少了程序中的类的数目。

但是也有缺点。就上例而言,假如我们要新增桃子类,写个1个桃子工厂类再写3个产品类就可以,不用修改已有代码,很方便,符合开闭原则。但是如果要新增香蕉类呢,香蕉没果核啊,是不是需要改动一系列接口?这个也是抽象工厂的最大的缺点了。可以说其满足纵向(产品族 )的开闭原则,而不满足横向(产品等级)的开闭原则。具体应用场景视情况而定,不要生搬硬套。

缺点
  • 更改产品等级比较麻烦,违反开闭原则
适用场景
  • 具体产品在逻辑上属于同一产品族,具有共同约束,比如苹果、梨子都有果肉
  • 产品等级比较稳定,不会对已有产品等级进行修改,比如系统需求稳定了,不会让你再往里加香蕉…

工厂方法模式

发表于 2017-12-01 | 分类于 设计模式 | | 阅读次数:

工厂方法模式


概念

简单工厂模式已经了解过了,它的缺点也很明显:违背开闭原则,容易造成Factory类复杂度过大。工厂方法模式可以适当解决这些问题。

  • 定义:工厂方法模式(Factory Method Pattern),定义一个创建对象的接口,让其子类来创建对象。结构图如下:

工厂方法模式

实例

下面让我们将简单工厂中的例子改写下,将GirlFactory改为接口,额外添加三个工厂类。

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 interface GirlFactory {
public Girl createGirl();
}
public class XiaoCaoFactory implements GirlFactory {
@Override
public Girl createGirl() {
Girl xiaoCao = new XiaoCao();
return xiaoCao;
}
}
public class XiaoHuaFactory implements GirlFactory {
@Override
public Girl createGirl() {
Girl xiaoHua = new XiaoHua();
return xiaoHua;
}
}
public class XiaoYeFactory implements GirlFactory {
@Override
public Girl createGirl() {
Girl xiaoYe = new XiaoYe();
return xiaoYe;
}
}

同时将客户端类Mother调用方式改写下

1
2
3
4
5
6
7
8
9
10
11
public class Mother {
public static void main(String[] args) {
GirlFactory factory= new XiaoCaoFactory();//可引入配置文件通过反射来实现
Girl girl = factory.createGirl();
girl.name();
girl.character();
}
}
运行结果:
我是一个漂亮的,身材棒的女孩!
我的名字是小花!

可以看到运行结果不变,不过类的数目增加了3个(项目结构复杂了),这样做的好处在哪里?答案是解耦。我们可以想想,如果需要添加新的concreteProduct时,需要怎么做。

1.建立新的Girl类(concreteProduct),比如XiaoHong、XiaoJing,让它继承于Girl接口。
2.建立新的Factory类(concreteFactory),让它继承于GirlFactory接口,通过这个Factory类来建立Girl对象。
3.改写客户端Mother,将Factory更换。

注意:在这个过程中我们并没有对原有的服务端类Factory和Product进行修改(Mother类属于客户端),而是通过java的多态特性根据需求对进行扩展(增加了concreteProduct类和concreteFactory类,没有改动原来服务端的代码,符合开闭原则,对修改关闭,对扩展开放)。

总结

缺点
  • 新增产品类时系统的类成对增加(concreteProduct与concreteFactory),增加系统的复杂度,额外的编译开销
适用场景
  • 客户端不需知道类名,只需要知道工厂就可以
  • 系统后续扩展趋势较大,用来保证系统解耦度

简单工厂模式

发表于 2017-11-28 | 分类于 设计模式 | | 阅读次数:

简单工厂模式


使用工厂模式创建对象的好处

通常我们在创建对象时最容易想到的就是new操作符,不过new操作符有许多局限:不够灵活,与业务类耦合度过大,也将初始化对象等无关业务细节暴露(我们只是要获取对象,不关心其他,初始化对象等细节操作何不交给其他类来完成?)。

一般可以采用工厂模式来解决这类问题。由一个第三方类(Factory类)来创建对象,将对象创建过程封装起来,降低系统耦合度。

概念

简单工厂模式(Simple Factory Pattern)并不属于GoF23个经典设计模式,不过它是学习其他工厂模式的基础。概念也比较简单。

  • 定义:定义一个工厂类,里面存在个static方法,根据不同参数返回不同实例,返回的实例通常具有共同父类。结构图如下:

简单工厂模式

实例

下面来举个简单的例子吧。想必很多同学都被家里长辈唠叨过怎么还不找女朋友。那我们就拿这个事来举个简单工厂的例子吧。

首先编写Product与ConcreteProduct类:

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
public abstract class Girl {
//女孩的品质
public void character() {
System.out.println("我是一个漂亮的,身材棒的女孩!");
}
//女孩的名字
public abstract void name ();
}
public class XiaoCao extends Girl {
@Override
public void name() {
System.out.println("我的名字是小草!");
}
}
public class XiaoHua extends Girl {
@Override
public void name() {
System.out.println("我的名字是小花!");
}
}
public class XiaoYe extends Girl {
@Override
public void name() {
System.out.println("我的名字是小叶!");
}
}

可以看到,我们创建了一个抽象类,并用这个类实现了三个具体类。

假如你现在有三个女朋友(不是让你脚踏三只船,只是为了例子说明),Mother要看看她们。我们很容易就想到下面实现:

1
2
3
4
5
6
7
8
9
10
11
public class Mother {
public static void main(String[] args) {
Girl girlFriend = new XiaoCao();//小草
girlFriend.character();
girlFriend.name();
}
}
运行结果:
我是一个漂亮的,身材棒的女孩!
我的名字是小草!

看起来很棒,运行结果也没问题。不过Mother看完小草不是那么满意,又想看小花了。或者Father、Sister什么的七大姑八大姨都要来看。这可怎么办,只能修改Mother类了。难道每次换想法我都要改一次吗?有没有一种方法可以直接给他们想要的,需要什么就给什么。

我们可以建立一个Factory类,你想看哪个直接取就是了。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class GirlFactory {
public static Girl getGrilFriend (String name) {
Girl girl = null;
if (name.equals("小草")) {
girl = new XiaoCao();
} else if (name.equals("小花")) {
girl = new XiaoHua();
} else if (name.equals("小叶")) {
girl = new XiaoYe();
}
return girl;
}
}

Mother类变为:

1
2
3
4
5
6
7
8
9
10
11
public class Mother {
public static void main(String[] args) {
Girl girlFriend = GirlFactory.getGrilFriend("小花");
girlFriend.character();
girlFriend.name();
}
}
运行结果:
我是一个漂亮的,身材棒的女孩!
我的名字是小花!

将创建对象的职责交给第三方类(Factory)来执行,来达到解耦合的目的。什么七大姑八大姨想看也直接从Factory里取就是了。

不过缺点想必大家也可以看出,如果需要增加新的ConcreteProduct类时必须修改Factory类,这违背了开闭原则。当需要创建的对象很多时也势必会增加Factory类的复杂度,所以它适用于需要用Factory类创建少量对象的场合。

总结

缺点
  • Factory类负责相关所有对象的创建,职责过重,复杂度高
  • 添加新产品时必须修改Factory类,违背开闭原则
适用场景
  • 需要创建的对象不多时,Factory类复杂度不会太高
  • 客户端只需要知道传入的参数,对如何创建对象并不关心
12
LiQiang

LiQiang

亦余心之所向兮,虽九死其犹未悔!

19 日志
6 分类
13 标签
GitHub E-Mail
© 2016 — 2019 LiQiang