实现List, RandomAccess, Cloneable, java.io.Serializable 接口

1
2
3
4
5
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();

/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

主要的成员变量有2个,一个ReentrantLock,一个是内部存值的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}

Add方法,先是前后两个lock与unlock,保证安全性。
实现实际上是复制一个原来数组,将新值添加到新数组的最后一位,内部变量指向新的数组。

1
2
3
4
5
6
7
8
9
10
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;

private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}

内部的遍历,其实是将原来数组进行一个快照,这样即使数组变化了,不影响快照的遍历。

CopyOnWriteArrayList 的添加元素很消耗资源,因为每次都要新建一个数组,然后复制所有数据,只适用于读远大于写的情况,如果写比较多,性能会很差。

原文地址:https://github.com/aphyr/distsys-class

这周没怎么看,周末临时抱佛脚看了看。
还是关注广度,跟其他的分布式介绍对比了一下。一部分是知道为什么会需要分布式,接着两个主要方面就是扩展性和可用性。
应该主要3方面,高可用,高性能,高扩展性。
理解清楚CAP,从理论上清楚。
然后就是具体的每个方面,不同的实际技术。东西很多,但要把握脉络。

打算从事什么工作

大部分人在21岁的时候很迷茫,接下来要从学生时代到工作了,多少有些不知所措,如果你从来没想过,规划过,更是如此。
如果能提前想好阶段,即使时间不固定,也能更好的处理接下来的变化,而不是随波逐流。
自然界的事物都是符合熵增的,就是不断的变得更混乱,只有人为的注入的能量,才能有秩序。比如nick说的那句话,只有不断努力才能维持停滞不前,只有突破自己,才能进行改变。

你对政治的看法

要关心社会的发展,历史的进程,虽然自己很努力,但如果一个东西被历史淘汰,你再努力也是白费,比如林赛后面干了很久的图书馆,被强制淘汰了,这个不是她的错,但社会淘汰了你。

未来你害怕什么

知道自己的边界,自己的短板很重要

题目

给定一个排序好的数组,两个整数 k 和 x,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。如果有两个数与 x 的差值一样,优先选择数值较小的那个数。
示例 1:
输入: [1,2,3,4,5], k=4, x=3
输出: [1,2,3,4]

示例 2:
输入: [1,2,3,4,5], k=4, x=-1
输出: [1,2,3,4]

说明:

  1. k 的值为正数,且总是小于给定排序数组的长度。
  2. 数组不为空,且长度不超过 104
  3. 数组里的每个元素与 x 的绝对值不超过 104

思路

首先这是一个有序的数组,那就可以先二分法查找x,然后返回对于的索引,这个分几种情况
1)找到等于x的值,直接返回索引
2)找不到具体等于x的值,就只能找到离x最近且最小的索引值

根据上面返回的索引值,作为第二步的初始索引。分别判断前后的是不是到上下边界了,
1)没到边界,分别找前后离x最近的边界,左或右移一位
2)到边界,只能反向移动,左移或右移动一位

结束条件是k,每次移动边界,k递减

解题过程

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
class Solution {
public int binarySearch(int[] arr,int x){
int left = 0;
int right = arr.length -1;
while(left <= right){
int index = (right -left)/2+left;
int value = arr[index];

if(value == x){
return index;
}else if(value > x){
right = index - 1;
}else if(value < x){
if(index + 1 < (arr.length -1)){
int next = arr[index+1];
if(next < x){
left = index + 1;
continue;
}
int comp = (next - x) - (x - value);
if(comp >= 0){
return index;
}else{
return index+1;
}
}else{
return index;
}

}
}
return -1;
}
public List<Integer> findClosestElements(int[] arr, int k, int x) {
int flag = binarySearch(arr,x);
if(flag == -1){
List<Integer> list = new ArrayList<Integer>();
for(int i = 0 ; i<k;i++){
list.add(arr[i]);
}
return list;
}
int nums = k-1;
int left = flag;
int right = flag;
int size = arr.length;
while(nums != 0){
int innerLeft = left;
int innerRight = right;
if(right + 1 > size -1){
left = innerLeft -1;
nums--;
continue;
}else if(left -1 < 0){
right = innerRight + 1;
nums--;
continue;
}else{
if( right + 1 <= size - 1 ){
innerRight = innerRight + 1;
}
if(left -1 >= 0){
innerLeft = innerLeft -1;
}
}
int leftValue = arr[innerLeft];
int rightValue = arr[innerRight];
int leftDiv = x - leftValue;
int rightDiv = rightValue - x;
if(leftDiv <= rightDiv){
left = innerLeft;
}else{
right = innerRight;
}
nums--;
}
List<Integer> list2 = new ArrayList<Integer>();
for(int i = left ; i<= right;i++){
list2.add(arr[i]);
}
return list2;
}
}

