diff --git a/2016/09/27/2016-9-27-leetcode-200/index.html b/2016/09/27/2016-9-27-leetcode-200/index.html index 89268585..dadf4981 100644 --- a/2016/09/27/2016-9-27-leetcode-200/index.html +++ b/2016/09/27/2016-9-27-leetcode-200/index.html @@ -26,9 +26,9 @@ + - @@ -68,7 +68,7 @@ - + @@ -346,12 +346,12 @@
同事写R程序的时候,问我能不能获取一个变量的name
,
+我说这个好办啊,在R里可以这样写,用quote()
> a <- 1
+> quote(a)
+a
+
+不过我把问题想简单了,他实际需要的是要获得传入
+参数的name
。例如定义一个函数foo(c)
, 给它传入参数a
,我能够在函数
+内部知道传入参数的名字a
。
我说可以增加一个参数嘛。把参数的名字直接传入。
+> foo(a,"a")
+
+但是我立刻意识到,这里存在重复,而写代码是提倡
+do not repeat yourself
的。而且对R这种可以随意获取环境变量的高级语言。
+肯定有办法做到。
但是我还是先看看Python吧,毕竟最近用Python
+多一些。Google关键字python print variable name and value
,立刻可以获得
+答案。这段代码的思想也很直白,就是对输入的obj在环境变量(名称,对象)键值对
+中查找,返回名称。
def namestr(obj, namespace=globals()):
+ return [name for name in namespace if namespace[name] is obj]
+
+这样可以这样使用
+>>> a = 1
+>>> namestr(a)
+['a']
+
+那么R肯定也有办法做到。Google关键字
+r print variable name and value
myfunc <- function(v1) {
+ deparse(substitute(v1))
+}
+
+myfunc(foo)
+[1] "foo"
+
+真是很不错啊。把结果告诉了同事,他也很高兴。
+但是substitute()
和deparse()
究竟是啥意思呢?在R里输入?substitute
,可以
+看到解释。
++substitute returns the parse tree for +the (unevaluated) expression expr, substituting any variables bound in env.
+
够晦涩难懂的吧。不过仔细思考一下,也大概明白了。
+在R里,从语法角度来说,所有都是expr
,(这跟其他语言都是statement
不同)
+在R的函数里,可以对传入的未进行求值的expr
进行操作。这点非常有趣。
+而deparse()
,比较好理解了至少在我看来,与as.character()
的作用是一样的。
以下Python代码实现对Excel
转存的csv文件进行读取。
|
把csv
文件入库是一件脏活。表面上看csv
文件是一个非常简单的
+逗号分隔符文件。但是其实不然。Excel
转存的csv
文件并不是标准的以逗号作为分隔符,
+并且对所有的项用双引号包裹。现在我就遇到了从Oracle
导出的csv
文件,以上的代码
+不起作用了。
究竟怎么回事呢,找了一圈也没有发现使用pandas.read_csv()
读取
+这种标准csv文件的方法。还是先把问题简化一下,看看Python
的csv模块
是如何读取的吧。
+简单的查找就可以找到答案。
|
好的,pandas.read_csv()
肯定是要调用csv模块的,那么看看
+它的方法参数表吧。
++pandas.read_csv(filepath_or_buffer, sep=’, ‘, delimiter=None, header=’infer’, names=None, index_col=None, usecols=None, squeeze=False, prefix=None, mangle_dupe_cols=True, dtype=None, engine=None, converters=None, true_values=None, false_values=None, skipinitialspace=False, skiprows=None, nrows=None, na_values=None, keep_default_na=True, na_filter=True, verbose=False, skip_blank_lines=True, parse_dates=False, infer_datetime_format=False, keep_date_col=False, date_parser=None, dayfirst=False, iterator=False, chunksize=None, compression=’infer’, thousands=None, decimal=’.’, lineterminator=None, quotechar=’”‘, quoting=0, escapechar=None, comment=None, encoding=None, dialect=None, tupleize_cols=False, error_bad_lines=True, warn_bad_lines=True, skipfooter=0, skip_footer=0, doublequote=True, delim_whitespace=False, as_recarray=False, compact_ints=False, use_unsigned=False, low_memory=True, buffer_lines=None, memory_map=False, float_precision=None)[source]¶
+
真是够长的,还是搜一下有没有dialect=
,呵呵,果然有。
++dialect : str or csv.Dialect instance, default None
+
+ If None defaults to Excel dialect. Ignored if sep longer than 1 char See csv.Dialect documentation for more details
那么问题解决了。把第一段代码改改吧。
+
|
一运行还是报错,这是怎么回事呢,编码换成utf8
,也不行。最后才发现
+需要使用gb18030
才行。即使使用chardet
的编码探测模块,也不一定能探测出来,因为整个文档
+只有少数字符是超出了gbk
,所以不可能既高效又准确的解决这个问题。
|
同事写R程序的时候,问我能不能获取一个变量的name
,
-我说这个好办啊,在R里可以这样写,用quote()
> a <- 1
-> quote(a)
-a
-
-不过我把问题想简单了,他实际需要的是要获得传入
-参数的name
。例如定义一个函数foo(c)
, 给它传入参数a
,我能够在函数
-内部知道传入参数的名字a
。
我说可以增加一个参数嘛。把参数的名字直接传入。
-> foo(a,"a")
-
-但是我立刻意识到,这里存在重复,而写代码是提倡
-do not repeat yourself
的。而且对R这种可以随意获取环境变量的高级语言。
-肯定有办法做到。
但是我还是先看看Python吧,毕竟最近用Python
-多一些。Google关键字python print variable name and value
,立刻可以获得
-答案。这段代码的思想也很直白,就是对输入的obj在环境变量(名称,对象)键值对
-中查找,返回名称。
def namestr(obj, namespace=globals()):
- return [name for name in namespace if namespace[name] is obj]
-
-这样可以这样使用
->>> a = 1
->>> namestr(a)
-['a']
-
-那么R肯定也有办法做到。Google关键字
-r print variable name and value
myfunc <- function(v1) {
- deparse(substitute(v1))
-}
-
-myfunc(foo)
-[1] "foo"
-
-真是很不错啊。把结果告诉了同事,他也很高兴。
-但是substitute()
和deparse()
究竟是啥意思呢?在R里输入?substitute
,可以
-看到解释。
--substitute returns the parse tree for -the (unevaluated) expression expr, substituting any variables bound in env.
-
够晦涩难懂的吧。不过仔细思考一下,也大概明白了。
-在R里,从语法角度来说,所有都是expr
,(这跟其他语言都是statement
不同)
-在R的函数里,可以对传入的未进行求值的expr
进行操作。这点非常有趣。
-而deparse()
,比较好理解了至少在我看来,与as.character()
的作用是一样的。
O(N*logN)
,接下来的处理也是O(N*logN)
。
]]>
以下Python代码实现对Excel
转存的csv文件进行读取。
|
把csv
文件入库是一件脏活。表面上看csv
文件是一个非常简单的
-逗号分隔符文件。但是其实不然。Excel
转存的csv
文件并不是标准的以逗号作为分隔符,
-并且对所有的项用双引号包裹。现在我就遇到了从Oracle
导出的csv
文件,以上的代码
-不起作用了。
究竟怎么回事呢,找了一圈也没有发现使用pandas.read_csv()
读取
-这种标准csv文件的方法。还是先把问题简化一下,看看Python
的csv模块
是如何读取的吧。
-简单的查找就可以找到答案。
|
好的,pandas.read_csv()
肯定是要调用csv模块的,那么看看
-它的方法参数表吧。
--pandas.read_csv(filepath_or_buffer, sep=’, ‘, delimiter=None, header=’infer’, names=None, index_col=None, usecols=None, squeeze=False, prefix=None, mangle_dupe_cols=True, dtype=None, engine=None, converters=None, true_values=None, false_values=None, skipinitialspace=False, skiprows=None, nrows=None, na_values=None, keep_default_na=True, na_filter=True, verbose=False, skip_blank_lines=True, parse_dates=False, infer_datetime_format=False, keep_date_col=False, date_parser=None, dayfirst=False, iterator=False, chunksize=None, compression=’infer’, thousands=None, decimal=’.’, lineterminator=None, quotechar=’”‘, quoting=0, escapechar=None, comment=None, encoding=None, dialect=None, tupleize_cols=False, error_bad_lines=True, warn_bad_lines=True, skipfooter=0, skip_footer=0, doublequote=True, delim_whitespace=False, as_recarray=False, compact_ints=False, use_unsigned=False, low_memory=True, buffer_lines=None, memory_map=False, float_precision=None)[source]¶
-
真是够长的,还是搜一下有没有dialect=
,呵呵,果然有。
--dialect : str or csv.Dialect instance, default None
-
- If None defaults to Excel dialect. Ignored if sep longer than 1 char See csv.Dialect documentation for more details
那么问题解决了。把第一段代码改改吧。
-
|
一运行还是报错,这是怎么回事呢,编码换成utf8
,也不行。最后才发现
-需要使用gb18030
才行。即使使用chardet
的编码探测模块,也不一定能探测出来,因为整个文档
-只有少数字符是超出了gbk
,所以不可能既高效又准确的解决这个问题。
|
list
而是使用一个自己创建的链表,那么插入
_heapify_max()
但是却没有_heappushpop_max
这样的方法。真是无语啊。
]]>
Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.
+
+Find the maximum coins you can collect by bursting the balloons wisely.
+
+examples:
+nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
+coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
+
+
+
+遍历回溯法,建立一个集合,把遍历过的数字放在该集合里。算法的复杂度是O(n!)。解法如下
+
|
以上的方法无疑会超时,但是它是我们理解问题,验证其它算法的基础。
+我们希望能够得到一个多项式时间的解法,按照以往的经验,我们如果能够找到一个贪心算法是最好的,
+但是如果我们能够有一个贪心算法,那么也一定能够有一个动态规划方法。CRLS
对贪心和动态规划做了清楚的
+描述,核心都是要找到最优子结构,并且证明存在最优子结构——这其实也是最难的部分,因为动态规划方法从来只是一个思想,本质上不过是 递归的一种优化而已
。
CRLS
中在动态规划一章中用了几个例子来详细展示动态规划方法。其中第二个例子就是矩阵链问题————如果有一个
+矩阵链A1*A2*A3*A4
,其中A1是1x5的矩阵,A2是5x1的矩阵,A3是1x5的矩阵,A4是5x1的矩阵。那么先计算A1*A2
和A3*A4
+则是比较好的,代价较小都是1x5x1=5
。而A2*A3
则是比较糟糕的,因为A2*A3
的计算代价是5x1x5=25
。
++对给定的两个矩阵相乘
+A_ik*A_kj
, +计算的代价是i*k*j
。
矩阵链中计算的代价是三个数相乘,而我们这里也是三个数字相乘。对于三个气球(数字)5·1·5
,如果戳破第一个气球(数字),则得分(代价)是1x5x1,如果戳破第二个气球(数字)
+得分(代价)是5x1x5,戳破第三个气球(数字)得分(代价)是1x5x1。对于1x5x1可以转换为1x5的矩阵和5x1的矩阵的代价。
+5x1x5,可以转换为5x1的矩阵和1x5的矩阵的代价。最终,三个数字5·1·5
可以等价转换为求解四个矩阵
+A1*A2*A3*A4
的最大计算代价的问题。所以第一步就是把n个数字转换为n+1个矩阵表示,第二步就是把矩阵链问题的递归求解中
+的min改成max。如果这两个问题是等价的,那么我们的解法一定可以通过测试。并且似乎也不用去证明了。(其实这是最难的部分。)
以下是通过的memoization
解法,算法的复杂度是O(n^2),与矩阵链问题的复杂度一样:
|
list
而是使用一个自己创建的链表,那么插入
Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.
-
-Find the maximum coins you can collect by bursting the balloons wisely.
+ [leetcode 329]Longest Increasing Path in a Matrix 原创解法
+ /2017/03/12/2017-3-12-leetcode-329/
+ 题目概述
+Given an integer matrix, find the length of the longest increasing path
+From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).
-examples:
-nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
-coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
+example:
+nums = [
+ [9,9,4],
+ [6,6,8],
+ [2,1,1]
+]
+return 4
- 简单直接的解法
遍历回溯法,建立一个集合,把遍历过的数字放在该集合里。算法的复杂度是O(n!)。解法如下
-class Solution1(object):
def maxCoins(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
path = []
max_path = [path]
max = [0]
s = [0]
D = set()
self.find(nums, path, D, s, max, max_path)
print(max_path[0])
return max[0]
def find(self, nums, path, D, s, max, max_path):
if len(path) == len(nums):
if s[0] > max[0]:
max[0] = s[0]
max_path[0] = path[:]
else:
for i in range(len(nums)):
if i not in D:
path.append(i)
D.add(i)
tmp = self.compute(i, nums, D)
s[0] += tmp
self.find(nums, path, D, s, max, max_path)
path.pop()
D.remove(i)
s[0] -= tmp
def compute(self, i, nums, D):
left, right = 1, 1
for j in range(i - 1, -1, -1):
if j not in D:
left = nums[j]
break
for k in range(i + 1, len(nums)):
if k not in D:
right = nums[k]
break
return left * right * nums[i]
+ 回溯法
首先想到的就是回溯法(backtracking)。思路也很简单,就是遍历每个位置,
+并以此位置的数字作为开始,向四个方向进行回溯遍历。最终找到最长的序列。
+以下代码的36行和38行进行回溯。不过毫无疑问超时了。在这个基础上还可以
+进一步优化,比如只对in-degree为0的位置的数字进行考察。但是还是不行。
+class Solution(object):
def longestIncreasingPath(self, matrix):
"""
回溯法解法。
:type matrix: List[List[int]]
:rtype: int
"""
if matrix:
hight = len(matrix)
width = len(matrix[0])
s = set((u, v) for u in range(hight) for v in range(width))
result = [0]
for item in s:
p = [item]
self.find(matrix, item, p, result, s)
return result[0]
else:
return 0
def find(self, matrix, item, p, result, s):
if self.has_no_choice(matrix, item, p, s):
if (len(p) > result[0]):
result[0] = len(p)
else:
u, v = item
four = [(u + 1, v), (u - 1, v), (u, v + 1), (u, v - 1)]
for one in four:
x, y = one
if (x, y) in s and (x, y) not in p and matrix[x][y] < matrix[u][v]:
p.append((x, y))
self.find(matrix, (x, y), p, result, s)
p.pop()
def has_no_choice(self, matrix, item, p, s):
u, v = item
four = [(u + 1, v), (u - 1, v), (u, v + 1), (u, v - 1)]
for one in four:
x, y = one
if one in s and one not in p and matrix[x][y] < matrix[u][v]:
return False
return True
- 回顾矩阵链解法
以上的方法无疑会超时,但是它是我们理解问题,验证其它算法的基础。
-我们希望能够得到一个多项式时间的解法,按照以往的经验,我们如果能够找到一个贪心算法是最好的,
-但是如果我们能够有一个贪心算法,那么也一定能够有一个动态规划方法。CRLS
对贪心和动态规划做了清楚的
-描述,核心都是要找到最优子结构,并且证明存在最优子结构——这其实也是最难的部分,因为动态规划方法从来只是一个思想,本质上不过是 递归的一种优化而已
。
-CRLS
中在动态规划一章中用了几个例子来详细展示动态规划方法。其中第二个例子就是矩阵链问题————如果有一个
-矩阵链A1*A2*A3*A4
,其中A1是1x5的矩阵,A2是5x1的矩阵,A3是1x5的矩阵,A4是5x1的矩阵。那么先计算A1*A2
和A3*A4
-则是比较好的,代价较小都是1x5x1=5
。而A2*A3
则是比较糟糕的,因为A2*A3
的计算代价是5x1x5=25
。
-
-对给定的两个矩阵相乘A_ik*A_kj
,
-计算的代价是i*k*j
。
-
- 等价转换
矩阵链中计算的代价是三个数相乘,而我们这里也是三个数字相乘。对于三个气球(数字)5·1·5
,如果戳破第一个气球(数字),则得分(代价)是1x5x1,如果戳破第二个气球(数字)
-得分(代价)是5x1x5,戳破第三个气球(数字)得分(代价)是1x5x1。对于1x5x1可以转换为1x5的矩阵和5x1的矩阵的代价。
-5x1x5,可以转换为5x1的矩阵和1x5的矩阵的代价。最终,三个数字5·1·5
可以等价转换为求解四个矩阵
-A1*A2*A3*A4
的最大计算代价的问题。所以第一步就是把n个数字转换为n+1个矩阵表示,第二步就是把矩阵链问题的递归求解中
-的min改成max。如果这两个问题是等价的,那么我们的解法一定可以通过测试。并且似乎也不用去证明了。(其实这是最难的部分。)
-以下是通过的memoization
解法,算法的复杂度是O(n^2),与矩阵链问题的复杂度一样:
-class Solution2(object):
def maxCoins(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
list = self.get(nums) # 转换为n+1个矩阵的表示。每个pair tuple对应矩阵的长和宽。
print(list)
n += 1
m = [[None] * n for i in range(n)] # 声明一个子问题空间是n^2的记忆体
print(m)
for i in range(n):
m[i][i] = 0
return self.recursive(list, m, 0, n - 1)
def get(self, nums):
n = len(nums)
list = [(1, nums[0])]
for i in range(n):
if (i + 1 < n):
list.append((nums[i], nums[i + 1]))
else:
list.append((nums[i], 1))
return list
def recursive(self, list, m, i, j):
if m[i][j] is not None:
return m[i][j]
else:
max = -1
for k in range(i, j):
a = self.recursive(list, m, i, k)
b = self.recursive(list, m, k + 1, j)
v = a + b + list[i][0] * list[k][1] * list[j][1]
if v > max :
max = v
m[i][j] = max
return m[i][j]
+ 拓扑排序和记忆体
看看leetcode会给我们什么提示呢?点开show tags
可以看到这个问题的标签,
+Topological Sort
和Memoization
。既然使用topsort那么肯定要把这个问题
+转化为DAG问题了。如此一来,令人想到,可以先把矩阵数字使用topsort排成
+一排,然后再利用曾经遇到的求解最长递增子序列的动态规划算法进行求解。
+以下是实现的代码,拓扑排序加动态规划。
+class Solution(object):
def longestIncreasingPath(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: int
"""
if matrix:
hight = len(matrix)
width = len(matrix[0])
s = set((u, v) for u in range(hight) for v in range(width))
G = self.create_graph(matrix, s)
topsort_list = self.topsort(G)
result = self.find_longest_path(G, topsort_list)
return result
else:
return 0
def create_graph(self, matrix, s):
G = {}
for item in s:
u, v = item
G[item] = set()
center = matrix[u][v]
four = [(u + 1, v), (u - 1, v), (u, v + 1), (u, v - 1)]
for one in four:
if one in s:
x, y = one
adjacent = matrix[x][y]
if center > adjacent:
G[item].add(one)
return G
def tr(self, G):
H = {}
for u in G:
H[u] = 0
for u in G:
for v in G[u]:
H[v] += 1
return H
def topsort(self, G):
H = self.tr(G)
q = deque()
sorted_list = []
for u in H :
if H[u] == 0 :
q.append(u)
while q:
u = q.popleft()
sorted_list.append(u)
for v in G[u]:
H[v] -= 1
if H[v] == 0 :
q.append(v)
return sorted_list
def find_longest_path(self, G, topsort_list):
n = len(topsort_list)
memo = [1] * n
for i in range(1, n):
node = topsort_list[i]
value = 0
for pre in range(0, i):
pre_node = topsort_list[pre]
if node in G[pre_node]:
value = max(value, memo[pre] + 1)
else:
value = max(value, memo[pre])
memo[i] = value
result = max(memo)
#print(memo)
return result
+
+ 进一步优化
上边的解法还是超时了,时间不会消耗在topsort上,因为这个方法是O(|V|+|E|)
的。
+主要的时间花在了动态规划上,因为这是一个O(N^2)
的算法。
+我们仔细看看第72到76行这个内循环,发现遍历所有当前节点之前的节点是无效的。
+因为可以进入当前节点的节点不会超过4个。所以我们可以修改一下这个内循环。
+def tr2(self, G):
H = {}
for u in G:
H[u] = set()
for u in G:
for v in G[u]:
H[v].add(u)
return H
def find_longest_path(self, G, topsort_list):
n = len(topsort_list)
dic = { node : 1 for node in topsort_list }
H = self.tr2(G)
s = {topsort_list[0]}
for i in range(1, n):
node = topsort_list[i]
s.add(node)
value = 1
for pre_node in H[node] :
if pre_node in s :
value = max(value, dic[pre_node] + 1)
dic[node] = value
result = max(dic.values())
return result
+
+这里我们在16行使用一个dict来代替之前的memo,
+这样就可以把一个O(N^2)
改成一个O(N)
的了。
+测试一下,幸运的通过了。不过只打败了1.7%
。
]]>
+ python
leetcode
算法
- python
我的Grails
的环境是
grails -v
-| Grails Version: 3.2.6
-| Groovy Version: 2.4.7
-| JVM Version: 1.8.0_71
-
+ Java世界里构建项目用什么工具呢,ant,maven和gradle。maven非常流行, +原因无非是maven仓库和项目架构的约定。但是ant构建过程更加容易理解, +因为ant展示了所有的操作。gradle拥有基于groovy语言的DSL语言且继承了 +maven仓库的思想所以笔者认为未来是属于gradle的。
-grails 3.2.6是用gradle进行构建的。所以如果要添加java类,
-就需要修改build.gradle
。
创建src/main/java
目录。对于com.yanggeorge.XMLtest
类,
-则要创建src/main/java/com/yanggeorge/
目录,并把XMLtest.java放在该
-路径下。
在build.gradle
文件中添加如下代码
|
编译XMLtest.java。可以用grails compile
进行编译。
D:\work\grails>grails compile
-:compileJava UP-TO-DATE
-:compileGroovy UP-TO-DATE
-:buildProperties
-:processResources UP-TO-DATE
-:classes UP-TO-DATE
-
-BUILD SUCCESSFUL
-
-Total time: 23.638 secs
-D:\work\grails>
+特别是最近在搭建springmvc-jpa-mysql开发框架的时候,发现怎么也找不到一个
+archetype可以做到一键构建demo project。
+ 何谓一键构建
何谓一键构建呢,有人用maven也可以很快搭建一个,通常可以把之前的
+项目复制一份修改一下,似乎没有那么麻烦。但笔者还是觉得执行几个命令,
+回车一下就构建完成,更加让人舒心。如图所示,创建一个空项目dir,然后把build.gradle
+放入该路径下,执行
+gradle init
+gradle build -x test
+gradle run
- 第四步
修改grails-app/conf/spring/resources.groovy
-import com.yanggeorge.XMLtest
beans = {
myXMLtest(XMLtest)
}
-
- 第五步
已经可以使用myXMLtest
了,例如创建一个service,grails-app/services/rss/RssService.groovy
-第6行就是依赖注入的bean。
-import grails.transaction.Transactional
@Transactional
class RssService {
def myXMLtest
def serviceMethod(String url, String keyword) {
def items = myXMLtest.getAllItems(url)
}
}
-
+就可以在浏览器里访问http://localhost:8080/greeting?name=GoodJob
链接了。
+以下是过程展示。当然了因为jar包都已经下载过了,所以似乎还比较快。
+否则的话一定会联网下载jar包的。
+
+以下导入IDEA中后所示的文件结构。
+
+ build.gradle文件内容
build.gradle
构建基于spring-boot的springmvc-jpa-mysql框架demo项目的脚本如下。
+该脚本一目了然,无需赘述。
+def createFile = { String path, String content ->
File f = file(path)
if (f.exists()) {
f.delete()
}
f.withWriter("UTF-8") { writer ->
writer.write(content)
}
}
task init(type: InitBuild, dependsOn : ["bak-build-file", "create-dirs", "create-files"]) {
doLast {
String buildGradleContent = '''
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
}
}
apply plugin: 'java\'
apply plugin: 'eclipse\'
apply plugin: 'idea\'
apply plugin: 'application\'
apply plugin: 'org.springframework.boot\'
jar {
baseName = 'spring-boot-demo\'
version = '0.1.0\'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web") {
exclude module: "spring-boot-starter-tomcat"
}
compile("org.springframework.boot:spring-boot-starter-jetty")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-devtools")
compile("mysql:mysql-connector-java")
compile("org.apache.commons:commons-lang3:3.5")
testCompile("junit:junit")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
mainClassName = "hello.Application"
'''
createFile("build.gradle", buildGradleContent)
}
}
task "bak-build-file"(type: Copy) {
from "build.gradle"
into "build.gradle.bak"
}
task "create-dirs" {
description "create spring-boot-jpa-test project dirs."
doLast {
file("src/main/java/hello").mkdirs()
file("src/main/resources/templates").mkdirs()
file("src/main/webapp/WEB-INF/classes").mkdirs()
file("src/test/java/hello").mkdirs()
file("src/test/resources").mkdirs()
}
}
task "create-files" {
description "create spring-boot-jpa-test related files."
doLast {
String ApplicationContent, HelloControllerContent, UserRepositoryContent, UserContent, GreetingControllerContent
String ApplicationTestsContent, ServletInitializerContent, HelloControllerTestContent
String applicationPropertiesContent
String webXmlContent, indexJspContent, greetingHtmlContent
ApplicationContent = '''
package hello;
import java.util.Arrays;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
//System.out.println(beanName);
}
};
}
}
'''
HelloControllerContent = '''
package hello;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
public class HelloController {
@RequestMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
}
'''
UserRepositoryContent = '''
package hello;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
/**
* Created by YM on 5/16/2017.
*/
public interface UserRepository extends JpaRepository<User, Long> {
User findByName(String name);
User findByNameAndAge(String name, Integer age);
@Query("from User u where u.name=:name")
User findUser(@Param("name") String name);
}
'''
UserContent = '''
package hello;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* Created by YM on 5/16/2017.
*/
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public Integer getAge() {
return age;
}
public String getName() {
return name;
}
public Long getId() {
return id;
}
public void setAge(Integer age) {
this.age = age;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
'''
ApplicationTestsContent = '''
import hello.Application;
import hello.User;
import hello.UserRepository;
import java.util.Date;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Created by YM on 5/16/2017.
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ApplicationTests {
@Autowired
private UserRepository userRepository;
@Test
public void test() throws Exception {
// 创建10条记录
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));
// 测试findAll, 查询所有记录
Assert.assertEquals(10, userRepository.findAll().size());
// 测试findByName, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());
// 测试findUser, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());
// 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User
Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());
// 测试删除姓名为AAA的User
userRepository.delete(userRepository.findByName("AAA"));
// 测试findAll, 查询所有记录, 验证上面的删除是否成功
Assert.assertEquals(9, userRepository.findAll().size());
}
}
'''
GreetingControllerContent = '''
package hello;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class GreetingController {
@RequestMapping("/greeting")
public String greeting(@RequestParam(value = "name", required = false, defaultValue = "World") String name,
Model model) {
model.addAttribute("name", name);
return "greeting";
}
}
'''
ServletInitializerContent = '''
package hello;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
/**
* Created by YM on 5/16/2017.
*/
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(hello.Application.class);
}
}
'''
HelloControllerTestContent = '''
package hello;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import java.net.URL;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerTest {
@LocalServerPort
private int port;
private URL base;
@Autowired
private TestRestTemplate template;
@Before
public void setUp() throws Exception {
this.base = new URL("http://localhost:" + port + "/");
}
@Test
public void getHello() throws Exception {
ResponseEntity<String> response = template.getForEntity(base.toString(),
String.class);
assertThat(response.getBody(), equalTo("Greetings from Spring Boot!"));
}
}
'''
createFile("src/main/java/hello/Application.java", ApplicationContent)
createFile("src/main/java/hello/HelloController.java", HelloControllerContent)
createFile("src/main/java/hello/UserRepository.java", UserRepositoryContent)
createFile("src/main/java/hello/User.java", UserContent)
createFile("src/main/java/hello/GreetingController.java", GreetingControllerContent)
createFile("src/main/java/hello/ServletInitializer.java", ServletInitializerContent)
createFile("src/test/java/ApplicationTests.java", ApplicationTestsContent)
createFile("src/test/java/hello/HelloControllerTest.java", HelloControllerTestContent)
applicationPropertiesContent = '''
spring.datasource.driverClassName=
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.hbm2ddl.auto=create
'''
createFile("src/main/resources/application.properties", applicationPropertiesContent)
println "Please modify src/main/resources/application.properties"
webXmlContent = '''
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
'''
indexJspContent = '''
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
'''
greetingHtmlContent = '''
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Serving Web Content</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>
'''
createFile("src/main/webapp/WEB-INF/web.xml", webXmlContent)
createFile("src/main/webapp/index.jsp", indexJspContent)
createFile("src/main/resources/templates/greeting.html", greetingHtmlContent)
}
}
]]>
Given an integer matrix, find the length of the longest increasing path
-From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).
-
-example:
-nums = [
- [9,9,4],
- [6,6,8],
- [2,1,1]
-]
-return 4
-
-
-
-首先想到的就是回溯法(backtracking)。思路也很简单,就是遍历每个位置, -并以此位置的数字作为开始,向四个方向进行回溯遍历。最终找到最长的序列。 -以下代码的36行和38行进行回溯。不过毫无疑问超时了。在这个基础上还可以 -进一步优化,比如只对in-degree为0的位置的数字进行考察。但是还是不行。
-
|
看看leetcode会给我们什么提示呢?点开show tags
可以看到这个问题的标签,
-Topological Sort
和Memoization
。既然使用topsort那么肯定要把这个问题
-转化为DAG问题了。如此一来,令人想到,可以先把矩阵数字使用topsort排成
-一排,然后再利用曾经遇到的求解最长递增子序列的动态规划算法进行求解。
-以下是实现的代码,拓扑排序加动态规划。
|
上边的解法还是超时了,时间不会消耗在topsort上,因为这个方法是O(|V|+|E|)
的。
-主要的时间花在了动态规划上,因为这是一个O(N^2)
的算法。
-我们仔细看看第72到76行这个内循环,发现遍历所有当前节点之前的节点是无效的。
-因为可以进入当前节点的节点不会超过4个。所以我们可以修改一下这个内循环。
|
这里我们在16行使用一个dict来代替之前的memo,
-这样就可以把一个O(N^2)
改成一个O(N)
的了。
-测试一下,幸运的通过了。不过只打败了1.7%
。
Java世界里构建项目用什么工具呢,ant,maven和gradle。maven非常流行, -原因无非是maven仓库和项目架构的约定。但是ant构建过程更加容易理解, -因为ant展示了所有的操作。gradle拥有基于groovy语言的DSL语言且继承了 -maven仓库的思想所以笔者认为未来是属于gradle的。
+我的Grails
的环境是
grails -v
+| Grails Version: 3.2.6
+| Groovy Version: 2.4.7
+| JVM Version: 1.8.0_71
+
-特别是最近在搭建springmvc-jpa-mysql开发框架的时候,发现怎么也找不到一个 -archetype可以做到一键构建demo project。
-何谓一键构建呢,有人用maven也可以很快搭建一个,通常可以把之前的 -项目复制一份修改一下,似乎没有那么麻烦。但笔者还是觉得执行几个命令, -回车一下就构建完成,更加让人舒心。如图所示,创建一个空项目dir,然后把build.gradle -放入该路径下,执行
-gradle init
-gradle build -x test
-gradle run
+grails 3.2.6是用gradle进行构建的。所以如果要添加java类,
+就需要修改build.gradle
。
+ 第一步
创建src/main/java
目录。对于com.yanggeorge.XMLtest
类,
+则要创建src/main/java/com/yanggeorge/
目录,并把XMLtest.java放在该
+路径下。
+ 第二步
在build.gradle
文件中添加如下代码
+apply plugin: "java"
task compileOne (type: JavaCompile) {
source = sourceSets.main.java.srcDirs
include 'com/yanggeorge/XMLtest.java'
classpath = sourceSets.main.compileClasspath
destinationDir = sourceSets.main.output.classesDir
}
compileOne.options.compilerArgs = ["-sourcepath", "$projectDir/src/main/java"]
+
+ 第三步
编译XMLtest.java。可以用grails compile
进行编译。
+D:\work\grails>grails compile
+:compileJava UP-TO-DATE
+:compileGroovy UP-TO-DATE
+:buildProperties
+:processResources UP-TO-DATE
+:classes UP-TO-DATE
+
+BUILD SUCCESSFUL
+
+Total time: 23.638 secs
+D:\work\grails>
-就可以在浏览器里访问http://localhost:8080/greeting?name=GoodJob
链接了。
-以下是过程展示。当然了因为jar包都已经下载过了,所以似乎还比较快。
-否则的话一定会联网下载jar包的。
-
-以下导入IDEA中后所示的文件结构。
-
- build.gradle文件内容
build.gradle
构建基于spring-boot的springmvc-jpa-mysql框架demo项目的脚本如下。
-该脚本一目了然,无需赘述。
-def createFile = { String path, String content ->
File f = file(path)
if (f.exists()) {
f.delete()
}
f.withWriter("UTF-8") { writer ->
writer.write(content)
}
}
task init(type: InitBuild, dependsOn : ["bak-build-file", "create-dirs", "create-files"]) {
doLast {
String buildGradleContent = '''
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
}
}
apply plugin: 'java\'
apply plugin: 'eclipse\'
apply plugin: 'idea\'
apply plugin: 'application\'
apply plugin: 'org.springframework.boot\'
jar {
baseName = 'spring-boot-demo\'
version = '0.1.0\'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web") {
exclude module: "spring-boot-starter-tomcat"
}
compile("org.springframework.boot:spring-boot-starter-jetty")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-devtools")
compile("mysql:mysql-connector-java")
compile("org.apache.commons:commons-lang3:3.5")
testCompile("junit:junit")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
mainClassName = "hello.Application"
'''
createFile("build.gradle", buildGradleContent)
}
}
task "bak-build-file"(type: Copy) {
from "build.gradle"
into "build.gradle.bak"
}
task "create-dirs" {
description "create spring-boot-jpa-test project dirs."
doLast {
file("src/main/java/hello").mkdirs()
file("src/main/resources/templates").mkdirs()
file("src/main/webapp/WEB-INF/classes").mkdirs()
file("src/test/java/hello").mkdirs()
file("src/test/resources").mkdirs()
}
}
task "create-files" {
description "create spring-boot-jpa-test related files."
doLast {
String ApplicationContent, HelloControllerContent, UserRepositoryContent, UserContent, GreetingControllerContent
String ApplicationTestsContent, ServletInitializerContent, HelloControllerTestContent
String applicationPropertiesContent
String webXmlContent, indexJspContent, greetingHtmlContent
ApplicationContent = '''
package hello;
import java.util.Arrays;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
//System.out.println(beanName);
}
};
}
}
'''
HelloControllerContent = '''
package hello;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
public class HelloController {
@RequestMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
}
'''
UserRepositoryContent = '''
package hello;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
/**
* Created by YM on 5/16/2017.
*/
public interface UserRepository extends JpaRepository<User, Long> {
User findByName(String name);
User findByNameAndAge(String name, Integer age);
@Query("from User u where u.name=:name")
User findUser(@Param("name") String name);
}
'''
UserContent = '''
package hello;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* Created by YM on 5/16/2017.
*/
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public Integer getAge() {
return age;
}
public String getName() {
return name;
}
public Long getId() {
return id;
}
public void setAge(Integer age) {
this.age = age;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
'''
ApplicationTestsContent = '''
import hello.Application;
import hello.User;
import hello.UserRepository;
import java.util.Date;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Created by YM on 5/16/2017.
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ApplicationTests {
@Autowired
private UserRepository userRepository;
@Test
public void test() throws Exception {
// 创建10条记录
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));
// 测试findAll, 查询所有记录
Assert.assertEquals(10, userRepository.findAll().size());
// 测试findByName, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());
// 测试findUser, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());
// 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User
Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());
// 测试删除姓名为AAA的User
userRepository.delete(userRepository.findByName("AAA"));
// 测试findAll, 查询所有记录, 验证上面的删除是否成功
Assert.assertEquals(9, userRepository.findAll().size());
}
}
'''
GreetingControllerContent = '''
package hello;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class GreetingController {
@RequestMapping("/greeting")
public String greeting(@RequestParam(value = "name", required = false, defaultValue = "World") String name,
Model model) {
model.addAttribute("name", name);
return "greeting";
}
}
'''
ServletInitializerContent = '''
package hello;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
/**
* Created by YM on 5/16/2017.
*/
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(hello.Application.class);
}
}
'''
HelloControllerTestContent = '''
package hello;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import java.net.URL;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerTest {
@LocalServerPort
private int port;
private URL base;
@Autowired
private TestRestTemplate template;
@Before
public void setUp() throws Exception {
this.base = new URL("http://localhost:" + port + "/");
}
@Test
public void getHello() throws Exception {
ResponseEntity<String> response = template.getForEntity(base.toString(),
String.class);
assertThat(response.getBody(), equalTo("Greetings from Spring Boot!"));
}
}
'''
createFile("src/main/java/hello/Application.java", ApplicationContent)
createFile("src/main/java/hello/HelloController.java", HelloControllerContent)
createFile("src/main/java/hello/UserRepository.java", UserRepositoryContent)
createFile("src/main/java/hello/User.java", UserContent)
createFile("src/main/java/hello/GreetingController.java", GreetingControllerContent)
createFile("src/main/java/hello/ServletInitializer.java", ServletInitializerContent)
createFile("src/test/java/ApplicationTests.java", ApplicationTestsContent)
createFile("src/test/java/hello/HelloControllerTest.java", HelloControllerTestContent)
applicationPropertiesContent = '''
spring.datasource.driverClassName=
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.hbm2ddl.auto=create
'''
createFile("src/main/resources/application.properties", applicationPropertiesContent)
println "Please modify src/main/resources/application.properties"
webXmlContent = '''
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
'''
indexJspContent = '''
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
'''
greetingHtmlContent = '''
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Serving Web Content</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>
'''
createFile("src/main/webapp/WEB-INF/web.xml", webXmlContent)
createFile("src/main/webapp/index.jsp", indexJspContent)
createFile("src/main/resources/templates/greeting.html", greetingHtmlContent)
}
}
+ 第四步
修改grails-app/conf/spring/resources.groovy
+import com.yanggeorge.XMLtest
beans = {
myXMLtest(XMLtest)
}
+
+ 第五步
已经可以使用myXMLtest
了,例如创建一个service,grails-app/services/rss/RssService.groovy
+第6行就是依赖注入的bean。
+import grails.transaction.Transactional
@Transactional
class RssService {
def myXMLtest
def serviceMethod(String url, String keyword) {
def items = myXMLtest.getAllItems(url)
}
}
+
]]>
++ + +Given a nested list of integers, implement an iterator to flatten it.
+Each element is either an integer, or a list – whose elements may also be integers or other lists. + Input: [[1,1],2,[1,1]] + Output: [1,1,2,1,1]
+
如果只是遍历一遍,用类似二叉树的中序遍历就好了。但是因为要生成迭代子,那么就得 +考虑用栈来模拟递归了。
+当然这个题目是比较简单的,压栈的是一个tuple-(list, index)。第一个是list,第二个指向 +list的第几个item。
+比较慢。
+
|
之前的解法大概是140ms跑通了44个测试。而大多数的解法则在70ms左右。 +怎么改进呢? +has_next和next方法本质上是一样的,可以只保留一个next就可以实现两个方法了。 +那么我们可以提前跑next方法,缓存两个值。
+以下是改进后的速度提升了一倍。
+
|
-- - -Given a nested list of integers, implement an iterator to flatten it.
-Each element is either an integer, or a list – whose elements may also be integers or other lists. - Input: [[1,1],2,[1,1]] - Output: [1,1,2,1,1]
-
如果只是遍历一遍,用类似二叉树的中序遍历就好了。但是因为要生成迭代子,那么就得 -考虑用栈来模拟递归了。
-当然这个题目是比较简单的,压栈的是一个tuple-(list, index)。第一个是list,第二个指向 -list的第几个item。
-比较慢。
-
|
之前的解法大概是140ms跑通了44个测试。而大多数的解法则在70ms左右。 -怎么改进呢? -has_next和next方法本质上是一样的,可以只保留一个next就可以实现两个方法了。 -那么我们可以提前跑next方法,缓存两个值。
-以下是改进后的速度提升了一倍。
-
|
可以看到,确实是110个1。
+]]> +bt通过种子文件分享已经是一个过去时了,2009年btChina
就已经关闭了。现在一般都是
+使用磁力链接来分享文件。那么为什么种子文件分享不再流行了呢?为什么要用磁力链接呢?
+磁力链接怎么实现的呢?
嗯这是这个系列要研究的问题。但是要研究磁力链接的实现原理,最好先从种子文件开始。
+ + +官网文档 BEP3 中的metainfo files章节 +讲的很清楚。
+简单的说就是把tracker列表和分享的文件信息编码为一个二进制的文件。
+
|
如果想要找到下载源,就要通过tracker找到peer节点。
+包含了文件的大小,分块个数,分块的sha1散列值。
+bt文件的编码逻辑取名为bencoding。
+++Strings are length-prefixed base ten followed by a colon and the string. For example 4:spam corresponds to ‘spam’.
+Integers are represented by an ‘i’ followed by the number in base 10 followed by an ‘e’. For example i3e corresponds to 3 and i-3e corresponds to -3. Integers have no size limitation. i-0e is invalid. All encodings with a leading zero, such as i03e, are invalid, other than i0e, which of course corresponds to 0.
+Lists are encoded as an ‘l’ followed by their elements (also bencoded) followed by an ‘e’. For example l4:spam4:eggse corresponds to [‘spam’, ‘eggs’].
+Dictionaries are encoded as a ‘d’ followed by a list of alternating keys and their corresponding values followed by an ‘e’. For example, d3:cow3:moo4:spam4:eggse corresponds to {‘cow’: ‘moo’, ‘spam’: ‘eggs’} and d4:spaml1:a1:bee corresponds to {‘spam’: [‘a’, ‘b’]}. Keys must be strings and appear in sorted order (sorted as raw strings, not alphanumerics).
+
翻译为eBNF语法呢,就是如下
+
|
根据eBNF实现的解码代码如下, 把get_content()
方法中path替换为种子文件的路径,运行就可以看到。
+返回的解析结果中会有info_hash
,该值是根据info的bencoding的二进制串计算的sha1值。这个值很重要
+因为之后很多协议都会用到。
|
用最近的毒液电影的种子进行解析,打印如下, +其中announce-list就是tracker列表。
+
|
K3s是什么?k8s的精简版。编译之后执行程序大小不到50M。 +可以用在物联网的边缘计算侧。如果想深入了解k8s,那么k3s是个很好的起点。 +那么如果能够断点调试k3s,就更好了。下面我们来看看怎么做。
+ + +GOPATH
,同时把$GOPATH/bin
加入到PATH
|
$GOPATH/bin/dlv
|
k3s
,所在路径是$GOPATH/src/github.com/rancher/k3s/
|
|
$GOPATH
,然后配置增加一个remote调试。在运行之前,在main.go上打一个断点。运行远程调试之后,成功。 +
+]]>实现一个简单的BT种子嗅探器才算是有点实际价值的吧。这样就可以把种子的metadata信息 +缓存下来,提供按照文件名进行检索。
+ + +不过这里只是实现一个demo,有兴趣的话可以看看github上的dht项目。
+简单的说,就是把嗅探节点加入到其他节点的路由表中,等待其他节点发来的announce_peer请求,然后获取种子的metadata信息。
+我们根据以下的代码具体说明。
+89行代码,向路由节点发送find_node
请求。这些路由节点。就是25行代码的3个地址。
+这样做的意义有两个,一方面,路由节点会把我们的节点加入到他们的路由表中。另一方面,路由节点会返回一个好节点列表。
252行代码,启动了6个线程。
+线程 | +说明 | +
---|---|
listener | +代码93行,监听所有接收到的udp数据,并把这些放入到队列recv_q |
+
t_dispatch | +代码106行,从队列recv_q 中取出数据,并进行bdecoding,根据消息类别分别放入对应的队列。一共有三种:应答,请求,错误。 |
+
t_hand_reply | +代码132行,从reply_q 中取出数据,解码节点列表。每一个节点都插入到本地的路由表(这里不太好,最好先进行ping_node,确定是好节点再加入),并向其发送find_node 消息 |
+
t_hand_query | +代码165行,从query_q 中取出请求,分别对四种请求进行应答处理,分别是ping,find_node, get_peers, announce_peer。 |
+
t_hand_error | +错误消息的处理。 | +
t_handle_metadata | +代码234行,从metadata_q 中取出已经获取的种子的metadata,打印并把种子保存再字典表中。 |
+
metadata_q
中。
|
我们在一台有公网ip的电脑上运行十几分钟,过滤日志,查看接收到的种子信息如下,
+
|
有些种子文件为什么没有文件大小呢?例如size=None
,
|
用info_hash过滤日志,可以看到metadata包含了一个files
项,该项包含了多个文件。
|
bt通过种子文件分享已经是一个过去时了,2009年btChina
就已经关闭了。现在一般都是
-使用磁力链接来分享文件。那么为什么种子文件分享不再流行了呢?为什么要用磁力链接呢?
-磁力链接怎么实现的呢?
嗯这是这个系列要研究的问题。但是要研究磁力链接的实现原理,最好先从种子文件开始。
+用google搜索关键词”java bytes to string and back not equal”,第一个就是我说的这个问题。
+什么意思呢?就是在java中,bytes转化为string之后,再转换回bytes的时候,发现不相同了。
+但是Go
语言就没有这个问题哦。
官网文档 BEP3 中的metainfo files章节 -讲的很清楚。
-简单的说就是把tracker列表和分享的文件信息编码为一个二进制的文件。
-
|
运行如下java代码
+
|
如果想要找到下载源,就要通过tracker找到peer节点。
-包含了文件的大小,分块个数,分块的sha1散列值。
-bt文件的编码逻辑取名为bencoding。
---Strings are length-prefixed base ten followed by a colon and the string. For example 4:spam corresponds to ‘spam’.
-Integers are represented by an ‘i’ followed by the number in base 10 followed by an ‘e’. For example i3e corresponds to 3 and i-3e corresponds to -3. Integers have no size limitation. i-0e is invalid. All encodings with a leading zero, such as i03e, are invalid, other than i0e, which of course corresponds to 0.
-Lists are encoded as an ‘l’ followed by their elements (also bencoded) followed by an ‘e’. For example l4:spam4:eggse corresponds to [‘spam’, ‘eggs’].
-Dictionaries are encoded as a ‘d’ followed by a list of alternating keys and their corresponding values followed by an ‘e’. For example, d3:cow3:moo4:spam4:eggse corresponds to {‘cow’: ‘moo’, ‘spam’: ‘eggs’} and d4:spaml1:a1:bee corresponds to {‘spam’: [‘a’, ‘b’]}. Keys must be strings and appear in sorted order (sorted as raw strings, not alphanumerics).
-
翻译为eBNF语法呢,就是如下
-
|
输出的结果如下:
+
|
根据eBNF实现的解码代码如下, 把get_content()
方法中path替换为种子文件的路径,运行就可以看到。
-返回的解析结果中会有info_hash
,该值是根据info的bencoding的二进制串计算的sha1值。这个值很重要
-因为之后很多协议都会用到。
|
bytes转化为string类型,本质上要选择一种编码。那么选择的是什么呢?
+我们看看new String()
执行的代码。通过跟踪,可以看到,使用了默认的编码。
+csn为UTF-8
。
|
用最近的毒液电影的种子进行解析,打印如下, -其中announce-list就是tracker列表。
-
|
因为通常java编译的时候的默认编码是UTF-8
。
+那么如何保证转化为字符串还能够转换回来呢?
一种方法是使用ISO-8859-1
。例如,
|
结果是相等的。
+
|
Go语言则对这个问题解决的非常好。
+
|
运行结果是相等的。
+
|
K3s是什么?k8s的精简版。编译之后执行程序大小不到50M。 -可以用在物联网的边缘计算侧。如果想深入了解k8s,那么k3s是个很好的起点。 -那么如果能够断点调试k3s,就更好了。下面我们来看看怎么做。
- - -GOPATH
,同时把$GOPATH/bin
加入到PATH
|
$GOPATH/bin/dlv
|
k3s
,所在路径是$GOPATH/src/github.com/rancher/k3s/
|
|
$GOPATH
,然后配置增加一个remote调试。在运行之前,在main.go上打一个断点。运行远程调试之后,成功。 -
-]]>实现一个简单的BT种子嗅探器才算是有点实际价值的吧。这样就可以把种子的metadata信息 -缓存下来,提供按照文件名进行检索。
- - -不过这里只是实现一个demo,有兴趣的话可以看看github上的dht项目。
-简单的说,就是把嗅探节点加入到其他节点的路由表中,等待其他节点发来的announce_peer请求,然后获取种子的metadata信息。
-我们根据以下的代码具体说明。
-89行代码,向路由节点发送find_node
请求。这些路由节点。就是25行代码的3个地址。
-这样做的意义有两个,一方面,路由节点会把我们的节点加入到他们的路由表中。另一方面,路由节点会返回一个好节点列表。
252行代码,启动了6个线程。
-线程 | -说明 | -
---|---|
listener | -代码93行,监听所有接收到的udp数据,并把这些放入到队列recv_q |
-
t_dispatch | -代码106行,从队列recv_q 中取出数据,并进行bdecoding,根据消息类别分别放入对应的队列。一共有三种:应答,请求,错误。 |
-
t_hand_reply | -代码132行,从reply_q 中取出数据,解码节点列表。每一个节点都插入到本地的路由表(这里不太好,最好先进行ping_node,确定是好节点再加入),并向其发送find_node 消息 |
-
t_hand_query | -代码165行,从query_q 中取出请求,分别对四种请求进行应答处理,分别是ping,find_node, get_peers, announce_peer。 |
-
t_hand_error | -错误消息的处理。 | -
t_handle_metadata | -代码234行,从metadata_q 中取出已经获取的种子的metadata,打印并把种子保存再字典表中。 |
-
metadata_q
中。
|
我们在一台有公网ip的电脑上运行十几分钟,过滤日志,查看接收到的种子信息如下,
-
|
有些种子文件为什么没有文件大小呢?例如size=None
,
|
用info_hash过滤日志,可以看到metadata包含了一个files
项,该项包含了多个文件。
|
用google搜索关键词”java bytes to string and back not equal”,第一个就是我说的这个问题。
-什么意思呢?就是在java中,bytes转化为string之后,再转换回bytes的时候,发现不相同了。
-但是Go
语言就没有这个问题哦。
运行如下java代码
-
|
输出的结果如下:
-
|
bytes转化为string类型,本质上要选择一种编码。那么选择的是什么呢?
-我们看看new String()
执行的代码。通过跟踪,可以看到,使用了默认的编码。
-csn为UTF-8
。
|
因为通常java编译的时候的默认编码是UTF-8
。
-那么如何保证转化为字符串还能够转换回来呢?
一种方法是使用ISO-8859-1
。例如,
|
结果是相等的。
-
|
Go语言则对这个问题解决的非常好。
-
|
运行结果是相等的。
-
|
在Kubernetes中,Ingress是一个对象,该对象允许从Kubernetes集群外部访问Kubernetes服务。 您可以 -通过创建一组规则来配置访问权限,这些规则定义了哪些入站连接可以访问哪些服务。
- - -这里有篇文章很好的说明了Ingress,并给出了例子 —— Kubernetes Ingress with Nginx Example
-但是要想跑一下,首先要有一个k8s集群。下面首先在mac上安装一个k8s集群。
-我的mbp的配置是8G内存。
-Docker.dmg
。https://github.com/gotok8s/k8s-docker-desktop-for-mac
-使用的方法就是如下,我用的docker desktop的kubernates的版本是1.19.3所以,相应的要把文件images
中的版本号进行修改,以匹配。
|
Enable Kubernetes
和Show system containers (advanced)
,启动,然后耐心等待。需要等待的时间比较长,因为还会下载多个镜像,如docker/desktop-kubernetes
等等。
我们从ingress-nginx的官网可以看到不同的安装方式,对于 Docker for Mac,一行命令搞定
-
|
这个需要下载镜像quay.io/kubernetes-ingress-controller/nginx-ingress-controller
,所以也需要时间。直到看到如下pods
|
Kubernetes Ingress with Nginx Example中的yaml文件需要外网才能看到。
-我在此列出三个yaml文件
-apple.yaml:
-
|
banana.yaml :
-
|
ingress.yaml:
-
|
执行命令,等待一下。因为要下载一个镜像“jettech/kube-webhook-certgen”
-
|
查看ingress
-
|
最后测试
-
|
我最近遇到这样一个需求,需要从容器内的ClickHouse访问安装在mac主机的kafka。这个问题似乎很简单, -因为在windows上,虚拟机可以和host组成一个局域网,因此kafka只要绑定此网段的ip地址即可。 -但是在我的mac主机下,这个方案行不通。
+我最近遇到这样一个需求,需要从容器内的ClickHouse访问安装在mac主机的kafka。这个问题似乎很简单, +因为在windows上,虚拟机可以和host组成一个局域网,因此kafka只要绑定此网段的ip地址即可。 +但是在我的mac主机下,这个方案行不通。
有几个原因,
@@ -2317,9 +2265,9 @@ csn为UTF-8
。
|
UTF-8
。
在Kubernetes中,Ingress是一个对象,该对象允许从Kubernetes集群外部访问Kubernetes服务。 您可以 +通过创建一组规则来配置访问权限,这些规则定义了哪些入站连接可以访问哪些服务。
+ + +这里有篇文章很好的说明了Ingress,并给出了例子 —— Kubernetes Ingress with Nginx Example
+但是要想跑一下,首先要有一个k8s集群。下面首先在mac上安装一个k8s集群。
+我的mbp的配置是8G内存。
+Docker.dmg
。https://github.com/gotok8s/k8s-docker-desktop-for-mac
+使用的方法就是如下,我用的docker desktop的kubernates的版本是1.19.3所以,相应的要把文件images
中的版本号进行修改,以匹配。
|
Enable Kubernetes
和Show system containers (advanced)
,启动,然后耐心等待。需要等待的时间比较长,因为还会下载多个镜像,如docker/desktop-kubernetes
等等。
我们从ingress-nginx的官网可以看到不同的安装方式,对于 Docker for Mac,一行命令搞定
+
|
这个需要下载镜像quay.io/kubernetes-ingress-controller/nginx-ingress-controller
,所以也需要时间。直到看到如下pods
|
Kubernetes Ingress with Nginx Example中的yaml文件需要外网才能看到。
+我在此列出三个yaml文件
+apple.yaml:
+
|
banana.yaml :
+
|
ingress.yaml:
+
|
执行命令,等待一下。因为要下载一个镜像“jettech/kube-webhook-certgen”
+
|
查看ingress
+
|
最后测试
+
|
/home/keyvalue/ym/operation
,local path为/Use
jetson tx2开发板上安装了docker
和k3s
,部署了一个pod,发现日志报错
|
其中esmp-cloud-sync.dev.ennew.com
是内网域名,说明pod无法解析该域名。
X.509
证书是用来认证身份的,例如在访问一个HTTPS网站的时候,浏览器会首先下载该网站
+的证书,验证是否有效。如果无效,浏览器会提示您的连接不是私密连接
,进一步访问网站有风险。
+如果有效则可以直接访问,不会告警。
浏览器怎么验证网站证书是否有效呢?简单说就是看网站证书的颁发机构是不是已经被操作系统信任,即看 +颁发机构的身份证书是否已经安装到操作系统里,并被设置为信任。
+那么我们接下来做的事情就是在mac系统上验证浏览器的上述行为。
-但是宿主机上能ping通该域名。
-验证步骤,
+颁发机构通常会有根机构和中间机构,根机构的证书自己签发自己,中间机构的证书由根机构签发,而Server端证书由中间机构签发。本人参考以下三篇文章创建了根证书,中间证书和Server端证书。
+ -首先检查宿主机的域名服务器设置。 -如果不正常,则需要设置正确的域名服务器。并重启k3s服务和pod。 -如果正常,则进一步检查k3s的DNS设置。而k3s的网络域名解析是由coreDNS控制的。
-宿主机的DNS设置,查看/etc/resolv.conf
$ cat /etc/resolv.conf
-# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
-# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
-# 127.0.0.53 is the systemd-resolved stub resolver.
-# run "systemd-resolve --status" to see details about the actual nameservers.
+不过需要注意的是,Server端证书有效期不要太长,否则即使安装了根证书也会告警。
+第二步,本机搭建nginx服务器,并设置Server端证书
mac下安装nginx很简单
+brew install nginx
+然后修改/usr/local/etc/nginx/nginx.conf
文件,在server里添加三行
+listen 443 ssl;
ssl_certificate /Users/ym/tmp/myca/localhost/certs/localhost.cert.pem;
ssl_certificate_key /Users/ym/tmp/myca/localhost/private/localhost.key.pem;
-nameserver 127.0.0.53
-search addom.xinaogroup.com
-
-运行”systemd-resolve –status”查看实际的nameservers。
-$ systemd-resolve --status
-...
- DNS Servers: 10.36.8.40
- 10.36.8.41
- 127.0.0.1
- DNS Domain: ~.
- addom.xinaogroup.com
-...
-
-可以看到实际的域名服务器。pod里无法ping通的域名esmp-cloud-sync.dev.ennew.com
,在宿主机环境是可以的。
pod的DNS设置是由CoreDNS
控制的。但是进入CoreDNS的pod,使用kubectl exec
是不行的。需要使用边车模式,
-先查看运行CoreDNS的容器的ID,然后用docker再启动一个容器。(因为k3s server是基于dockerd运行的,所以
-可以用docker ps查看CoreDNS pod里的容器)
docker ps | grep coredns
-ID=8afb33b91c9f
-docker run -it --net=container:$ID --pid=container:$ID --volumes-from=$ID alpine sh
-
-然后就可以查看CoreDNS的corefile配置文件
-# cat /etc/coredns/Corefile
-.:53 {
- errors
- health
- ready
- kubernetes cluster.local in-addr.arpa ip6.arpa {
- pods insecure
- upstream
- fallthrough in-addr.arpa ip6.arpa
- }
- hosts /etc/coredns/NodeHosts {
- reload 1s
- fallthrough
- }
- prometheus :9153
- forward . /etc/resolv.conf
- cache 30
- loop
- reload
- loadbalance
-}
-
-从forward . /etc/resolv.conf
可以看出,由/etc/resolv.conf
文件接管DNS设置。进一步查看该文件,
# cat /etc/resolv.conf
- nameserver 8.8.8.8
-
-可以发现这里设置的nameserver
是8.8.8.8
,而不是10.36.8.40
。因此在pod里虽然可以ping通公网域名
-,例如www.baidu.com
,但是无法ping通内网域名esmp-cloud-sync.dev.ennew.com
另外查看CoreDNS的pod日志也可以看到无法解析内网域名esmp-cloud-sync.dev.ennew.com
的错误。
从排查结果可以看到,主要问题在于CoreDNS的DSN设置与宿主机的DNS设置不同,导致解析内网域名解析失败。这一点比较奇怪,通常k3s默认 -会继承宿主机的DNS设置。
-也就是pod里的/etc/resolv.conf
,没有与宿主机的/etc/resolv.conf
的内容一致。
再回头看看我们要解决的问题——pod里解析内网域名,那么最直接的方案就是修改CoreDNS的Corefile,
-修改forward . /etc/resolv.conf
为forward . 10.36.8.40
但是这种方法的缺点是显而易见的,写死了。 我们还是希望找到一个方法能够令/etc/resolv.conf
与宿主机的/etc/resolv.conf
的内容一致
宿主机的/etc/resolv.conf
文件是一个指向 /run/systemd/resolve/resolv.conf
的软链接,查看该文件的内容
$ cat /run/systemd/resolve/resolv.conf
-# This file is managed by man:systemd-resolved(8). Do not edit.
-#
-# This is a dynamic resolv.conf file for connecting local clients directly to
-# all known uplink DNS servers. This file lists all configured search domains.
-#
-# Third party programs must not access this file directly, but only through the
-# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,
-# replace this symlink by a static file or a different symlink.
-#
-# See man:systemd-resolved.service(8) for details about the supported modes of
-# operation for /etc/resolv.conf.
+启动nginx
+brew sevices start nginx
-nameserver 10.36.8.40
-nameserver 10.36.8.41
-nameserver 127.0.0.1
-search addom.xinaogroup.com
-
-可以看到含有正确的nameservers。
-另外 k3s 有启动参数--resolv-conf
,可以指定默认的resolv.conf
。
修改/etc/systemd/system/k3s.service
,增加启动参数--resolv-conf /run/systemd/resolve/resolv.conf
...
-ExecStart=/usr/local/bin/k3s \
-server \
-'--docker' \
-'--write-kubeconfig' \
-'/home/tx2/.kube/config' \
-'--write-kubeconfig-mode' \
-'666' \
-'--resolv-conf' \
-'/run/systemd/resolve/resolv.conf'
-
-然后重新启动k3s,并且删除CoreDNS的pod(kubectl delete pod -n kube-system coredns-xxxx
),令其自动创建一个新的pod。
这时候再查看CoreDNS pod里的/etc/resolv.conf, 内容一致了,
-/ # cat /etc/resolv.conf
-nameserver 10.36.8.40
-nameserver 10.36.8.41
-nameserver 127.0.0.1
-search addom.xinaogroup.com
-
-问题解决了。
-显示告警信息 +
+在mac系统下,点击lauchpad,并搜索钥匙串访问
。可以看到登录下安装的证书。
+在finder中打开根证书和中间证书,可以添加到登录账号下。
+只需要设置根证书为永久信任即可。以为中间证书是由根证书签发的。
+以下是已经安装的根证书
+
以下是已经安装的中间证书 +
+那么具体看一下以上ca证书的前四个字节是什么含义。
|
首先看是什么type。第一个字节0x30
描述了type信息。tagClass = 0x30 >> 6 = 0 ,表示universal isConstructed = 0x30 & 0x20 = True
,对于sequence
基本都是true,
-tagNumber = 0x30 & 0x1F = 0x10
,因此对应的Sequence
接下来计算长度。
-第二个字节为0x82
,
-分两种情况,判断表达式,byte & 0x7F == byte
如果为true就是小于127。否则就是大于127。
首先看是什么type。第一个字节0x30
描述了type信息。tagClass = 0x30 >> 6 = 0 ,表示universal isConstructed = 0x30 & 0x20 = True
,对于sequence
基本都是true,
+tagNumber = 0x30 & 0x1F = 0x10
,因此对应的Sequence
接下来计算长度。
+第二个字节为0x82
,
+分两种情况,判断表达式,byte & 0x7F == byte
如果为true就是小于127。否则就是大于127。
对于以上的例子,0x82
是long form
+因为
|
那么表示长度的字节数量是
+
|
因此’0x82’之后的两个字节’0x05’和’0xC1’组成长度。
+
|
两个字节表示value的长度1473。
+因此,
+
|
这个头四个字节的含义是,这个是Sequence
类型,
+长度是1473,这四节之后的1473个字节就是Sequence
类型的值。
jetson tx2开发板上安装了docker
和k3s
,部署了一个pod,发现日志报错
|
其中esmp-cloud-sync.dev.ennew.com
是内网域名,说明pod无法解析该域名。
但是宿主机上能ping通该域名。
+首先检查宿主机的域名服务器设置。 +如果不正常,则需要设置正确的域名服务器。并重启k3s服务和pod。 +如果正常,则进一步检查k3s的DNS设置。而k3s的网络域名解析是由coreDNS控制的。
+宿主机的DNS设置,查看/etc/resolv.conf
$ cat /etc/resolv.conf
+# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
+# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
+# 127.0.0.53 is the systemd-resolved stub resolver.
+# run "systemd-resolve --status" to see details about the actual nameservers.
+
+nameserver 127.0.0.53
+search addom.xinaogroup.com
+
+运行”systemd-resolve –status”查看实际的nameservers。
+$ systemd-resolve --status
+...
+ DNS Servers: 10.36.8.40
+ 10.36.8.41
+ 127.0.0.1
+ DNS Domain: ~.
+ addom.xinaogroup.com
+...
+
+可以看到实际的域名服务器。pod里无法ping通的域名esmp-cloud-sync.dev.ennew.com
,在宿主机环境是可以的。
pod的DNS设置是由CoreDNS
控制的。但是进入CoreDNS的pod,使用kubectl exec
是不行的。需要使用边车模式,
+先查看运行CoreDNS的容器的ID,然后用docker再启动一个容器。(因为k3s server是基于dockerd运行的,所以
+可以用docker ps查看CoreDNS pod里的容器)
docker ps | grep coredns
+ID=8afb33b91c9f
+docker run -it --net=container:$ID --pid=container:$ID --volumes-from=$ID alpine sh
+
+然后就可以查看CoreDNS的corefile配置文件
+# cat /etc/coredns/Corefile
+.:53 {
+ errors
+ health
+ ready
+ kubernetes cluster.local in-addr.arpa ip6.arpa {
+ pods insecure
+ upstream
+ fallthrough in-addr.arpa ip6.arpa
+ }
+ hosts /etc/coredns/NodeHosts {
+ reload 1s
+ fallthrough
+ }
+ prometheus :9153
+ forward . /etc/resolv.conf
+ cache 30
+ loop
+ reload
+ loadbalance
+}
+
+从forward . /etc/resolv.conf
可以看出,由/etc/resolv.conf
文件接管DNS设置。进一步查看该文件,
# cat /etc/resolv.conf
+ nameserver 8.8.8.8
+
+可以发现这里设置的nameserver
是8.8.8.8
,而不是10.36.8.40
。因此在pod里虽然可以ping通公网域名
+,例如www.baidu.com
,但是无法ping通内网域名esmp-cloud-sync.dev.ennew.com
另外查看CoreDNS的pod日志也可以看到无法解析内网域名esmp-cloud-sync.dev.ennew.com
的错误。
从排查结果可以看到,主要问题在于CoreDNS的DSN设置与宿主机的DNS设置不同,导致解析内网域名解析失败。这一点比较奇怪,通常k3s默认 +会继承宿主机的DNS设置。
+也就是pod里的/etc/resolv.conf
,没有与宿主机的/etc/resolv.conf
的内容一致。
再回头看看我们要解决的问题——pod里解析内网域名,那么最直接的方案就是修改CoreDNS的Corefile,
+修改forward . /etc/resolv.conf
为forward . 10.36.8.40
但是这种方法的缺点是显而易见的,写死了。 我们还是希望找到一个方法能够令/etc/resolv.conf
与宿主机的/etc/resolv.conf
的内容一致
宿主机的/etc/resolv.conf
文件是一个指向 /run/systemd/resolve/resolv.conf
的软链接,查看该文件的内容
$ cat /run/systemd/resolve/resolv.conf
+# This file is managed by man:systemd-resolved(8). Do not edit.
+#
+# This is a dynamic resolv.conf file for connecting local clients directly to
+# all known uplink DNS servers. This file lists all configured search domains.
+#
+# Third party programs must not access this file directly, but only through the
+# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,
+# replace this symlink by a static file or a different symlink.
+#
+# See man:systemd-resolved.service(8) for details about the supported modes of
+# operation for /etc/resolv.conf.
+
+nameserver 10.36.8.40
+nameserver 10.36.8.41
+nameserver 127.0.0.1
+search addom.xinaogroup.com
+
+可以看到含有正确的nameservers。
+另外 k3s 有启动参数--resolv-conf
,可以指定默认的resolv.conf
。
修改/etc/systemd/system/k3s.service
,增加启动参数--resolv-conf /run/systemd/resolve/resolv.conf
...
+ExecStart=/usr/local/bin/k3s \
+server \
+'--docker' \
+'--write-kubeconfig' \
+'/home/tx2/.kube/config' \
+'--write-kubeconfig-mode' \
+'666' \
+'--resolv-conf' \
+'/run/systemd/resolve/resolv.conf'
+
+然后重新启动k3s,并且删除CoreDNS的pod(kubectl delete pod -n kube-system coredns-xxxx
),令其自动创建一个新的pod。
这时候再查看CoreDNS pod里的/etc/resolv.conf, 内容一致了,
+/ # cat /etc/resolv.conf
+nameserver 10.36.8.40
+nameserver 10.36.8.41
+nameserver 127.0.0.1
+search addom.xinaogroup.com
+
+问题解决了。
+对于以上的例子,0x82
是long form
-因为
|
那么表示长度的字节数量是
-
|
因此’0x82’之后的两个字节’0x05’和’0xC1’组成长度。
-
|
两个字节表示value的长度1473。
-因此,
-
|
这个头四个字节的含义是,这个是Sequence
类型,
-长度是1473,这四节之后的1473个字节就是Sequence
类型的值。
X.509
证书是用来认证身份的,例如在访问一个HTTPS网站的时候,浏览器会首先下载该网站
-的证书,验证是否有效。如果无效,浏览器会提示您的连接不是私密连接
,进一步访问网站有风险。
-如果有效则可以直接访问,不会告警。
浏览器怎么验证网站证书是否有效呢?简单说就是看网站证书的颁发机构是不是已经被操作系统信任,即看 -颁发机构的身份证书是否已经安装到操作系统里,并被设置为信任。
-那么我们接下来做的事情就是在mac系统上验证浏览器的上述行为。
- - -验证步骤,
-颁发机构通常会有根机构和中间机构,根机构的证书自己签发自己,中间机构的证书由根机构签发,而Server端证书由中间机构签发。本人参考以下三篇文章创建了根证书,中间证书和Server端证书。
- -不过需要注意的是,Server端证书有效期不要太长,否则即使安装了根证书也会告警。
-mac下安装nginx很简单
-
|
然后修改/usr/local/etc/nginx/nginx.conf
文件,在server里添加三行
|
启动nginx
-
|
显示告警信息 -
-在mac系统下,点击lauchpad,并搜索钥匙串访问
。可以看到登录下安装的证书。
-在finder中打开根证书和中间证书,可以添加到登录账号下。
-只需要设置根证书为永久信任即可。以为中间证书是由根证书签发的。
-以下是已经安装的根证书
-
以下是已经安装的中间证书 -
-自从ChatGPT出现以来,就一直在使用,那么ChatGPT毕竟是有局限性的,因为ChatGPT训练的语料是有限的。很多问题回答不了, +也经常会胡言乱语闹笑话。
+但是ChatGPT背后的大语言模型LLM是可以扩展的,也就是说,可以把特定的领域知识让LLM(大语言模型)学习。这样就在一定 +程度上解决了局限性。
+而LangChain项目就是这样的杀手锏,这里是官方文档。
+ + +本文代码和例子参考了使用langchain打造自己的大型语言模型(LLMs),对中文资料进行处理。
+LangChain是一个框架,如果要使用,则需要调用大语言模型的API。正好有一朋友申请了OPENAI_API_KEY
,这样就可以开始
+跑跑代码了。
以下是Python代码。
+
|
Python3.11安装依赖
+
|
requirements.txt内容如下:
+
|
而文本则放在 “./data/“ 目录下,
+天龙八部.txt
内容如下,就是一些明确的信息。
|
如果直接问ChatGPT,”段誉的兄弟是谁”, “段誉有几个兄弟”则ChatGPT回答有些混乱。
+ +直接运行后,测试提问,如下。
+ + +很有意思。
+
|
自从ChatGPT出现以来,就一直在使用,那么ChatGPT毕竟是有局限性的,因为ChatGPT训练的语料是有限的。很多问题回答不了, -也经常会胡言乱语闹笑话。
-但是ChatGPT背后的大语言模型LLM是可以扩展的,也就是说,可以把特定的领域知识让LLM(大语言模型)学习。这样就在一定 -程度上解决了局限性。
-而LangChain项目就是这样的杀手锏,这里是官方文档。
- - -本文代码和例子参考了使用langchain打造自己的大型语言模型(LLMs),对中文资料进行处理。
-LangChain是一个框架,如果要使用,则需要调用大语言模型的API。正好有一朋友申请了OPENAI_API_KEY
,这样就可以开始
-跑跑代码了。
以下是Python代码。
-
|
Python3.11安装依赖
-
|
requirements.txt内容如下:
-
|
而文本则放在 “./data/“ 目录下,
-天龙八部.txt
内容如下,就是一些明确的信息。
|
如果直接问ChatGPT,”段誉的兄弟是谁”, “段誉有几个兄弟”则ChatGPT回答有些混乱。
- -直接运行后,测试提问,如下。
- - -很有意思。
-
|