From 2005ad685e4ec760541bb5e6ddd352227ec53328 Mon Sep 17 00:00:00 2001
From: yanggeorge
Date: Tue, 23 Jul 2024 12:00:08 +0800
Subject: [PATCH] Site updated: 2024-07-23 12:00:08
---
2016/09/27/2016-9-27-leetcode-200/index.html | 8 +-
2016/10/24/leetcode-224/index.html | 8 +-
2016/12/19/leetcode-282/index.html | 8 +-
2016/12/21/get-val-name-and-value/index.html | 8 +-
2016/12/22/pandas-read-csv/index.html | 2 +-
2016/12/26/leetcode-315/index.html | 8 +-
2016/12/31/leetcode-327/index.html | 8 +-
.../12/2017-1-12-cython-on-win10/index.html | 2 +-
2017/01/18/2017-1-18-venv-no-pip/index.html | 2 +-
2017/01/31/2017-1-31-leetcode-295/index.html | 8 +-
.../06/2017-3-6-grails-call-java/index.html | 8 +-
2017/03/12/2017-3-12-leetcode-329/index.html | 8 +-
.../index.html | 2 +-
.../19/2017-5-19-javacc-minilisp/index.html | 2 +-
.../2017-5-24-gradle-build-project/index.html | 2 +-
.../09/2017-7-9-resize-fixed-image/index.html | 2 +-
2017/10/07/leetcode-312/index.html | 8 +-
2017/11/27/find-top-n/index.html | 2 +-
2018/02/24/2018-2-24-robber-3/index.html | 8 +-
2018/08/18/2018-8-18-leetcode-341/index.html | 8 +-
2018/11/08/2018-11-8-leetcode-352/index.html | 8 +-
2018/11/09/minilisp/index.html | 2 +-
2018/12/09/macro/index.html | 2 +-
2019/01/09/2019-1-9-bt-1/index.html | 2 +-
2019/01/10/2019-1-10-bt-2/index.html | 2 +-
2019/01/21/2019-1-21-bt-3/index.html | 2 +-
2019/01/21/2019-1-23-bt-4/index.html | 2 +-
2019/01/25/2019-1-25-bt-5/index.html | 2 +-
2019/02/18/2019-2-18-bt-6/index.html | 2 +-
.../index.html | 2 +-
2019/11/19/debug-k3s/index.html | 2 +-
2020/03/09/connect-kafka/index.html | 8 +-
.../06/run-ingress-example-on-mac/index.html | 8 +-
.../index.html | 2 +-
2020/09/08/remote-debug-with-clion/index.html | 2 +-
2020/10/09/x509-ca/index.html | 8 +-
2020/11/08/asn-1/index.html | 2 +-
.../2021-7-15-clion-makefile-debug/index.html | 2 +-
2021/12/26/pod-resolve-dn/index.html | 2 +-
2022/02/06/2022-2-6-leetcode-390/index.html | 2 +-
.../12/2023-1-12-8-queens-chatgpt/index.html | 2 +-
.../28/2023-2-28-associated-type/index.html | 2 +-
.../2023-6-7-qt5-macos-bundle-app/index.html | 2 +-
.../08/2023-6-8-langchain-openai/index.html | 2 +-
.../2023-6-9-qt5-macos-lldb-debug/index.html | 2 +-
.../2024-7-18-slang-ast-hier-tree/index.html | 2 +-
404.html | 2 +-
about/2019-4-2-bt-7.html | 2 +-
about/index.html | 2 +-
archives/2016/09/index.html | 2 +-
archives/2016/10/index.html | 2 +-
archives/2016/12/index.html | 2 +-
archives/2016/index.html | 2 +-
archives/2017/01/index.html | 2 +-
archives/2017/03/index.html | 2 +-
archives/2017/05/index.html | 2 +-
archives/2017/07/index.html | 2 +-
archives/2017/10/index.html | 2 +-
archives/2017/11/index.html | 2 +-
archives/2017/index.html | 2 +-
archives/2017/page/2/index.html | 2 +-
archives/2018/02/index.html | 2 +-
archives/2018/08/index.html | 2 +-
archives/2018/11/index.html | 2 +-
archives/2018/12/index.html | 2 +-
archives/2018/index.html | 2 +-
archives/2019/01/index.html | 2 +-
archives/2019/02/index.html | 2 +-
archives/2019/04/index.html | 2 +-
archives/2019/11/index.html | 2 +-
archives/2019/index.html | 2 +-
archives/2020/03/index.html | 2 +-
archives/2020/07/index.html | 2 +-
archives/2020/08/index.html | 2 +-
archives/2020/09/index.html | 2 +-
archives/2020/10/index.html | 2 +-
archives/2020/11/index.html | 2 +-
archives/2020/index.html | 2 +-
archives/2021/07/index.html | 2 +-
archives/2021/12/index.html | 2 +-
archives/2021/index.html | 2 +-
archives/2022/02/index.html | 2 +-
archives/2022/index.html | 2 +-
archives/2023/01/index.html | 2 +-
archives/2023/02/index.html | 2 +-
archives/2023/06/index.html | 2 +-
archives/2023/index.html | 2 +-
archives/2024/07/index.html | 2 +-
archives/2024/index.html | 2 +-
archives/index.html | 2 +-
archives/page/2/index.html | 2 +-
archives/page/3/index.html | 2 +-
archives/page/4/index.html | 2 +-
archives/page/5/index.html | 2 +-
categories/index.html | 2 +-
css/font_4629291_x5qj7u7xj0n/demo_index.html | 2 +-
.../2024-7-18-slang-ast-hier-tree/index.html | 2 +-
en/404.html | 2 +-
en/about/index.html | 2 +-
en/archives/2024/07/index.html | 2 +-
en/archives/2024/index.html | 2 +-
en/archives/index.html | 2 +-
en/categories/index.html | 2 +-
.../font_4629291_x5qj7u7xj0n/demo_index.html | 2 +-
en/index.html | 2 +-
en/links/index.html | 2 +-
en/sitemap.xml | 4 +-
en/tags/AST/index.html | 2 +-
en/tags/c/index.html | 2 +-
en/tags/index.html | 2 +-
en/tags/tree/index.html | 2 +-
index.html | 2 +-
links/index.html | 2 +-
local-search.xml | 64 +-
page/2/index.html | 14 +-
page/3/index.html | 18 +-
page/4/index.html | 18 +-
page/5/index.html | 22 +-
search.xml | 1528 ++++++++---------
sitemap.xml | 104 +-
tags/ASN-1/index.html | 2 +-
tags/AST/index.html | 2 +-
tags/CLion/index.html | 2 +-
tags/ChatGPT/index.html | 2 +-
tags/Cython/index.html | 2 +-
tags/R/index.html | 2 +-
tags/X-509/index.html | 2 +-
tags/algorithm/index.html | 2 +-
tags/bitTorrent/index.html | 2 +-
tags/build/index.html | 2 +-
tags/bundle/index.html | 2 +-
tags/c/index.html | 2 +-
tags/certificate/index.html | 2 +-
tags/clojure/index.html | 2 +-
tags/cmake/index.html | 2 +-
tags/container/index.html | 2 +-
tags/coredns/index.html | 2 +-
tags/debug/index.html | 2 +-
tags/delve/index.html | 2 +-
tags/docker/index.html | 2 +-
tags/file/index.html | 2 +-
tags/gdb/index.html | 2 +-
tags/go/index.html | 2 +-
tags/golang/index.html | 2 +-
tags/gradle/index.html | 2 +-
tags/grails/index.html | 2 +-
tags/iced/index.html | 2 +-
tags/image/index.html | 2 +-
tags/index.html | 2 +-
tags/ingress/index.html | 2 +-
tags/integration/index.html | 2 +-
tags/java/index.html | 2 +-
tags/javacc/index.html | 2 +-
tags/jvm/index.html | 2 +-
tags/k3s/index.html | 2 +-
tags/k8s/index.html | 2 +-
tags/kafka/index.html | 2 +-
tags/langchain/index.html | 2 +-
tags/leetcode/index.html | 2 +-
tags/leetcode/page/2/index.html | 2 +-
tags/lisp/index.html | 2 +-
tags/lldb/index.html | 2 +-
tags/mac/index.html | 2 +-
tags/macos/index.html | 2 +-
tags/macro/index.html | 2 +-
tags/makefile/index.html | 2 +-
tags/nginx/index.html | 2 +-
tags/openai/index.html | 2 +-
tags/openssl/index.html | 2 +-
tags/p2p/index.html | 2 +-
tags/pod/index.html | 2 +-
tags/protocol/index.html | 2 +-
tags/python/index.html | 2 +-
tags/python/page/2/index.html | 2 +-
tags/qt5/index.html | 2 +-
tags/remote/index.html | 2 +-
tags/resize/index.html | 2 +-
tags/rust/index.html | 2 +-
tags/spring/index.html | 2 +-
tags/ssl/index.html | 2 +-
tags/tree/index.html | 2 +-
tags/trie/index.html | 2 +-
tags/venv/index.html | 2 +-
tags/virutalbox/index.html | 2 +-
tags/write/index.html | 2 +-
"tags/\346\212\200\345\267\247/index.html" | 2 +-
"tags/\347\256\227\346\263\225/index.html" | 2 +-
.../page/2/index.html" | 2 +-
188 files changed, 1114 insertions(+), 1114 deletions(-)
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 @@ #python
+
#leetcode
#算法
- #python
-
diff --git a/2016/10/24/leetcode-224/index.html b/2016/10/24/leetcode-224/index.html
index 8887ac8f..f30b0e86 100644
--- a/2016/10/24/leetcode-224/index.html
+++ b/2016/10/24/leetcode-224/index.html
@@ -26,9 +26,9 @@
+
-
@@ -68,7 +68,7 @@
-
+
@@ -393,12 +393,12 @@
+ #python
+
#leetcode
#算法
- #python
-
diff --git a/2016/12/19/leetcode-282/index.html b/2016/12/19/leetcode-282/index.html
index 84418515..1c1e5dee 100644
--- a/2016/12/19/leetcode-282/index.html
+++ b/2016/12/19/leetcode-282/index.html
@@ -28,9 +28,9 @@
+
-
@@ -71,7 +71,7 @@
-
+
@@ -369,12 +369,12 @@ #python
+
#leetcode
#算法
- #python
-
diff --git a/2016/12/21/get-val-name-and-value/index.html b/2016/12/21/get-val-name-and-value/index.html
index 2b5325b7..376860cb 100644
--- a/2016/12/21/get-val-name-and-value/index.html
+++ b/2016/12/21/get-val-name-and-value/index.html
@@ -26,8 +26,8 @@
-
+
@@ -67,7 +67,7 @@
-
+
@@ -370,10 +370,10 @@ #python
-
#R
+ #python
+
diff --git a/2016/12/22/pandas-read-csv/index.html b/2016/12/22/pandas-read-csv/index.html
index 46bd0f32..2d2a4f0d 100644
--- a/2016/12/22/pandas-read-csv/index.html
+++ b/2016/12/22/pandas-read-csv/index.html
@@ -67,7 +67,7 @@
-
+
diff --git a/2016/12/26/leetcode-315/index.html b/2016/12/26/leetcode-315/index.html
index 7adc6ac9..fcf3008a 100644
--- a/2016/12/26/leetcode-315/index.html
+++ b/2016/12/26/leetcode-315/index.html
@@ -28,9 +28,9 @@
+
-
@@ -71,7 +71,7 @@
-
+
@@ -408,12 +408,12 @@
+ #python
+
#leetcode
#算法
- #python
-
diff --git a/2016/12/31/leetcode-327/index.html b/2016/12/31/leetcode-327/index.html
index 487d1687..7890796a 100644
--- a/2016/12/31/leetcode-327/index.html
+++ b/2016/12/31/leetcode-327/index.html
@@ -30,9 +30,9 @@
+
-
@@ -73,7 +73,7 @@
-
+
@@ -384,12 +384,12 @@
+ #python
+
#leetcode
#算法
- #python
-
diff --git a/2017/01/12/2017-1-12-cython-on-win10/index.html b/2017/01/12/2017-1-12-cython-on-win10/index.html
index 4fd4958f..b90df903 100644
--- a/2017/01/12/2017-1-12-cython-on-win10/index.html
+++ b/2017/01/12/2017-1-12-cython-on-win10/index.html
@@ -67,7 +67,7 @@
-
+
diff --git a/2017/01/18/2017-1-18-venv-no-pip/index.html b/2017/01/18/2017-1-18-venv-no-pip/index.html
index 43a0836f..1de4521e 100644
--- a/2017/01/18/2017-1-18-venv-no-pip/index.html
+++ b/2017/01/18/2017-1-18-venv-no-pip/index.html
@@ -70,7 +70,7 @@
-
+
diff --git a/2017/01/31/2017-1-31-leetcode-295/index.html b/2017/01/31/2017-1-31-leetcode-295/index.html
index 166bea95..17ecf3bb 100644
--- a/2017/01/31/2017-1-31-leetcode-295/index.html
+++ b/2017/01/31/2017-1-31-leetcode-295/index.html
@@ -26,9 +26,9 @@
+
-
@@ -68,7 +68,7 @@
-
+
@@ -385,12 +385,12 @@
+ #python
+
#leetcode
#算法
- #python
-
diff --git a/2017/03/06/2017-3-6-grails-call-java/index.html b/2017/03/06/2017-3-6-grails-call-java/index.html
index 07606d9e..3b362716 100644
--- a/2017/03/06/2017-3-6-grails-call-java/index.html
+++ b/2017/03/06/2017-3-6-grails-call-java/index.html
@@ -27,8 +27,8 @@
-
+
@@ -68,7 +68,7 @@
-
+
@@ -360,10 +360,10 @@ #java
- #grails
-
#gradle
+ #grails
+
diff --git a/2017/03/12/2017-3-12-leetcode-329/index.html b/2017/03/12/2017-3-12-leetcode-329/index.html
index cd3e8e0e..65ea20e5 100644
--- a/2017/03/12/2017-3-12-leetcode-329/index.html
+++ b/2017/03/12/2017-3-12-leetcode-329/index.html
@@ -26,9 +26,9 @@
+
-
@@ -68,7 +68,7 @@
-
+
@@ -358,12 +358,12 @@ #python
+
#leetcode
#算法
- #python
-
diff --git a/2017/03/23/2017-3-23-compile-and-run-clojure/index.html b/2017/03/23/2017-3-23-compile-and-run-clojure/index.html
index 268dbebc..2b1b1406 100644
--- a/2017/03/23/2017-3-23-compile-and-run-clojure/index.html
+++ b/2017/03/23/2017-3-23-compile-and-run-clojure/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2017/05/19/2017-5-19-javacc-minilisp/index.html b/2017/05/19/2017-5-19-javacc-minilisp/index.html
index 7f7f3f04..537cff53 100644
--- a/2017/05/19/2017-5-19-javacc-minilisp/index.html
+++ b/2017/05/19/2017-5-19-javacc-minilisp/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2017/05/24/2017-5-24-gradle-build-project/index.html b/2017/05/24/2017-5-24-gradle-build-project/index.html
index 02527cdf..8596d152 100644
--- a/2017/05/24/2017-5-24-gradle-build-project/index.html
+++ b/2017/05/24/2017-5-24-gradle-build-project/index.html
@@ -71,7 +71,7 @@
-
+
diff --git a/2017/07/09/2017-7-9-resize-fixed-image/index.html b/2017/07/09/2017-7-9-resize-fixed-image/index.html
index a5956a23..a3dfcb0b 100644
--- a/2017/07/09/2017-7-9-resize-fixed-image/index.html
+++ b/2017/07/09/2017-7-9-resize-fixed-image/index.html
@@ -71,7 +71,7 @@
-
+
diff --git a/2017/10/07/leetcode-312/index.html b/2017/10/07/leetcode-312/index.html
index a06a3419..c3d352ce 100644
--- a/2017/10/07/leetcode-312/index.html
+++ b/2017/10/07/leetcode-312/index.html
@@ -26,9 +26,9 @@
+
-
@@ -68,7 +68,7 @@
-
+
@@ -354,12 +354,12 @@ #python
+
#leetcode
#算法
- #python
-
diff --git a/2017/11/27/find-top-n/index.html b/2017/11/27/find-top-n/index.html
index 9d19a390..e48ffb86 100644
--- a/2017/11/27/find-top-n/index.html
+++ b/2017/11/27/find-top-n/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2018/02/24/2018-2-24-robber-3/index.html b/2018/02/24/2018-2-24-robber-3/index.html
index 41a01ca6..56607066 100644
--- a/2018/02/24/2018-2-24-robber-3/index.html
+++ b/2018/02/24/2018-2-24-robber-3/index.html
@@ -27,9 +27,9 @@
+
-
@@ -70,7 +70,7 @@
-
+
@@ -340,12 +340,12 @@ #python
+
#leetcode
#算法
- #python
-
diff --git a/2018/08/18/2018-8-18-leetcode-341/index.html b/2018/08/18/2018-8-18-leetcode-341/index.html
index 9f69839f..8a3026d3 100644
--- a/2018/08/18/2018-8-18-leetcode-341/index.html
+++ b/2018/08/18/2018-8-18-leetcode-341/index.html
@@ -26,9 +26,9 @@
+
-
@@ -68,7 +68,7 @@
-
+
@@ -345,12 +345,12 @@ #python
+
#leetcode
#算法
- #python
-
diff --git a/2018/11/08/2018-11-8-leetcode-352/index.html b/2018/11/08/2018-11-8-leetcode-352/index.html
index 6f1faa59..532a7168 100644
--- a/2018/11/08/2018-11-8-leetcode-352/index.html
+++ b/2018/11/08/2018-11-8-leetcode-352/index.html
@@ -26,9 +26,9 @@
+
-
@@ -68,7 +68,7 @@
-
+
@@ -360,12 +360,12 @@ #python
+
#leetcode
#算法
- #python
-
diff --git a/2018/11/09/minilisp/index.html b/2018/11/09/minilisp/index.html
index 688156a8..dd5aa8db 100644
--- a/2018/11/09/minilisp/index.html
+++ b/2018/11/09/minilisp/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2018/12/09/macro/index.html b/2018/12/09/macro/index.html
index 3a660f44..eb3938ba 100644
--- a/2018/12/09/macro/index.html
+++ b/2018/12/09/macro/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2019/01/09/2019-1-9-bt-1/index.html b/2019/01/09/2019-1-9-bt-1/index.html
index 823f073a..30ed2815 100644
--- a/2019/01/09/2019-1-9-bt-1/index.html
+++ b/2019/01/09/2019-1-9-bt-1/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2019/01/10/2019-1-10-bt-2/index.html b/2019/01/10/2019-1-10-bt-2/index.html
index 3f5f3068..bf23f99a 100644
--- a/2019/01/10/2019-1-10-bt-2/index.html
+++ b/2019/01/10/2019-1-10-bt-2/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2019/01/21/2019-1-21-bt-3/index.html b/2019/01/21/2019-1-21-bt-3/index.html
index 86ef7a24..eb94d1d0 100644
--- a/2019/01/21/2019-1-21-bt-3/index.html
+++ b/2019/01/21/2019-1-21-bt-3/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2019/01/21/2019-1-23-bt-4/index.html b/2019/01/21/2019-1-23-bt-4/index.html
index 494e9c53..29477bf7 100644
--- a/2019/01/21/2019-1-23-bt-4/index.html
+++ b/2019/01/21/2019-1-23-bt-4/index.html
@@ -70,7 +70,7 @@
-
+
diff --git a/2019/01/25/2019-1-25-bt-5/index.html b/2019/01/25/2019-1-25-bt-5/index.html
index f4d789de..43d9e2a1 100644
--- a/2019/01/25/2019-1-25-bt-5/index.html
+++ b/2019/01/25/2019-1-25-bt-5/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2019/02/18/2019-2-18-bt-6/index.html b/2019/02/18/2019-2-18-bt-6/index.html
index 60223afd..a2758fb5 100644
--- a/2019/02/18/2019-2-18-bt-6/index.html
+++ b/2019/02/18/2019-2-18-bt-6/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2019/04/28/2019-4-28-byte-to-string-and-back/index.html b/2019/04/28/2019-4-28-byte-to-string-and-back/index.html
index 93cc67bb..50fd2798 100644
--- a/2019/04/28/2019-4-28-byte-to-string-and-back/index.html
+++ b/2019/04/28/2019-4-28-byte-to-string-and-back/index.html
@@ -67,7 +67,7 @@
-
+
diff --git a/2019/11/19/debug-k3s/index.html b/2019/11/19/debug-k3s/index.html
index 467101ec..c57a77be 100644
--- a/2019/11/19/debug-k3s/index.html
+++ b/2019/11/19/debug-k3s/index.html
@@ -70,7 +70,7 @@
-
+
diff --git a/2020/03/09/connect-kafka/index.html b/2020/03/09/connect-kafka/index.html
index 1a51540f..e1d683c6 100644
--- a/2020/03/09/connect-kafka/index.html
+++ b/2020/03/09/connect-kafka/index.html
@@ -26,9 +26,9 @@
-
+
@@ -68,7 +68,7 @@
-
+
@@ -384,12 +384,12 @@ 测试
diff --git a/2020/07/06/run-ingress-example-on-mac/index.html b/2020/07/06/run-ingress-example-on-mac/index.html
index b6380015..432bbce5 100644
--- a/2020/07/06/run-ingress-example-on-mac/index.html
+++ b/2020/07/06/run-ingress-example-on-mac/index.html
@@ -26,9 +26,9 @@
+
-
@@ -69,7 +69,7 @@
-
+
@@ -364,12 +364,12 @@
+ #docker
+
#k8s
#ingress
- #docker
-
#mac
diff --git a/2020/08/14/write-file-with-spring-integration/index.html b/2020/08/14/write-file-with-spring-integration/index.html
index d499e0d5..858c4e64 100644
--- a/2020/08/14/write-file-with-spring-integration/index.html
+++ b/2020/08/14/write-file-with-spring-integration/index.html
@@ -69,7 +69,7 @@
-
+
diff --git a/2020/09/08/remote-debug-with-clion/index.html b/2020/09/08/remote-debug-with-clion/index.html
index b1752c6d..ecd50dae 100644
--- a/2020/09/08/remote-debug-with-clion/index.html
+++ b/2020/09/08/remote-debug-with-clion/index.html
@@ -72,7 +72,7 @@
-
+
diff --git a/2020/10/09/x509-ca/index.html b/2020/10/09/x509-ca/index.html
index f3d704f5..710fbbaf 100644
--- a/2020/10/09/x509-ca/index.html
+++ b/2020/10/09/x509-ca/index.html
@@ -30,8 +30,8 @@
-
+
@@ -75,7 +75,7 @@
-
+
@@ -371,10 +371,10 @@
- #X.509
-
#certificate
+ #X.509
+
#nginx
#openssl
diff --git a/2020/11/08/asn-1/index.html b/2020/11/08/asn-1/index.html
index 91a02484..9567945d 100644
--- a/2020/11/08/asn-1/index.html
+++ b/2020/11/08/asn-1/index.html
@@ -69,7 +69,7 @@
-
+
diff --git a/2021/07/15/2021-7-15-clion-makefile-debug/index.html b/2021/07/15/2021-7-15-clion-makefile-debug/index.html
index 189ecff1..6a9d4c25 100644
--- a/2021/07/15/2021-7-15-clion-makefile-debug/index.html
+++ b/2021/07/15/2021-7-15-clion-makefile-debug/index.html
@@ -71,7 +71,7 @@
-
+
diff --git a/2021/12/26/pod-resolve-dn/index.html b/2021/12/26/pod-resolve-dn/index.html
index d4ceebf2..795ec8a9 100644
--- a/2021/12/26/pod-resolve-dn/index.html
+++ b/2021/12/26/pod-resolve-dn/index.html
@@ -69,7 +69,7 @@
-
+
diff --git a/2022/02/06/2022-2-6-leetcode-390/index.html b/2022/02/06/2022-2-6-leetcode-390/index.html
index 12e35559..65d483d1 100644
--- a/2022/02/06/2022-2-6-leetcode-390/index.html
+++ b/2022/02/06/2022-2-6-leetcode-390/index.html
@@ -68,7 +68,7 @@
-
+
diff --git a/2023/01/12/2023-1-12-8-queens-chatgpt/index.html b/2023/01/12/2023-1-12-8-queens-chatgpt/index.html
index 9b2e1448..c95b6b39 100644
--- a/2023/01/12/2023-1-12-8-queens-chatgpt/index.html
+++ b/2023/01/12/2023-1-12-8-queens-chatgpt/index.html
@@ -71,7 +71,7 @@
-
+
diff --git a/2023/02/28/2023-2-28-associated-type/index.html b/2023/02/28/2023-2-28-associated-type/index.html
index 758122c2..e2f84a0a 100644
--- a/2023/02/28/2023-2-28-associated-type/index.html
+++ b/2023/02/28/2023-2-28-associated-type/index.html
@@ -70,7 +70,7 @@
-
+
diff --git a/2023/06/07/2023-6-7-qt5-macos-bundle-app/index.html b/2023/06/07/2023-6-7-qt5-macos-bundle-app/index.html
index 0f09a813..ce58840a 100644
--- a/2023/06/07/2023-6-7-qt5-macos-bundle-app/index.html
+++ b/2023/06/07/2023-6-7-qt5-macos-bundle-app/index.html
@@ -69,7 +69,7 @@
-
+
diff --git a/2023/06/08/2023-6-8-langchain-openai/index.html b/2023/06/08/2023-6-8-langchain-openai/index.html
index 99452041..dd6f9ad8 100644
--- a/2023/06/08/2023-6-8-langchain-openai/index.html
+++ b/2023/06/08/2023-6-8-langchain-openai/index.html
@@ -71,7 +71,7 @@
-
+
diff --git a/2023/06/09/2023-6-9-qt5-macos-lldb-debug/index.html b/2023/06/09/2023-6-9-qt5-macos-lldb-debug/index.html
index 180694ef..467e4981 100644
--- a/2023/06/09/2023-6-9-qt5-macos-lldb-debug/index.html
+++ b/2023/06/09/2023-6-9-qt5-macos-lldb-debug/index.html
@@ -71,7 +71,7 @@
-
+
diff --git a/2024/07/18/2024-7-18-slang-ast-hier-tree/index.html b/2024/07/18/2024-7-18-slang-ast-hier-tree/index.html
index 11ccb3f6..cdba5417 100644
--- a/2024/07/18/2024-7-18-slang-ast-hier-tree/index.html
+++ b/2024/07/18/2024-7-18-slang-ast-hier-tree/index.html
@@ -71,7 +71,7 @@
-
+
diff --git a/404.html b/404.html
index ce8d3c77..ae5e71b2 100644
--- a/404.html
+++ b/404.html
@@ -55,7 +55,7 @@
-
+
diff --git a/about/2019-4-2-bt-7.html b/about/2019-4-2-bt-7.html
index 9616a6d9..a23b5887 100644
--- a/about/2019-4-2-bt-7.html
+++ b/about/2019-4-2-bt-7.html
@@ -62,7 +62,7 @@
-
+
diff --git a/about/index.html b/about/index.html
index 1dccfdae..3daa9917 100644
--- a/about/index.html
+++ b/about/index.html
@@ -65,7 +65,7 @@
-
+
diff --git a/archives/2016/09/index.html b/archives/2016/09/index.html
index 616149d1..dd649900 100644
--- a/archives/2016/09/index.html
+++ b/archives/2016/09/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2016/10/index.html b/archives/2016/10/index.html
index cb2dea33..e8ac9fbb 100644
--- a/archives/2016/10/index.html
+++ b/archives/2016/10/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2016/12/index.html b/archives/2016/12/index.html
index 9f1170a5..77960eaf 100644
--- a/archives/2016/12/index.html
+++ b/archives/2016/12/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2016/index.html b/archives/2016/index.html
index f29d43ec..421b35dd 100644
--- a/archives/2016/index.html
+++ b/archives/2016/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2017/01/index.html b/archives/2017/01/index.html
index 8e89dc45..1ff17731 100644
--- a/archives/2017/01/index.html
+++ b/archives/2017/01/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2017/03/index.html b/archives/2017/03/index.html
index bf1b1dd5..d858b434 100644
--- a/archives/2017/03/index.html
+++ b/archives/2017/03/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2017/05/index.html b/archives/2017/05/index.html
index 3ef5c456..fe7eff8b 100644
--- a/archives/2017/05/index.html
+++ b/archives/2017/05/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2017/07/index.html b/archives/2017/07/index.html
index 25e8ce50..c8f6c222 100644
--- a/archives/2017/07/index.html
+++ b/archives/2017/07/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2017/10/index.html b/archives/2017/10/index.html
index 688ecf18..d8158f62 100644
--- a/archives/2017/10/index.html
+++ b/archives/2017/10/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2017/11/index.html b/archives/2017/11/index.html
index c984aa83..e5aa825c 100644
--- a/archives/2017/11/index.html
+++ b/archives/2017/11/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2017/index.html b/archives/2017/index.html
index 7a6d9d6a..64a61c3b 100644
--- a/archives/2017/index.html
+++ b/archives/2017/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2017/page/2/index.html b/archives/2017/page/2/index.html
index 6c220606..7a8648da 100644
--- a/archives/2017/page/2/index.html
+++ b/archives/2017/page/2/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2018/02/index.html b/archives/2018/02/index.html
index cd7ab95a..bb72e663 100644
--- a/archives/2018/02/index.html
+++ b/archives/2018/02/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2018/08/index.html b/archives/2018/08/index.html
index 7c40fd4d..f880be3d 100644
--- a/archives/2018/08/index.html
+++ b/archives/2018/08/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2018/11/index.html b/archives/2018/11/index.html
index f5c170f2..cc15f7b7 100644
--- a/archives/2018/11/index.html
+++ b/archives/2018/11/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2018/12/index.html b/archives/2018/12/index.html
index 328833c2..4d195e5c 100644
--- a/archives/2018/12/index.html
+++ b/archives/2018/12/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2018/index.html b/archives/2018/index.html
index e7330a6a..9eb12a82 100644
--- a/archives/2018/index.html
+++ b/archives/2018/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2019/01/index.html b/archives/2019/01/index.html
index 7ee42c74..49674cc2 100644
--- a/archives/2019/01/index.html
+++ b/archives/2019/01/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2019/02/index.html b/archives/2019/02/index.html
index 5164b9dd..b44d4580 100644
--- a/archives/2019/02/index.html
+++ b/archives/2019/02/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2019/04/index.html b/archives/2019/04/index.html
index 74a7601f..03ab5468 100644
--- a/archives/2019/04/index.html
+++ b/archives/2019/04/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2019/11/index.html b/archives/2019/11/index.html
index ad235693..b321ffb3 100644
--- a/archives/2019/11/index.html
+++ b/archives/2019/11/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2019/index.html b/archives/2019/index.html
index b60c9399..0374a9ef 100644
--- a/archives/2019/index.html
+++ b/archives/2019/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2020/03/index.html b/archives/2020/03/index.html
index 1600022e..5744b0bf 100644
--- a/archives/2020/03/index.html
+++ b/archives/2020/03/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2020/07/index.html b/archives/2020/07/index.html
index 24401c81..7a622fb9 100644
--- a/archives/2020/07/index.html
+++ b/archives/2020/07/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2020/08/index.html b/archives/2020/08/index.html
index a408b548..c37af52c 100644
--- a/archives/2020/08/index.html
+++ b/archives/2020/08/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2020/09/index.html b/archives/2020/09/index.html
index e262bbdd..afa8c172 100644
--- a/archives/2020/09/index.html
+++ b/archives/2020/09/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2020/10/index.html b/archives/2020/10/index.html
index d8c4d4d3..5027bc38 100644
--- a/archives/2020/10/index.html
+++ b/archives/2020/10/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2020/11/index.html b/archives/2020/11/index.html
index 62957483..7d165726 100644
--- a/archives/2020/11/index.html
+++ b/archives/2020/11/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2020/index.html b/archives/2020/index.html
index 1cbfc455..85038b6b 100644
--- a/archives/2020/index.html
+++ b/archives/2020/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2021/07/index.html b/archives/2021/07/index.html
index 598b1839..388e006f 100644
--- a/archives/2021/07/index.html
+++ b/archives/2021/07/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2021/12/index.html b/archives/2021/12/index.html
index 01f04a33..062a2753 100644
--- a/archives/2021/12/index.html
+++ b/archives/2021/12/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2021/index.html b/archives/2021/index.html
index 65cc93c0..afde77e9 100644
--- a/archives/2021/index.html
+++ b/archives/2021/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2022/02/index.html b/archives/2022/02/index.html
index f507be33..6cdd1a82 100644
--- a/archives/2022/02/index.html
+++ b/archives/2022/02/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2022/index.html b/archives/2022/index.html
index 2044c723..001038d5 100644
--- a/archives/2022/index.html
+++ b/archives/2022/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2023/01/index.html b/archives/2023/01/index.html
index 4289cc13..5c683bff 100644
--- a/archives/2023/01/index.html
+++ b/archives/2023/01/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2023/02/index.html b/archives/2023/02/index.html
index d8d2af76..44ccb0ec 100644
--- a/archives/2023/02/index.html
+++ b/archives/2023/02/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2023/06/index.html b/archives/2023/06/index.html
index bdc3623e..fc5d54f4 100644
--- a/archives/2023/06/index.html
+++ b/archives/2023/06/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2023/index.html b/archives/2023/index.html
index 8db540c1..99852b9e 100644
--- a/archives/2023/index.html
+++ b/archives/2023/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2024/07/index.html b/archives/2024/07/index.html
index 1f86887e..6c585737 100644
--- a/archives/2024/07/index.html
+++ b/archives/2024/07/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/2024/index.html b/archives/2024/index.html
index 6293c94f..0281e8eb 100644
--- a/archives/2024/index.html
+++ b/archives/2024/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/index.html b/archives/index.html
index b87272c7..f37e57fa 100644
--- a/archives/index.html
+++ b/archives/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/page/2/index.html b/archives/page/2/index.html
index 938a2973..86816b36 100644
--- a/archives/page/2/index.html
+++ b/archives/page/2/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/page/3/index.html b/archives/page/3/index.html
index da91762d..c4267e9a 100644
--- a/archives/page/3/index.html
+++ b/archives/page/3/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/page/4/index.html b/archives/page/4/index.html
index 3ea37b61..19d98152 100644
--- a/archives/page/4/index.html
+++ b/archives/page/4/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/archives/page/5/index.html b/archives/page/5/index.html
index fb24df65..5ccfdda8 100644
--- a/archives/page/5/index.html
+++ b/archives/page/5/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/categories/index.html b/categories/index.html
index 8e75538f..b807e35e 100644
--- a/categories/index.html
+++ b/categories/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/css/font_4629291_x5qj7u7xj0n/demo_index.html b/css/font_4629291_x5qj7u7xj0n/demo_index.html
index 81317d77..00e02241 100644
--- a/css/font_4629291_x5qj7u7xj0n/demo_index.html
+++ b/css/font_4629291_x5qj7u7xj0n/demo_index.html
@@ -61,7 +61,7 @@
-
+
diff --git a/en/2024/07/18/2024-7-18-slang-ast-hier-tree/index.html b/en/2024/07/18/2024-7-18-slang-ast-hier-tree/index.html
index 16d2eaf7..8d5df7cf 100644
--- a/en/2024/07/18/2024-7-18-slang-ast-hier-tree/index.html
+++ b/en/2024/07/18/2024-7-18-slang-ast-hier-tree/index.html
@@ -71,7 +71,7 @@
-
+
diff --git a/en/404.html b/en/404.html
index 1a304f1c..588115ee 100644
--- a/en/404.html
+++ b/en/404.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/about/index.html b/en/about/index.html
index b1c95146..f56b88c7 100644
--- a/en/about/index.html
+++ b/en/about/index.html
@@ -65,7 +65,7 @@
-
+
diff --git a/en/archives/2024/07/index.html b/en/archives/2024/07/index.html
index f7d64487..00ef1982 100644
--- a/en/archives/2024/07/index.html
+++ b/en/archives/2024/07/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/archives/2024/index.html b/en/archives/2024/index.html
index d1a3200a..e7f35abf 100644
--- a/en/archives/2024/index.html
+++ b/en/archives/2024/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/archives/index.html b/en/archives/index.html
index 70049bf6..105a9598 100644
--- a/en/archives/index.html
+++ b/en/archives/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/categories/index.html b/en/categories/index.html
index 134c80b7..15a5c0b8 100644
--- a/en/categories/index.html
+++ b/en/categories/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/css/font_4629291_x5qj7u7xj0n/demo_index.html b/en/css/font_4629291_x5qj7u7xj0n/demo_index.html
index bfb8cc98..52f8764e 100644
--- a/en/css/font_4629291_x5qj7u7xj0n/demo_index.html
+++ b/en/css/font_4629291_x5qj7u7xj0n/demo_index.html
@@ -61,7 +61,7 @@
-
+
diff --git a/en/index.html b/en/index.html
index 1a60abb3..c6aa27fc 100644
--- a/en/index.html
+++ b/en/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/links/index.html b/en/links/index.html
index 73fa2a27..e4338e90 100644
--- a/en/links/index.html
+++ b/en/links/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/sitemap.xml b/en/sitemap.xml
index d1aa29c7..1b14ddaf 100644
--- a/en/sitemap.xml
+++ b/en/sitemap.xml
@@ -2,7 +2,7 @@
- https://threelambda.com/en/css/font_4629291_x5qj7u7xj0n/iconfont.json
+ https://threelambda.com/en/css/font_4629291_x5qj7u7xj0n/demo_index.html
2024-07-23
@@ -11,7 +11,7 @@
- https://threelambda.com/en/css/font_4629291_x5qj7u7xj0n/demo_index.html
+ https://threelambda.com/en/css/font_4629291_x5qj7u7xj0n/iconfont.json
2024-07-23
diff --git a/en/tags/AST/index.html b/en/tags/AST/index.html
index 8b38fcfc..6a90bb63 100644
--- a/en/tags/AST/index.html
+++ b/en/tags/AST/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/tags/c/index.html b/en/tags/c/index.html
index 0546cf46..e57d43a9 100644
--- a/en/tags/c/index.html
+++ b/en/tags/c/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/tags/index.html b/en/tags/index.html
index 889f65c5..e01e58f0 100644
--- a/en/tags/index.html
+++ b/en/tags/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/en/tags/tree/index.html b/en/tags/tree/index.html
index a206480c..5d5da897 100644
--- a/en/tags/tree/index.html
+++ b/en/tags/tree/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/index.html b/index.html
index ed488295..dcd104ae 100644
--- a/index.html
+++ b/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/links/index.html b/links/index.html
index 1562be92..bee590cf 100644
--- a/links/index.html
+++ b/links/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/local-search.xml b/local-search.xml
index 6e783b77..929986a4 100644
--- a/local-search.xml
+++ b/local-search.xml
@@ -244,10 +244,10 @@
- X.509
-
certificate
+ X.509
+
nginx
openssl
@@ -321,12 +321,12 @@
+ docker
+
k8s
ingress
- docker
-
mac
@@ -346,12 +346,12 @@
- docker
-
kafka
container
+ docker
+
@@ -597,12 +597,12 @@
+ python
+
leetcode
算法
- python
-
@@ -620,12 +620,12 @@
+ python
+
leetcode
算法
- python
-
@@ -643,12 +643,12 @@
+ python
+
leetcode
算法
- python
-
@@ -689,12 +689,12 @@
+ python
+
leetcode
算法
- python
-
@@ -804,12 +804,12 @@
+ python
+
leetcode
算法
- python
-
@@ -829,10 +829,10 @@
java
- grails
-
gradle
+ grails
+
@@ -850,12 +850,12 @@
+ python
+
leetcode
算法
- python
-
@@ -915,12 +915,12 @@
+ python
+
leetcode
算法
- python
-
@@ -938,12 +938,12 @@
+ python
+
leetcode
算法
- python
-
@@ -982,10 +982,10 @@
- python
-
R
+ python
+
@@ -1003,12 +1003,12 @@
+ python
+
leetcode
算法
- python
-
@@ -1026,12 +1026,12 @@
+ python
+
leetcode
算法
- python
-
@@ -1049,12 +1049,12 @@
+ python
+
leetcode
算法
- python
-
diff --git a/page/2/index.html b/page/2/index.html
index fe657be7..cb542748 100644
--- a/page/2/index.html
+++ b/page/2/index.html
@@ -55,7 +55,7 @@
-
+
@@ -285,10 +285,10 @@ 同事写R程序的时候,问我能不能获取一个变量的name
,
+我说这个好办啊,在R里可以这样写,用quote()
+> a <- 1
+> quote(a)
+a
+
+不过我把问题想简单了,他实际需要的是要获得传入
+参数的name
。例如定义一个函数foo(c)
, 给它传入参数a
,我能够在函数
+内部知道传入参数的名字a
。
+
+
+我说可以增加一个参数嘛。把参数的名字直接传入。
+> foo(a,"a")
+
+但是我立刻意识到,这里存在重复,而写代码是提倡
+do not repeat yourself
的。而且对R这种可以随意获取环境变量的高级语言。
+肯定有办法做到。
+ python可以做到但是我还是先看看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也可以那么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()
的作用是一样的。
+]]>
+
+ R
+ python
+
+
[leetcode 224 & 227]Basic Calculator I & II 原创解法
/2016/10/24/leetcode-224/
@@ -76,9 +134,48 @@ python默认的递归栈深度是10000。
如何把一个调用自身的递归改成非递归。
]]>
+ python
leetcode
算法
+
+
+
+ 使用pandas.read_csv()读取csv文件
+ /2016/12/22/pandas-read-csv/
+ 问题以下Python代码实现对Excel
转存的csv文件进行读取。
+import pandas as pd df = pd.read_csv(file_path + file_name + ".csv" , encoding="gbk" )
+
+
+
+把csv
文件入库是一件脏活。表面上看csv
文件是一个非常简单的
+逗号分隔符文件。但是其实不然。Excel
转存的csv
文件并不是标准的以逗号作为分隔符,
+并且对所有的项用双引号包裹。现在我就遇到了从Oracle
导出的csv
文件,以上的代码
+不起作用了。
+ 解决问题究竟怎么回事呢,找了一圈也没有发现使用pandas.read_csv()
读取
+这种标准csv文件的方法。还是先把问题简化一下,看看Python
的csv模块
是如何读取的吧。
+简单的查找就可以找到答案。
+import csv csv.register_dialect( 'mydialect' , delimiter = ',' , quotechar = '"' , doublequote = True , skipinitialspace = True , lineterminator = '\r\n' , quoting = csv.QUOTE_MINIMAL)print ('\n Output from an iterable object created from the csv file' )with open ('smallsample.csv' , 'rb' ) as mycsvfile: thedata = csv.reader(mycsvfile, dialect='mydialect' ) for row in thedata: print (row[0 ]+"\t \t" +row[1 ]+"\t \t" +row[4 ])
+
+好的,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
+
+那么问题解决了。把第一段代码改改吧。
+import pandas as pd df = pd.read_csv(file_path + file_name + ".csv" , encoding="gbk" , dialect='mydialect' )
+
+一运行还是报错,这是怎么回事呢,编码换成utf8
,也不行。最后才发现
+需要使用gb18030
才行。即使使用chardet
的编码探测模块,也不一定能探测出来,因为整个文档
+只有少数字符是超出了gbk
,所以不可能既高效又准确的解决这个问题。
+]]>
+
python
+ 技巧
@@ -130,67 +227,9 @@ not unary) +, -, or * between the digits so they evaluate to the target value.
class Solution2 (object ): def addOperators (self, num, target ): """ 这个居然通过了。呵呵。不过是勉强通过的。 :type num: str :type target: int :rtype: List[str] """ if num: end = len (num) result = [] path = [num[0 ]] s1 = [int (num[0 ])] s2 = [] begin = 1 self .find(begin, end, num, s1, s2, path, target, result) return result else : return [] def find (self, begin, end, num, s1, s2, path, target, result ): if begin == end: if s1[0 ] == target: result.append("" .join(path)) else : ops = [("+" , 10 ), ("-" , 10 ), ("*" , 20 ), ("." , 30 )] ps = ["+" , "-" , "*" , "" ] if begin + 1 < end: for i in range (4 ): s11 = s1[:] s22 = s2[:] path.append(ps[i] + num[begin]) self .helper(begin, end, num, ops[i], path, result, s11, s22, target) path.pop() elif begin + 1 == end: for i in range (4 ): s11 = s1[:] s22 = s2[:] path.append(ps[i] + num[begin]) self .helper2(begin, end, num, ops[i], path, result, s11, s22, target) path.pop() def helper (self, begin, end, num, op, path, result, s1, s2, target ): if s2: op_pre = s2[-1 ] if op_pre[1 ] < op[1 ]: s1.append(int (num[begin])) s2.append(op) self .find(begin + 1 , end, num, s1, s2, path, target, result) else : is_valid = True while s2: op_pre = s2[-1 ] if op_pre[1 ] < op[1 ]: break op_pre = s2.pop() n2 = s1.pop() n1 = s1[-1 ] if op_pre[0 ] == "+" : s1[-1 ] = n1 + n2 elif op_pre[0 ] == "-" : s1[-1 ] = n1 - n2 elif op_pre[0 ] == "*" : s1[-1 ] = n1 * n2 elif op_pre[0 ] == "." and n1 != 0 : s1[-1 ] = n1 * 10 + n2 else : is_valid = False break if is_valid: s1.append(int (num[begin])) s2.append(op) self .find(begin + 1 , end, num, s1, s2, path, target, result) else : s1.append(int (num[begin])) s2.append(op) self .find(begin + 1 , end, num, s1, s2, path, target, result) def helper2 (self, begin, end, num, op, path, result, s1, s2, target ): if s2: op_pre = s2[-1 ] if op_pre[1 ] < op[1 ]: s1.append(int (num[begin])) s2.append(op) is_valid = True while s2: op_pre = s2.pop() n2 = s1.pop() n1 = s1[-1 ] if op_pre[0 ] == "+" : s1[-1 ] = n1 + n2 elif op_pre[0 ] == "-" : s1[-1 ] = n1 - n2 elif op_pre[0 ] == "*" : s1[-1 ] = n1 * n2 elif op_pre[0 ] == "." and n1 != 0 : s1[-1 ] = n1 * 10 + n2 else : is_valid = False break if is_valid: self .find(begin + 1 , end, num, s1, s2, path, target, result) else : is_valid = True while s2: op_pre = s2[-1 ] if op_pre[1 ] < op[1 ]: break op_pre = s2.pop() n2 = s1.pop() n1 = s1[-1 ] if op_pre[0 ] == "+" : s1[-1 ] = n1 + n2 elif op_pre[0 ] == "-" : s1[-1 ] = n1 - n2 elif op_pre[0 ] == "*" : s1[-1 ] = n1 * n2 elif op_pre[0 ] == "." and n1 != 0 : s1[-1 ] = n1 * 10 + n2 else : is_valid = False break if is_valid: s2.append(op) s1.append(int (num[begin])) while s2: op_pre = s2.pop() n2 = s1.pop() n1 = s1[-1 ] if op_pre[0 ] == "+" : s1[-1 ] = n1 + n2 elif op_pre[0 ] == "-" : s1[-1 ] = n1 - n2 elif op_pre[0 ] == "*" : s1[-1 ] = n1 * n2 elif op_pre[0 ] == "." and n1 != 0 : s1[-1 ] = n1 * 10 + n2 else : is_valid = False break if is_valid: self .find(begin + 1 , end, num, s1, s2, path, target, result) else : n2 = int (num[begin]) n1 = s1[-1 ] is_valid = True if op[0 ] == "+" : s1[-1 ] = n1 + n2 elif op[0 ] == "-" : s1[-1 ] = n1 - n2 elif op[0 ] == "*" : s1[-1 ] = n1 * n2 elif op[0 ] == "." and n1 != 0 : s1[-1 ] = n1 * 10 + n2 else : is_valid = False if is_valid: self .find(begin + 1 , end, num, s1, s2, path, target, result)
]]>
+ python
leetcode
算法
- python
-
-
-
- R和Python里得到传入参数的变量名
- /2016/12/21/get-val-name-and-value/
- 问题同事写R程序的时候,问我能不能获取一个变量的name
,
-我说这个好办啊,在R里可以这样写,用quote()
-> a <- 1
-> quote(a)
-a
-
-不过我把问题想简单了,他实际需要的是要获得传入
-参数的name
。例如定义一个函数foo(c)
, 给它传入参数a
,我能够在函数
-内部知道传入参数的名字a
。
-
-
-我说可以增加一个参数嘛。把参数的名字直接传入。
-> foo(a,"a")
-
-但是我立刻意识到,这里存在重复,而写代码是提倡
-do not repeat yourself
的。而且对R这种可以随意获取环境变量的高级语言。
-肯定有办法做到。
- python可以做到但是我还是先看看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也可以那么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
- R
@@ -281,48 +320,9 @@ Return the array [2, 1, 1, 0].
时间复杂度是O(N*logN)
,接下来的处理也是O(N*logN)
。
]]>
+ python
leetcode
算法
- python
-
-
-
- 使用pandas.read_csv()读取csv文件
- /2016/12/22/pandas-read-csv/
- 问题以下Python代码实现对Excel
转存的csv文件进行读取。
-import pandas as pd df = pd.read_csv(file_path + file_name + ".csv" , encoding="gbk" )
-
-
-
-把csv
文件入库是一件脏活。表面上看csv
文件是一个非常简单的
-逗号分隔符文件。但是其实不然。Excel
转存的csv
文件并不是标准的以逗号作为分隔符,
-并且对所有的项用双引号包裹。现在我就遇到了从Oracle
导出的csv
文件,以上的代码
-不起作用了。
- 解决问题究竟怎么回事呢,找了一圈也没有发现使用pandas.read_csv()
读取
-这种标准csv文件的方法。还是先把问题简化一下,看看Python
的csv模块
是如何读取的吧。
-简单的查找就可以找到答案。
-import csv csv.register_dialect( 'mydialect' , delimiter = ',' , quotechar = '"' , doublequote = True , skipinitialspace = True , lineterminator = '\r\n' , quoting = csv.QUOTE_MINIMAL)print ('\n Output from an iterable object created from the csv file' )with open ('smallsample.csv' , 'rb' ) as mycsvfile: thedata = csv.reader(mycsvfile, dialect='mydialect' ) for row in thedata: print (row[0 ]+"\t \t" +row[1 ]+"\t \t" +row[4 ])
-
-好的,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
-
-那么问题解决了。把第一段代码改改吧。
-import pandas as pd df = pd.read_csv(file_path + file_name + ".csv" , encoding="gbk" , dialect='mydialect' )
-
-一运行还是报错,这是怎么回事呢,编码换成utf8
,也不行。最后才发现
-需要使用gb18030
才行。即使使用chardet
的编码探测模块,也不一定能探测出来,因为整个文档
-只有少数字符是超出了gbk
,所以不可能既高效又准确的解决这个问题。
-]]>
-
- python
- 技巧
@@ -387,9 +387,9 @@ The three ranges are : [0, 0], [2, 2], [0, 2] and their respective sums are: -2,
]]>
+ python
leetcode
算法
- python
@@ -421,9 +421,9 @@ Answer: 3
class Solution (object ): def numIslands (self, grid ): """ 基本思路,一行一行的扫描。 :type grid: List[List[str]] :rtype: int """ print ("====" ) n = len (grid) if n == 0 : return 0 length = len (grid[0 ]) h_pre = {} h_curr = {} a = [] debug = [] accumulator = 1 for i in range (0 , length): if i == 0 and grid[0 ][i] == '1' : h_curr['0,' + str (i)] = [accumulator] a.append(h_curr['0,' + str (i)]) debug.append('0,' + str (i)) elif i > 0 and grid[0 ][i] == '1' : if grid[0 ][i - 1 ] == '1' : h_curr[str (0 ) + ',' + str (i)] = h_curr[str (0 ) + ',' + str (i - 1 )] else : accumulator += 1 h_curr['0,' + str (i)] = [accumulator] a.append(h_curr['0,' + str (i)]) debug.append('0,' + str (i)) for i in range (1 , n): h_pre = h_curr h_curr = {} for j in range (0 , length): if j == 0 and grid[i][j] == '1' : if grid[i - 1 ][j] == '1' : h_curr[str (i) + ',' + str (j)] = h_pre[str (i - 1 ) + ',' + str (j)] else : accumulator += 1 h_curr[str (i) + ',' + str (j)] = [accumulator] a.append(h_curr[str (i) + ',' + str (j)]) debug.append(str (i) + ',' + str (j)) elif j > 0 and grid[i][j] == '1' : if grid[i - 1 ][j] == '1' and grid[i][j - 1 ] == '1' : pre = h_curr[str (i) + ',' + str (j - 1 )] above = h_pre[str (i - 1 ) + ',' + str (j)] if pre[0 ] == above[0 ]: h_curr[str (i) + ',' + str (j)] = pre else : h_curr[str (i) + ',' + str (j)] = above v1 = pre[0 ] v2 = above[0 ] for k in range (0 , len (a)): if a[k][0 ] == v1: a[k][0 ] = v2 elif grid[i][j - 1 ] == '1' and grid[i - 1 ][j] != '1' : pre = h_curr[str (i) + ',' + str (j - 1 )] h_curr[str (i) + ',' + str (j)] = pre elif grid[i][j - 1 ] != '1' and grid[i - 1 ][j] == '1' : above = h_pre[str (i - 1 ) + ',' + str (j)] h_curr[str (i) + ',' + str (j)] = above else : accumulator += 1 h_curr[str (i) + ',' + str (j)] = [accumulator] a.append(h_curr[str (i) + ',' + str (j)]) debug.append(str (i) + ',' + str (j)) s = set () for item in a: s.add(item[0 ]) return len (s)if __name__ == "__main__" : grid = ["11000" , "11000" , "00100" , "00011" ] print (Solution().numIslands(grid)) grid = ["10111" , "10101" , "11101" ] print (Solution().numIslands(grid)) grid = ["1111111" , "0000001" , "1111101" , "1000101" , "1010101" , "1011101" , "1111111" ] print (Solution().numIslands(grid)) grid = ["10011101100000000000" , "10011001000101010010" , "00011110101100001010" , "00011001000111001001" , "00000001110000000000" , "10000101011000000101" , "00010001010101010101" , "00010100110101101110" , "00001001100001000101" , "00100100000100100010" , "10010000000100101010" , "01000101011011101100" , "11010000100000010001" , "01001110001111101000" , "00111000110001010000" , "10010100001000101011" , "10100000010001010000" , "01100011101010111100" , "01000011001010010011" , "00000011110100011000" ] print (Solution().numIslands(grid))
]]>
+ python
leetcode
算法
- python
@@ -645,9 +645,51 @@ python的list
而是使用一个自己创建的链表,那么插入
_heapify_max()
但是却没有_heappushpop_max
这样的方法。真是无语啊。
]]>
+ python
leetcode
算法
+
+
+
+ [leetcode 312]Burst Balloons原创解法
+ /2017/10/07/leetcode-312/
+ 题目概述312原题链接
+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!)。解法如下
+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]
+
+ 回顾矩阵链解法以上的方法无疑会超时,但是它是我们理解问题,验证其它算法的基础。
+我们希望能够得到一个多项式时间的解法,按照以往的经验,我们如果能够找到一个贪心算法是最好的,
+但是如果我们能够有一个贪心算法,那么也一定能够有一个动态规划方法。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) print (list ) n += 1 m = [[None ] * n for i in range (n)] 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]
+]]>
+
python
+ leetcode
+ 算法
@@ -698,45 +740,49 @@ python的list
而是使用一个自己创建的链表,那么插入
- [leetcode 312]Burst Balloons原创解法
- /2017/10/07/leetcode-312/
- 题目概述312原题链接
-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) print (list ) n += 1 m = [[None ] * n for i in range (n)] 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) 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 Hdef 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
@@ -815,49 +861,38 @@ hello world
- How to call java code in Grails 3.2.6
- /2017/03/06/2017-3-6-grails-call-java/
- 解决方法我的Grails
的环境是
-grails -v
-| Grails Version: 3.2.6
-| Groovy Version: 2.4.7
-| JVM Version: 1.8.0_71
-
+ Gradle脚本实现web开发框架的一键构建
+ /2017/05/24/2017-5-24-gradle-build-project/
+ 问题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
文件中添加如下代码
-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>
+特别是最近在搭建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) } }
]]>
java
- grails
gradle
+ build
@@ -915,52 +950,6 @@ Replace the swap UUID with the new one (run sudo blkid to find it) after the pri
virutalbox
-
- [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).
-
-example:
-nums = [
- [9,9,4],
- [6,6,8],
- [2,1,1]
-]
-return 4
-
-
-
- 回溯法首先想到的就是回溯法(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
-
- 拓扑排序和记忆体看看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) 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 Hdef 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%
。
-]]>
-
- leetcode
- 算法
- python
-
-
Write a simple parser for MiniLisp by using JavaCC
/2017/05/19/2017-5-19-javacc-minilisp/
@@ -1024,38 +1013,49 @@ MiniLisp项目下的life.lisp和nqueens.lisp文件内容完整解析测试。
- Gradle脚本实现web开发框架的一键构建
- /2017/05/24/2017-5-24-gradle-build-project/
- 问题Java世界里构建项目用什么工具呢,ant,maven和gradle。maven非常流行,
-原因无非是maven仓库和项目架构的约定。但是ant构建过程更加容易理解,
-因为ant展示了所有的操作。gradle拥有基于groovy语言的DSL语言且继承了
-maven仓库的思想所以笔者认为未来是属于gradle的。
+ How to call java code in Grails 3.2.6
+ /2017/03/06/2017-3-6-grails-call-java/
+ 解决方法我的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) } }
+
]]>
java
gradle
- build
+ grails
@@ -1169,9 +1169,9 @@ gradle run
我的这个解法会超时呢,结果居然击败的80%的提交。
]]>
+ python
leetcode
算法
- python
@@ -1223,9 +1223,42 @@ Determine the maximum amount of money the thief can rob tonight without alerting
]]>
+ python
leetcode
算法
+
+
+
+ [leetcode 341]练练手
+ /2018/08/18/2018-8-18-leetcode-341/
+ 题目概述原题链接
+
+ 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。
+ 第一次通过的解法比较慢。
+class NestedIterator (object ): def __init__ (self, nestedList ): """ Initialize your data structure here. :type nestedList: List[NestedInteger] """ self .q = [(nestedList, 0 )] def next (self ): """ :rtype: int """ val = None not_find = True while self .q and not_find: arr, num = self .q.pop() if num < len (arr): ele = arr[num] if isinstance (ele, int ): val = ele not_find = False self .q.append((arr, num + 1 )) else : self .q.append((arr, num + 1 )) self .q.append((ele, 0 )) return None if not_find else val def hasNext (self ): """ :rtype: bool """ has_next = False while not has_next and self .q: arr, num = self .q.pop() if num < len (arr): ele = arr[num] if isinstance (ele, int ): has_next = True self .q.append((arr, num)) else : self .q.append((arr, num + 1 )) self .q.append((ele, 0 )) return has_nextif __name__ == "__main__" : nestedlist = [[1 , 2 ], 3 , [4 , 5 ], 6 ] i, v = NestedIterator(nestedlist), [] while i.hasNext(): v.append(i.next ()) print (v)
+
+ 优化后的解法之前的解法大概是140ms跑通了44个测试。而大多数的解法则在70ms左右。
+怎么改进呢?
+has_next和next方法本质上是一样的,可以只保留一个next就可以实现两个方法了。
+那么我们可以提前跑next方法,缓存两个值。
+以下是改进后的速度提升了一倍。
+class NestedIterator2 (object ): def __init__ (self, nestedList ): """ Initialize your data structure here. :type nestedList: List[NestedInteger] """ self .q = [(nestedList, 0 )] self .next_val = self .next0() if self .next_val is not None : self .has_next = True else : self .has_next = False def next (self ): curr_val = self .next_val if self .has_next: self .next_val = self .next0() if self .next_val is not None : self .has_next = True else : self .has_next = False return curr_val def hasNext (self ): """ :rtype: bool """ return self .has_next def next0 (self ): """ :rtype: int """ val = None not_find = True while self .q and not_find: arr, num = self .q.pop() if num < len (arr): ele = arr[num] if isinstance (ele, int ): val = ele not_find = False self .q.append((arr, num + 1 )) else : self .q.append((arr, num + 1 )) self .q.append((ele, 0 )) return None if not_find else val
+
+]]>
+
python
+ leetcode
+ 算法
@@ -1294,39 +1327,6 @@ port = 6969 # get from tracker list
p2p
-
- [leetcode 341]练练手
- /2018/08/18/2018-8-18-leetcode-341/
- 题目概述原题链接
-
- 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。
- 第一次通过的解法比较慢。
-class NestedIterator (object ): def __init__ (self, nestedList ): """ Initialize your data structure here. :type nestedList: List[NestedInteger] """ self .q = [(nestedList, 0 )] def next (self ): """ :rtype: int """ val = None not_find = True while self .q and not_find: arr, num = self .q.pop() if num < len (arr): ele = arr[num] if isinstance (ele, int ): val = ele not_find = False self .q.append((arr, num + 1 )) else : self .q.append((arr, num + 1 )) self .q.append((ele, 0 )) return None if not_find else val def hasNext (self ): """ :rtype: bool """ has_next = False while not has_next and self .q: arr, num = self .q.pop() if num < len (arr): ele = arr[num] if isinstance (ele, int ): has_next = True self .q.append((arr, num)) else : self .q.append((arr, num + 1 )) self .q.append((ele, 0 )) return has_nextif __name__ == "__main__" : nestedlist = [[1 , 2 ], 3 , [4 , 5 ], 6 ] i, v = NestedIterator(nestedlist), [] while i.hasNext(): v.append(i.next ()) print (v)
-
- 优化后的解法之前的解法大概是140ms跑通了44个测试。而大多数的解法则在70ms左右。
-怎么改进呢?
-has_next和next方法本质上是一样的,可以只保留一个next就可以实现两个方法了。
-那么我们可以提前跑next方法,缓存两个值。
-以下是改进后的速度提升了一倍。
-class NestedIterator2 (object ): def __init__ (self, nestedList ): """ Initialize your data structure here. :type nestedList: List[NestedInteger] """ self .q = [(nestedList, 0 )] self .next_val = self .next0() if self .next_val is not None : self .has_next = True else : self .has_next = False def next (self ): curr_val = self .next_val if self .has_next: self .next_val = self .next0() if self .next_val is not None : self .has_next = True else : self .has_next = False return curr_val def hasNext (self ): """ :rtype: bool """ return self .has_next def next0 (self ): """ :rtype: int """ val = None not_find = True while self .q and not_find: arr, num = self .q.pop() if num < len (arr): ele = arr[num] if isinstance (ele, int ): val = ele not_find = False self .q.append((arr, num + 1 )) else : self .q.append((arr, num + 1 )) self .q.append((ele, 0 )) return None if not_find else val
-
-]]>
-
- leetcode
- 算法
- python
-
-
BitTorrent协议(四)之bitfield消息
/2019/01/21/2019-1-23-bt-4/
@@ -1415,6 +1415,157 @@ socket.timeout: timed out
Process finished with exit code 0
可以看到,确实是110个1。
+]]>
+
+ bitTorrent
+ protocol
+ p2p
+
+
+
+ BitTorrent协议(一)之解析种子文件
+ /2019/01/09/2019-1-9-bt-1/
+ bt种子文件bt通过种子文件分享已经是一个过去时了,2009年btChina
就已经关闭了。现在一般都是
+使用磁力链接来分享文件。那么为什么种子文件分享不再流行了呢?为什么要用磁力链接呢?
+磁力链接怎么实现的呢?
+嗯这是这个系列要研究的问题。但是要研究磁力链接的实现原理,最好先从种子文件开始。
+
+
+官网文档 BEP3 中的metainfo files章节
+讲的很清楚。
+简单的说就是把tracker列表和分享的文件信息编码为一个二进制的文件。
+
+
+
+如果想要找到下载源,就要通过tracker找到peer节点。
+
+包含了文件的大小,分块个数,分块的sha1散列值。
+编码方式(bencoding) 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语法呢,就是如下
+string : num ':' {CHAR}*num : 0 | [1-9][0-9]+ | '-' [1-9][0-9]+integer : 'i' num 'e'list : 'l' {element}* 'e'dic : 'd' {pair}* 'e'pair : string elementelement : string | integer | list | dic
+
+解码 根据eBNF实现的解码代码如下, 把get_content()
方法中path替换为种子文件的路径,运行就可以看到。
+返回的解析结果中会有info_hash
,该值是根据info的bencoding的二进制串计算的sha1值。这个值很重要
+因为之后很多协议都会用到。
+ __author__ = 'ym' """ Date : '2019/1/7' Description : 解析torrent文件 """ from datetime import datetimeclass BDecode (object ): def __init__ (self, arr ): self .arr = arr self .n = len (arr) self .i = 0 def parse (self ): return self .dic() def peek (self ): next = None if self .i < self .n: next = self .arr[self .i] return chr (next ) def next (self ): next = None if self .i < self .n: next = self .arr[self .i] self .i += 1 return chr (next ) def num (self ): num_str = "" peek = self .peek() if peek is None : raise Exception("malformed num." ) if peek == '-' and self .peek(1 ) in '123456789' : self .next () while self .peek() in "0123456789" : num_str += self .next () if num_str[0 ] in '0' and len (num_str) > 1 : raise Exception("error : 0 starts with num" ) return -int (num_str) elif peek in '0123456789' : while self .peek() in "0123456789" : num_str += self .next () if num_str[0 ] in '0' and len (num_str) > 1 : raise Exception("error : 0 starts with num" ) return int (num_str) else : raise Exception("malformed num." ) def string (self, pieces=False ): length = self .num() if self .next () != ':' : raise Exception("String must contain colon" ) s = self .arr[self .i:(self .i + length)] self .i += length if not pieces: return s.decode("utf8" ) else : result = [] for j in range (0 , length, 20 ): hash = s[j:j + 20 ] result.append(hash .hex ().lower()) return result def integer (self, timestamp=False ): if self .next () != "i" : raise Exception("Integer must begin with i" ) val = self .num() if timestamp: val = datetime.fromtimestamp(val).__str__() if self .next () != "e" : raise Exception("Integer must end with e" ) return val def element (self, pieces=False , timestamp=False ): peek = self .peek() if peek == 'i' : return self .integer(timestamp) elif peek == "l" : return self .list () elif peek == 'd' : return self .dic() elif peek in "0123456789" : return self .string(pieces) else : raise Exception("not recognize." ) def list (self ): if self .next () != "l" : raise Exception("list must begin with l" ) result = [] while self .peek() != 'e' : result.append(self .element()) self .next () return result def dic (self ): if self .next () != 'd' : raise Exception("dic must begin with d" ) result = dict () while self .peek() != "e" : key = self .string() val = None if key == "pieces" : val = self .element(pieces=True ) elif key == 'creation date' : val = self .element(timestamp=True ) else : info_start = None info_end = None if key == 'info' : info_start = self .i val = self .element() if key == 'info' : info_end = self .i result['info_hash' ] = self .sha1(self .arr[info_start:info_end]) result[key] = val self .next () return result def sha1 (self, info ): import hashlib p = hashlib.sha1() p.update(info) return p.hexdigest()def get_content (): path = "/Users/ym/tmp/venom.torrent" with open (path, "rb" ) as f: return f.read()def main (): content = get_content() result = BDecode(content).parse() import pprint pprint.pprint(result)if __name__ == '__main__' : main()
+
+运行结果 用最近的毒液电影的种子进行解析,打印如下,
+其中announce-list就是tracker列表。
+/usr/local/bin/python3.7 /Users/ym/charm/pytest/ym/bt/parse_torrent.py {'announce': 'http://tracker.trackerfix.com:80/announce', 'announce-list': [['http://tracker.trackerfix.com:80/announce'], ['udp://9.rarbg.me:2710 /announce'], ['udp://9.rarbg.to:2710 /announce']], 'comment': 'Torrent downloaded from https://rarbg.to', 'created by': 'mktorrent 1.0', 'creation date': '2018-11-28 16:04:22', 'info': {'files': [{'length': 100074 , 'path': ['English.srt']}, {'length': 31 , 'path': ['RARBG.txt']}, {'length': 4431023676 , 'path': ['Venom.2018.72 0p.WEBRip.x264.AAC2.0-SHITBOX.mp4']}], 'name': 'Venom.2018.72 0p.WEBRip.x264.AAC2.0-SHITBOX', 'piece length': 1048576 , 'pieces': ['a958677 e48a77aff6357 4c885d7fd7091515903 4', '0c713356 b6345 4a914452 cdcc76a8470 fb4bc419', ... ... ... 'b926b048253 bc506cb3f4e52acab9df6b93cf614', '610f8485 ab8c56f53f594e0973 0a34e8529 e13b4']}, 'info_hash': '3329 7ac9c46f07150671 1f1281 4a3dd8ed8b73ed'} Process finished with exit code 0
+]]>
+
+ bitTorrent
+ protocol
+ p2p
+
+
+
+ 使用delve调试K3s
+ /2019/11/19/debug-k3s/
+ k3s是什么K3s 是什么?k8s的精简版。编译之后执行程序大小不到50M。
+可以用在物联网的边缘计算侧。如果想深入了解k8s,那么k3s是个很好的起点。
+那么如果能够断点调试k3s,就更好了。下面我们来看看怎么做。
+
+
+步骤
+建议使用linux 64位操作系统。这样可以native构建。
+建议性能好一些的机器,虚拟机编译会很慢
+建议安装好docker
+配置好go的环境,设置GOPATH
,同时把$GOPATH/bin
加入到PATH
+安装Goland这个集成开发环境
+从github克隆k3s的代码,加上depth参数则不下载历史,速度会快很多$ git clone --depth 1 https://github.com/rancher/k3s.git $GOPATH /src/github.com/rancher/k3s
+安装delve的debug工具。完成之后会生成可执行文件$GOPATH/bin/dlv
$ git clone --depth 1 https://github.com/go-delve/delve.git $GOPATH /src/github.com/go-delve/delve $ cd $GOPATH /src/github.com/go-delve/delve $ make install
+构建含有调试信息的可执行文件k3s
,所在路径是$GOPATH/src/github.com/rancher/k3s/
$ cd $GOPATH /src/github.com/rancher/k3s $ go build -gcflags "all=-N -l" -o k3s
+用delve执行。这时候线程会监听2345的远程调试接入。$ cd $GOPATH /src/github.com/rancher/k3s $ dlv --listen=:2345 --headless=true --api-version=2 exec -- ./k3s server --docker --disable-agent
+Goland中添加k3s项目。项目根路径为$GOPATH
,然后配置增加一个remote调试。在运行之前,在main.go上打一个断点。
+
+运行远程调试之后,成功。
+
+]]>
+
+ k3s
+ delve
+ golang
+
+
+
+ BitTorrent协议(六)之种子嗅探器
+ /2019/02/18/2019-2-18-bt-6/
+ Sniffer(嗅探器)实现一个简单的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,打印并把种子保存再字典表中。
+
+
+
+代码165行,t_hand_query线程执行对请求进行处理。
+
+
+ping: 返回本地节点id
+find_node: 从本地路由表中查出与请求节点id最近的8个节点,并返回。(据说把本地id进行返回,有利于本地节点加入其他路由表)
+get_peers: 这里进行了简单处理,仅仅返回空节点list。
+announce_peer: 这个请求是有节点告诉我们有新的种子文件发布了,并告诉我们info_hash。然后我们拿着info_hash取进行获取metadata的操作。
+代码207行,开启一个独立的获取metadata的线程。如果获取到metadata,则放入metadata_q
中。
+
+from __future__ import absolute_importfrom __future__ import divisionfrom __future__ import print_function __author__ = 'ym' """ Date : '2019/2/3' Description : """ from ym.bt.routing_table import RoutingTable, BTNodefrom ym.bt.util import id_generator, ip_me, decode_compact_node, encode_compact_node, format_sizefrom ym.bt.bencoding_bin import BEncodefrom ym.bt.bdecoding import BDecodefrom ym.bt.request_metadata import request_metadata_topfrom queue import Queueimport socketimport threadingimport logging log = logging.getLogger() NODES = [("router.bittorrent.com" , 6881 ), ("router.utorrent.com" , 6881 ), ("dht.transmissionbt.com" , 6881 )] ID = id_generator() SOURCE_IP = ip_me() SOURCE_PORT = 6881 TIME_OUT = 1 INFO_HASH_METADATA_DIC = {} RLOCK = threading.RLock()def boot_step (sock: socket.socket, local_node: BTNode ): try : dic = {"t" : "aa" , "y" : "q" , "q" : "find_node" , "a" : {"id" : local_node.id .hex (), "target" : local_node.id .hex ()}} data = BEncode(dic).encode_bin() for NODE in NODES: dst_ip, dst_port = NODE sock.sendto(data, (dst_ip, dst_port)) print ("boot_step|send|dst_ip={}|dst_port={}" .format (dst_ip, dst_port)) except Exception as e: log.exception(e) raise Exception("boot_step error" )def request_metadata_thread (peer_id: bytes , info_hash, ip, port, metadata_q: Queue ): try : print ("request_metadata_thread|info_hash={}|ip={}|port={}" .format (info_hash, ip, port)) metadata_dic = request_metadata_top(peer_id, info_hash, ip, port) if metadata_dic is not None : metadata_q.put((metadata_dic, info_hash, ip, port)) except Exception as e: log.exception("request_metadata_thread error" ) raise Exception("request metadata error" )def main (): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setblocking(True ) sock.bind((SOURCE_IP, SOURCE_PORT)) recv_q = Queue(maxsize=0 ) reply_q = Queue(maxsize=0 ) query_q = Queue(maxsize=0 ) error_q = Queue(maxsize=0 ) metadata_q = Queue(maxsize=0 ) rt = RoutingTable() local_node = BTNode(id =ID, ip=SOURCE_IP, port=SOURCE_PORT) print ("local_node={}" .format (local_node)) rt.insert(local_node) print ("boot start." ) boot_step(sock, local_node) print ("boot end." ) def recv_listener (sock: socket.socket, recv_q: Queue ): while True : try : data, addr = sock.recvfrom(2048 ) recv_q.put((data, addr)) except Exception as e: print ("recv_listener error" ) listener = threading.Thread(target=recv_listener, args=(sock, recv_q,)) listener.daemon = True def dispatch_recv (recv_q: Queue, reply_q: Queue, query_q: Queue, error_q: Queue ): while True : data, addr = recv_q.get() try : dic, i, n = BDecode(data).parse() if dic.get("y" ) is not None : v = dic['y' ] if v == 'r' : reply_q.put((dic, addr)) elif v == 'q' : query_q.put((dic, addr)) elif v == 'e' : error_q.put((dic, addr)) else : print ("unknown type msg." ) except Exception as e: log.exception('dispatch_recv error|data={}' .format (data)) t_dispatch = threading.Thread(target=dispatch_recv, args=(recv_q, reply_q, query_q, error_q,)) t_dispatch.daemon = True def handle_reply (sock: socket.socket, reply_q: Queue, rt: RoutingTable ): while True : dic, addr = reply_q.get() try : r = dic['r' ] src_id = r['id' ] src_ip, src_port = addr src_node = BTNode(id =src_id, ip=src_ip, port=src_port) rt.insert(src_node) print ("rt.size={}" .format (rt.size())) if r.get('nodes' ) is not None : nodes = decode_compact_node(r['nodes' ]) dic = {"t" : "aa" , "y" : "q" , "q" : "find_node" , "a" : {"id" : local_node.id .hex (), "target" : local_node.id .hex ()}} data = BEncode(dic).encode_bin() for node in nodes: try : dst_ip, dst_port, id = node sock.sendto(data, (dst_ip, dst_port)) except Exception as e: log.exception("send node error|node={}" .format (node)) elif r.get('peers' ) is not None : print ("handle_reply|peers" ) except Exception as e: log.exception("handle_reply error|dic={}" .format (dic), e) t_hand_reply = threading.Thread(target=handle_reply, args=(sock, reply_q, rt,)) t_hand_reply.daemon = True def handle_query (sock: socket.socket, query_q: Queue ): while True : dic, addr = query_q.get() dst_ip, dst_port = addr try : print ("handle_query|addr={}|dic={}" .format (addr, dic)) if dic['q' ] == 'ping' : reply = {"t" : dic['t' ], "y" : "r" , "r" : {"id" : local_node.id .hex ()}} data = BEncode(reply).encode_bin() sock.sendto(data, (dst_ip, dst_port)) elif dic['q' ] == 'find_node' : nodes = rt.find_closer(bytes .fromhex(dic['a' ]['id' ])) print ("local_node.id={}" .format (local_node.id .hex ())) print (nodes) compact_node = encode_compact_node(nodes) reply = {"t" : dic['t' ], "y" : "r" , "r" : {"id" : local_node.id .hex (), "nodes" : compact_node}} data = BEncode(reply).encode_bin() sock.sendto(data, (dst_ip, dst_port)) print ("handle_query|find_node|send_reply|dst_ip={}|dst_port={}|data={}" .format (dst_ip, dst_port, data)) elif dic['q' ] == 'get_peers' : reply = {"t" : dic['t' ], "y" : "r" , "r" : {"id" : local_node.id .hex (), "token" : "alenym" .encode("utf8" ), "nodes" : b"" }} data = BEncode(reply).encode_bin() sock.sendto(data, (dst_ip, dst_port)) print ("handle_query|get_peers|send_reply|dst_ip={}|dst_port={}|data={}" .format (dst_ip, dst_port, data)) elif dic['q' ] == 'announce_peer' : print ("handle_query|announce_peer|dic={}" .format (dic)) info_hash = dic['a' ].get('info_hash' ) with RLOCK: if info_hash is None or info_hash in INFO_HASH_METADATA_DIC: continue target_ip = dst_ip target_port = dst_port t_request_meta = threading.Thread(target=request_metadata_thread, args=( local_node.id , info_hash, target_ip, target_port, metadata_q)) t_request_meta.start() else : print ("handle_query|other|dic={}" .format (dic)) except Exception as e: log.exception("handle_query error|dic={}" .format (dic)) t_hand_query = threading.Thread(target=handle_query, args=(sock, query_q,)) t_hand_query.daemon = True def handle_error (error_q: Queue ): while True : dic, addr = error_q.get() try : print ("handle_error|addr={}|dic={}" .format (addr, dic)) except Exception as e: log.exception('handle_error error' , e) t_hand_error = threading.Thread(target=handle_error, args=(error_q,)) t_hand_error.daemon = True def handle_metadata (metadata_q: Queue ): while True : try : metadata_dic, info_hash, ip, port = metadata_q.get() format_s = "handle_metadata|info_hash={}|ip={}|port={}|name={}|size={}" file_name = metadata_dic['name' ] size = format_size(metadata_dic.get('length' )) print (format_s.format (info_hash, ip, port, file_name, size)) with RLOCK: if info_hash not in INFO_HASH_METADATA_DIC: INFO_HASH_METADATA_DIC[info_hash] = metadata_dic except Exception as e: log.exception("handle_metadata error" ) t_handle_metadata = threading.Thread(target=handle_metadata, args=(metadata_q,)) t_handle_metadata.daemon = True listener.start() t_dispatch.start() t_hand_reply.start() t_hand_query.start() t_hand_error.start() t_handle_metadata.start() listener.join() t_dispatch.join() t_hand_reply.join() t_hand_query.join() t_hand_error.join() t_handle_metadata.join()if __name__ == '__main__' : main()
+运行日志 我们在一台有公网ip的电脑上运行十几分钟,过滤日志,查看接收到的种子信息如下,
+root@ubuntu:~# cat log.txt | grep "handle_metadata" handle_metadata|info_hash =5553330 daa12bde6a2f71ab26f7b26688219f276|ip =69.80 .12 .126 |port =11715 |name =War for the Planet of the Apes 2017 1080 p BluRay x264 DTS 5.1 MSubS-Hon3y|size =None handle_metadata|info_hash =5556 f3a9c605dd009f92fddb91848f565439e4f4|ip =59.169 .228 .207 |port =62215 |name =(C94) [くろすこスイッチ (くろすこ)] 冷泉さんといちゃいちゃする本 (ガールズ&パンツァー).zip|size =7.80 M handle_metadata|info_hash =555333 edb2519c3aa93db4a150f6e021f5a138ff|ip =96.40 .42 .32 |port =32796 |name =Princess Go Round|size =None handle_metadata|info_hash =572 d4df79a7151d9466d371b960cfece91289bf4|ip =1.175 .76 .59 |port =13283 |name =(同人ゲーム) [181102 ][RJ237312][ピンポイント/キングピン] 妻が隠していたビデオ…~元カレ寝取らせ観察記~ DL版 (files).rar|size =1.08 G handle_metadata|info_hash =572 edd8670066945538e4fe823294b45f2c2e3a3|ip =14.199 .224 .142 |port =22979 |name =0407 raw021|size =None handle_metadata|info_hash =574 b27358fc1604e65babf32abe839267d36ba4b|ip =14.199 .224 .142 |port =22979 |name =o0Akrios0o@www.sexinsex.com@假裝租屋 事實上在陽台勾引住宅區人妻|size =None handle_metadata|info_hash =54730 eeeb5d74a58f49c6da72eb90be922556f0a|ip =219.98 .7 .166 |port =18586 |name =DSAM-29 -DVD|size =None handle_metadata|info_hash =57039 c3ef3343c5b288c5ff219c66f837a88e808|ip =14.199 .224 .142 |port =22979 |name =tmem|size =None handle_metadata|info_hash =5687578 db92c7df4ae3ab7e33c895f04b1e27d37|ip =14.199 .224 .142 |port =22979 |name =GNE-148 |size =None handle_metadata|info_hash =57 b832066b2103e29f3a4af256e25175da6dbdc2|ip =14.199 .224 .142 |port =22979 |name =[Thz.la]supa-154 |size =None handle_metadata|info_hash =56936 a12cdb3a1a837c8faa206a2dea189dadf66|ip =14.199 .224 .142 |port =22979 |name =club-022 _1.wmv|size =2.96 G handle_metadata|info_hash =570e89058161385 b1d7dfadfcdc2d9f276ab829b|ip =14.199 .224 .142 |port =22979 |name =avidol.us-PTBI-026. wmv|size =2.71 G handle_metadata|info_hash =57 c30a1ddecac71142208425c610837b961f4de6|ip =14.199 .224 .142 |port =22979 |name =[HD]sad-039. wmv|size =3.52 G handle_metadata|info_hash =56794273 cf092e0b4f671885a16cf2dbe484f559|ip =14.199 .224 .142 |port =22979 |name =KRE-002. wmv|size =1.20 G handle_metadata|info_hash =5710 c35d85a9a0ed8cadc10bdcd09db5bb0c46fd|ip =14.199 .224 .142 |port =22979 |name =0111 -xv1088|size =None handle_metadata|info_hash =561 b55ec9a956f208f58b798d781bd5577a47b9e|ip =14.199 .224 .142 |port =22979 |name =52. R18-099 |size =None handle_metadata|info_hash =57 c262437e7cf0ca5f24bff947757026f9cef9a8|ip =14.199 .224 .142 |port =22979 |name =DF-35976 |size =None handle_metadata|info_hash =565 bedb7fc17d20f017a668e2931cb1d30b53556|ip =14.199 .224 .142 |port =22979 |name =judexkwok@片瀬まこ合集06 |size =None handle_metadata|info_hash =57e93 ca1527cd35045f0ca75d0c96579dcf96d2a|ip =14.199 .224 .142 |port =22979 |name =SMDV-10 -DVD|size =None handle_metadata|info_hash =56747025 a196cb78db9f94d8ca933677f57e54b0|ip =14.199 .224 .142 |port =22979 |name =HUNT-759. mp4|size =2.01 G handle_metadata|info_hash =56e45 f76194dadb2c799c7f2c9b34bc2fff07cee|ip =14.199 .224 .142 |port =22979 |name =0510 -sama538|size =None handle_metadata|info_hash =57 fae1a68ee9241a593c1623f64feb7927b40469|ip =14.199 .224 .142 |port =22979 |name =[thz.la]chunta-219 |size =None handle_metadata|info_hash =56 b89964da08b923b20722b89ccfc6ae4928aca3|ip =14.199 .224 .142 |port =22979 |name =HUNT-665 _2.mp4|size =1.60 G handle_metadata|info_hash =57528 d8633ee8b823aea17e47def172082072dd5|ip =14.199 .224 .142 |port =22979 |name =HUNT|size =None handle_metadata|info_hash =57 b14ca4cf260763e170e515b1f235ae5dca487f|ip =14.199 .224 .142 |port =22979 |name =SAMA-477 |size =None handle_metadata|info_hash =57 bc38a6f182dbd6be5b42371d645cc43cfa12e1|ip =14.199 .224 .142 |port =22979 |name =HUNT-710. mp4|size =1.82 G handle_metadata|info_hash =57 f2b65672462ff81834a928cf8e9863687dc220|ip =14.199 .224 .142 |port =22979 |name =3208 |size =None ...
+
+有些种子文件为什么没有文件大小呢?例如size=None
,
+handle_metadata|info_hash =54730 eeeb5d74a58f49c6da72eb90be922556f0a|ip =219.98 .7 .166 |port =18586 |name =DSAM-29 -DVD|size =None
+
+用info_hash过滤日志,可以看到metadata包含了一个files
项,该项包含了多个文件。
+root@ubuntu:~# cat log.txt | grep "54730eeeb5d74a58f49c6da72eb90be922556f0a" | grep "metadata_dic" request_metadata|info_hash=54730 eeeb5d74a58f49c6da72eb90be922556f0a|metadata_dic={'files' : [{'ed2k' : b'\xc4"%lT\xa14s\xd86\xd7a\xb1:\x8bC' , 'filehash' : '788c531731917e92334c03423df5433b4d3d941d' , 'length' : 228076 , 'path' : ['C9~Fang Ping Bi Cheng Xu防屏蔽程序.rar' ], 'path.utf-8' : ['C9~Fang Ping Bi Cheng Xu防屏蔽程序.rar' ]}, {'ed2k' : b'\xff,5\xb4W\x1b\xf1\xb0\x16\x13=\x0c\xc1\xc5\xb56' , 'filehash' : 'b760813c2b6690123eb174b5561ce6b4a0b6e000' , 'length' : 1966849 , 'path' : ['HOTAVXXX,Free Adult Movie, Fastest & Newest Porn Movie Site.jpg' ], 'path.utf-8' : ['HOTAVXXX,Free Adult Movie, Fastest & Newest Porn Movie Site.jpg' ]}, {'ed2k' : b'\xfaggnH\xf8e}O\x97\x04\xbb\x86\x9e\x92\xf5' , 'filehash' : '9a8b43c663ecc46d3a29196cf527d8431fef842b' , 'length' : 180 , 'path' : ['HOTAVXXX~最新最快的AV影片每日更新.url' ], 'path.utf-8' : ['HOTAVXXX~最新最快的AV影片每日更新.url' ]}, {'ed2k' : b'\xff,5\xb4W\x1b\xf1\xb0\x16\x13=\x0c\xc1\xc5\xb56' , 'filehash' : 'b760813c2b6690123eb174b5561ce6b4a0b6e000' , 'length' : 1966849 , 'path' : ['HOTAVXXX、自由な成人映画、最も速く&最新ポルノ映画サイト.jpg' ], 'path.utf-8' : ['HOTAVXXX、自由な成人映画、最も速く&最新ポルノ映画サイト.jpg' ]}, {'ed2k' : b'$\x13\xfd\\\x8b\xa5\xee=\xc0\xfb\x88\xff~\x03/\x18' , 'filehash' : 'dddbfe3e917739e1da7afcdd8a78e1a6677b6648' , 'length' : 235 , 'path' : ['QQ愛真人視頻交友聊天室.url' ], 'path.utf-8' : ['QQ愛真人視頻交友聊天室.url' ]}, {'ed2k' : b'/\xe2\xcf\x1b\xc06\xea[\xda\x93\xb9\xd0\\\xc1\x14\x98' , 'filehash' : '715c4e653db4bb74bb7e40073d55113f512299e9' , 'length' : 628211 , 'path' : ['SIS001全面封殺.jpg' ], 'path.utf-8' : ['SIS001全面封殺.jpg' ]}, {'ed2k' : b'\xf3\x19\x893\x8b\r\xc4\r\x99|W)\xf4\xce\x00J' , 'filehash' : 'c4eadc74cdbf36df8a66710f62932a60b1a0949a' , 'length' : 267 , 'path' : ['[城風 - C9]~成人精品長篇區 最新http一手資源.url' ], 'path.utf-8' : ['[城風 - C9]~成人精品長篇區 最新http一手資源.url' ]}, {'ed2k' : b'r\xbab\x10i\xc4\x10<f$\x10+p\x84\xd2Z' , 'filehash' : 'c6a1cb245511e4013c7510220ce3a6d912f8290f' , 'length' : 217 , 'path' : ['dioguitar23(第六天魔王)@草榴社區.url' ], 'path.utf-8' : ['dioguitar23(第六天魔王)@草榴社區.url' ]}, {'ed2k' : b'\xe4z.\xa4\xfa\x88h\xfe\x07\xedy\xc3\x13D\xe8\x7f' , 'filehash' : 'dddd22e1c4027a8e5c6cec98a4ef3dd29b951682' , 'length' : 229 , 'path' : ['dioguitar23@ HD1080.org.url' ], 'path.utf-8' : ['dioguitar23@ HD1080.org.url' ]}, {'ed2k' : b'\xf9\xee!X\xb3\x9d\xd9\x104\xc52\xc4\x872&a' , 'filehash' : '7191792635319be376c5ff3edd251e7bda81d069' , 'length' : 267 , 'path' : ['dioguitar23@AV 天空.url' ], 'path.utf-8' : ['dioguitar23@AV 天空.url' ]}, {'ed2k' : b'\xba\x18f&*y&\xf5H\xe8\x1f;8x\x1eA' , 'filehash' : 'd774c64c18ec44e607ba6825675e841332a0d4f4' , 'length' : 188 , 'path' : ['dioguitar23@D.C.資訊交流網.url' ], 'path.utf-8' : ['dioguitar23@D.C.資訊交流網.url' ]}, {'ed2k' : b'\x91\x97\x15O\rxh\x7fa\xbf^\xbf \x187K' , 'filehash' : 'f06835576489512e2c4a3ff58a5c4a2fed3131a3' , 'length' : 174 , 'path' : ['dioguitar23@KTzone.url' ], 'path.utf-8' : ['dioguitar23@KTzone.url' ]}, {'ed2k' : b"\xf7\xf1\x9c\xa0+\x15\xc5_\xdb2u'\x81\xcb\xefU" , 'filehash' : '25a54a9fdab494a60fdbd206089ee75c08913f24' , 'length' : 235 , 'path' : ['dioguitar23@SexInSex! Board.url' ], 'path.utf-8' : ['dioguitar23@SexInSex! Board.url' ]}, {'ed2k' : b'\x10k\x08\xafU\xcb\x05\x8b\x0f\xf9\xaf\xcb\r\xdd\xf3\xc4' , 'filehash' : 'a9bb602e06bb33483a1960db95bc65b535831c5d' , 'length' : 1086 , 'path' : ['dioguitar23@公仔箱論壇.url' ], 'path.utf-8' : ['dioguitar23@公仔箱論壇.url' ]}, {'ed2k' : b'6-*X\x12\xf3f\xbe\xd8q\x15\xbe\x91[\xee4' , 'filehash' : 'd19dd08e932c7db940491e5ac259c6ad0ff225c3' , 'length' : 188 , 'path' : ['dioguitar23@痴漢俱樂部.url' ], 'path.utf-8' : ['dioguitar23@痴漢俱樂部.url' ]}, {'ed2k' : b'\x89\xe1CN\xef\xd1\x83\xc2\x8c?,|;\xdb\t\x10' , 'filehash' : '47255a6ce6a43dc9af1de780fcffd210de793bfd' , 'length' : 190 , 'path' : ['dioguitar23@香港廣場.url' ], 'path.utf-8' : ['dioguitar23@香港廣場.url' ]}, {'ed2k' : b'\xaaJh&\x1f\x88\x93\xda\x9bL\xca\xd9\x9c+V\xa9' , 'filehash' : '168c3faa1f01a31b31c8d5a1bb7e699333e0bfe4' , 'length' : 226 , 'path' : ['dioguitar23_Plus28 討論區.url' ], 'path.utf-8' : ['dioguitar23_Plus28 討論區.url' ]}, {'ed2k' : b"\x04k`{\x0b'\xee\x80\xb4\x8e\x91=\xa92\xaf\x95" , 'filehash' : 'c7181e934379905222bf363317d8ae8ddfb12eb4' , 'length' : 220 , 'path' : ['dioguitar23_Touch99.url' ], 'path.utf-8' : ['dioguitar23_Touch99.url' ]}, {'ed2k' : b'\xaf\xbe+\xd5\xde\x96\xdc\xe0\xa8a\x18\x87b\x8d\xf6\x0c' , 'filehash' : 'a92caa360c647d14ae05655c6e43e9f28d7edcf5' , 'length' : 208 , 'path' : ['dioguitar23_WK綜合論壇.url' ], 'path.utf-8' : ['dioguitar23_WK綜合論壇.url' ]}, {'ed2k' : b'\x05>\x9b.\nS\xb2\x9c&L\xbc5H\x82\xcc\xcb' , 'filehash' : '6fa079db3cafb196163e9a6b126f60a5d4aa85b1' , 'length' : 156 , 'path' : ['dioguitar23_mimip2p.url' ], 'path.utf-8' : ['dioguitar23_mimip2p.url' ]}, {'ed2k' : b'\xc3|\x1fKA\xb7\xdd\xb9\x87\xd3}\x04\x8f}\xb3m' , 'filehash' : '1a709fb06ff75ced80995ab8fcdefa69744d7aa0' , 'length' : 229 , 'path' : ['dioguitar23_※=色界论坛=※ (开放注册) 色界论坛.url' ], 'path.utf-8' : ['dioguitar23_※=色界论坛=※ (开放注册) 色界论坛.url' ]}, {'ed2k' : b'>\x13\x98U3q\xf6u\xe6\x06\xc4\xa4\x91\xb24\xc5' , 'filehash' : '350cdfa69ce763f8c2fad85e1f0f4323c3fb5dfa' , 'length' : 208 , 'path' : ['dioguitar23_九九情色帝国.url' ], 'path.utf-8' : ['dioguitar23_九九情色帝国.url' ]}, {'ed2k' : b'E\x0c\x8a\xed5{\x00\xe6\xdb\xfc\x9b\xb1|U%\xf5' , 'filehash' : '384d925cb1da6605d49014622ed9f0b455efe343' , 'length' : 261 , 'path' : ['dioguitar23_性吧春暖花开,春暖花开性吧有你.url' ], 'path.utf-8' : ['dioguitar23_性吧春暖花开,春暖花开性吧有你.url' ]}, {'ed2k' : b'\xefE\xbe\xc2\xcf(\xf5\xe3\x18\x04\xdan\x8f\x08j\xcf' , 'filehash' : '2509953b0831793667a904ee08229da3114835cf' , 'length' : 214 , 'path' : ['dioguitar23_找樂子論壇.url' ], 'path.utf-8' : ['dioguitar23_找樂子論壇.url' ]}, {'ed2k' : b"QuDc'zc]\xcb\xeb\xc0\x86-\xfe\xa5\xf4" , 'filehash' : '2e92574d6c46add45048b08aa15b4eae132c2cc1' , 'length' : 235 , 'path' : ['dioguitar23_無限討論區.url' ], 'path.utf-8' : ['dioguitar23_無限討論區.url' ]}, {'ed2k' : b'tf\xe0\xda\x8c&\x96A\x9d\x9e~P\xe9\xd6\x83\xdb' , 'filehash' : '6b2a83a72df99bd7f2a197cb6fd423a41e061b70' , 'length' : 138 , 'path' : ['hav.tv-新幹線ONLINE~慶祝開站包月對折優惠~免費影片天看到爽!!.url' ], 'path.utf-8' : ['hav.tv-新幹線ONLINE~慶祝開站包月對折優惠~免費影片天看到爽!!.url' ]}, {'ed2k' : b'\'\xf0\x08\xf1 {\xa9"\x13//(\xc7o\x9d`' , 'filehash' : '226f0d140af4689833613f0f21a95608fff324ef' , 'length' : 4324384768 , 'path' : ['hotavxxx.com_DSAM-29.ISO' ], 'path.utf-8' : ['hotavxxx.com_DSAM-29.ISO' ]}, {'ed2k' : b'\x92]\xef\x0b\xa5\xee\xff\xd9\xb9\xd2\xd2\xc2J\x9e\x1a\xdc' , 'filehash' : 'eb8316fc354049e3bc1fb251bf6928dc55ed157a' , 'length' : 131523 , 'path' : ['hotavxxx.com_DSAM-29.jpg' ], 'path.utf-8' : ['hotavxxx.com_DSAM-29.jpg' ]}, {'ed2k' : b'0\xaf\xc8Q\xc9\xa3\xfb\xacn\xf4\xde\xc1\r\x9c\xda\xbd' , 'filehash' : 'aa6975d6341d2a079661c066eb5244ee0fd1aaff' , 'length' : 2254263 , 'path' : ['hotavxxx.com_DSAM-29A.jpg' ], 'path.utf-8' : ['hotavxxx.com_DSAM-29A.jpg' ]}, {'ed2k' : b'\xa1\xaf\x1b\xd7\x84\xd2g\x87\x99>\x82\xec\x94j\xb1U' , 'filehash' : '7254c5e02a0119f38e9c0e7576a1d8ac04b59f67' , 'length' : 214 , 'path' : ['❤dioguitar23❤18P2P-2013年04月2日 18p2p開放註冊3天.url' ], 'path.utf-8' : ['❤dioguitar23❤18P2P-2013年04月2日 18p2p開放註冊3天.url' ]}, {'ed2k' : b'\xe0\x82]\xe7\xe2\xc3\xe0\x9bn\x81\x1d\xa4\x0fr\x9f+' , 'filehash' : 'cad3021392437e48d35faa57861e8ce0a89a6a0d' , 'length' : 233 , 'path' : ['アジア表動画公開板 [城風 - C9].url' ], 'path.utf-8' : ['アジア表動画公開板 [城風 - C9].url' ]}, {'ed2k' : b'\x9a\xdeh5D\x1e}5`FeM\xf3\xcf\x1dt' , 'filehash' : '37402d4e9e9fedb26ff5c833fbc399fb5462a4af' , 'length' : 168 , 'path' : ['京色屋~即日起大降價.一片只要30元起.藍光片只要150元.url' ], 'path.utf-8' : ['京色屋~即日起大降價.一片只要30元起.藍光片只要150元.url' ]}, {'ed2k' : b'W\x9a\xf4E\x1d\xb0b(\x05_v\x00\xef\xe5\xe4\xe6' , 'filehash' : '6d6df82810c4aa00152c3912d28bc13b6589a780' , 'length' : 223 , 'path' : ['堂本屋 TW DMM~即日起大降價.一片只要30元起.藍光片只要150元.url' ], 'path.utf-8' : ['堂本屋 TW DMM~即日起大降價.一片只要30元起.藍光片只要150元.url' ]}, {'ed2k' : b'\x83\x05t=\xda\x85\x84\xbc\x1c\xb3J\xac\x04\x94\xfea' , 'filehash' : '52dcc77f3f2aad3ffd1cf4af73157deb1aeb8f4c' , 'length' : 2994 , 'path' : ['更多精彩.rar' ], 'path.utf-8' : ['更多精彩.rar' ]}, {'ed2k' : b'<\xf8Y\xfd\x08\x99.\xc5\xec\x0e\x03V\r\xb4%\x06' , 'filehash' : 'ab91ce91df44509574450c875435f3c2a7bf18cf' , 'length' : 226 , 'path' : ['金花娱乐城-澳门网上真人赌博,90后性感荷官24小时在线存提款(5分钟到账).url' ], 'path.utf-8' : ['金花娱乐城-澳门网上真人赌博,90后性感荷官24小时在线存提款(5分钟到账).url' ]}, {'ed2k' : b'\xfc*"\xad\xdbva\xdf\xbf\x92\x0e\x83.\x08/\x84' , 'filehash' : 'b60f8d54f1756a04753905be9a9dc7982f33ad14' , 'length' : 235 , 'path' : ['魔王の家-情慾視界~最新最快的成人資訊平台 魔王の家,http--bbs.hotavxxx.com.url' ], 'path.utf-8' : ['魔王の家-情慾視界~最新最快的成人資訊平台 魔王の家,http--bbs.hotavxxx.com.url' ]}], 'name' : 'DSAM-29-DVD' , 'name.utf-8' : 'DSAM-29-DVD' , 'piece length' : 1048576 , 'pieces' : ['557c428f62f38fe8ac8d11411bc9796abb28254d' , '53effb4976edd7d32c9be7a0315bfd40ff93875e' , 'c38ea041059367193bc3e9cc6fac40f67cfb377f' , '63d640ba1942f834e4ec0340f84d8c0703a1f992' , '8b783f6a2dc8ec649ad78acc8c49f892a8bf7663' , '961a8f0b0cdfe010c979061082982c3548173948' , '57dd2532983cf4d30bd442281d007ce0189b6eac' , 'd858de50e2a5d5dd60945ac687baa159697d88cd' , 'f7a262965c87d1c4d3cc10660176c4d063d16807' , '43f7e4f982c4a47d94757071e8b280798d5fbfa9' , ...
]]>
bitTorrent
@@ -2012,250 +2163,47 @@ Process finished with exit code 0
- BitTorrent协议(一)之解析种子文件
- /2019/01/09/2019-1-9-bt-1/
- bt种子文件bt通过种子文件分享已经是一个过去时了,2009年btChina
就已经关闭了。现在一般都是
-使用磁力链接来分享文件。那么为什么种子文件分享不再流行了呢?为什么要用磁力链接呢?
-磁力链接怎么实现的呢?
-嗯这是这个系列要研究的问题。但是要研究磁力链接的实现原理,最好先从种子文件开始。
+ Java语言中bytes convert to string and back not equal
+ /2019/04/28/2019-4-28-byte-to-string-and-back/
+ 问题用google搜索关键词”java bytes to string and back not equal”,第一个就是我说的这个问题。
+什么意思呢?就是在java中,bytes转化为string之后,再转换回bytes的时候,发现不相同了。
+但是Go
语言就没有这个问题哦 。
-官网文档 BEP3 中的metainfo files章节
-讲的很清楚。
-简单的说就是把tracker列表和分享的文件信息编码为一个二进制的文件。
-
-
+运行如下java代码
+byte [] a = new byte []{(byte ) 0xc0 , (byte ) 0xa8 , (byte ) 0x00 , (byte ) 0x01 , (byte ) 0x04 , (byte ) 0x38 }; System.out.println(ByteBufUtil.hexDump(a));String s = new String (a); System.out.println(ByteBufUtil.hexDump(s.getBytes()));
-如果想要找到下载源,就要通过tracker找到peer节点。
-
-包含了文件的大小,分块个数,分块的sha1散列值。
-编码方式(bencoding) 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语法呢,就是如下
-string : num ':' {CHAR}*num : 0 | [1-9][0-9]+ | '-' [1-9][0-9]+integer : 'i' num 'e'list : 'l' {element}* 'e'dic : 'd' {pair}* 'e'pair : string elementelement : string | integer | list | dic
+输出的结果如下:
+c0a800010438 efbfbdefbfbd00010438
-解码 根据eBNF实现的解码代码如下, 把get_content()
方法中path替换为种子文件的路径,运行就可以看到。
-返回的解析结果中会有info_hash
,该值是根据info的bencoding的二进制串计算的sha1值。这个值很重要
-因为之后很多协议都会用到。
- __author__ = 'ym' """ Date : '2019/1/7' Description : 解析torrent文件 """ from datetime import datetimeclass BDecode (object ): def __init__ (self, arr ): self .arr = arr self .n = len (arr) self .i = 0 def parse (self ): return self .dic() def peek (self ): next = None if self .i < self .n: next = self .arr[self .i] return chr (next ) def next (self ): next = None if self .i < self .n: next = self .arr[self .i] self .i += 1 return chr (next ) def num (self ): num_str = "" peek = self .peek() if peek is None : raise Exception("malformed num." ) if peek == '-' and self .peek(1 ) in '123456789' : self .next () while self .peek() in "0123456789" : num_str += self .next () if num_str[0 ] in '0' and len (num_str) > 1 : raise Exception("error : 0 starts with num" ) return -int (num_str) elif peek in '0123456789' : while self .peek() in "0123456789" : num_str += self .next () if num_str[0 ] in '0' and len (num_str) > 1 : raise Exception("error : 0 starts with num" ) return int (num_str) else : raise Exception("malformed num." ) def string (self, pieces=False ): length = self .num() if self .next () != ':' : raise Exception("String must contain colon" ) s = self .arr[self .i:(self .i + length)] self .i += length if not pieces: return s.decode("utf8" ) else : result = [] for j in range (0 , length, 20 ): hash = s[j:j + 20 ] result.append(hash .hex ().lower()) return result def integer (self, timestamp=False ): if self .next () != "i" : raise Exception("Integer must begin with i" ) val = self .num() if timestamp: val = datetime.fromtimestamp(val).__str__() if self .next () != "e" : raise Exception("Integer must end with e" ) return val def element (self, pieces=False , timestamp=False ): peek = self .peek() if peek == 'i' : return self .integer(timestamp) elif peek == "l" : return self .list () elif peek == 'd' : return self .dic() elif peek in "0123456789" : return self .string(pieces) else : raise Exception("not recognize." ) def list (self ): if self .next () != "l" : raise Exception("list must begin with l" ) result = [] while self .peek() != 'e' : result.append(self .element()) self .next () return result def dic (self ): if self .next () != 'd' : raise Exception("dic must begin with d" ) result = dict () while self .peek() != "e" : key = self .string() val = None if key == "pieces" : val = self .element(pieces=True ) elif key == 'creation date' : val = self .element(timestamp=True ) else : info_start = None info_end = None if key == 'info' : info_start = self .i val = self .element() if key == 'info' : info_end = self .i result['info_hash' ] = self .sha1(self .arr[info_start:info_end]) result[key] = val self .next () return result def sha1 (self, info ): import hashlib p = hashlib.sha1() p.update(info) return p.hexdigest()def get_content (): path = "/Users/ym/tmp/venom.torrent" with open (path, "rb" ) as f: return f.read()def main (): content = get_content() result = BDecode(content).parse() import pprint pprint.pprint(result)if __name__ == '__main__' : main()
+分析 bytes转化为string类型,本质上要选择一种编码。那么选择的是什么呢?
+我们看看new String()
执行的代码。通过跟踪,可以看到,使用了默认的编码。
+csn为UTF-8
。
+static char [] decode(byte [] ba, int off, int len) { String csn = Charset.defaultCharset().name(); try { return decode (csn, ba, off, len) ; } catch (UnsupportedEncodingException x) { warnUnsupportedCharset(csn); } try { return decode ("ISO-8859-1" , ba, off, len) ; } catch (UnsupportedEncodingException x) { MessageUtils.err("ISO-8859-1 charset not available: " + x.toString()); System.exit(1 ); return null ; } }
-运行结果 用最近的毒液电影的种子进行解析,打印如下,
-其中announce-list就是tracker列表。
-/usr/local/bin/python3.7 /Users/ym/charm/pytest/ym/bt/parse_torrent.py {'announce': 'http://tracker.trackerfix.com:80/announce', 'announce-list': [['http://tracker.trackerfix.com:80/announce'], ['udp://9.rarbg.me:2710 /announce'], ['udp://9.rarbg.to:2710 /announce']], 'comment': 'Torrent downloaded from https://rarbg.to', 'created by': 'mktorrent 1.0', 'creation date': '2018-11-28 16:04:22', 'info': {'files': [{'length': 100074 , 'path': ['English.srt']}, {'length': 31 , 'path': ['RARBG.txt']}, {'length': 4431023676 , 'path': ['Venom.2018.72 0p.WEBRip.x264.AAC2.0-SHITBOX.mp4']}], 'name': 'Venom.2018.72 0p.WEBRip.x264.AAC2.0-SHITBOX', 'piece length': 1048576 , 'pieces': ['a958677 e48a77aff6357 4c885d7fd7091515903 4', '0c713356 b6345 4a914452 cdcc76a8470 fb4bc419', ... ... ... 'b926b048253 bc506cb3f4e52acab9df6b93cf614', '610f8485 ab8c56f53f594e0973 0a34e8529 e13b4']}, 'info_hash': '3329 7ac9c46f07150671 1f1281 4a3dd8ed8b73ed'} Process finished with exit code 0
+因为通常java编译的时候的默认编码是UTF-8
。
+那么如何保证转化为字符串还能够转换回来呢?
+一种方法是使用ISO-8859-1
。例如,
+byte [] a = new byte []{(byte ) 0xC0 , (byte ) 0xa8 , (byte ) 0x00 , (byte ) 0x01 , (byte ) 0x04 , (byte ) 0x38 }; System.out.println(ByteBufUtil.hexDump(a));String s = new String (a, Charset.forName("ISO-8859-1" )); System.out.println(ByteBufUtil.hexDump(s.getBytes(Charset.forName("ISO-8859-1" ))));
+结果是相等的。
+
+
+Go语言没有这个问题 Go语言则对这个问题解决的非常好。
+a := []byte {0xc0 , 0xa8 , 0x00 , 0x01 , 0x04 , 0x38 } fmt.Println(a) s := string (a) b := []byte (s) fmt.Println(b)
+运行结果是相等的。
+[192 168 0 1 4 56] [192 168 0 1 4 56]
]]>
- bitTorrent
- protocol
- p2p
+ java
+ golang
- 使用delve调试K3s
- /2019/11/19/debug-k3s/
- k3s是什么K3s 是什么?k8s的精简版。编译之后执行程序大小不到50M。
-可以用在物联网的边缘计算侧。如果想深入了解k8s,那么k3s是个很好的起点。
-那么如果能够断点调试k3s,就更好了。下面我们来看看怎么做。
-
-
-步骤
-建议使用linux 64位操作系统。这样可以native构建。
-建议性能好一些的机器,虚拟机编译会很慢
-建议安装好docker
-配置好go的环境,设置GOPATH
,同时把$GOPATH/bin
加入到PATH
-安装Goland这个集成开发环境
-从github克隆k3s的代码,加上depth参数则不下载历史,速度会快很多$ git clone --depth 1 https://github.com/rancher/k3s.git $GOPATH /src/github.com/rancher/k3s
-安装delve的debug工具。完成之后会生成可执行文件$GOPATH/bin/dlv
$ git clone --depth 1 https://github.com/go-delve/delve.git $GOPATH /src/github.com/go-delve/delve $ cd $GOPATH /src/github.com/go-delve/delve $ make install
-构建含有调试信息的可执行文件k3s
,所在路径是$GOPATH/src/github.com/rancher/k3s/
$ cd $GOPATH /src/github.com/rancher/k3s $ go build -gcflags "all=-N -l" -o k3s
-用delve执行。这时候线程会监听2345的远程调试接入。$ cd $GOPATH /src/github.com/rancher/k3s $ dlv --listen=:2345 --headless=true --api-version=2 exec -- ./k3s server --docker --disable-agent
-Goland中添加k3s项目。项目根路径为$GOPATH
,然后配置增加一个remote调试。在运行之前,在main.go上打一个断点。
-
-运行远程调试之后,成功。
-
-]]>
-
- k3s
- delve
- golang
-
-
-
- BitTorrent协议(六)之种子嗅探器
- /2019/02/18/2019-2-18-bt-6/
- Sniffer(嗅探器)实现一个简单的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,打印并把种子保存再字典表中。
-
-
-
-代码165行,t_hand_query线程执行对请求进行处理。
-
-
-ping: 返回本地节点id
-find_node: 从本地路由表中查出与请求节点id最近的8个节点,并返回。(据说把本地id进行返回,有利于本地节点加入其他路由表)
-get_peers: 这里进行了简单处理,仅仅返回空节点list。
-announce_peer: 这个请求是有节点告诉我们有新的种子文件发布了,并告诉我们info_hash。然后我们拿着info_hash取进行获取metadata的操作。
-代码207行,开启一个独立的获取metadata的线程。如果获取到metadata,则放入metadata_q
中。
-
-from __future__ import absolute_importfrom __future__ import divisionfrom __future__ import print_function __author__ = 'ym' """ Date : '2019/2/3' Description : """ from ym.bt.routing_table import RoutingTable, BTNodefrom ym.bt.util import id_generator, ip_me, decode_compact_node, encode_compact_node, format_sizefrom ym.bt.bencoding_bin import BEncodefrom ym.bt.bdecoding import BDecodefrom ym.bt.request_metadata import request_metadata_topfrom queue import Queueimport socketimport threadingimport logging log = logging.getLogger() NODES = [("router.bittorrent.com" , 6881 ), ("router.utorrent.com" , 6881 ), ("dht.transmissionbt.com" , 6881 )] ID = id_generator() SOURCE_IP = ip_me() SOURCE_PORT = 6881 TIME_OUT = 1 INFO_HASH_METADATA_DIC = {} RLOCK = threading.RLock()def boot_step (sock: socket.socket, local_node: BTNode ): try : dic = {"t" : "aa" , "y" : "q" , "q" : "find_node" , "a" : {"id" : local_node.id .hex (), "target" : local_node.id .hex ()}} data = BEncode(dic).encode_bin() for NODE in NODES: dst_ip, dst_port = NODE sock.sendto(data, (dst_ip, dst_port)) print ("boot_step|send|dst_ip={}|dst_port={}" .format (dst_ip, dst_port)) except Exception as e: log.exception(e) raise Exception("boot_step error" )def request_metadata_thread (peer_id: bytes , info_hash, ip, port, metadata_q: Queue ): try : print ("request_metadata_thread|info_hash={}|ip={}|port={}" .format (info_hash, ip, port)) metadata_dic = request_metadata_top(peer_id, info_hash, ip, port) if metadata_dic is not None : metadata_q.put((metadata_dic, info_hash, ip, port)) except Exception as e: log.exception("request_metadata_thread error" ) raise Exception("request metadata error" )def main (): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setblocking(True ) sock.bind((SOURCE_IP, SOURCE_PORT)) recv_q = Queue(maxsize=0 ) reply_q = Queue(maxsize=0 ) query_q = Queue(maxsize=0 ) error_q = Queue(maxsize=0 ) metadata_q = Queue(maxsize=0 ) rt = RoutingTable() local_node = BTNode(id =ID, ip=SOURCE_IP, port=SOURCE_PORT) print ("local_node={}" .format (local_node)) rt.insert(local_node) print ("boot start." ) boot_step(sock, local_node) print ("boot end." ) def recv_listener (sock: socket.socket, recv_q: Queue ): while True : try : data, addr = sock.recvfrom(2048 ) recv_q.put((data, addr)) except Exception as e: print ("recv_listener error" ) listener = threading.Thread(target=recv_listener, args=(sock, recv_q,)) listener.daemon = True def dispatch_recv (recv_q: Queue, reply_q: Queue, query_q: Queue, error_q: Queue ): while True : data, addr = recv_q.get() try : dic, i, n = BDecode(data).parse() if dic.get("y" ) is not None : v = dic['y' ] if v == 'r' : reply_q.put((dic, addr)) elif v == 'q' : query_q.put((dic, addr)) elif v == 'e' : error_q.put((dic, addr)) else : print ("unknown type msg." ) except Exception as e: log.exception('dispatch_recv error|data={}' .format (data)) t_dispatch = threading.Thread(target=dispatch_recv, args=(recv_q, reply_q, query_q, error_q,)) t_dispatch.daemon = True def handle_reply (sock: socket.socket, reply_q: Queue, rt: RoutingTable ): while True : dic, addr = reply_q.get() try : r = dic['r' ] src_id = r['id' ] src_ip, src_port = addr src_node = BTNode(id =src_id, ip=src_ip, port=src_port) rt.insert(src_node) print ("rt.size={}" .format (rt.size())) if r.get('nodes' ) is not None : nodes = decode_compact_node(r['nodes' ]) dic = {"t" : "aa" , "y" : "q" , "q" : "find_node" , "a" : {"id" : local_node.id .hex (), "target" : local_node.id .hex ()}} data = BEncode(dic).encode_bin() for node in nodes: try : dst_ip, dst_port, id = node sock.sendto(data, (dst_ip, dst_port)) except Exception as e: log.exception("send node error|node={}" .format (node)) elif r.get('peers' ) is not None : print ("handle_reply|peers" ) except Exception as e: log.exception("handle_reply error|dic={}" .format (dic), e) t_hand_reply = threading.Thread(target=handle_reply, args=(sock, reply_q, rt,)) t_hand_reply.daemon = True def handle_query (sock: socket.socket, query_q: Queue ): while True : dic, addr = query_q.get() dst_ip, dst_port = addr try : print ("handle_query|addr={}|dic={}" .format (addr, dic)) if dic['q' ] == 'ping' : reply = {"t" : dic['t' ], "y" : "r" , "r" : {"id" : local_node.id .hex ()}} data = BEncode(reply).encode_bin() sock.sendto(data, (dst_ip, dst_port)) elif dic['q' ] == 'find_node' : nodes = rt.find_closer(bytes .fromhex(dic['a' ]['id' ])) print ("local_node.id={}" .format (local_node.id .hex ())) print (nodes) compact_node = encode_compact_node(nodes) reply = {"t" : dic['t' ], "y" : "r" , "r" : {"id" : local_node.id .hex (), "nodes" : compact_node}} data = BEncode(reply).encode_bin() sock.sendto(data, (dst_ip, dst_port)) print ("handle_query|find_node|send_reply|dst_ip={}|dst_port={}|data={}" .format (dst_ip, dst_port, data)) elif dic['q' ] == 'get_peers' : reply = {"t" : dic['t' ], "y" : "r" , "r" : {"id" : local_node.id .hex (), "token" : "alenym" .encode("utf8" ), "nodes" : b"" }} data = BEncode(reply).encode_bin() sock.sendto(data, (dst_ip, dst_port)) print ("handle_query|get_peers|send_reply|dst_ip={}|dst_port={}|data={}" .format (dst_ip, dst_port, data)) elif dic['q' ] == 'announce_peer' : print ("handle_query|announce_peer|dic={}" .format (dic)) info_hash = dic['a' ].get('info_hash' ) with RLOCK: if info_hash is None or info_hash in INFO_HASH_METADATA_DIC: continue target_ip = dst_ip target_port = dst_port t_request_meta = threading.Thread(target=request_metadata_thread, args=( local_node.id , info_hash, target_ip, target_port, metadata_q)) t_request_meta.start() else : print ("handle_query|other|dic={}" .format (dic)) except Exception as e: log.exception("handle_query error|dic={}" .format (dic)) t_hand_query = threading.Thread(target=handle_query, args=(sock, query_q,)) t_hand_query.daemon = True def handle_error (error_q: Queue ): while True : dic, addr = error_q.get() try : print ("handle_error|addr={}|dic={}" .format (addr, dic)) except Exception as e: log.exception('handle_error error' , e) t_hand_error = threading.Thread(target=handle_error, args=(error_q,)) t_hand_error.daemon = True def handle_metadata (metadata_q: Queue ): while True : try : metadata_dic, info_hash, ip, port = metadata_q.get() format_s = "handle_metadata|info_hash={}|ip={}|port={}|name={}|size={}" file_name = metadata_dic['name' ] size = format_size(metadata_dic.get('length' )) print (format_s.format (info_hash, ip, port, file_name, size)) with RLOCK: if info_hash not in INFO_HASH_METADATA_DIC: INFO_HASH_METADATA_DIC[info_hash] = metadata_dic except Exception as e: log.exception("handle_metadata error" ) t_handle_metadata = threading.Thread(target=handle_metadata, args=(metadata_q,)) t_handle_metadata.daemon = True listener.start() t_dispatch.start() t_hand_reply.start() t_hand_query.start() t_hand_error.start() t_handle_metadata.start() listener.join() t_dispatch.join() t_hand_reply.join() t_hand_query.join() t_hand_error.join() t_handle_metadata.join()if __name__ == '__main__' : main()
-运行日志 我们在一台有公网ip的电脑上运行十几分钟,过滤日志,查看接收到的种子信息如下,
-root@ubuntu:~# cat log.txt | grep "handle_metadata" handle_metadata|info_hash =5553330 daa12bde6a2f71ab26f7b26688219f276|ip =69.80 .12 .126 |port =11715 |name =War for the Planet of the Apes 2017 1080 p BluRay x264 DTS 5.1 MSubS-Hon3y|size =None handle_metadata|info_hash =5556 f3a9c605dd009f92fddb91848f565439e4f4|ip =59.169 .228 .207 |port =62215 |name =(C94) [くろすこスイッチ (くろすこ)] 冷泉さんといちゃいちゃする本 (ガールズ&パンツァー).zip|size =7.80 M handle_metadata|info_hash =555333 edb2519c3aa93db4a150f6e021f5a138ff|ip =96.40 .42 .32 |port =32796 |name =Princess Go Round|size =None handle_metadata|info_hash =572 d4df79a7151d9466d371b960cfece91289bf4|ip =1.175 .76 .59 |port =13283 |name =(同人ゲーム) [181102 ][RJ237312][ピンポイント/キングピン] 妻が隠していたビデオ…~元カレ寝取らせ観察記~ DL版 (files).rar|size =1.08 G handle_metadata|info_hash =572 edd8670066945538e4fe823294b45f2c2e3a3|ip =14.199 .224 .142 |port =22979 |name =0407 raw021|size =None handle_metadata|info_hash =574 b27358fc1604e65babf32abe839267d36ba4b|ip =14.199 .224 .142 |port =22979 |name =o0Akrios0o@www.sexinsex.com@假裝租屋 事實上在陽台勾引住宅區人妻|size =None handle_metadata|info_hash =54730 eeeb5d74a58f49c6da72eb90be922556f0a|ip =219.98 .7 .166 |port =18586 |name =DSAM-29 -DVD|size =None handle_metadata|info_hash =57039 c3ef3343c5b288c5ff219c66f837a88e808|ip =14.199 .224 .142 |port =22979 |name =tmem|size =None handle_metadata|info_hash =5687578 db92c7df4ae3ab7e33c895f04b1e27d37|ip =14.199 .224 .142 |port =22979 |name =GNE-148 |size =None handle_metadata|info_hash =57 b832066b2103e29f3a4af256e25175da6dbdc2|ip =14.199 .224 .142 |port =22979 |name =[Thz.la]supa-154 |size =None handle_metadata|info_hash =56936 a12cdb3a1a837c8faa206a2dea189dadf66|ip =14.199 .224 .142 |port =22979 |name =club-022 _1.wmv|size =2.96 G handle_metadata|info_hash =570e89058161385 b1d7dfadfcdc2d9f276ab829b|ip =14.199 .224 .142 |port =22979 |name =avidol.us-PTBI-026. wmv|size =2.71 G handle_metadata|info_hash =57 c30a1ddecac71142208425c610837b961f4de6|ip =14.199 .224 .142 |port =22979 |name =[HD]sad-039. wmv|size =3.52 G handle_metadata|info_hash =56794273 cf092e0b4f671885a16cf2dbe484f559|ip =14.199 .224 .142 |port =22979 |name =KRE-002. wmv|size =1.20 G handle_metadata|info_hash =5710 c35d85a9a0ed8cadc10bdcd09db5bb0c46fd|ip =14.199 .224 .142 |port =22979 |name =0111 -xv1088|size =None handle_metadata|info_hash =561 b55ec9a956f208f58b798d781bd5577a47b9e|ip =14.199 .224 .142 |port =22979 |name =52. R18-099 |size =None handle_metadata|info_hash =57 c262437e7cf0ca5f24bff947757026f9cef9a8|ip =14.199 .224 .142 |port =22979 |name =DF-35976 |size =None handle_metadata|info_hash =565 bedb7fc17d20f017a668e2931cb1d30b53556|ip =14.199 .224 .142 |port =22979 |name =judexkwok@片瀬まこ合集06 |size =None handle_metadata|info_hash =57e93 ca1527cd35045f0ca75d0c96579dcf96d2a|ip =14.199 .224 .142 |port =22979 |name =SMDV-10 -DVD|size =None handle_metadata|info_hash =56747025 a196cb78db9f94d8ca933677f57e54b0|ip =14.199 .224 .142 |port =22979 |name =HUNT-759. mp4|size =2.01 G handle_metadata|info_hash =56e45 f76194dadb2c799c7f2c9b34bc2fff07cee|ip =14.199 .224 .142 |port =22979 |name =0510 -sama538|size =None handle_metadata|info_hash =57 fae1a68ee9241a593c1623f64feb7927b40469|ip =14.199 .224 .142 |port =22979 |name =[thz.la]chunta-219 |size =None handle_metadata|info_hash =56 b89964da08b923b20722b89ccfc6ae4928aca3|ip =14.199 .224 .142 |port =22979 |name =HUNT-665 _2.mp4|size =1.60 G handle_metadata|info_hash =57528 d8633ee8b823aea17e47def172082072dd5|ip =14.199 .224 .142 |port =22979 |name =HUNT|size =None handle_metadata|info_hash =57 b14ca4cf260763e170e515b1f235ae5dca487f|ip =14.199 .224 .142 |port =22979 |name =SAMA-477 |size =None handle_metadata|info_hash =57 bc38a6f182dbd6be5b42371d645cc43cfa12e1|ip =14.199 .224 .142 |port =22979 |name =HUNT-710. mp4|size =1.82 G handle_metadata|info_hash =57 f2b65672462ff81834a928cf8e9863687dc220|ip =14.199 .224 .142 |port =22979 |name =3208 |size =None ...
-
-有些种子文件为什么没有文件大小呢?例如size=None
,
-handle_metadata|info_hash =54730 eeeb5d74a58f49c6da72eb90be922556f0a|ip =219.98 .7 .166 |port =18586 |name =DSAM-29 -DVD|size =None
-
-用info_hash过滤日志,可以看到metadata包含了一个files
项,该项包含了多个文件。
-root@ubuntu:~# cat log.txt | grep "54730eeeb5d74a58f49c6da72eb90be922556f0a" | grep "metadata_dic" request_metadata|info_hash=54730 eeeb5d74a58f49c6da72eb90be922556f0a|metadata_dic={'files' : [{'ed2k' : b'\xc4"%lT\xa14s\xd86\xd7a\xb1:\x8bC' , 'filehash' : '788c531731917e92334c03423df5433b4d3d941d' , 'length' : 228076 , 'path' : ['C9~Fang Ping Bi Cheng Xu防屏蔽程序.rar' ], 'path.utf-8' : ['C9~Fang Ping Bi Cheng Xu防屏蔽程序.rar' ]}, {'ed2k' : b'\xff,5\xb4W\x1b\xf1\xb0\x16\x13=\x0c\xc1\xc5\xb56' , 'filehash' : 'b760813c2b6690123eb174b5561ce6b4a0b6e000' , 'length' : 1966849 , 'path' : ['HOTAVXXX,Free Adult Movie, Fastest & Newest Porn Movie Site.jpg' ], 'path.utf-8' : ['HOTAVXXX,Free Adult Movie, Fastest & Newest Porn Movie Site.jpg' ]}, {'ed2k' : b'\xfaggnH\xf8e}O\x97\x04\xbb\x86\x9e\x92\xf5' , 'filehash' : '9a8b43c663ecc46d3a29196cf527d8431fef842b' , 'length' : 180 , 'path' : ['HOTAVXXX~最新最快的AV影片每日更新.url' ], 'path.utf-8' : ['HOTAVXXX~最新最快的AV影片每日更新.url' ]}, {'ed2k' : b'\xff,5\xb4W\x1b\xf1\xb0\x16\x13=\x0c\xc1\xc5\xb56' , 'filehash' : 'b760813c2b6690123eb174b5561ce6b4a0b6e000' , 'length' : 1966849 , 'path' : ['HOTAVXXX、自由な成人映画、最も速く&最新ポルノ映画サイト.jpg' ], 'path.utf-8' : ['HOTAVXXX、自由な成人映画、最も速く&最新ポルノ映画サイト.jpg' ]}, {'ed2k' : b'$\x13\xfd\\\x8b\xa5\xee=\xc0\xfb\x88\xff~\x03/\x18' , 'filehash' : 'dddbfe3e917739e1da7afcdd8a78e1a6677b6648' , 'length' : 235 , 'path' : ['QQ愛真人視頻交友聊天室.url' ], 'path.utf-8' : ['QQ愛真人視頻交友聊天室.url' ]}, {'ed2k' : b'/\xe2\xcf\x1b\xc06\xea[\xda\x93\xb9\xd0\\\xc1\x14\x98' , 'filehash' : '715c4e653db4bb74bb7e40073d55113f512299e9' , 'length' : 628211 , 'path' : ['SIS001全面封殺.jpg' ], 'path.utf-8' : ['SIS001全面封殺.jpg' ]}, {'ed2k' : b'\xf3\x19\x893\x8b\r\xc4\r\x99|W)\xf4\xce\x00J' , 'filehash' : 'c4eadc74cdbf36df8a66710f62932a60b1a0949a' , 'length' : 267 , 'path' : ['[城風 - C9]~成人精品長篇區 最新http一手資源.url' ], 'path.utf-8' : ['[城風 - C9]~成人精品長篇區 最新http一手資源.url' ]}, {'ed2k' : b'r\xbab\x10i\xc4\x10<f$\x10+p\x84\xd2Z' , 'filehash' : 'c6a1cb245511e4013c7510220ce3a6d912f8290f' , 'length' : 217 , 'path' : ['dioguitar23(第六天魔王)@草榴社區.url' ], 'path.utf-8' : ['dioguitar23(第六天魔王)@草榴社區.url' ]}, {'ed2k' : b'\xe4z.\xa4\xfa\x88h\xfe\x07\xedy\xc3\x13D\xe8\x7f' , 'filehash' : 'dddd22e1c4027a8e5c6cec98a4ef3dd29b951682' , 'length' : 229 , 'path' : ['dioguitar23@ HD1080.org.url' ], 'path.utf-8' : ['dioguitar23@ HD1080.org.url' ]}, {'ed2k' : b'\xf9\xee!X\xb3\x9d\xd9\x104\xc52\xc4\x872&a' , 'filehash' : '7191792635319be376c5ff3edd251e7bda81d069' , 'length' : 267 , 'path' : ['dioguitar23@AV 天空.url' ], 'path.utf-8' : ['dioguitar23@AV 天空.url' ]}, {'ed2k' : b'\xba\x18f&*y&\xf5H\xe8\x1f;8x\x1eA' , 'filehash' : 'd774c64c18ec44e607ba6825675e841332a0d4f4' , 'length' : 188 , 'path' : ['dioguitar23@D.C.資訊交流網.url' ], 'path.utf-8' : ['dioguitar23@D.C.資訊交流網.url' ]}, {'ed2k' : b'\x91\x97\x15O\rxh\x7fa\xbf^\xbf \x187K' , 'filehash' : 'f06835576489512e2c4a3ff58a5c4a2fed3131a3' , 'length' : 174 , 'path' : ['dioguitar23@KTzone.url' ], 'path.utf-8' : ['dioguitar23@KTzone.url' ]}, {'ed2k' : b"\xf7\xf1\x9c\xa0+\x15\xc5_\xdb2u'\x81\xcb\xefU" , 'filehash' : '25a54a9fdab494a60fdbd206089ee75c08913f24' , 'length' : 235 , 'path' : ['dioguitar23@SexInSex! Board.url' ], 'path.utf-8' : ['dioguitar23@SexInSex! Board.url' ]}, {'ed2k' : b'\x10k\x08\xafU\xcb\x05\x8b\x0f\xf9\xaf\xcb\r\xdd\xf3\xc4' , 'filehash' : 'a9bb602e06bb33483a1960db95bc65b535831c5d' , 'length' : 1086 , 'path' : ['dioguitar23@公仔箱論壇.url' ], 'path.utf-8' : ['dioguitar23@公仔箱論壇.url' ]}, {'ed2k' : b'6-*X\x12\xf3f\xbe\xd8q\x15\xbe\x91[\xee4' , 'filehash' : 'd19dd08e932c7db940491e5ac259c6ad0ff225c3' , 'length' : 188 , 'path' : ['dioguitar23@痴漢俱樂部.url' ], 'path.utf-8' : ['dioguitar23@痴漢俱樂部.url' ]}, {'ed2k' : b'\x89\xe1CN\xef\xd1\x83\xc2\x8c?,|;\xdb\t\x10' , 'filehash' : '47255a6ce6a43dc9af1de780fcffd210de793bfd' , 'length' : 190 , 'path' : ['dioguitar23@香港廣場.url' ], 'path.utf-8' : ['dioguitar23@香港廣場.url' ]}, {'ed2k' : b'\xaaJh&\x1f\x88\x93\xda\x9bL\xca\xd9\x9c+V\xa9' , 'filehash' : '168c3faa1f01a31b31c8d5a1bb7e699333e0bfe4' , 'length' : 226 , 'path' : ['dioguitar23_Plus28 討論區.url' ], 'path.utf-8' : ['dioguitar23_Plus28 討論區.url' ]}, {'ed2k' : b"\x04k`{\x0b'\xee\x80\xb4\x8e\x91=\xa92\xaf\x95" , 'filehash' : 'c7181e934379905222bf363317d8ae8ddfb12eb4' , 'length' : 220 , 'path' : ['dioguitar23_Touch99.url' ], 'path.utf-8' : ['dioguitar23_Touch99.url' ]}, {'ed2k' : b'\xaf\xbe+\xd5\xde\x96\xdc\xe0\xa8a\x18\x87b\x8d\xf6\x0c' , 'filehash' : 'a92caa360c647d14ae05655c6e43e9f28d7edcf5' , 'length' : 208 , 'path' : ['dioguitar23_WK綜合論壇.url' ], 'path.utf-8' : ['dioguitar23_WK綜合論壇.url' ]}, {'ed2k' : b'\x05>\x9b.\nS\xb2\x9c&L\xbc5H\x82\xcc\xcb' , 'filehash' : '6fa079db3cafb196163e9a6b126f60a5d4aa85b1' , 'length' : 156 , 'path' : ['dioguitar23_mimip2p.url' ], 'path.utf-8' : ['dioguitar23_mimip2p.url' ]}, {'ed2k' : b'\xc3|\x1fKA\xb7\xdd\xb9\x87\xd3}\x04\x8f}\xb3m' , 'filehash' : '1a709fb06ff75ced80995ab8fcdefa69744d7aa0' , 'length' : 229 , 'path' : ['dioguitar23_※=色界论坛=※ (开放注册) 色界论坛.url' ], 'path.utf-8' : ['dioguitar23_※=色界论坛=※ (开放注册) 色界论坛.url' ]}, {'ed2k' : b'>\x13\x98U3q\xf6u\xe6\x06\xc4\xa4\x91\xb24\xc5' , 'filehash' : '350cdfa69ce763f8c2fad85e1f0f4323c3fb5dfa' , 'length' : 208 , 'path' : ['dioguitar23_九九情色帝国.url' ], 'path.utf-8' : ['dioguitar23_九九情色帝国.url' ]}, {'ed2k' : b'E\x0c\x8a\xed5{\x00\xe6\xdb\xfc\x9b\xb1|U%\xf5' , 'filehash' : '384d925cb1da6605d49014622ed9f0b455efe343' , 'length' : 261 , 'path' : ['dioguitar23_性吧春暖花开,春暖花开性吧有你.url' ], 'path.utf-8' : ['dioguitar23_性吧春暖花开,春暖花开性吧有你.url' ]}, {'ed2k' : b'\xefE\xbe\xc2\xcf(\xf5\xe3\x18\x04\xdan\x8f\x08j\xcf' , 'filehash' : '2509953b0831793667a904ee08229da3114835cf' , 'length' : 214 , 'path' : ['dioguitar23_找樂子論壇.url' ], 'path.utf-8' : ['dioguitar23_找樂子論壇.url' ]}, {'ed2k' : b"QuDc'zc]\xcb\xeb\xc0\x86-\xfe\xa5\xf4" , 'filehash' : '2e92574d6c46add45048b08aa15b4eae132c2cc1' , 'length' : 235 , 'path' : ['dioguitar23_無限討論區.url' ], 'path.utf-8' : ['dioguitar23_無限討論區.url' ]}, {'ed2k' : b'tf\xe0\xda\x8c&\x96A\x9d\x9e~P\xe9\xd6\x83\xdb' , 'filehash' : '6b2a83a72df99bd7f2a197cb6fd423a41e061b70' , 'length' : 138 , 'path' : ['hav.tv-新幹線ONLINE~慶祝開站包月對折優惠~免費影片天看到爽!!.url' ], 'path.utf-8' : ['hav.tv-新幹線ONLINE~慶祝開站包月對折優惠~免費影片天看到爽!!.url' ]}, {'ed2k' : b'\'\xf0\x08\xf1 {\xa9"\x13//(\xc7o\x9d`' , 'filehash' : '226f0d140af4689833613f0f21a95608fff324ef' , 'length' : 4324384768 , 'path' : ['hotavxxx.com_DSAM-29.ISO' ], 'path.utf-8' : ['hotavxxx.com_DSAM-29.ISO' ]}, {'ed2k' : b'\x92]\xef\x0b\xa5\xee\xff\xd9\xb9\xd2\xd2\xc2J\x9e\x1a\xdc' , 'filehash' : 'eb8316fc354049e3bc1fb251bf6928dc55ed157a' , 'length' : 131523 , 'path' : ['hotavxxx.com_DSAM-29.jpg' ], 'path.utf-8' : ['hotavxxx.com_DSAM-29.jpg' ]}, {'ed2k' : b'0\xaf\xc8Q\xc9\xa3\xfb\xacn\xf4\xde\xc1\r\x9c\xda\xbd' , 'filehash' : 'aa6975d6341d2a079661c066eb5244ee0fd1aaff' , 'length' : 2254263 , 'path' : ['hotavxxx.com_DSAM-29A.jpg' ], 'path.utf-8' : ['hotavxxx.com_DSAM-29A.jpg' ]}, {'ed2k' : b'\xa1\xaf\x1b\xd7\x84\xd2g\x87\x99>\x82\xec\x94j\xb1U' , 'filehash' : '7254c5e02a0119f38e9c0e7576a1d8ac04b59f67' , 'length' : 214 , 'path' : ['❤dioguitar23❤18P2P-2013年04月2日 18p2p開放註冊3天.url' ], 'path.utf-8' : ['❤dioguitar23❤18P2P-2013年04月2日 18p2p開放註冊3天.url' ]}, {'ed2k' : b'\xe0\x82]\xe7\xe2\xc3\xe0\x9bn\x81\x1d\xa4\x0fr\x9f+' , 'filehash' : 'cad3021392437e48d35faa57861e8ce0a89a6a0d' , 'length' : 233 , 'path' : ['アジア表動画公開板 [城風 - C9].url' ], 'path.utf-8' : ['アジア表動画公開板 [城風 - C9].url' ]}, {'ed2k' : b'\x9a\xdeh5D\x1e}5`FeM\xf3\xcf\x1dt' , 'filehash' : '37402d4e9e9fedb26ff5c833fbc399fb5462a4af' , 'length' : 168 , 'path' : ['京色屋~即日起大降價.一片只要30元起.藍光片只要150元.url' ], 'path.utf-8' : ['京色屋~即日起大降價.一片只要30元起.藍光片只要150元.url' ]}, {'ed2k' : b'W\x9a\xf4E\x1d\xb0b(\x05_v\x00\xef\xe5\xe4\xe6' , 'filehash' : '6d6df82810c4aa00152c3912d28bc13b6589a780' , 'length' : 223 , 'path' : ['堂本屋 TW DMM~即日起大降價.一片只要30元起.藍光片只要150元.url' ], 'path.utf-8' : ['堂本屋 TW DMM~即日起大降價.一片只要30元起.藍光片只要150元.url' ]}, {'ed2k' : b'\x83\x05t=\xda\x85\x84\xbc\x1c\xb3J\xac\x04\x94\xfea' , 'filehash' : '52dcc77f3f2aad3ffd1cf4af73157deb1aeb8f4c' , 'length' : 2994 , 'path' : ['更多精彩.rar' ], 'path.utf-8' : ['更多精彩.rar' ]}, {'ed2k' : b'<\xf8Y\xfd\x08\x99.\xc5\xec\x0e\x03V\r\xb4%\x06' , 'filehash' : 'ab91ce91df44509574450c875435f3c2a7bf18cf' , 'length' : 226 , 'path' : ['金花娱乐城-澳门网上真人赌博,90后性感荷官24小时在线存提款(5分钟到账).url' ], 'path.utf-8' : ['金花娱乐城-澳门网上真人赌博,90后性感荷官24小时在线存提款(5分钟到账).url' ]}, {'ed2k' : b'\xfc*"\xad\xdbva\xdf\xbf\x92\x0e\x83.\x08/\x84' , 'filehash' : 'b60f8d54f1756a04753905be9a9dc7982f33ad14' , 'length' : 235 , 'path' : ['魔王の家-情慾視界~最新最快的成人資訊平台 魔王の家,http--bbs.hotavxxx.com.url' ], 'path.utf-8' : ['魔王の家-情慾視界~最新最快的成人資訊平台 魔王の家,http--bbs.hotavxxx.com.url' ]}], 'name' : 'DSAM-29-DVD' , 'name.utf-8' : 'DSAM-29-DVD' , 'piece length' : 1048576 , 'pieces' : ['557c428f62f38fe8ac8d11411bc9796abb28254d' , '53effb4976edd7d32c9be7a0315bfd40ff93875e' , 'c38ea041059367193bc3e9cc6fac40f67cfb377f' , '63d640ba1942f834e4ec0340f84d8c0703a1f992' , '8b783f6a2dc8ec649ad78acc8c49f892a8bf7663' , '961a8f0b0cdfe010c979061082982c3548173948' , '57dd2532983cf4d30bd442281d007ce0189b6eac' , 'd858de50e2a5d5dd60945ac687baa159697d88cd' , 'f7a262965c87d1c4d3cc10660176c4d063d16807' , '43f7e4f982c4a47d94757071e8b280798d5fbfa9' , ...
-]]>
-
- bitTorrent
- protocol
- p2p
-
-
-
- Java语言中bytes convert to string and back not equal
- /2019/04/28/2019-4-28-byte-to-string-and-back/
- 问题用google搜索关键词”java bytes to string and back not equal”,第一个就是我说的这个问题。
-什么意思呢?就是在java中,bytes转化为string之后,再转换回bytes的时候,发现不相同了。
-但是Go
语言就没有这个问题哦 。
-
-
-运行如下java代码
-byte [] a = new byte []{(byte ) 0xc0 , (byte ) 0xa8 , (byte ) 0x00 , (byte ) 0x01 , (byte ) 0x04 , (byte ) 0x38 }; System.out.println(ByteBufUtil.hexDump(a));String s = new String (a); System.out.println(ByteBufUtil.hexDump(s.getBytes()));
-
-输出的结果如下:
-c0a800010438 efbfbdefbfbd00010438
-
-分析 bytes转化为string类型,本质上要选择一种编码。那么选择的是什么呢?
-我们看看new String()
执行的代码。通过跟踪,可以看到,使用了默认的编码。
-csn为UTF-8
。
-static char [] decode(byte [] ba, int off, int len) { String csn = Charset.defaultCharset().name(); try { return decode (csn, ba, off, len) ; } catch (UnsupportedEncodingException x) { warnUnsupportedCharset(csn); } try { return decode ("ISO-8859-1" , ba, off, len) ; } catch (UnsupportedEncodingException x) { MessageUtils.err("ISO-8859-1 charset not available: " + x.toString()); System.exit(1 ); return null ; } }
-
-因为通常java编译的时候的默认编码是UTF-8
。
-那么如何保证转化为字符串还能够转换回来呢?
-一种方法是使用ISO-8859-1
。例如,
-byte [] a = new byte []{(byte ) 0xC0 , (byte ) 0xa8 , (byte ) 0x00 , (byte ) 0x01 , (byte ) 0x04 , (byte ) 0x38 }; System.out.println(ByteBufUtil.hexDump(a));String s = new String (a, Charset.forName("ISO-8859-1" )); System.out.println(ByteBufUtil.hexDump(s.getBytes(Charset.forName("ISO-8859-1" ))));
-结果是相等的。
-
-
-Go语言没有这个问题 Go语言则对这个问题解决的非常好。
-a := []byte {0xc0 , 0xa8 , 0x00 , 0x01 , 0x04 , 0x38 } fmt.Println(a) s := string (a) b := []byte (s) fmt.Println(b)
-运行结果是相等的。
-[192 168 0 1 4 56] [192 168 0 1 4 56]
-]]>
-
- java
- golang
-
-
-
- 在mac下跑一个Ingress的例子
- /2020/07/06/run-ingress-example-on-mac/
- Ingress是什么在Kubernetes中,Ingress是一个对象,该对象允许从Kubernetes集群外部访问Kubernetes服务。 您可以
-通过创建一组规则来配置访问权限,这些规则定义了哪些入站连接可以访问哪些服务。
-
-
-这里有篇文章很好的说明了Ingress,并给出了例子 —— Kubernetes Ingress with Nginx Example
-但是要想跑一下,首先要有一个k8s集群。下面首先在mac上安装一个k8s集群。
-在mac上安装k8s集群 我的mbp的配置是8G内存。
-
-下载Docker.dmg
。
-从这里下载搭建k8s所需要的镜像——https://github.com/gotok8s/k8s-docker-desktop-for-mac
-使用的方法就是如下,我用的docker desktop的kubernates的版本是1.19.3所以,相应的要把文件images
中的版本号进行修改,以匹配。$ git clone https://github.com/gotok8s/k8s-docker-desktop-for-mac.git $ cd k8s-docker-desktop-for-mac $ cat images k8s.gcr.io/kube-proxy:v1.19.3=gotok8s/kube-proxy:v1.19.3 k8s.gcr.io/kube-controller-manager:v1.19.3=gotok8s/kube-controller-manager:v1.19.3 k8s.gcr.io/kube-scheduler:v1.19.3=gotok8s/kube-scheduler:v1.19.3 k8s.gcr.io/kube-apiserver:v1.19.3=gotok8s/kube-apiserver:v1.19.3 k8s.gcr.io/coredns:1.7.0=gotok8s/coredns:1.7.0 k8s.gcr.io/pause:3.2=gotok8s/pause:3.2 k8s.gcr.io/etcd:3.4.13-0=gotok8s/etcd:3.4.13-0 $ ./load_images.sh
-启动docker desktop,并在dashboard中修改docker使用的资源大小。我分配了5G,
-交换内存设置为4G,cpu数量为3
-勾选Enable Kubernetes
和Show system containers (advanced)
,启动,然后耐心等待。
-
-需要等待的时间比较长,因为还会下载多个镜像,如docker/desktop-kubernetes
等等。
-安装ingress-nginx-controller 我们从ingress-nginx的官网可以看到不同的安装方式,对于 Docker for Mac,一行命令搞定
-kubectl apply -f https:// raw.githubusercontent.com/kubernetes/i ngress-nginx/master/ deploy/static/ provider/cloud/ deploy.yaml
-
-这个需要下载镜像quay.io/kubernetes-ingress-controller/nginx-ingress-controller
,所以也需要时间。直到看到如下pods
-ingress -nginx ingress-nginx-admission-create-frllm 0 /1 Completed 0 56 mingress -nginx ingress-nginx-admission-patch-gbwpd 0 /1 Completed 0 56 mingress -nginx ingress-nginx-controller-8 f7b9d799-c67xw 1 /1 Running 2 56 m
-
-跑一个Ingress的例子 Kubernetes Ingress with Nginx Example 中的yaml文件需要外网才能看到。
-我在此列出三个yaml文件
-apple.yaml:
-kind: Pod apiVersion: v1 metadata: name: apple-app labels: app: apple spec: containers: - name: apple-app image: hashicorp/http-echo args: - "-text=apple" --- kind: Service apiVersion: v1 metadata: name: apple-service spec: selector: app: apple ports: - port: 5678
-
-banana.yaml :
-kind: Pod apiVersion: v1 metadata: name: banana-app labels: app: banana spec: containers: - name: banana-app image: hashicorp/http-echo args: - "-text=banana" --- kind: Service apiVersion: v1 metadata: name: banana-service spec: selector: app: banana ports: - port: 5678
-
-ingress.yaml:
-apiVersion: extensions/v1beta1 kind: Ingress metadata: name: example-ingress annotations: ingress.kubernetes.io/rewrite-target: / spec: rules: - http: paths: - path: /apple backend: serviceName: apple-service servicePort: 5678 - path: /banana backend: serviceName: banana-service servicePort: 5678
-
-执行命令,等待一下。因为要下载一个镜像“jettech/kube-webhook-certgen”
-$ kubectl apply -f apple.yaml$ kubectl apply -f banana.yaml$ kubectl create -f ingress.yaml
-
-查看ingress
-$ kubectl describe ingress - n default example- ingress Name: example- ingress Namespace: default Address: localhostDefault backend: default - http- backend:80 (< error: endpoints "default-http-backend" not found> ) Rules: Host Path Backends * / apple apple- service:5678 (10.1 .0 .30 :5678 ) / banana banana- service:5678 (10.1 .0 .28 :5678 ) Annotations: ingress.kubernetes.io/ rewrite- target: / Events: Type Reason Age From Message Normal CREATE 45 m nginx- ingress- controller Ingress default / example- ingress Normal UPDATE 44 m nginx- ingress- controller Ingress default / example- ingress Normal CREATE 14 m nginx- ingress- controller Ingress default / example- ingress
-
-最后测试
-$ curl -kL http: //localhost/apple apple$ curl -kL http: //localhost/banana banana$ curl -kL http: //localhost/notfound default backend - 404
-]]>
-
- k8s
- ingress
- docker
- mac
-
-
-
- docker容器内访问mac主机的kafka
- /2020/03/09/connect-kafka/
- 从容器内访问主机的kafka我最近遇到这样一个需求,需要从容器内的ClickHouse访问安装在mac主机的kafka。这个问题似乎很简单,
-因为在windows上,虚拟机可以和host组成一个局域网,因此kafka只要绑定此网段的ip地址即可。
-但是在我的mac主机下,这个方案行不通。
+ docker容器内访问mac主机的kafka
+ /2020/03/09/connect-kafka/
+ 从容器内访问主机的kafka我最近遇到这样一个需求,需要从容器内的ClickHouse访问安装在mac主机的kafka。这个问题似乎很简单,
+因为在windows上,虚拟机可以和host组成一个局域网,因此kafka只要绑定此网段的ip地址即可。
+但是在我的mac主机下,这个方案行不通。
有几个原因,
@@ -2317,9 +2265,9 @@ csn为UTF-8
。
^Croot@bb6b85562200:/# kafkacat -b host.docker.internal:9092 -C -t flink-test -o beginning {seqNo: 1, eventTs: 1583739799986, id: even偶数, value: 4.57}% Reached end of topic flink-test [0] at offset 1
]]>
- docker
kafka
container
+ docker
@@ -2350,6 +2298,58 @@ csn为UTF-8
。
file
+
+ 在mac下跑一个Ingress的例子
+ /2020/07/06/run-ingress-example-on-mac/
+ Ingress是什么在Kubernetes中,Ingress是一个对象,该对象允许从Kubernetes集群外部访问Kubernetes服务。 您可以
+通过创建一组规则来配置访问权限,这些规则定义了哪些入站连接可以访问哪些服务。
+
+
+这里有篇文章很好的说明了Ingress,并给出了例子 —— Kubernetes Ingress with Nginx Example
+但是要想跑一下,首先要有一个k8s集群。下面首先在mac上安装一个k8s集群。
+在mac上安装k8s集群 我的mbp的配置是8G内存。
+
+下载Docker.dmg
。
+从这里下载搭建k8s所需要的镜像——https://github.com/gotok8s/k8s-docker-desktop-for-mac
+使用的方法就是如下,我用的docker desktop的kubernates的版本是1.19.3所以,相应的要把文件images
中的版本号进行修改,以匹配。$ git clone https://github.com/gotok8s/k8s-docker-desktop-for-mac.git $ cd k8s-docker-desktop-for-mac $ cat images k8s.gcr.io/kube-proxy:v1.19.3=gotok8s/kube-proxy:v1.19.3 k8s.gcr.io/kube-controller-manager:v1.19.3=gotok8s/kube-controller-manager:v1.19.3 k8s.gcr.io/kube-scheduler:v1.19.3=gotok8s/kube-scheduler:v1.19.3 k8s.gcr.io/kube-apiserver:v1.19.3=gotok8s/kube-apiserver:v1.19.3 k8s.gcr.io/coredns:1.7.0=gotok8s/coredns:1.7.0 k8s.gcr.io/pause:3.2=gotok8s/pause:3.2 k8s.gcr.io/etcd:3.4.13-0=gotok8s/etcd:3.4.13-0 $ ./load_images.sh
+启动docker desktop,并在dashboard中修改docker使用的资源大小。我分配了5G,
+交换内存设置为4G,cpu数量为3
+勾选Enable Kubernetes
和Show system containers (advanced)
,启动,然后耐心等待。
+
+需要等待的时间比较长,因为还会下载多个镜像,如docker/desktop-kubernetes
等等。
+安装ingress-nginx-controller 我们从ingress-nginx的官网可以看到不同的安装方式,对于 Docker for Mac,一行命令搞定
+kubectl apply -f https:// raw.githubusercontent.com/kubernetes/i ngress-nginx/master/ deploy/static/ provider/cloud/ deploy.yaml
+
+这个需要下载镜像quay.io/kubernetes-ingress-controller/nginx-ingress-controller
,所以也需要时间。直到看到如下pods
+ingress -nginx ingress-nginx-admission-create-frllm 0 /1 Completed 0 56 mingress -nginx ingress-nginx-admission-patch-gbwpd 0 /1 Completed 0 56 mingress -nginx ingress-nginx-controller-8 f7b9d799-c67xw 1 /1 Running 2 56 m
+
+跑一个Ingress的例子 Kubernetes Ingress with Nginx Example 中的yaml文件需要外网才能看到。
+我在此列出三个yaml文件
+apple.yaml:
+kind: Pod apiVersion: v1 metadata: name: apple-app labels: app: apple spec: containers: - name: apple-app image: hashicorp/http-echo args: - "-text=apple" --- kind: Service apiVersion: v1 metadata: name: apple-service spec: selector: app: apple ports: - port: 5678
+
+banana.yaml :
+kind: Pod apiVersion: v1 metadata: name: banana-app labels: app: banana spec: containers: - name: banana-app image: hashicorp/http-echo args: - "-text=banana" --- kind: Service apiVersion: v1 metadata: name: banana-service spec: selector: app: banana ports: - port: 5678
+
+ingress.yaml:
+apiVersion: extensions/v1beta1 kind: Ingress metadata: name: example-ingress annotations: ingress.kubernetes.io/rewrite-target: / spec: rules: - http: paths: - path: /apple backend: serviceName: apple-service servicePort: 5678 - path: /banana backend: serviceName: banana-service servicePort: 5678
+
+执行命令,等待一下。因为要下载一个镜像“jettech/kube-webhook-certgen”
+$ kubectl apply -f apple.yaml$ kubectl apply -f banana.yaml$ kubectl create -f ingress.yaml
+
+查看ingress
+$ kubectl describe ingress - n default example- ingress Name: example- ingress Namespace: default Address: localhostDefault backend: default - http- backend:80 (< error: endpoints "default-http-backend" not found> ) Rules: Host Path Backends * / apple apple- service:5678 (10.1 .0 .30 :5678 ) / banana banana- service:5678 (10.1 .0 .28 :5678 ) Annotations: ingress.kubernetes.io/ rewrite- target: / Events: Type Reason Age From Message Normal CREATE 45 m nginx- ingress- controller Ingress default / example- ingress Normal UPDATE 44 m nginx- ingress- controller Ingress default / example- ingress Normal CREATE 14 m nginx- ingress- controller Ingress default / example- ingress
+
+最后测试
+$ curl -kL http: //localhost/apple apple$ curl -kL http: //localhost/banana banana$ curl -kL http: //localhost/notfound default backend - 404
+]]>
+
+ docker
+ k8s
+ ingress
+ mac
+
+
基于CLion和gdbserver实现远程调试c程序
/2020/09/08/remote-debug-with-clion/
@@ -2399,142 +2399,57 @@ remote path为/home/keyvalue/ym/operation
,local path为/Use
- k3s的Pod无法解析内网域名
- /2021/12/26/pod-resolve-dn/
- 问题jetson tx2开发板上安装了docker
和k3s
,部署了一个pod,发现日志报错
-"dial tcp: lookup esmp-cloud-sync.dev.ennew.com on 10.43.0.10:53: no such host"
-其中esmp-cloud-sync.dev.ennew.com
是内网域名,说明pod无法解析该域名。
+ 自建根证书,中间证书和Server端X.509证书并搭建nginx验证Server端证书有效性
+ /2020/10/09/x509-ca/
+ X.509证书的颁发和使用X.509
证书是用来认证身份的,例如在访问一个HTTPS网站的时候,浏览器会首先下载该网站
+的证书,验证是否有效。如果无效,浏览器会提示您的连接不是私密连接
,进一步访问网站有风险。
+如果有效则可以直接访问,不会告警。
+浏览器怎么验证网站证书是否有效呢?简单说就是看网站证书的颁发机构是不是已经被操作系统信任,即看
+颁发机构的身份证书是否已经安装到操作系统里,并被设置为信任。
+那么我们接下来做的事情就是在mac系统上 验证浏览器的上述行为。
-但是宿主机上能ping通该域名。
-环境
-os : ubuntu18.04
-k3s: v1.18.9+k3s1
-cpu arch : arm64
-coreDNS: 1.6.9
+验证步骤,
+
+生成根证书,中间证书和Server端证书(即用户证书)
+本机搭建nginx服务器,并设置Server端证书(!!注意此时未安装根证书到操作系统)
+浏览器访问https://localhost,出现告警提示
+把根证书和中间证书安装到操作系统,并设置根证书为永久信任
+重新访问https://localhost,正常,没有出现告警。
+
+第一步,生成根证书,中间证书和Server端证书 颁发机构通常会有根机构和中间机构,根机构的证书自己签发自己,中间机构的证书由根机构签发,而Server端证书由中间机构签发。本人参考以下三篇文章创建了根证书,中间证书和Server端证书。
+
-排查过程 首先检查宿主机的域名服务器设置。
-如果不正常,则需要设置正确的域名服务器。并重启k3s服务和pod。
-如果正常,则进一步检查k3s的DNS设置。而k3s的网络域名解析是由coreDNS控制的。
-查看宿主机的DNS设置 宿主机的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很简单
+
+然后修改/usr/local/etc/nginx/nginx.conf
文件,在server里添加三行
+listen 443 ssl; ssl_certificate /Users/ym /tmp/my ca/localhost/ certs/localhost.cert.pem; ssl_certificate_key /Users/ym /tmp/my ca/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设置 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
+
-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中打开根证书和中间证书,可以添加到登录账号下。
+只需要设置根证书为永久信任即可。以为中间证书是由根证书签发的。
+以下是已经安装的根证书
+
+以下是已经安装的中间证书
+
+重新访问https://localhost。正常。
+
]]>
- k3s
- docker
- pod
- coredns
+ certificate
+ X.509
+ nginx
+ openssl
+ ssl
@@ -2622,37 +2537,176 @@ search addom.xinaogroup.com
那么具体看一下以上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。
-
-
-
-长度是小于127(short form)
-那么该该字节就是length,之后就是value的内容。
-长度大于127(long form),
-byte & 0x7F 表示length的编码长度。
+
+首先看是什么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。
+
+
+
+长度是小于127(short form)
+那么该该字节就是length,之后就是value的内容。
+长度大于127(long form),
+byte & 0x7F 表示length的编码长度。
+
+对于以上的例子,0x82
是long form
+因为
+( 0 x82 == (0 x82 & 0 x7F) = False
+那么表示长度的字节数量是
+
+因此’0x82’之后的两个字节’0x05’和’0xC1’组成长度。
+
+两个字节表示value的长度1473。
+因此,
+
+这个头四个字节的含义是,这个是Sequence
类型,
+长度是1473,这四节之后的1473个字节就是Sequence
类型的值。
+]]>
+
+ X.509
+ ASN.1
+
+
+
+ k3s的Pod无法解析内网域名
+ /2021/12/26/pod-resolve-dn/
+ 问题jetson tx2开发板上安装了docker
和k3s
,部署了一个pod,发现日志报错
+"dial tcp: lookup esmp-cloud-sync.dev.ennew.com on 10.43.0.10:53: no such host"
+其中esmp-cloud-sync.dev.ennew.com
是内网域名,说明pod无法解析该域名。
+
+
+但是宿主机上能ping通该域名。
+环境
+os : ubuntu18.04
+k3s: v1.18.9+k3s1
+cpu arch : arm64
+coreDNS: 1.6.9
+
+排查过程 首先检查宿主机的域名服务器设置。
+如果不正常,则需要设置正确的域名服务器。并重启k3s服务和pod。
+如果正常,则进一步检查k3s的DNS设置。而k3s的网络域名解析是由coreDNS控制的。
+查看宿主机的DNS设置 宿主机的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设置 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
-因为
-( 0 x82 == (0 x82 & 0 x7F) = False
-那么表示长度的字节数量是
-
-因此’0x82’之后的两个字节’0x05’和’0xC1’组成长度。
-
-两个字节表示value的长度1473。
-因此,
-
-这个头四个字节的含义是,这个是Sequence
类型,
-长度是1473,这四节之后的1473个字节就是Sequence
类型的值。
]]>
- X.509
- ASN.1
+ k3s
+ docker
+ pod
+ coredns
@@ -2715,60 +2769,6 @@ cmd/dist
makefile
-
- 自建根证书,中间证书和Server端X.509证书并搭建nginx验证Server端证书有效性
- /2020/10/09/x509-ca/
- X.509证书的颁发和使用X.509
证书是用来认证身份的,例如在访问一个HTTPS网站的时候,浏览器会首先下载该网站
-的证书,验证是否有效。如果无效,浏览器会提示您的连接不是私密连接
,进一步访问网站有风险。
-如果有效则可以直接访问,不会告警。
-浏览器怎么验证网站证书是否有效呢?简单说就是看网站证书的颁发机构是不是已经被操作系统信任,即看
-颁发机构的身份证书是否已经安装到操作系统里,并被设置为信任。
-那么我们接下来做的事情就是在mac系统上 验证浏览器的上述行为。
-
-
-验证步骤,
-
-生成根证书,中间证书和Server端证书(即用户证书)
-本机搭建nginx服务器,并设置Server端证书(!!注意此时未安装根证书到操作系统)
-浏览器访问https://localhost,出现告警提示
-把根证书和中间证书安装到操作系统,并设置根证书为永久信任
-重新访问https://localhost,正常,没有出现告警。
-
-第一步,生成根证书,中间证书和Server端证书 颁发机构通常会有根机构和中间机构,根机构的证书自己签发自己,中间机构的证书由根机构签发,而Server端证书由中间机构签发。本人参考以下三篇文章创建了根证书,中间证书和Server端证书。
-
-不过需要注意的是,Server端证书有效期不要太长,否则即使安装了根证书也会告警 。
-第二步,本机搭建nginx服务器,并设置Server端证书 mac下安装nginx很简单
-
-然后修改/usr/local/etc/nginx/nginx.conf
文件,在server里添加三行
-listen 443 ssl; ssl_certificate /Users/ym /tmp/my ca/localhost/ certs/localhost.cert.pem; ssl_certificate_key /Users/ym /tmp/my ca/localhost/ private /localhost.key.pem;
-
-启动nginx
-
-
-显示告警信息
-
-第四步,把根证书和中间证书安装到操作系统,并设置根证书为永久信任 在mac系统下,点击lauchpad,并搜索钥匙串访问
。可以看到登录下安装的证书。
-在finder中打开根证书和中间证书,可以添加到登录账号下。
-只需要设置根证书为永久信任即可。以为中间证书是由根证书签发的。
-以下是已经安装的根证书
-
-以下是已经安装的中间证书
-
-重新访问https://localhost。正常。
-
-]]>
-
- X.509
- certificate
- nginx
- openssl
- ssl
-
-
[leetcode 390]Elimination Game原创解法
/2022/02/06/2022-2-6-leetcode-390/
@@ -2863,6 +2863,52 @@ Given the integer n, return the last number that remains in arr.
rust
+
+ 玩一下LangChain
+ /2023/06/08/2023-6-8-langchain-openai/
+ 什么是LangChain自从ChatGPT出现以来,就一直在使用,那么ChatGPT毕竟是有局限性的,因为ChatGPT训练的语料是有限的。很多问题回答不了,
+也经常会胡言乱语闹笑话。
+但是ChatGPT背后的大语言模型LLM是可以扩展的,也就是说,可以把特定的领域知识让LLM(大语言模型)学习。这样就在一定
+程度上解决了局限性。
+而LangChain项目 就是这样的杀手锏,这里是官方文档 。
+
+
+本文代码和例子参考了使用langchain打造自己的大型语言模型(LLMs) ,对中文资料进行处理。
+OpenAI的key LangChain是一个框架,如果要使用,则需要调用大语言模型的API。正好有一朋友申请了OPENAI_API_KEY
,这样就可以开始
+跑跑代码了。
+代码 以下是Python代码。
+import osfrom langchain.embeddings.openai import OpenAIEmbeddingsfrom langchain.vectorstores import Chromafrom langchain.text_splitter import TokenTextSplitterfrom langchain.llms import OpenAIfrom langchain.document_loaders import DirectoryLoaderfrom langchain.chains import RetrievalQAWithSourcesChain, ChatVectorDBChainimport jieba as jb os.environ["OPENAI_API_KEY" ] = "sk-xxxxxxx" def preprocess_txt (): """ 由于中文的语法的特殊性,对于中文的文档必须要做一些预处理工作:词语的拆分, 也就是要把中文的语句拆分成一个个基本的词语单位,这里我们会用的一个分词工具:jieba, 它会帮助我们对资料库中的所有文本文件进行分词处理。不过我们首先将这3个时事新闻的文本文件放置到Data文件夹下面, 然后在data文件夹下面再建一个子文件夹:cut, 用来存放被分词过的文档: """ files = ['天龙八部.txt' ] for file in files: my_file = f"./data/{file} " with open (my_file, "r" , encoding='utf-8' ) as f: data = f.read() cut_data = " " .join([w for w in list (jb.cut(data))]) cut_file = f"./data/cut/cut_{file} " with open (cut_file, 'w' ) as f: f.write(cut_data) f.close()def embeddings (): loader = DirectoryLoader('./data/cut' , glob='**/*.txt' ) docs = loader.load() text_splitter = TokenTextSplitter(chunk_size=1000 , chunk_overlap=0 ) doc_texts = text_splitter.split_documents(docs) embeddings = OpenAIEmbeddings() vectordb = Chroma.from_documents(doc_texts, embeddings, persist_directory="./data/cut" ) vectordb.persist()def ask (): embeddings = OpenAIEmbeddings() docsearch = Chroma(persist_directory="./data/cut" , embedding_function=embeddings) chain = ChatVectorDBChain.from_llm(OpenAI(temperature=0 , model_name="gpt-3.5-turbo" ), docsearch, return_source_documents=True ) while True : try : user_input = input ("What's your question: " ) chat_history = [] result = chain({"question" : user_input, "chat_history" : chat_history}) print ("Answer: " + result["answer" ].replace('\n' , ' ' )) except KeyboardInterrupt: break if __name__ == '__main__' : preprocess_txt() embeddings() ask()
+
+代码依赖模块 Python3.11安装依赖
+pip install -r requirements.txt
+
+requirements.txt内容如下:
+aiohttp==3.8.4 aiosignal==1.3.1 anyio==3.7.0 argilla==1.8.0 async-timeout==4.0.2 attrs==23.1.0 backoff==2.2.1 certifi==2023.5.7 cffi==1.15.1 chardet==5.1.0 charset-normalizer==3.1.0 chromadb==0.3.26 click==8.1.3 clickhouse-connect==0.6.0 coloredlogs==15.0.1 commonmark==0.9.1 contourpy==1.0.7 cryptography==41.0.1 cycler==0.11.0 dataclasses-json==0.5.7 Deprecated==1.2.14 duckdb==0.8.0 et-xmlfile==1.1.0 fastapi==0.96.0 flatbuffers==23.5.26 fonttools==4.39.4 frozenlist==1.3.3 greenlet==2.0.2 h11==0.14.0 hnswlib==0.7.0 httpcore==0.16.3 httptools==0.5.0 httpx==0.23.3 humanfriendly==10.0 idna==3.4 jieba==0.42.1 joblib==1.2.0 kiwisolver==1.4.4 langchain==0.0.191 lxml==4.9.2 lz4==4.3.2 Markdown==3.4.3 marshmallow==3.19.0 marshmallow-enum==1.5.1 matplotlib==3.7.1 monotonic==1.6 mpmath==1.3.0 msg-parser==1.2.0 multidict==6.0.4 mypy-extensions==1.0.0 nltk==3.8.1 numexpr==2.8.4 numpy==1.23.5 olefile==0.46 onnxruntime==1.15.0 openai==0.27.7 openapi-schema-pydantic==1.2.4 openpyxl==3.1.2 overrides==7.3.1 packaging==23.1 pandas==1.5.3 pdf2image==1.16.3 pdfminer.six==20221105 Pillow==9.5.0 posthog==3.0.1 protobuf==4.23.2 pulsar-client==3.2.0 pycparser==2.21 pydantic==1.10.8 Pygments==2.15.1 pypandoc==1.11 pyparsing==3.0.9 python-dateutil==2.8.2 python-docx==0.8.11 python-dotenv==1.0.0 python-magic==0.4.27 python-pptx==0.6.21 pytz==2023.3 PyYAML==6.0 regex==2023.6.3 requests==2.31.0 rfc3986==1.5.0 rich==13.0.1 six==1.16.0 sniffio==1.3.0 SQLAlchemy==2.0.15 starlette==0.27.0 sympy==1.12 tabulate==0.9.0 tenacity==8.2.2 tiktoken==0.4.0 tokenizers==0.13.3 tqdm==4.65.0 typer==0.9.0 typing-inspect==0.9.0 typing_extensions==4.6.3 tzdata==2023.3 unstructured==0.7.1 urllib3==2.0.2 uvicorn==0.22.0 uvloop==0.17.0 watchfiles==0.19.0 websockets==11.0.3 wrapt==1.14.1 xlrd==2.0.1 XlsxWriter==3.1.2 yarl==1.9.2 zstandard==0.21.0
+
+文本 而文本则放在 “./data/“ 目录下,
+天龙八部.txt
内容如下,就是一些明确的信息。
+段誉和乔峰,虚竹是兄弟。 阿朱喜欢乔峰。 段誉喜欢王语嫣。
+
+如果直接问ChatGPT,”段誉的兄弟是谁”, “段誉有几个兄弟”则ChatGPT回答有些混乱。
+
+运行测试 直接运行后,测试提问,如下。
+
+
+很有意思。
+总结
+中文需要进行分词,与英文的处理不同。
+需要避免4096 TOKEN的限制,则只能使用文章中提到模型建立Chain。ChatVectorDBChain.from_llm(OpenAI(temperature=0 , model_name="gpt-3.5-turbo" ), docsearch, return_source_documents=True )
+还是需要VPN才能使用,另外OPEN_API_KEY是需要花钱的。
+
+参考
+]]>
+
+ langchain
+ openai
+
+
使用Iced的过程中理解Rust的关联类型
/2023/02/28/2023-2-28-associated-type/
@@ -3042,52 +3088,6 @@ command source ~/qtlldb/QtFormatters.lldb
lldb
-
- 玩一下LangChain
- /2023/06/08/2023-6-8-langchain-openai/
- 什么是LangChain自从ChatGPT出现以来,就一直在使用,那么ChatGPT毕竟是有局限性的,因为ChatGPT训练的语料是有限的。很多问题回答不了,
-也经常会胡言乱语闹笑话。
-但是ChatGPT背后的大语言模型LLM是可以扩展的,也就是说,可以把特定的领域知识让LLM(大语言模型)学习。这样就在一定
-程度上解决了局限性。
-而LangChain项目 就是这样的杀手锏,这里是官方文档 。
-
-
-本文代码和例子参考了使用langchain打造自己的大型语言模型(LLMs) ,对中文资料进行处理。
-OpenAI的key LangChain是一个框架,如果要使用,则需要调用大语言模型的API。正好有一朋友申请了OPENAI_API_KEY
,这样就可以开始
-跑跑代码了。
-代码 以下是Python代码。
-import osfrom langchain.embeddings.openai import OpenAIEmbeddingsfrom langchain.vectorstores import Chromafrom langchain.text_splitter import TokenTextSplitterfrom langchain.llms import OpenAIfrom langchain.document_loaders import DirectoryLoaderfrom langchain.chains import RetrievalQAWithSourcesChain, ChatVectorDBChainimport jieba as jb os.environ["OPENAI_API_KEY" ] = "sk-xxxxxxx" def preprocess_txt (): """ 由于中文的语法的特殊性,对于中文的文档必须要做一些预处理工作:词语的拆分, 也就是要把中文的语句拆分成一个个基本的词语单位,这里我们会用的一个分词工具:jieba, 它会帮助我们对资料库中的所有文本文件进行分词处理。不过我们首先将这3个时事新闻的文本文件放置到Data文件夹下面, 然后在data文件夹下面再建一个子文件夹:cut, 用来存放被分词过的文档: """ files = ['天龙八部.txt' ] for file in files: my_file = f"./data/{file} " with open (my_file, "r" , encoding='utf-8' ) as f: data = f.read() cut_data = " " .join([w for w in list (jb.cut(data))]) cut_file = f"./data/cut/cut_{file} " with open (cut_file, 'w' ) as f: f.write(cut_data) f.close()def embeddings (): loader = DirectoryLoader('./data/cut' , glob='**/*.txt' ) docs = loader.load() text_splitter = TokenTextSplitter(chunk_size=1000 , chunk_overlap=0 ) doc_texts = text_splitter.split_documents(docs) embeddings = OpenAIEmbeddings() vectordb = Chroma.from_documents(doc_texts, embeddings, persist_directory="./data/cut" ) vectordb.persist()def ask (): embeddings = OpenAIEmbeddings() docsearch = Chroma(persist_directory="./data/cut" , embedding_function=embeddings) chain = ChatVectorDBChain.from_llm(OpenAI(temperature=0 , model_name="gpt-3.5-turbo" ), docsearch, return_source_documents=True ) while True : try : user_input = input ("What's your question: " ) chat_history = [] result = chain({"question" : user_input, "chat_history" : chat_history}) print ("Answer: " + result["answer" ].replace('\n' , ' ' )) except KeyboardInterrupt: break if __name__ == '__main__' : preprocess_txt() embeddings() ask()
-
-代码依赖模块 Python3.11安装依赖
-pip install -r requirements.txt
-
-requirements.txt内容如下:
-aiohttp==3.8.4 aiosignal==1.3.1 anyio==3.7.0 argilla==1.8.0 async-timeout==4.0.2 attrs==23.1.0 backoff==2.2.1 certifi==2023.5.7 cffi==1.15.1 chardet==5.1.0 charset-normalizer==3.1.0 chromadb==0.3.26 click==8.1.3 clickhouse-connect==0.6.0 coloredlogs==15.0.1 commonmark==0.9.1 contourpy==1.0.7 cryptography==41.0.1 cycler==0.11.0 dataclasses-json==0.5.7 Deprecated==1.2.14 duckdb==0.8.0 et-xmlfile==1.1.0 fastapi==0.96.0 flatbuffers==23.5.26 fonttools==4.39.4 frozenlist==1.3.3 greenlet==2.0.2 h11==0.14.0 hnswlib==0.7.0 httpcore==0.16.3 httptools==0.5.0 httpx==0.23.3 humanfriendly==10.0 idna==3.4 jieba==0.42.1 joblib==1.2.0 kiwisolver==1.4.4 langchain==0.0.191 lxml==4.9.2 lz4==4.3.2 Markdown==3.4.3 marshmallow==3.19.0 marshmallow-enum==1.5.1 matplotlib==3.7.1 monotonic==1.6 mpmath==1.3.0 msg-parser==1.2.0 multidict==6.0.4 mypy-extensions==1.0.0 nltk==3.8.1 numexpr==2.8.4 numpy==1.23.5 olefile==0.46 onnxruntime==1.15.0 openai==0.27.7 openapi-schema-pydantic==1.2.4 openpyxl==3.1.2 overrides==7.3.1 packaging==23.1 pandas==1.5.3 pdf2image==1.16.3 pdfminer.six==20221105 Pillow==9.5.0 posthog==3.0.1 protobuf==4.23.2 pulsar-client==3.2.0 pycparser==2.21 pydantic==1.10.8 Pygments==2.15.1 pypandoc==1.11 pyparsing==3.0.9 python-dateutil==2.8.2 python-docx==0.8.11 python-dotenv==1.0.0 python-magic==0.4.27 python-pptx==0.6.21 pytz==2023.3 PyYAML==6.0 regex==2023.6.3 requests==2.31.0 rfc3986==1.5.0 rich==13.0.1 six==1.16.0 sniffio==1.3.0 SQLAlchemy==2.0.15 starlette==0.27.0 sympy==1.12 tabulate==0.9.0 tenacity==8.2.2 tiktoken==0.4.0 tokenizers==0.13.3 tqdm==4.65.0 typer==0.9.0 typing-inspect==0.9.0 typing_extensions==4.6.3 tzdata==2023.3 unstructured==0.7.1 urllib3==2.0.2 uvicorn==0.22.0 uvloop==0.17.0 watchfiles==0.19.0 websockets==11.0.3 wrapt==1.14.1 xlrd==2.0.1 XlsxWriter==3.1.2 yarl==1.9.2 zstandard==0.21.0
-
-文本 而文本则放在 “./data/“ 目录下,
-天龙八部.txt
内容如下,就是一些明确的信息。
-段誉和乔峰,虚竹是兄弟。 阿朱喜欢乔峰。 段誉喜欢王语嫣。
-
-如果直接问ChatGPT,”段誉的兄弟是谁”, “段誉有几个兄弟”则ChatGPT回答有些混乱。
-
-运行测试 直接运行后,测试提问,如下。
-
-
-很有意思。
-总结
-中文需要进行分词,与英文的处理不同。
-需要避免4096 TOKEN的限制,则只能使用文章中提到模型建立Chain。ChatVectorDBChain.from_llm(OpenAI(temperature=0 , model_name="gpt-3.5-turbo" ), docsearch, return_source_documents=True )
-还是需要VPN才能使用,另外OPEN_API_KEY是需要花钱的。
-
-参考
-]]>
-
- langchain
- openai
-
-
如何更好的打印语法树结构
/2024/07/18/2024-7-18-slang-ast-hier-tree/
diff --git a/sitemap.xml b/sitemap.xml
index d4c5b54b..196018b2 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -56,7 +56,7 @@
- https://threelambda.com/2023/06/09/2023-6-9-qt5-macos-lldb-debug/
+ https://threelambda.com/2023/06/08/2023-6-8-langchain-openai/
2024-07-22
@@ -65,7 +65,7 @@
- https://threelambda.com/2023/06/08/2023-6-8-langchain-openai/
+ https://threelambda.com/2023/06/09/2023-6-9-qt5-macos-lldb-debug/
2024-07-22
@@ -119,7 +119,7 @@
- https://threelambda.com/2021/12/26/pod-resolve-dn/
+ https://threelambda.com/2020/11/08/asn-1/
2024-07-22
@@ -128,7 +128,7 @@
- https://threelambda.com/2020/11/08/asn-1/
+ https://threelambda.com/2021/12/26/pod-resolve-dn/
2024-07-22
@@ -155,7 +155,7 @@
- https://threelambda.com/2020/07/06/run-ingress-example-on-mac/
+ https://threelambda.com/2020/03/09/connect-kafka/
2024-07-22
@@ -164,7 +164,7 @@
- https://threelambda.com/2020/03/09/connect-kafka/
+ https://threelambda.com/2020/08/14/write-file-with-spring-integration/
2024-07-22
@@ -173,7 +173,7 @@
- https://threelambda.com/2020/08/14/write-file-with-spring-integration/
+ https://threelambda.com/2020/07/06/run-ingress-example-on-mac/
2024-07-22
@@ -200,7 +200,7 @@
- https://threelambda.com/2019/01/25/2019-1-25-bt-5/
+ https://threelambda.com/2019/01/09/2019-1-9-bt-1/
2024-07-22
@@ -209,7 +209,7 @@
- https://threelambda.com/2019/01/09/2019-1-9-bt-1/
+ https://threelambda.com/2019/11/19/debug-k3s/
2024-07-22
@@ -218,7 +218,7 @@
- https://threelambda.com/2019/11/19/debug-k3s/
+ https://threelambda.com/2019/01/25/2019-1-25-bt-5/
2024-07-22
@@ -254,7 +254,7 @@
- https://threelambda.com/2019/01/10/2019-1-10-bt-2/
+ https://threelambda.com/2018/08/18/2018-8-18-leetcode-341/
2024-07-22
@@ -263,7 +263,7 @@
- https://threelambda.com/2018/08/18/2018-8-18-leetcode-341/
+ https://threelambda.com/2019/01/10/2019-1-10-bt-2/
2024-07-22
@@ -290,7 +290,7 @@
- https://threelambda.com/2017/07/09/2017-7-9-resize-fixed-image/
+ https://threelambda.com/2017/05/24/2017-5-24-gradle-build-project/
2024-07-22
@@ -299,7 +299,7 @@
- https://threelambda.com/2017/05/24/2017-5-24-gradle-build-project/
+ https://threelambda.com/2017/07/09/2017-7-9-resize-fixed-image/
2024-07-22
@@ -317,7 +317,7 @@
- https://threelambda.com/2017/03/06/2017-3-6-grails-call-java/
+ https://threelambda.com/2017/05/19/2017-5-19-javacc-minilisp/
2024-07-22
@@ -326,7 +326,7 @@
- https://threelambda.com/2017/05/19/2017-5-19-javacc-minilisp/
+ https://threelambda.com/2017/03/06/2017-3-6-grails-call-java/
2024-07-22
@@ -344,7 +344,7 @@
- https://threelambda.com/2017/03/23/2017-3-23-compile-and-run-clojure/
+ https://threelambda.com/2017/03/12/2017-3-12-leetcode-329/
2024-07-22
@@ -353,7 +353,7 @@
- https://threelambda.com/2017/03/12/2017-3-12-leetcode-329/
+ https://threelambda.com/2017/03/23/2017-3-23-compile-and-run-clojure/
2024-07-22
@@ -470,28 +470,28 @@
- https://threelambda.com/tags/leetcode/
+ https://threelambda.com/tags/R/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/%E7%AE%97%E6%B3%95/
+ https://threelambda.com/tags/python/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/python/
+ https://threelambda.com/tags/leetcode/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/R/
+ https://threelambda.com/tags/%E7%AE%97%E6%B3%95/
2024-07-23
weekly
0.2
@@ -554,14 +554,14 @@
- https://threelambda.com/tags/grails/
+ https://threelambda.com/tags/gradle/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/gradle/
+ https://threelambda.com/tags/build/
2024-07-23
weekly
0.2
@@ -603,7 +603,7 @@
- https://threelambda.com/tags/build/
+ https://threelambda.com/tags/grails/
2024-07-23
weekly
0.2
@@ -659,14 +659,14 @@
- https://threelambda.com/tags/k8s/
+ https://threelambda.com/tags/kafka/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/ingress/
+ https://threelambda.com/tags/container/
2024-07-23
weekly
0.2
@@ -680,49 +680,49 @@
- https://threelambda.com/tags/mac/
+ https://threelambda.com/tags/spring/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/kafka/
+ https://threelambda.com/tags/integration/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/container/
+ https://threelambda.com/tags/write/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/spring/
+ https://threelambda.com/tags/file/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/integration/
+ https://threelambda.com/tags/k8s/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/write/
+ https://threelambda.com/tags/ingress/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/file/
+ https://threelambda.com/tags/mac/
2024-07-23
weekly
0.2
@@ -757,70 +757,70 @@
- https://threelambda.com/tags/pod/
+ https://threelambda.com/tags/certificate/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/coredns/
+ https://threelambda.com/tags/X-509/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/X-509/
+ https://threelambda.com/tags/nginx/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/ASN-1/
+ https://threelambda.com/tags/openssl/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/clion/
+ https://threelambda.com/tags/ssl/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/makefile/
+ https://threelambda.com/tags/ASN-1/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/certificate/
+ https://threelambda.com/tags/pod/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/nginx/
+ https://threelambda.com/tags/coredns/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/openssl/
+ https://threelambda.com/tags/clion/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/ssl/
+ https://threelambda.com/tags/makefile/
2024-07-23
weekly
0.2
@@ -848,56 +848,56 @@
- https://threelambda.com/tags/iced/
+ https://threelambda.com/tags/langchain/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/qt5/
+ https://threelambda.com/tags/openai/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/cmake/
+ https://threelambda.com/tags/iced/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/macos/
+ https://threelambda.com/tags/qt5/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/bundle/
+ https://threelambda.com/tags/cmake/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/lldb/
+ https://threelambda.com/tags/macos/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/langchain/
+ https://threelambda.com/tags/bundle/
2024-07-23
weekly
0.2
- https://threelambda.com/tags/openai/
+ https://threelambda.com/tags/lldb/
2024-07-23
weekly
0.2
diff --git a/tags/ASN-1/index.html b/tags/ASN-1/index.html
index fbb0b995..7aa98e9b 100644
--- a/tags/ASN-1/index.html
+++ b/tags/ASN-1/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/AST/index.html b/tags/AST/index.html
index 790c9fe3..8f7f0b20 100644
--- a/tags/AST/index.html
+++ b/tags/AST/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/CLion/index.html b/tags/CLion/index.html
index 17d0f3a7..3f2fb5ad 100644
--- a/tags/CLion/index.html
+++ b/tags/CLion/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/ChatGPT/index.html b/tags/ChatGPT/index.html
index c9c1a5b1..173696e6 100644
--- a/tags/ChatGPT/index.html
+++ b/tags/ChatGPT/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/Cython/index.html b/tags/Cython/index.html
index 08dc0759..883b16c0 100644
--- a/tags/Cython/index.html
+++ b/tags/Cython/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/R/index.html b/tags/R/index.html
index 9360c1c4..f211785c 100644
--- a/tags/R/index.html
+++ b/tags/R/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/X-509/index.html b/tags/X-509/index.html
index 61056580..4fa6d722 100644
--- a/tags/X-509/index.html
+++ b/tags/X-509/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/algorithm/index.html b/tags/algorithm/index.html
index 0b3ed628..f7afd3e2 100644
--- a/tags/algorithm/index.html
+++ b/tags/algorithm/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/bitTorrent/index.html b/tags/bitTorrent/index.html
index 2a5cf82b..331b2d03 100644
--- a/tags/bitTorrent/index.html
+++ b/tags/bitTorrent/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/build/index.html b/tags/build/index.html
index 071cb44c..e5f7aa0e 100644
--- a/tags/build/index.html
+++ b/tags/build/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/bundle/index.html b/tags/bundle/index.html
index b1d1b4e9..2c37c1ec 100644
--- a/tags/bundle/index.html
+++ b/tags/bundle/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/c/index.html b/tags/c/index.html
index eeba2e71..a627f430 100644
--- a/tags/c/index.html
+++ b/tags/c/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/certificate/index.html b/tags/certificate/index.html
index 12ff3772..d81f8e51 100644
--- a/tags/certificate/index.html
+++ b/tags/certificate/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/clojure/index.html b/tags/clojure/index.html
index 884ccb2e..156cdec4 100644
--- a/tags/clojure/index.html
+++ b/tags/clojure/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/cmake/index.html b/tags/cmake/index.html
index 9a9d5269..0b99d55a 100644
--- a/tags/cmake/index.html
+++ b/tags/cmake/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/container/index.html b/tags/container/index.html
index 3cace3a4..d313d23f 100644
--- a/tags/container/index.html
+++ b/tags/container/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/coredns/index.html b/tags/coredns/index.html
index 57701970..32decd10 100644
--- a/tags/coredns/index.html
+++ b/tags/coredns/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/debug/index.html b/tags/debug/index.html
index 03885628..fa1d3430 100644
--- a/tags/debug/index.html
+++ b/tags/debug/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/delve/index.html b/tags/delve/index.html
index d126c3b2..8ded594d 100644
--- a/tags/delve/index.html
+++ b/tags/delve/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/docker/index.html b/tags/docker/index.html
index 02cbad09..29166626 100644
--- a/tags/docker/index.html
+++ b/tags/docker/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/file/index.html b/tags/file/index.html
index 499c2d0e..4b669383 100644
--- a/tags/file/index.html
+++ b/tags/file/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/gdb/index.html b/tags/gdb/index.html
index 1d1b0f92..10a876fd 100644
--- a/tags/gdb/index.html
+++ b/tags/gdb/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/go/index.html b/tags/go/index.html
index e0d3f3ce..43c878cf 100644
--- a/tags/go/index.html
+++ b/tags/go/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/golang/index.html b/tags/golang/index.html
index 818a6caa..8dba2758 100644
--- a/tags/golang/index.html
+++ b/tags/golang/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/gradle/index.html b/tags/gradle/index.html
index 3d2deddd..7a394eec 100644
--- a/tags/gradle/index.html
+++ b/tags/gradle/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/grails/index.html b/tags/grails/index.html
index 8274eb47..d0c1a188 100644
--- a/tags/grails/index.html
+++ b/tags/grails/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/iced/index.html b/tags/iced/index.html
index 76480f8f..88dabf7a 100644
--- a/tags/iced/index.html
+++ b/tags/iced/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/image/index.html b/tags/image/index.html
index 1934bb9b..ac9bd8b0 100644
--- a/tags/image/index.html
+++ b/tags/image/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/index.html b/tags/index.html
index df4aeff8..ddec7f84 100644
--- a/tags/index.html
+++ b/tags/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/ingress/index.html b/tags/ingress/index.html
index e3332183..35c60e79 100644
--- a/tags/ingress/index.html
+++ b/tags/ingress/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/integration/index.html b/tags/integration/index.html
index 961aeda9..8f9d0731 100644
--- a/tags/integration/index.html
+++ b/tags/integration/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/java/index.html b/tags/java/index.html
index 53d6973c..40ae0d26 100644
--- a/tags/java/index.html
+++ b/tags/java/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/javacc/index.html b/tags/javacc/index.html
index 07072cbf..c141ca02 100644
--- a/tags/javacc/index.html
+++ b/tags/javacc/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/jvm/index.html b/tags/jvm/index.html
index a0c192d9..340217d6 100644
--- a/tags/jvm/index.html
+++ b/tags/jvm/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/k3s/index.html b/tags/k3s/index.html
index 2d4e3f25..c01cbabf 100644
--- a/tags/k3s/index.html
+++ b/tags/k3s/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/k8s/index.html b/tags/k8s/index.html
index c915040d..7da0165d 100644
--- a/tags/k8s/index.html
+++ b/tags/k8s/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/kafka/index.html b/tags/kafka/index.html
index cf7cf07b..9da4a7b8 100644
--- a/tags/kafka/index.html
+++ b/tags/kafka/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/langchain/index.html b/tags/langchain/index.html
index 65bf0d3e..7ff82bcd 100644
--- a/tags/langchain/index.html
+++ b/tags/langchain/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/leetcode/index.html b/tags/leetcode/index.html
index 1126530a..fac5e466 100644
--- a/tags/leetcode/index.html
+++ b/tags/leetcode/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/leetcode/page/2/index.html b/tags/leetcode/page/2/index.html
index 0780afec..6f812c4a 100644
--- a/tags/leetcode/page/2/index.html
+++ b/tags/leetcode/page/2/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/lisp/index.html b/tags/lisp/index.html
index 49ab8da5..68b25f8c 100644
--- a/tags/lisp/index.html
+++ b/tags/lisp/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/lldb/index.html b/tags/lldb/index.html
index b7b76c84..53ac36ed 100644
--- a/tags/lldb/index.html
+++ b/tags/lldb/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/mac/index.html b/tags/mac/index.html
index e3c8cbe4..30bf1c02 100644
--- a/tags/mac/index.html
+++ b/tags/mac/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/macos/index.html b/tags/macos/index.html
index 453cebf3..f82c18eb 100644
--- a/tags/macos/index.html
+++ b/tags/macos/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/macro/index.html b/tags/macro/index.html
index 0ddc7051..22c4647d 100644
--- a/tags/macro/index.html
+++ b/tags/macro/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/makefile/index.html b/tags/makefile/index.html
index fbf8b094..68d32080 100644
--- a/tags/makefile/index.html
+++ b/tags/makefile/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/nginx/index.html b/tags/nginx/index.html
index e544b8f0..8d966838 100644
--- a/tags/nginx/index.html
+++ b/tags/nginx/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/openai/index.html b/tags/openai/index.html
index edcb731c..4e1372fc 100644
--- a/tags/openai/index.html
+++ b/tags/openai/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/openssl/index.html b/tags/openssl/index.html
index 7d4cd082..ef8ada9b 100644
--- a/tags/openssl/index.html
+++ b/tags/openssl/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/p2p/index.html b/tags/p2p/index.html
index 1d6da141..a55601a9 100644
--- a/tags/p2p/index.html
+++ b/tags/p2p/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/pod/index.html b/tags/pod/index.html
index 6af64d5d..cbb5605a 100644
--- a/tags/pod/index.html
+++ b/tags/pod/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/protocol/index.html b/tags/protocol/index.html
index 5b004377..f8a72c2e 100644
--- a/tags/protocol/index.html
+++ b/tags/protocol/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/python/index.html b/tags/python/index.html
index 8b34551d..dcadf731 100644
--- a/tags/python/index.html
+++ b/tags/python/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/python/page/2/index.html b/tags/python/page/2/index.html
index 514a69f2..9b01cf68 100644
--- a/tags/python/page/2/index.html
+++ b/tags/python/page/2/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/qt5/index.html b/tags/qt5/index.html
index ab9a1048..5c577bfc 100644
--- a/tags/qt5/index.html
+++ b/tags/qt5/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/remote/index.html b/tags/remote/index.html
index d25659d2..b20e3c50 100644
--- a/tags/remote/index.html
+++ b/tags/remote/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/resize/index.html b/tags/resize/index.html
index 424aa1e5..97458d40 100644
--- a/tags/resize/index.html
+++ b/tags/resize/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/rust/index.html b/tags/rust/index.html
index 63255582..79d9c88a 100644
--- a/tags/rust/index.html
+++ b/tags/rust/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/spring/index.html b/tags/spring/index.html
index 1f7aa9c4..fff161ce 100644
--- a/tags/spring/index.html
+++ b/tags/spring/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/ssl/index.html b/tags/ssl/index.html
index 6a09d744..1b8f34fb 100644
--- a/tags/ssl/index.html
+++ b/tags/ssl/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/tree/index.html b/tags/tree/index.html
index f454c801..0a3122aa 100644
--- a/tags/tree/index.html
+++ b/tags/tree/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/trie/index.html b/tags/trie/index.html
index e0d8c720..b7878603 100644
--- a/tags/trie/index.html
+++ b/tags/trie/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/venv/index.html b/tags/venv/index.html
index 3b13f954..0691693f 100644
--- a/tags/venv/index.html
+++ b/tags/venv/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/virutalbox/index.html b/tags/virutalbox/index.html
index d5c869f7..8d5a230d 100644
--- a/tags/virutalbox/index.html
+++ b/tags/virutalbox/index.html
@@ -55,7 +55,7 @@
-
+
diff --git a/tags/write/index.html b/tags/write/index.html
index 403d24ac..b0a6d032 100644
--- a/tags/write/index.html
+++ b/tags/write/index.html
@@ -55,7 +55,7 @@
-
+
diff --git "a/tags/\346\212\200\345\267\247/index.html" "b/tags/\346\212\200\345\267\247/index.html"
index 33255a1a..db9843a0 100644
--- "a/tags/\346\212\200\345\267\247/index.html"
+++ "b/tags/\346\212\200\345\267\247/index.html"
@@ -55,7 +55,7 @@
-
+
diff --git "a/tags/\347\256\227\346\263\225/index.html" "b/tags/\347\256\227\346\263\225/index.html"
index c6d76310..f7bc8458 100644
--- "a/tags/\347\256\227\346\263\225/index.html"
+++ "b/tags/\347\256\227\346\263\225/index.html"
@@ -55,7 +55,7 @@
-
+
diff --git "a/tags/\347\256\227\346\263\225/page/2/index.html" "b/tags/\347\256\227\346\263\225/page/2/index.html"
index 5e41d1d8..58d9973b 100644
--- "a/tags/\347\256\227\346\263\225/page/2/index.html"
+++ "b/tags/\347\256\227\346\263\225/page/2/index.html"
@@ -55,7 +55,7 @@
-
+