比较容易出错的有几点
1.二分查找不光要找出等于的值,也要查找离的最近最小的值
2.第二次进行查询的时候,可能会有相等数字重复多次的情况,也要考虑
3.第二次查找,比较大小左右边界移动的判断条件很复杂,很容易出错。

ArryList实现接口List, RandomAccess, Cloneable, java.io.Serializable
基础的数据结构是数组,根据存储数据的多少动态扩容。

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
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;

/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;

elementData是实际存储数据的数组。DEFAULT_CAPACITY是默认的容量,如果没有指定数组的大小。DEFAULTCAPACITY_EMPTY_ELEMENTDATA 与EMPTY_ELEMENTDATA都是 空的初始数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是用来判断是不是通过默认方法创建的空数组,而不是删除后才空的。如果指定了数组大小,就用EMPTY_ELEMENTDATA作为默认空数组。

扩容是在添加元素的时候进行,这样不会初始化没有数据的情况下就占据空间。

1
2
3
4
5
6
7
8
9
10
11
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

添加方法很简单,先去内部扩容,然后在最后一个添加值,看扩容的内部实现。

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
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
}

ensureCapacityInternal 先是一个判断数组是怎么创建,如果是默认空参数的构造器创建,就使用默认的初始大小10作为最小容量,这样就能避免不能手动创建更小数组的问题。
主要扩容是grow方法,可以看出 newCapacity是扩容0.5倍大小,到原来的1.5倍。
还有就是容器上下限的判断。扩容就是迁移数组newCapacity的大小的数组里。
归根到底,就是将数组扩容1.5倍,旧数组搬迁到新数组。
里面还有几个内部类,一个Itr,实现了Iterator接口,

1
2
3
4
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

ListItr 实现了 ListIterator,可以前后访问遍历

1
2
3
4
5
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}

ArrayList使用遍历接口,都是使用内部类实现的,重新记录状态,这部分还有些不太懂的,后面继续补充。

最近真的没有怎么实际写后端代码了,主要都是写一些简单的前端,而且不懂了也很少弄懂,真的很不好。
这周要分享的也很水,可以说算得上一个失败的经历。

在实现导航栏跳转的时候,类似视频网站多个tab切换,使用父子路由实现,子路由的页面几乎一直,所以css也差不多,因为都是全局的css,所以有些css重复了,我想做一个互相不影响的,隔离的css作用域,发现vue有一个scoped的概念,在style标签里加上scoped,编译的时候就会加上唯一的编号类似 class-123132,这样,那么就是全局唯一的了。
但是我尝试加上postcss,就和bootstrap-vue起了冲突,解决了一阵未果,没有继续尝试了。

第二遍重看这个纪录片,第一遍是初读的话,这一遍,我会比较细,记录记者的提问,不同人的不同答案。横向对比与之前7年的差别,以及为什么会有这样的变化。
这次只看了7岁和14岁的,第一次看感觉很粗略,有种震撼的感觉,但你不是能清楚说明白为什么。

先从记者提问入手

旅游

穷人家的孩子没怎么出去过,富人家的孩子去过很多地方,这个确实是经济因素导致。穷人孩子连吃饭都不富足,想出去很难。开阔了眼界对于孩子认识世界很重要。

放学后空闲时间做什么

7岁时候的问题,空闲时间对性格形成有很大的影响。
富人家的孩子学校有很多时间可以做,可以学音乐等。到了14岁,也是一样,富人家的孩子有很多事情做,一般人家没什么做,去玩或者去俱乐部,或者去看电视,消磨时间。
在自己意识下能去做一些事情,时间上利用会多很多,不会去看电视等消磨时间,常年累月下来就是产生很大的不同。

怎么花钱,对于金钱的看法

John对于钱财的概念在14岁已经成熟了,不需要特别多的钱,但要足够能去做自己喜欢的事情,不能为了钱做无聊的工作。大部分人都表现不是很喜欢钱,认为钱够花就好了,这个时候他们还都没有赚钱,对于钱的需求感很弱,所以回答都很高尚,都没被贫穷困扰过。

对于我来说,现在真的很缺钱,不是生活上的缺钱,衣食住行都没问题,但房子特别贵,贵到不能承受,可能要背起20-30年的房贷,才能勉强买个40-50平的小房子。选工作上,如果工资高也会很动心,即使工作可能很累,重复没成长。也想自己出去赚钱,但能力有限,不能独立去市场上赚钱,还需要给别人打工。所以要有能赚足够多的钱的能力还是很重要的,有了钱很多事情都会轻松很多。做真正想做的事情。
要在主能力足够优秀的情况下,发展其他能力,但主的本身就不强的话,很难把其他发展很强。

有男女朋友吗

7岁的时候,大家都有男女朋友,但那个是很童真的。14岁的时候,大家都没有的真正的男女朋友,这个时候处于萌芽期。
多接触异性,对于后面的婚姻有帮助。

对于罢工的看法,对于政党的看法

14岁的时候,只有Join表现出了,想要从政的想法,对这方面很有见解,其他人都处于漠不关心的状态。
上层阶级反对罢工,下层人民接受罢工,虽然也觉得有影响,但两个层级的人的看法角度不相同,都是符合自己利益的角度。查尔斯能从自由的角度去看,罢工的意义,人民有罢工的权利
人很难跳出自己的层级,自己的角度去看问题, 所以对一个问题认识的全面,要么需要经历,要么需要看不同层级的人是什么观点,是什么行为。

对于寄宿学校的看法

大部分人提到了会让人更独立,离开父母。但有人也提到了坏处,本应该跟父母相处,导致了部分的缺失。福利院的孩子在童年就缺失与父母的生活,整个人生中就缺少自信,在paul和symon身上体现很多。富人家的孩子更多的自信,穷人相对自卑。

对于有钱人的看法

大家对于有钱人更多的是不好的看法,有钱人会欺负穷人,穷人嫉妒有钱人,他们一出生什么也不用做,就能达到他们一辈子工作的水平,很不公平。

相信上帝吗

大部分人相信,但说不清楚原因,有的因为人总得有点信仰。

原文地址:https://github.com/aphyr/distsys-class

这篇文章相当于一个分布式学习的大纲,以我的3遍学习发来看,我这周只是通读了2遍,只达到了第一层级,那这周的文章就当作一个阅读大纲的提炼,先理清楚知识架构。分布式内容本身就十分多,如果不理清结构,很容易混乱,今天就是一个入门的开始。

  1. What makes a thing distributed
  2. Nodes and networks
  3. When networks go wrong
  4. Low level protocols
  5. Clocks

Review
We’ve covered the fundamental primitives of distributed systems. Nodes exchange messages through a network, and both nodes and networks can fail in various ways. Protocols like TCP and UDP give us primitive channels for processes to communicate, and we can order events using clocks. Now, we’ll discuss some high-level properties of distributed systems.


  1. Availability
  2. Consistency
  3. Tradeoffs

Review
Availability is a measure of how often operations succeed. Consistency models are the rules that govern what operations can happen and when. Stronger consistency models generally come at the cost of performance and availability. Next, we’ll talk about different ways to build systems, from weak to strong consistency.


  1. Avoid Consensus Wherever Possible
  2. Fine, We Need Consensus, What Now?

Review
Systems which only add facts, not retract them, require less coordination to build. We can use gossip systems to broadcast messages to other processes, CRDTs to merge updates from our peers, and HATs for weakly isolated transactions. Serializability and linearizability require consensus, which we can obtain through Paxos, ZAB, VR, or Raft. Now, we’ll talk about different scales of distributed systems.


  1. Characteristic latencies

Review
We discussed three characteristic scales for distributed systems: multicore processors coupled with a synchronous network, computers linked by a LAN, and datacenters linked by the internet or dedicated fiber. CPU consequences are largely performance concerns: knowing how to minimize coordination. On LANs, latencies are short enough for many network hops before users take notice. In geographically replicated systems, high latencies drive eventually consistent and datacenter-pinned solutions.


  1. Common distributed systems

Review
We use data structure stores as outsourced heaps: they’re the duct tape of distributed systems. KV stores and relational databases are commonly deployed as systems of record; KV stores use independent keys and are not well-suited to relational data, but offer improved scalability and partial failure vs SQL stores, which offer rich queries and strong transactional guarantees. Distributed search and coordination services round out our basic toolkit for building applications. Streaming systems are applied for continuous, low-latency processing of datasets, and tend to look more like frameworks than databases. Their dual, distributed queues, focus on the messages rather than the transformations.


  1. A Pattern Language

Review
When possible, try to use a single node instead of a distributed system. Accept that some failures are unavoidable: SLAs and apologies can be cost-effective. To handle catastrophic failure, we use backups. To improve reliability, we introduce redundancy. To scale to large problems, we divide the problem into shards. Immutable values are easy to store and cache, and can be referenced by mutable identities, allowing us to build strongly consistent systems at large scale. As software grows, different components must scale independently, and we break out libraries into distinct services. Service structure goes hand-in-hand with teams.


  1. Production Concerns

Review
Running distributed systems requires cooperation between developers, QA, and operations engineers. Static analysis, and a test suite including example- and property-based tests, can help ensure program correctness, but understanding production behavior requires comprehensive instrumentation and alerting. Mature distributed systems teams often invest in tooling: traffic shadowing, versioning, incremental deploys, and feature flags. Finally, queues require special care.


题目

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 / 缓存容量 / );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4

原题地址 https://leetcode-cn.com/problems/lru-cache

解题思路

思路1

使用linkedhashmap实现,问题在于初始大小小于16,默认会用16,后面的大小是2^n,需要自己控制大小,
容量超过最大,需要用迭代取最老的值删除

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
class LRUCache {
private LinkedHashMap<Integer,Integer> map;
private int capacity;

public LRUCache(int capacity) {
map = new LinkedHashMap<Integer,Integer>(capacity,0.75f,true);
this.capacity = capacity;
}

public int get(int key) {
Integer value = map.get(key);
if(value == null){
return -1;
}else{
return value;
}
}

public void put(int key, int value) {
if(map.containsKey(key)){
map.remove(key);
}
if(map.size() >= capacity){
map.remove(map.keySet().iterator().next());
}
map.put(key,value);
}
}

思路2

哈希表+双向链表
一个hashmap存储key,快速访问,capacity容量记录大小,一个head,tail指针用于添加和删除。
注意点:

  1. hashmap是key+Node,不然取不出node。
  2. get方法
  • 空表处理
  • 存在就删除这个结点,放到head上,要判断head和tail为空的情况
  1. put
  • 小于容量,看是否存在,存在删除,直接放在head
  • 等于容量,看是否存在,存在删除,直接放在head
  • 不存在,删除tail,放在head
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
class LRUCache {
private int capacity;
private Map<Integer,Node> map;
private Node head;
private Node tail;

class Node{
int key;
int value;
Node pre;
Node next;
public Node(int key,int value){
this.key = key;
this.value = value;
}
}

public LRUCache(int capacity) {
map = new HashMap<Integer,Node>();
this.capacity = capacity;

}
public int get(int key) {
//空表处理
Node node = map.get(key);
if(node == null){
return -1;
}
//存在就删除这个结点,放到head上,要判断head和tail为空的情况
removeNode(node);
insertHead(node);
return node.value;
}
private void removeNode(Node node){
//存在就删除这个结点,放到head上,要判断head和tail为空的情况
Node pre = node.pre;
Node next = node.next;

if(node == tail && node == head){
head = tail = null;
}else if(node == tail){

pre.next = null;
tail = pre;

}else if(node == head){

next.pre = null;
head = next;

}else{

pre.next = next;
next.pre = pre;
}

map.remove(node.key);

}
private void insertHead(Node node){
//插入头部要判断head的空问题
if(head == null){
head = node;
tail = node;
}else{
Node oldHead = head;
node.next = oldHead;
oldHead.pre = node;
head = node;
}
map.put(node.key,node);

}
private void removeTail(){

if(tail == null){
return;
}

map.remove(tail.key);
if(tail.pre == null){
tail = head = null;
}else{
tail = tail.pre;
tail.next = null;
}

}
public void put(int key, int value) {
Node node = map.get(key);

if(map.size() < capacity){
if(node != null){
removeNode(node);
}
Node newNode = new Node(key,value);
insertHead(newNode);
map.put(key,newNode);
}else if(map.size() == capacity){
if(node != null){
removeNode(node);
insertHead(new Node(key,value));
}else{
removeTail();
Node newNode = new Node(key,value);
insertHead(newNode);
}
}
//小于容量,看是否存在,存在删除,直接放在head
//等于容量,看是否存在,存在删除,直接放在head
//不存在,删除tail,放在head

}
}

曾经错误点

1.头尾都是一个结点删除结点错误

思路3

哈希表,+带前后哨兵的双向链表,为了简化头尾空的判断。思路2的主要容易错的点是头尾空的情况,那是不是可以用哨兵简化判断,都头尾都是空的结点,没有实际值就不会出现判断空值的特殊情况。

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
class LRUCache {
private int capacity;
private Map<Integer,Node> map;
//dummy
private Node head;
private Node tail;

class Node{
int key;
int value;
Node pre;
Node next;
public Node(int key, int value){
this.key = key;
this.value = value;
}
public Node(){

}
}

public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<Integer,Node>();
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
}

public int get(int key) {
Node node = map.get(key);
if(node == null){
return -1;
}
removeNode(node);
insertHeadNode(node);
return node.value;
}
public void put(int key, int value) {
int size = map.size();
Node node = map.get(key);
if(node == null){
if(size == capacity){
removeTail();
}
}else{

removeNode(node);

}
Node newNode = new Node(key,value);
insertHeadNode(newNode);
}
private void removeTail(){
Node preNode = tail.pre;

if(preNode == head){
return;
}
Node node = preNode.pre;
node.next = tail;
tail.pre = node;
map.remove(preNode.key);
}
private void removeNode(Node node){
Node pre = node.pre;
Node next = node.next;
map.remove(node.key);

pre.next = next;
next.pre = pre;
}
private void insertHeadNode(Node node){
Node oldNext = head.next;

node.next = oldNext;
head.next = node;

node.pre = head;
oldNext.pre = node;

map.put(node.key,node);
}
}

曾经错误点

1.在put方法内,已经存在的值,重新put的时候,出现了错误,这个时候,不管容量是不是达到最大,也都是要删除该结点重新插入。这个很难发现,测试用例是30上的那个发现的,我几乎不能人力复现,测试用例真的很重要,要是我自己构建测试用例,估计很难发现这个问题。这也是我一直缺失的质量问题。


总结

可以效仿linkedHashmap去实现,它已经预设了实现lru的方法,使用双线链表方便删除与添加,使用hashmap快速查找是否存储,时间复杂度是O(1),空间复杂度是O(n),双向链表和hash表还是比较耗费空间的,但也在允许范围内。哨兵可以简化头尾的判断,减少空值的情况,但会多耗费2个空间。

原文地址 https://www.slideshare.net/jboner/scalability-availability-stability-patterns

这个ppt总结真的很全面,从可扩展性,可用性,稳定性,3个方面分别讲了达到每个目的,以及有什么手段,什么方法来解决这个问题。
没看文章前,我是有一些零碎的概念的,但看了文章后,我突然有一些理解分布式了,架构的目的是达到现实目标,要达到哪方面目的,就用哪些技术。(第一遍读还有很多,不懂的地方,先做一个粗略的总结)

可用性(availability)

达到可用性,有2种手段,故障转移(Fail-over)和冗余备份(Replication)

Fail-over

故障转移,表面上看是只要有失败就转移到其他正常的服务上去,然后再回写同步数据。但实际情况要复杂的多,可能出现各种失败的情况。

Replication

备份分为几种:master-slave,tree-replication,master-master,Buddy replication

同步方式:
active replication:push
passive replication: pull

稳定性(stability)

Timeouts

保证每个调用都有超时时间,不会无限期假死。

Circuit Breaker

将失败作为一种正常的状态,而不是维护正常的状态。出现异常,尽早发现,采取其他处理。

Let-it-crash

如果服务出现异常,不用尽力恢复,尽早发现,及时止损。

Fail-fast

跟上一个有点像

Bulkheads

有点像线程池,将一些资源放在一个池子里,与其他进行隔离,即使出问题,也不会导致全局卡死,也保护了资源。

Steady State

清理状态,时刻保持可用。

Throttling

限流,比如控制请求的数量

可扩展性(scalability)

可扩展性主要是为了管理过载,分两种内容:状态与行为。
状态:分布式缓存,数据网格,服务状态,HTTP缓存,CAP,同步,分区,备份
行为:计算网格,事件驱动架构,负载均衡,并行计算

对于可扩展的一般的建议
默认不可变
引用透明(函数式编程)
懒加载
考虑数据存储

权衡

性能 vs 可扩展

如何确定性能问题
单用户很慢,
如何确定可扩展问题
单用户很快,压力大就慢

延迟 vs 吞吐

最大的吞吐量,加上可接受的延迟

可用性 vs 一致性

CAP
consistency
availability
Partition
ACID
Atomic
Consistency
Isolated
Durable

什么时候需要强一致性,什么时候需要最终一致性

Status(状态管理)

Partitioning,
HTTP caching:reverse proxy,cdn,
,Data Grids,
Service of record:RDBMS sharing,NOSQL(key-value,Column,document,graph,datastructure)
Distrbuted caching:write-through,write-behind,eviction policites,replication,peer to peer
Concurrency:Shared-state, message-passing,dataflow,software transactional memory

behavior(行为管理)

Event-Driven Archietecture(
Domian events,Event sourcing, command and Query Responsiblity Segregation(CQRS)pattern,
Event Stream Processing,Messaging(Publish-Subscribe,Point-to-point,Store-forward,Request-Reply),Enterprice Service Bus ,Actors,Enterprice Integration Architecture(EIA)

Compute Grids
Load-balancing
Parallel computing