<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Greycode's Blog</title><link>https://blog.greycode.top/</link><description>Recent content on Greycode's Blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sat, 25 Mar 2023 14:39:37 +0800</lastBuildDate><atom:link href="https://blog.greycode.top/index.xml" rel="self" type="application/rss+xml"/><item><title>Create a simple kernel module</title><link>https://blog.greycode.top/posts/create-simple-kernel-programer/</link><pubDate>Sat, 25 Mar 2023 14:39:37 +0800</pubDate><guid>https://blog.greycode.top/posts/create-simple-kernel-programer/</guid><description>编写模块代码 创建一个文件夹，命名为hello，并在该文件夹下创建一个名为hello.c的文件，输入以下代码：
#include &amp;lt;linux/init.h&amp;gt; #include &amp;lt;linux/module.h&amp;gt; #include &amp;lt;linux/kernel.h&amp;gt; MODULE_LICENSE(&amp;#34;GPL&amp;#34;); MODULE_AUTHOR(&amp;#34;Your Name&amp;#34;); MODULE_DESCRIPTION(&amp;#34;A simple Hello World module&amp;#34;); static int __init hello_init(void) { printk(KERN_INFO &amp;#34;Hello, world!\n&amp;#34;); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO &amp;#34;Goodbye, world!\n&amp;#34;); } module_init(hello_init); module_exit(hello_exit); 编写Makefile obj-m += hello.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 编译 在终端中进入hello文件夹，输入以下命令编译内核模块：
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules 编译成功后，会生成一个名为hello.ko的内核模块文件。
使用模块 加载内核模块，输入以下命令：
sudo insmod hello.</description></item><item><title>如何使用gin中间件【本篇文章由chatgpt生成】</title><link>https://blog.greycode.top/posts/how_to_use_gin_middleware/</link><pubDate>Wed, 22 Mar 2023 16:44:51 +0800</pubDate><guid>https://blog.greycode.top/posts/how_to_use_gin_middleware/</guid><description>本篇文章由chatgpt生成
什么是中间件 中间件是一种常见的Web开发模式，它是一种特殊的处理函数，用于在请求处理前或处理后执行一些公共的逻辑，例如日志记录、认证、权限控制等。中间件可以在全局和局部范围内注册，以实现对不同请求的不同处理。
Gin中间件的使用 Gin是一种基于Go语言的轻量级Web框架，它支持使用中间件来扩展框架的功能。在Gin框架中，中间件是一个函数，它接收一个Context对象作为参数，该对象包含了请求和响应的相关信息，同时还包括了一个Next方法。当一个请求到达时，Gin框架会将这些中间件函数按照顺序串联起来，形成一个函数调用链。当请求被处理时，Gin框架会从调用链的第一个中间件函数开始执行，执行到某个中间件函数时，如果需要继续执行后续的中间件函数，则调用Next方法，否则直接返回响应结果。
全局中间件 Gin框架支持使用Use方法将中间件函数注册到全局中间件链中，例如：
func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() c.Next() latency := time.Since(t) log.Print(latency) } } func main() { r := gin.Default() r.Use(Logger()) r.GET(&amp;#34;/ping&amp;#34;, func(c *gin.Context) { c.JSON(200, gin.H{ &amp;#34;message&amp;#34;: &amp;#34;pong&amp;#34;, }) }) r.Run() } 上面的例子中，我们定义了一个名为Logger的中间件函数，它用于记录每个请求的响应时间。然后我们使用Use方法将该中间件函数注册到全局中间件链中。最后我们定义了一个简单的路由处理函数，用于返回一个JSON格式的响应。当我们访问路由/ping时，Gin框架会自动调用中间件函数Logger，记录请求响应时间。
局部中间件 Gin框架还支持使用Group方法将中间件函数注册到特定的路由组中，例如：
func Auth() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader(&amp;#34;Authorization&amp;#34;) if token == &amp;#34;&amp;#34; { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{&amp;#34;error&amp;#34;: &amp;#34;authorization required&amp;#34;}) return } // TODO: validate token c.</description></item><item><title>LuaJIT Introduction</title><link>https://blog.greycode.top/posts/luajit-introduction/</link><pubDate>Mon, 02 Jan 2023 14:55:38 +0800</pubDate><guid>https://blog.greycode.top/posts/luajit-introduction/</guid><description>What&amp;rsquo;s JIT? Just-In-Time or JIT 是指在程序运行时进行代码编译的技术，像 Java，Python（这里指PyPy）、LuaJIT 都引入了这种技术。
一般 JIT 编译器与解释器一同工作，大部分时间代码由解释器进行转换成机器码进行运行，当某些代码运行的次数超过设定的阈值时，就会触发 JIT 编译进行工作，JIT 编译器会把这些热点代码编译为机器码，当下次运行到这些代码时，就不用解析器进行解释转换了，可以直接运行机器码来提高程序的运行速度。
How does work of LuaJIT? 顾名思义，LuaJIT 是一种即时(JIT) 编译器。这意味着函数是按需编译的，即当它们首先运行时。这既确保了应用程序的快速启动，也有助于避免无用的工作。例如，未使用的函数根本不会被编译。
另一种编译方法称为提前(AOT) 编译。这里所有的东西都是在运行任何函数之前编译的。这是许多语言的经典方式，例如 C、C++、Go、Rust 等等。
当启动 LuaJIT 时，一切都像在标准 Lua 中一样进行：初始化 Lua 核心，加载标准库并分析命令行。然后通常会加载第一个 Lua 源代码文件并将其转换为 Lua 字节码。最后运行初始主块的函数&amp;hellip;&amp;hellip;
example.lua:
local s = &amp;#34;hello,world!&amp;#34; for i=1,10000 do for j=1,10000 do string.find(s, &amp;#34;ll&amp;#34;, 1, true) end end 上面代码中，它会被先转换成 LuaJIT 自己定义的字节码，我们可以用下面的命令来查看：
$ luajit -bl example.lua -- BYTECODE -- example.lua:0-8 0001 KSTR 0 0 ; &amp;#34;hello,world!</description></item><item><title>Understand Rust Ownership</title><link>https://blog.greycode.top/posts/rust-ownership/</link><pubDate>Fri, 30 Dec 2022 14:37:51 +0800</pubDate><guid>https://blog.greycode.top/posts/rust-ownership/</guid><description>what&amp;rsquo;s ownership? 常见的高级语言都有自己的 Garbage Collection（GC）机制来管理程序运行的内存，例如 Java、Go 等。而 Rust 引入了一种全新的内存管理机制，就是 ownership（所有权）。它在编译时就能够保证内存安全，而不需要 GC 来进行运行时的内存回收。
在 Rust 中 ownership 有以下几个规则：
每个值都有一个 woner（所有者） 在同一时间，每个值只能有一个 owner 当 owner 离开作用域，这个值就会被丢弃 Scope (作用域) 通过作用域来划分 owner 的生命周期，作用域是一段代码的范围，例如函数体、代码块、if 语句等。当 owner 离开作用域，这个值就会被丢弃。
example:
fn main() { let s = String::from(&amp;#34;hello&amp;#34;); // 变量 s 进入作用域，分配内存 // s 在这里可用 } // 函数体结束，变量 s 离开作用域，s 被丢弃，内存被回收 ownership transfer（所有权转移） 和大多数语言一样，Rust 在栈上分配基本类型的值，例如整型、浮点型、布尔型等。而在堆上分配复杂类型的值，例如 String、Vec 等。所以，这里就引入了两个概念，move 和 clone。
move move 操作会将变量的所有权转移给另一个变量，这样原来的变量就不能再使用了。这里需要注意的是，move 操作只会发生在栈上的值，因为在堆上的值是不可复制的，所以只能通过 clone 操作来复制。
example:
fn main(){ let s1 = String::from(&amp;#34;hello&amp;#34;); let s2 = s1; print!</description></item><item><title>程序执行过程简述</title><link>https://blog.greycode.top/posts/program-exec-process/</link><pubDate>Wed, 28 Dec 2022 14:37:51 +0800</pubDate><guid>https://blog.greycode.top/posts/program-exec-process/</guid><description>代码编译过程 #include &amp;lt;stdio.h&amp;gt; int main(){ printf(&amp;#34;hello world\n&amp;#34;); return 0; } 一个 C 语言的程序，从代码到一个可执行文件，其中要经历 4 个阶段程序
预处理 编译器 汇编器 链接器 这四个阶段一起构成了编译系统 预处理阶段：直接修改原始程序，进行一些文本替换方面的操作，例如宏展开、文件包含、删除部分代码等。 可以用下面这个命令来使用 gcc 编译器来生成预处理的文件 main.i , 然后可以直接用你的 vscode 或文本编辑器来打开查看它
$ gcc -E main.c -o main.i 编译阶段：将源代码文件编译成汇编语言 可以通过执行下面这个命令来编译预处理阶段生成的 main.i 文件来生成汇编代码文件 main.s
$ gcc -S main.i -o main.s .file &amp;#34;main.c&amp;#34; .text .section .rodata .LC0: .string &amp;#34;hello world&amp;#34; .text .globl main .type main, @function main: .LFB0: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .</description></item><item><title>计算机组成简述</title><link>https://blog.greycode.top/posts/computer-system-introduction/</link><pubDate>Wed, 28 Dec 2022 14:37:51 +0800</pubDate><guid>https://blog.greycode.top/posts/computer-system-introduction/</guid><description>系统硬件组成 计算机主要由 4 个部分组成
总线：贯穿整个系统的是一组电子管道，称作总线，它携带信息字节并负责在各个部件间传递。通常总线被设计成传送定长的字节块 ，也就是字（word)。字中的字节数（即字长）是一个基本的系统参数 ，各个系统中都不尽相同。现在的大多数机器字长要么是 4 个字节（32位）， 要么是 8 个字节（64位）。 I/O 设备：(Input/Output)输入输出设备，包括键盘、显示器、磁盘、等。 主存：主存是计算机中的临时存储器，用来存放正在运行的程序和数据，断电数据就会丢失。它的容量比较小，速度比较快，但是比较昂贵。主存是由一组动态随机存取存储器（DRAM）芯片组成的。 CPU：处理器，是计算机的核心，它负责执行程序中的指令，控制其他部件的工作。它的主要组成部分有算术逻辑单元（ALU）、控制单元（CU）、寄存器和总线。 其中，CPU 主要做以下几个操作：
加载：将数据从主存复制到 CPU 的寄存器中 存储：将数据从 CPU 的寄存器复制到主存中 算术运算：把两个寄存器的内容复制到 ALU 中，ALU 对数据进行算术运算，然后将结果存储到寄存器中 跳转：从指令中取出一个地址，然后把这个地址复制到 PC 中，从而改变程序的执行顺序 高速缓存 由于 CPU 从寄存器中读取数据的速度是从主存中读取数据速度几百倍。所以主存成了拖慢 CPU 速度的主要原因，为了提高 CPU 的速度，引入了高速缓存（Cache）的概念。
高速缓存（Cache）是一种存储器，它位于主存和 CPU 之间，用来存放最近使用的数据和指令。高速缓存的容量比主存小，但是速度比主存快，所以 CPU 可以从高速缓存中读取数据和指令，而不必每次都到主存中读取。高速缓存的容量和速度都比较昂贵，所以一般只有少量的高速缓存。
现在一般比较新的处理器有有三级高速缓存：L1、L2、L3，它们是用一种叫做静态随机访问存储器（SRAM）的硬件技术实现的。
CPU 访问 L1 的速度和访问寄存器一样快，后面的 L2、L3 缓存由于离 CPU 更远，所以速度会慢一些。
存储设备层次结构 每个计算机系统中的存储设备都被组织成了一个存储器层次结构。在这个层次结构中，从上至下，设备的访问速度越来越慢、容量越来越大，并且每字节的造价也越来越便宜。寄存器文件在层次结构中位于最顶部 ，也就是第 0 级或记为 L0。这里我们展示的是三层高速缓存 L1 到 L3,占据存储器层次结构的第 1 层到第 3 层。主存在第 4 层，以此类推。
多核 CPU 多核 CPU 是指一个 CPU 包含多个核心，每个核心都有自己的寄存器文件和高速缓存。多核 CPU 的主要优点是可以同时执行多个程序，从而提高 CPU 的利用率。</description></item><item><title>高性能网关基石——OpenResty</title><link>https://blog.greycode.top/posts/openresty-instruction/</link><pubDate>Tue, 27 Dec 2022 15:08:48 +0800</pubDate><guid>https://blog.greycode.top/posts/openresty-instruction/</guid><description>什么是 OpenResty OpenResty 一个基于 Nginx 的高性能 Web 平台，能够方便地搭建处理超高并发的动态 Web 应用、 Web 服务和动态网关。例如有名的 Kong 网关和国产新秀 ApiSIX 网关都是基于 OpenResty 来进行打造的。
OpenResty 通过实现 ngx_lua 和 stream_lua 等 Nginx 模块，把 Lua/LuaJIT 完美地整合进了 Nginx，从而让我们能够在 Nginx 内部里嵌入 Lua 脚本，用 Lua 语言来实现复杂的 HTTP/TCP/UDP 业务逻辑，同时依然保持着高度的并发服务能力。
处理阶段 一个正常的 Web 服务的生命周期可以分成三个阶段：
initing：服务启动，读取配置文件，初始化内部数据结构 running：服务运行，接受客户端的请求，返回响应结果 exiting：服务停止，做一些必要的清理工作，如关闭监听端口 OpenResty 主要关注的是 initing 和 running 这两个阶段，并做了更细致的划分
OpenResty 的 initing 阶段 configuration：读取配置文件，解析配置指令，设置运行参数 master-initing：配置文件解析完毕，master 进程初始化公用的数据 worker-initing：worker 进程初始化自己专用的数据 OpenResty 的 running 阶段 在 running 阶段，收到客户端的请求后，OpenResty 对每个请求都会使用下面这条流水线进行处理：
ssl：SSL/TLS 安全通信和验证 preread： 在正式处理之前预读数据，接收 HTTP 请求头 rewrite：检查、改写 URI ，实现跳转重定向 access：访问权限控制 content：产生响应内容 filter：对 content 阶段产生的内容进行过滤加工处理 log： 请求处理完毕，记录日志，或者其他的收尾工作。 OpenResty 执行程序 OpenResty 根据上面的处理阶段提供了一些指令，在开发时使用它们就可以在这些阶段里面插入 Lua 代码，执行业务逻辑：</description></item><item><title>Nginx Model Instroction</title><link>https://blog.greycode.top/posts/nginx_model_instroction/</link><pubDate>Fri, 02 Dec 2022 11:31:54 +0800</pubDate><guid>https://blog.greycode.top/posts/nginx_model_instroction/</guid><description>Introduction Nginx adopts a unique master and multi workers process pool mechanism, which guarantees stable operation and flexible configuration of Nginx.
Usually, Nginx will start a master process and multiple worker processes to provide external services. The master process, known as the monitoring process, did not handle specific TCP/HTTP requests and received only the Unix signal.
Worker processes compete equally for accepting client connections, executing the main business logic of Nginx, and Using epoll, kqueue, and other mechanisms to process TCP/HTTP requests efficiently.</description></item><item><title>Log of Sep 26, 2022</title><link>https://blog.greycode.top/posts/log-of-220926/</link><pubDate>Mon, 26 Sep 2022 16:54:19 +0800</pubDate><guid>https://blog.greycode.top/posts/log-of-220926/</guid><description>Hello, guys!
I am a backend developer, I like to write code, and I want to write blogs.
Recently, I have felt very depressed, and I don&amp;rsquo;t know why I feel very depressed.
I can&amp;rsquo;t study, write a blog, or do anything because I can&amp;rsquo;t come down my heart.</description></item><item><title>用 manim 写一个排序算法动画</title><link>https://blog.greycode.top/posts/76cbbd57-2cb7-4934-ba99-a7773ec05759/</link><pubDate>Thu, 11 Aug 2022 10:35:24 +0800</pubDate><guid>https://blog.greycode.top/posts/76cbbd57-2cb7-4934-ba99-a7773ec05759/</guid><description>本文不介绍 manim 的安装教程，需要安装教程的请参考：https://docs.manim.org.cn/getting_started/installation.html
什么是 manim Manim 是一个用于精确编程动画的引擎，专为创建解释性数学视频而设计。
注意，有两个主要版本的 manim。该存储库最初是 3Blue1Brown 的作者的个人项目，目的是为这些视频制作动画，此处提供了视频专用代码。2020 年，一群开发人员将其分叉成现在的社区版，目标是更稳定、更好地测试、更快地响应社区贡献，以及更友好地开始使用。
主要版本如下：
3b1b/manim 【最新版】 cairo-backend【旧版】 ManimCommunity/manim 【社区版】 冒泡排序介绍 本文就使用 manim 来实现一个冒泡排序的动画，首先来了解下什么是冒泡排序
冒泡排序（Bubble Sort）也是一种简单直观的排序算法。它重复地走访过要排序的数列，一次比较两个元素，如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换，也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢&amp;quot;浮&amp;quot;到数列的顶端。
算法步骤 比较相邻的元素。如果第一个比第二个大，就交换他们两个。
对每一对相邻元素作同样的工作，从开始第一对到结尾的最后一对。这步做完后，最后的元素会是最大的数。
针对所有的元素重复以上的步骤，除了最后一个。
持续每次对越来越少的元素重复上面的步骤，直到没有任何一对数字需要比较。
初始化元素 比如我们需要排序数组为: [4,2,3,1,5]
首先，需要在 manim 场景上初始化我们的需要排序的所有元素,这里用矩形来表示。
在 manim 中，可以用 Rectangle 来初始化矩形，然后我们通过设置元素不同的高度来表示不同的元素大小。
main.py from manimlib import * class Test(Scene): def construct(self): COLOR = [BLUE, GREEN, RED, PINK, ORANGE, MAROON_B, TEAL, PURPLE_B, GREY_BROWN] arr = [4,2,3,1,5] g = VGroup() for i in range(len(arr)): r1=Rectangle(width=1,height=arr[i],fill_color=COLOR[i%len(COLOR)],fill_opacity=1) t1=Text(str(arr[i])).</description></item><item><title>解密安卓微信聊天信息存储</title><link>https://blog.greycode.top/posts/android-wechat-bak/</link><pubDate>Mon, 20 Jun 2022 14:19:48 +0800</pubDate><guid>https://blog.greycode.top/posts/android-wechat-bak/</guid><description>准备工作 （当前微信版本是：8.0.18）
一台 Root 的手机（手机不能 Root 的话用安卓模拟器，然后安卓模拟器获取 Root 应该也是可以的，不过我没试过） DB Browser for SQLite SQLCipher silk-v3-decoder 收集数据 需要收集的数据有：
image2 文件夹：里面存放着所有的微信聊天图片，位置在：/data/data/com.tencent.mm/MicroMsg/[32位字母]/image2 voice2 文件夹：里面存放着所有的微信语音，位置在：/sdcard/Android/data/com.tencent.mm/MicroMsg/[32位字母]/voice2 voide 文件夹：里面存放着所有的微信视频，位置在：/sdcard/Android/data/com.tencent.mm/MicroMsg/[32位字母]/voide avatar 文件夹：里面存放着所有的微信头像，位置在：/data/data/com.tencent.mm/MicroMsg/[32位字母]/avatar Download 文件夹: 微信的聊天发送的文件存放在这里，位置在：/sdcard/Android/data/com.tencent.mm/MicroMsg/Download EnMicroMsg.db: 微信的数据库文件，位置在：/data/data/com.tencent.mm/MicroMsg/[32位字母]/EnMicroMsg.db WxFileIndex.db: 微信的文件索引数据库文件，位置在：/data/data/com.tencent.mm/MicroMsg/[32位字母]/WxFileIndex.db 在上面的这些文件中，需要注意的是路径中有个 32 位字母的路径，这个是微信通过某种算法生成的，每个号的路径都不一样。 其中 voice2、voide、Download 这三个文件夹在 /sdcard 目录下，其他的在系统目录 /data 下。 Download 文件夹存放着当前手机上所有微信聊天时发送的文件，这里文件例如：文档，安装包、压缩包等。需要通过 WxFileIndex.db 来索引到这个文件夹。
把上面收集的所有文件放在电脑的同一个文件夹中，接下来对这些数据进行处理。
获取 DB 访问密码 在上面获取到的 EnMicroMsg.db、WxFileIndex.db 是经过加密的，所以我们需要获得这个的访问密码，通过这个密码来解密数据库。
方法一 可以直接通过 MD5(IMEI+uin) 取前 7 位即是访问密码，如果是大写的要转换成小写字母。(注意：拼接两个数据时不需要用 + 号) 其中IMEI 是手机的 IMEI 码，可以查询手机的设置，在设置中可以查看到。如果你手机刷过机，那么 IMEI 有可能是空白的。或者像 MIUI 系统一样，应用无法真正获取到手机的 IMEI。这时就可以用 1234567890ABCDEF 这个字符串来代替 IMEI。 uin 可以通过 adb 来查看当前登陆微信的 uin 数据：</description></item><item><title>【译】为什么 Web 3.0 很重要，你应该知道</title><link>https://blog.greycode.top/posts/7f482d9706354a288702dccb66c81bf2/</link><pubDate>Sat, 16 Apr 2022 10:42:57 +0000</pubDate><guid>https://blog.greycode.top/posts/7f482d9706354a288702dccb66c81bf2/</guid><description>原文链接：https://medium.com/@essentia1/why-the-web-3-0-matters-and-you-should-know-about-it-a5851d63c949
Web 3.0 及其将给行业带来的巨大变化引起了很多关注，但很少有人真正知道它为什么会产生以及它会带来什么。要理解这一点，有必要回到过去并检查它的前身，Web 1.0 和 2.0。
就像中世纪一样，Web 1.0 直到尘埃落定才被命名。众所周知，“万维网”只是一组静态网站，包含大量信息，没有互动内容。连接意味着通过摇摇晃晃的调制解调器拨号并阻止房子里的任何人使用电话。它是 AOL 聊天室和 MSN Messenger、AltaVista 和 Ask Jeeves 的网络。它慢得令人发指。流媒体视频和音乐？忘了它。下载一首歌曲至少需要一天时间。
然后是 2.0 令人毛骨悚然的调制解调器和无聊的接口的内存在很大程度上已经消失了。更快的互联网速度为互动内容铺平了道路，网络不再是为了观察，而是为了参与。全球信息共享催生了“社交媒体”时代。Youtube、维基百科、Flickr 和 Facebook 为无声者发声，并为志同道合的社区提供了蓬勃发展的途径。
发布这篇博文将花费我 30 秒的时间，这与设计师、开发人员和管理员共同努力进行简单的网站编辑相比，这是一个不可估量的改进。我们可以称之为“读写发布”时代——信息的传播就像这三个词一样简单。那么问题来了，web 2.0 很棒，哪里出了问题？
信息就是金钱 联合国估计，从 2000 年到 2015 年，互联网用户从 7.38 亿增加到 32 亿。这是一个不可估量的数据，正如大型数字公司所意识到的那样，个人信息是一项非常有价值的资产。于是开始在集中式服务器中大量存储数据，亚马逊、Facebook 和 Twitter 是最大的托管方。人们为了这些服务的便利而牺牲了安全性；不管他们是否知道，他们的身份、浏览习惯、搜索和在线购物信息都被卖给出价最高的人。
3.0 革命 到了这个阶段，Web 2.0 的倡导者已经在构想继任者了。他们设想，下一个网络将怀旧地转向网络 1.0 的愿景：更“人性化”和更多隐私。与其将权力（和数据）集中在动机可疑的庞然大物手中，不如归还给合法所有者。
一个更公平、更透明的网络的愿景可以追溯到 2006 年左右，但当时还没有工具和技术来实现它。比特币还有三年的时间，它带来了分布式账本或区块链的概念，用于点对点数字存储。去中心化是一个想法。区块链是手段。现在我们有了所谓的以人为本的互联网。
支持隐私、反垄断的网络 虽然 Web 2.0 使许多权力结构民主化并创造了新的机会，但经济引擎在很大程度上是私有化和垄断的。Facebook、Uber 和 AirBnB 已经为它们主导的公共基础设施创建了专用网络。Web 3.0 与此相反，它是关于通过开放网络共享价值的多个利润中心。
很容易想象在不久的将来，基于加密的手机、VPN、去中心化存储和加密货币钱包将广泛普及。未来不需要暂停或监视我们信息的网络和蜂窝提供商。如果我们要避免梦游进入黑镜式的隐私反乌托邦，这些就是我们需要的工具。Web 3.0 提供了许多优势：
**没有中心控制点：**中间人被从等式中删除，像以太坊这样的区块链提供了一个无需信任的平台，其中的规则是牢不可破的，数据是完全加密的。Alphabet 和 Apple 将不再控制用户数据。任何政府或实体都没有能力扼杀网站和服务；没有一个人可以控制他人的身份。</description></item><item><title>Redis是怎样通讯的？</title><link>https://blog.greycode.top/posts/8b135153fd9d41de928df42f84ad1eca/</link><pubDate>Tue, 29 Mar 2022 13:41:40 +0000</pubDate><guid>https://blog.greycode.top/posts/8b135153fd9d41de928df42f84ad1eca/</guid><description>模型 Redis 协议模型就是简单的请求-响应模型，和平常的 Http 协议有点类似。客户端发送 Redis 命令，然后服务端处理命令并返回结果给客户端。Redis 官方说这可能是最简单的网络协议模型了。
有两种情况下不 不适用这个模型，一个是批量流水线命令，一个是发布/订阅功能。
协议描述 Redis 协议一般简单的分为 5 类数据结构，简单字符串、错误信息、数值、大字符串、数组。每种数据类型在第一个字节用不同的符号来区分：
简单字符串(Simple Strings)：开头第一个符号为 +，对应 HEX 值为：0x2b 错误信息(Errors)：第一个字节符号为 -，对应 HEX 值为：0x2d 数值(Integers)：第一个字节符号为 :，对应 HEX 值为：0x3a 大字符串(Bulk Strings)：第一个字节符号为 $，对应 HEX 值为：0x24 数组(Arrays)：第一个字节符号为 *，对应 HEX 值为：0x2a 这 5 种数据类型可以组合起来使用，每种数据类型通过 CRLF 结尾，就是平常的 \r\n，对应的 HEX 值为：0x0d,0x0a。一般我们判断一种数据类型是否结束时，只要判断是否有 \r 出现就可以了。Redis 客户端和服务端之间就是通过这些规则来进行通信的。
简单字符串 一般简单字符串用于返回 Redis 服务端的系统响应，如果要响应用户存储的数据时，一般会用大字符串(Bulk Strings)的数据类型来返回。
比如说客户端发送 set 命令新增一个 Key 来存储字符串，此时客户端就会返回 +OK。这种方式返回的数据不能有空格和换行，因为空格和换行表示该类型的数据结尾。
redis:0&amp;gt;set name 灰灰 &amp;#34;OK&amp;#34; # Redis 服务端响应数据 0000 2b 4f 4b 0d 0a +OK·· 错误信息 当我们执行的命令发生错误时，Redis 服务端就会返回错误信息</description></item><item><title>MySQL是怎样通讯的？</title><link>https://blog.greycode.top/posts/2852f14d7d13471798ce28c544741e89/</link><pubDate>Sun, 27 Mar 2022 23:51:51 +0000</pubDate><guid>https://blog.greycode.top/posts/2852f14d7d13471798ce28c544741e89/</guid><description>前言 我们平常使用数据库的场景一般是程序里面代码直接连接使用，然后进行 CRUD 操作。或者使用有 GUI 界面的数据库软件来手动操作数据库， 这类软件有 DataGrip、Navicat等等&amp;hellip;。平常很少关心它们的底层数据交互是怎么样的，相信你看了这篇文章一定能有大概的了解。本篇文章的代码使用 Go 语言来实现 MySQL 的协议。
协议简介 MySQL 协议一般分为两个阶段，一个是连接阶段，一个是命令阶段。 连接阶段主要是客户端和服务端进行相互认证的阶段，就像我们平常登陆某个网站的一个操作。 命令阶段主要是客户端向服务端进行的一些指令的发送，然后服务端处理指令并返回结果的一个过程。 在客户端和服务端发送的数据包中，前 3 个字节表示这个数据包的大小，所以这里就有一个问题，就是它有一个大小的限制，数据包大小不能超过16777215 ($2^{24}-1$) bytes，也就是 16M 大小（16进制表示：ff ff ff，刚刚 3 个字节）。这就会有三种情况出现，一种是数据包小于 16M，一种是等于，一种是大于。所以在 MySQL 协议中是这样处理的：
小于 16M：发送一个数据包就可以了 等于 16M：发送两个数据包，第二个包为空包 大于 16M：发送多个数据包，每个数据包大小最大为 16M，当最后一个数据包等于 16M 时，再多发送一个空数据包 每个数据包中的第 4 个字节表示这个数据包的序号ID，这个 ID 在不同阶段会递增，比如在连接阶段，这个 ID 会随着包的数量而递增，当连接阶段完成后进入命令阶段，这个 ID 又会从 0 开始递增，直到这个命令的生命周期结束。
初始握手包 当客户端进行尝试使用 TCP 连接 MySQL 服务端时，服务端就会响应一个初始的握手包，这个握手包有 V9、V10 两个版本。不过现在一般用的都是 V10 版本，如果 MySQL 的版本在 3.21.0 之前，那么服务端响应的是 V9 版本的初始握手包。本篇文章就讲讲现在常用的 V10 版本的初始握手包。
我们可以使用以下代码来尝试连接我们本地的 MySQL 服务:</description></item><item><title>centos 多网卡配置优先级</title><link>https://blog.greycode.top/posts/0a5a3927baea4155849c174e4c613913/</link><pubDate>Wed, 01 Sep 2021 15:47:14 +0000</pubDate><guid>https://blog.greycode.top/posts/0a5a3927baea4155849c174e4c613913/</guid><description>过程 查看网卡配置
[root@localhost ~]# ip addr 1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: em1: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 34:17:eb:f0:18:8f brd ff:ff:ff:ff:ff:ff inet 192.168.0.84/24 brd 192.168.0.255 scope global noprefixroute em1 valid_lft forever preferred_lft forever inet6 240e:390:c6a:c3a0:3617:ebff:fef0:188f/64 scope global noprefixroute dynamic valid_lft 259182sec preferred_lft 172782sec inet6 fe80::3617:ebff:fef0:188f/64 scope link noprefixroute valid_lft forever preferred_lft forever 3: em2: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 34:17:eb:f0:18:90 brd ff:ff:ff:ff:ff:ff inet 192.</description></item><item><title>用 Json-Schema 来验证你的请求参数</title><link>https://blog.greycode.top/posts/775ff9a0ce4940ebbb45fed3fad7ab5b/</link><pubDate>Thu, 19 Aug 2021 16:40:05 +0000</pubDate><guid>https://blog.greycode.top/posts/775ff9a0ce4940ebbb45fed3fad7ab5b/</guid><description>简介 Json-Schema 是一个用来验证、描述 Json 数据的一个标准，它可以用来验证你的请求数据是否和你定义的 Schema 是否一致。比如下面的 Json 数据中：
{ &amp;#34;name&amp;#34;:&amp;#34;greycode&amp;#34;, &amp;#34;desc&amp;#34;:&amp;#34;coder&amp;#34; } 如果不预先告诉你字段的含义，你知道 name 是什么意思吗？它到底是指人名还是一个物品的名字还是其他？desc 又是什么意思呢？
这时候，就可以用 Json-Schema 来描述它了
{ &amp;#34;$schema&amp;#34;: &amp;#34;http://json-schema.org/draft-07/schema&amp;#34;, &amp;#34;$id&amp;#34;: &amp;#34;http://example.com/example.json&amp;#34;, &amp;#34;type&amp;#34;: &amp;#34;object&amp;#34;, &amp;#34;title&amp;#34;: &amp;#34;这是一个Json数据&amp;#34;, &amp;#34;description&amp;#34;: &amp;#34;描述个人信息的数据&amp;#34;, &amp;#34;required&amp;#34;: [ &amp;#34;name&amp;#34;, &amp;#34;desc&amp;#34; ], &amp;#34;properties&amp;#34;: { &amp;#34;name&amp;#34;: { &amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;, &amp;#34;description&amp;#34;: &amp;#34;人的姓名&amp;#34;, }, &amp;#34;desc&amp;#34;: { &amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;, &amp;#34;description&amp;#34;: &amp;#34;个人简介&amp;#34;, } } } 上面我们用 Json-Schema 来描述了刚开始的 Json 数据，这样就可以清楚的知道 name 是人的姓名，desc 是个人简介，在也不用自己去猜了。
Json Schema 字段说明 在上面的 Json-Schema 数据中，每个字段都有其的含义
$schema ：主要用于版本控制 $id ：定义字段在 schema 中的地址 title 和 description ：用于描述和说明 Schema 的作用 type ：定义字段的数据类型 required ：Json 数据中包含的字段 &amp;hellip;&amp;hellip; 由于 Json-Schema 有许多草案，每个草案的字段都有一点区别，具体可以看一下的草案资料：</description></item><item><title>AviatorScript轻量级高性能脚本语言</title><link>https://blog.greycode.top/posts/ca3ab1d58ea74b76a5fd69f79dd5ec79/</link><pubDate>Tue, 17 Aug 2021 16:10:53 +0000</pubDate><guid>https://blog.greycode.top/posts/ca3ab1d58ea74b76a5fd69f79dd5ec79/</guid><description>简介 在 5.0 版本以前，它的名字是叫 Aviator ，定位一直只是一个表达式引擎，不支持 if/else 条件语句（仅有三元运算符支持 ?: ），没有内置的 for/while 循环支持（虽然你可以用 seq 库类似函数式的方式来处理集合），也没有赋值（后来在 4.0 引入），没有作用域的概念（也在 4.0 引入 lambda 函数后部分实现）等等一般语言常见的能力。在 5.0 版本后，它变成了一门脚本语言，叫：AviatorScript 。
在 5.0 ，新加了如下新特性：
大括号 { ... } 括起来的词法作用域。
let 语句用于定义局部变量。
条件语句 if/elsif/else 。
循环语句 for 和 while ，以及相应的 break 和 continue 语句支持。
return 语句用于从脚本或者函数中返回值。
fn hello() { println(&amp;quot;hello&amp;quot;); } 新的 fn 语法用于定义命名函数。
## 单行注释 注释支持
模块系统
new 语法用于创建对象
异常处理
命令行工具 aviator
使用 AviatorScript 可以单纯的作为脚本语言使用，也可以和 Java 配合使用。
单纯的作为脚本语言使用 作为脚本语言使用时，需要下载一个 aviator，然后用它去执行脚本文件。</description></item><item><title>ZooKeeper客户端详解及可视化客户端</title><link>https://blog.greycode.top/posts/8b8235def437432c99c81e97d68d0644/</link><pubDate>Mon, 09 Aug 2021 14:54:39 +0000</pubDate><guid>https://blog.greycode.top/posts/8b8235def437432c99c81e97d68d0644/</guid><description>下载 我们可以去 ZooKeeper 官网 下载，这里我下载了 ZooKeeper 3.7.0 版本。
下载解压后，进入 apache-zookeeper-3.7.0-bin/bin 目录，这里有客户端、服务端和一些工具。在 Windows 中可以执行.cmd 结尾的执行文件，在 Mac 或 Linux 中可以执行 .sh 结尾的可执行文件。名为 zkCli 的文件就是 ZooKeeper 的客户端了，我们可以用这个客户端来连接到 ZooKeeper 注册中心，来对节点进行查看或增删操作。
使用 我是在 Mac 环境下演示使用的
接下来就说下这个客户端怎么使用。
连接 第一步就是要连接到 ZooKeeper 服务，当你执行 zkCli.sh 不带任何参数时，它默认是连接到本地的 localhost:2181 地址。如果你要连接到指定地址，可以使用 -server 配置：
./zkCli.sh -server localhost:2181 # 还可以加上连接超时时间（单位：毫秒） ./zkCli.sh -timeout 3000 -server localhost:2181 连接上后，终端就会变成这样：
[zk: localhost:2181(CONNECTED) 0] 然后你可以输入 help 来查看执行的命令：
ZooKeeper -server host:port -client-configuration properties-file cmd args addWatch [-m mode] path # optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE addauth scheme auth close config [-c] [-w] [-s] connect host:port create [-s] [-e] [-c] [-t ttl] path [data] [acl] delete [-v version] path deleteall path [-b batch size] delquota [-n|-b|-N|-B] path get [-s] [-w] path getAcl [-s] path getAllChildrenNumber path getEphemerals path history listquota path ls [-s] [-w] [-R] path printwatches on|off quit reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,.</description></item><item><title>小白学前端之TypeScript使用Vuex 4.0</title><link>https://blog.greycode.top/posts/2ca7c67d02e74713a030f09651a5f164/</link><pubDate>Thu, 29 Jul 2021 16:45:15 +0000</pubDate><guid>https://blog.greycode.top/posts/2ca7c67d02e74713a030f09651a5f164/</guid><description>简介 官方介绍：Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态，并以相应的规则保证状态以一种可预测的方式发生变化。
简单来说，Vuex 就像是前端的数据库或缓存，不管什么页面，只要 Vuex 里面有的数据，都可以去拿。
Vuex 分为 5 个部分：
State：是数据源，存放数据 Getters：可以取得 State 的数据，然后自定义组装返回新的数据 Mutations：可以改变 State 的数据，建议方法执行是同步的 Actions：可以异步执行 Mutations 里的方法 Modules：每个 Module 都有各自的 State、Getters、Mutations、Actions 这 5 个部分相辅相成。
TypeScript 使用 在 vue 项目根目录执行命令来进行 vuex 模块的安装
npm install vuex@next --save 安装好后我们新建文件 /src/store/store.ts ,然后在里面定义 InjectionKey 和 Store
import { InjectionKey } from &amp;#39;vue&amp;#39; import { createStore, useStore as baseUseStore, Store } from &amp;#39;vuex&amp;#39; // 定义 State 数据类型的接口 interface IState{ } // 类型传递 export const key: InjectionKey&amp;lt;Store&amp;lt;IState&amp;gt;&amp;gt; = Symbol() export const store = createStore&amp;lt;IState&amp;gt; ({ }) // 用于组合式API setup() 里，省的每次都传入 key export function useStore() { return baseUseStore(key) } 然后在 main.</description></item><item><title>利用腾讯位置API进行的阿里云DDNS工具</title><link>https://blog.greycode.top/posts/9acf61395a77470ca029d24aec2ca42f/</link><pubDate>Mon, 26 Jul 2021 16:08:12 +0000</pubDate><guid>https://blog.greycode.top/posts/9acf61395a77470ca029d24aec2ca42f/</guid><description>介绍 本应用是基于阿里云SDK进行开发的，可以动态更新阿里云域名的DNS解析，运行环境是 Python 3
利用腾讯位置提供的 API 进行公网 IP 的获取，可以查看如何获取腾讯位置的 API 密钥
由于腾讯位置的 API 免费配额为每日 10000 次，请合理使用
快速开始 程序从环境变量中获取配置，运行前先设置环境变量
环境变量 说明 ALI_ACCESS_KEY_ID 阿里云 ACCESS_KEY_ID（必填） ALI_ACCESS_KEY_SECRET 阿里云 ACCESS_KEY_SECRET（必填） ALI_REGION_ID 阿里云区域 ID（默认：cn-hangzhou） DNS_TYPE 解析类型（默认：A） DNS_DOMAIN 域名（必填） DNS_SUB_DOMAIN 二级域名（默认：@） TENCENT_LBS_KEY 腾讯位置应用 KEY TENCENT_LBS_SK 腾讯位置应用签名加密 SK 环境字段说明 DNS_TYPE 支持的解析类型：
点击查看官方详细说明
A：将域名指向一个IPV4地址 CNAME：将域名指向另外一个域名 AAAA：将域名指向一个IPV6地址 DNS_SUB_DOMAIN 域名前缀，常见用法有：
www：解析后的域名为www.aliyun.com。
@：直接解析主域名 aliyun.com。
*：泛解析，匹配其他所有域名 *.aliyun.com。
mail：将域名解析为mail.aliyun.com，通常用于解析邮箱服务器。
二级域名：如：abc.aliyun.com，填写abc。
手机网站：如：m.aliyun.com，填写m。
显性URL：不支持泛解析（泛解析：将所有子域名解析到同一地址）
运行 当设置好环境变量后，使用以下命令安装依赖
pip install -r requeirments.txt 安装依赖后，直接运行 main.py
python main.py 以 Docker 运行 如果你本地没有 python 环境，可以直接使用 docker 来运行本程序</description></item><item><title>UML箭头在Java中的含义</title><link>https://blog.greycode.top/posts/234c81b9931c44cb9b88e0e53210bb48/</link><pubDate>Sun, 18 Jul 2021 20:17:38 +0000</pubDate><guid>https://blog.greycode.top/posts/234c81b9931c44cb9b88e0e53210bb48/</guid><description>泛化 在 Java 中表示继承关系，空心箭头指向父类
示例：
class Parent { } class Son extends Parent { } 实现 表示实现接口，箭头指向接口类
示例：
interface Iter { } class TerImpl implements Iter { } 依赖 表示依赖关系，某个类的方法必须依赖另一个类才可以执行，箭头指向被依赖的类
示例：
class A { public void testA(){ System.out.println(&amp;#34;这是 A 类&amp;#34;); } } class B { public void testB(A a){ a.testA(); } } 关联关系 关联 表示关联关系，两个类的关系是平等的
可以双向关联，A 可以关联 B，B 也可以关联 A
箭头指向被关联的类
示例：
class A { public void testA(){ System.out.println(&amp;#34;这是 A 类&amp;#34;); } } class B { private A a; public B(A a) { this.</description></item><item><title>记一次很久以前做的梦【加密】</title><link>https://blog.greycode.top/posts/8b40046c17644103bd2a256eb938884c/</link><pubDate>Thu, 15 Jul 2021 08:14:37 +0000</pubDate><guid>https://blog.greycode.top/posts/8b40046c17644103bd2a256eb938884c/</guid><description>时间：2019-12-19 07:23 RGwc3OqLQr/FK7bX9qFgbl1FD1WZLruNkeFBP4vHyDejXpJhzV309eABiP0e+YiX5wKAD0GaALo74u1B39tyIUFaMrQAVV5M60sGphzkL2hf0ad5/0JAqGXgDy01ustT/w+bAGA8As3jc+3CexZ8CfqJsrOp7vt0aE8zzqKV0IbSL3b5u4wpBfp4Rb7+4b7lES9sbZmD37mXP5bJ0X94Z6W38oSSUj6aiLpDI9kllGxSWavZK+hQk1eWuAThn833cjXVtaxq7Mruglvc+eo4ok2Fm56uCdnq2RiUi5nu9e0bkGXyv8ELwr3RcVjMQdHQB8CK/OFuNXltCRWNKlH6G2fyrPO+D+Sg6zbolkLHl326Ja3co0fDuLm6MxbLLwubwjfci0/ZXjGOCKodAfvEtrDmkd0l2HYaOTbH6nOu+3GwXr+3QwdoxhFrVPjpkvjWcs4VNieiqiynXeNBflPR66ZrscQtZRVX77DUXT3jZdQ5rV7a6ZyWDr9ZgET86YQlGFxcW62wsBnMBt9STCFCUhbhYdccrBOAMJuZXfccIDuBgqelngufQRinLHRJiv0ihN2QhNuJFKkkTxSgswrMjLWhMGgKs8fbfnK82myiWIgqRnF6geCydTX8UAS3dTR1f0XJKEJuUNzc87uzszP+EZIcZ+JnCLjLNnJXJG9XVlLKElLmiwXNi4zDnP6NHGa7ikvac7mHR2SsWGL3/v4pZtSNiwoZW3pPaK6Id+eg+srKoB7GTQNKZIsDuJh8cMziJF7cr1KvH7LZRMpB4MpeyvLmgz8oo3soLiGFVhl5wMrcqbc8H4qtnC4mI9iT5Z1lPBtq13+bYyplOY+G0soMOvbtkei7nWxIz3fh17gqMZvlTikEGcMNHGTs+wwgtsbreqR54iWEVuOxfYD+eYArFujw+iJRCsUCYWepGeVGNpZwlsuKQ+EH0sIJ1c4srVN73BYyE9nHw34W9CHY1MkOhUmXflbaBxz7E+AWfiUiZEWpWwvXOfTtWRuJV0htuCPmA63XgHn6JAHadlf5OY4l1BNewOWXVDPYEChsONoPSmTFxjq/Qop83AqoOtxoaPqfWLXM+s8AqMjfWDQkzDMuxUnMO9fxiEze7J+qMZw7y2W+37NgPQHZBcG3Q8Vd/ArxjfMQaZO11cYsW+RQkhuXj6lvsbs31+j/Od4mA0XPpJ2rKIVC/ueVAP8BuTBAzMN8ndDHXkKctT17eCUXz7w+t9n4h4MZxQQPegz9d3ULLabcdCJ167fpJ+8/iXQhcJXihJeNXefkPcVrkxF+Tfj/r73rSbTgtK+LseIedY1u17/8itWpPlPqlVDC6dzSECBofynwSHADsXl+CBFpkstT3JPiSRTnVmWcta4H9kHthp4yG8Hqc2O6fJnodDIjwa6uzS0N8o95wHz79dDtDHKSpGsjtjotT54qCeI9z3T9FMSwKrN2AJn/1fQdRA56QjnGFLeE8EkYY5Ix6tsk9XFcqAeLC1UbWIoaEqUdILSo9e/TAUizEf+01YBHrsec+o2lEGl8QjthfxhlEFDNgTD0Lt4Cb2ytqpMJZXxFrqqUYOOc2+Lq0/xJPHCKHYf8vLZEfx7LzcLbwLk+BVnxm6JqQVXXWMM2BblrFCX2CAIuhd8m0lztKUrK5OVG0TyEeLrOpB6/YfXm6MPIOttooJFzCBZAKpWt70Xwwfwxrr7r0qMZziWv8oTiFG4lSvGE0VvCwN9hZoLvFh6bMFpnz/bv0L8HMSxBmOyanQm01HhX6KXcyNSbW9Ep2Qxkw+eC034r+HQzV2t+qFkbpNFMb0DatDetvdBZyvlTvlPNcxJCcxEqe/gxb9DRzKNPyy8eM/P4Wem+NW7GFZTfXZQ6uj712J/vQddDF9ukf/jXJkEShhLTs9+JFhR9zjnOljLfNhmB8UzIUBno1d+qqzlC+ZiHzkaRbMnATdOYqW0dBIf5f5wIq31M4GC5mkQzxBVQ8Zqxxu5LCLGRrm+2dro8O27/B5D74h0s/rUmbj8Qxxf32rNjj0+TAZIIFh/3pQjdbwNiABQZUpLBtYWSiZSZ5ivKAgzK6H1uVAlrillqMWJDjLsU6yO1sGb6lMBgKiIuGD2MWHj6moa+JUygW+gWo9m7XVe0q6y6CveMERblK4nXsgurUqH4j91uqyu6c0kccGY8Oei6opdzG49/53IJg8YQo6TnK+plLdg1ezfanf20QwHBV1IHNYFWiTUHVO88EJuvLSAPgDdiVCOzO0KjXxjqOiq7lDw+OFbYaYyUoE1nwywhgE1TbBJdR54em/CC4lY+14GNDTTAaN9ATWLCSPJWQnsDR799HCBZcGumWHB303Zcti4/hKKmUW1JH59XxL4dtLup7fER05tm7vtVJ5ZCd0r1+R7QOzPIszOqZ+B2OifcYLVAelM7OGtIZbOmIWQbU11K45P16Y6GVZicmSTPSKXrvAB6nhOD0/we/UbqpU6iNu5ZRTgcZcxyrgU2TzWaKgfcX5ynMjk0TAqvYKYHAXMhMIUE6tEMMithQ1WkasGcv0+r6Hu8jPrc9/Gvlf4u+4fl9knAf7JCgONAi4ds/p6rwGAGtZbvzcAvxF7p04RcX0MY6A53z0tsFIcE46Uu/KuxhWVyufVwlrHfIek6k8hHTspNH8bztwUJLU6gBRP9SpRrZrZIhDPII19VXNFVWdujdwcu7dcNuuoXPofulrbbHSyYaSaxK/RdHh7yjViodnoUp3WaTNDZtzRlICXyZm73kD7qqHyR6nyqnVFG43Zd+Xcic2HJzEhSMYG6Ddlyc6AzYSCJeoDCY9M6dpPFKplBpQRL8g4LjOLHwd7BMlXqqcvjAmJpfHSlByX5KEml7a3V4jVKoIhoq1B5EvoNELDwUah8nnJjh8CKJevnuNx2NRUr/QNzIfHTRGKCYHua70wUQX7m+lcNMc/EG3TLqr+qyXMFjXihPxRLL+Uc9LBDbCIMh1Zpirr/0+6U4twTr/ipWU5SmOxf0O+OFl2NjOH7u8MwkXT1yWTS09Nfu3tLHFA0EmqJxgceNea8uegh+KFo4Fnf4oTd1wwg6c3cE8RX7okJ5ZDMzWCifUEwzdturrhnaarzbZT/aCIAn4Epu4/G0wgJSZL5oos889CaEJcmGkkJ6XSTd87ArxLOu143GSoSxVj6dlMGYLfcnJIjX3NJ0kHJdSROY9f6Kd0Moh/3fupqbLxtyP9bKQKPtpfw4pT2/Kd2nmPeBhxMIqIap4b3zKL5PzBF/t6faSIMANeUoU9+oD1LU5FVfkoz5Icd7aW73SNLnwFxvXXUkuR/Dkcry3vJVOeKuxV6m6L6qqplQtCiyYzTc01Zllr4e34dYU0/QOfId3TI4Ork17iAHGGu+JY8Kud55p2Ph3NZN9nLryFzyjAsgaRY5YJnrJ0/vs+0yS3wLYvXWIy1rhJHytZRS7AzR+TjX+GRAFAajy5zkouA7owJbn1L8UFvzlbDTLFHzhxVMI+DsTddmNqU70wOP2Goh8c3HnOLf77Sn6l6qS+eL18+ZcTm8RhXN8+tT2/id+GLg60kgFIQggJVgQV8qKOqfSnyE4v1szSmSnGxrlZHNSzBg18XppzmNxAbNHjPiauLVGw/qPWvm4ZGvtYv23rLsA0cdLnjnmnlCg/oh/8juTf3GsX5VVS7sGIFZCJh/RRzLmwvXq2LZgBhhdD607xq7AFhuazYpVXcP6axrAWl3ikqYYGzAtesSzc/yK9hhhvPcO+UhhkfWrlLdubzSBP60ycIhCSMG+OhG2U5XZLaSpMZghnpf8tu2uFiH6xkiFHAunxseHecrBstXQZ4jzVL3aFv3TpyQG19dgKIlukZDpSFuLZLFW/fe5So5mNfp/zgezEXaZz/TIkD/HMpm18nov2oPuIOi2HFNwSZoExvORHzRCXcfci8/+m8VH9aF31EwiapPfFmjBWTAtcUI6s+T5Xf55d245Mi0XYQtWkqxLhIXfN0uyJBekVBNCKS7A2J0qjR5weYpAz/mnLX7xVfIGBanizxcag1GsOS70iPp4WuVTMrwVH9RvsCP+3n3wrMFkfrtQnjMx+GWJx3v3vl4fk1o5istwlrT3R3Z630OIgn6Wsdz43OWI4MIp8sJuKqQYntU7XkmSwxerSSOOr64+u9H3WKyUxzgQ8bEqH+i8xjRajjlrYQ8iZGr85c2JEE6HB7QcxbA6IpDz0WI7WdA2WBM/c5lGWNE+gMg7abztKO9CPnQscEvCZGr85c2JEE6HB7QcxbA6IpDz0WI7WdA2WBM/c5lGWNE+gMg7abztKO9CPnQscEvCZGr85c2JEE6HB7QcxbA6IpDz0WI7WdA2WBM/c5lGWNE+gMg7abztKO9CPnQscEvCZGr85c2JEE6HB7QcxbA6IpDz0WI7WdA2WBM/c5lGWNE+gMg7abztKO9CPnQscEvCZGr85c2JEE6HB7QcxbA6IpDz0WI7WdA2WBM/c5lGWNE+gMg7abztKO9CPnQscEvCZGr85c2JEE6HB7QcxbA6IpDz0WI7WdA2WBM/c5lGWNE+gMg7abztKO9CPnQscEvCZGr85c2JEE6HB7QcxbA6IpDz0WI7WdA2WBM/c5lGWNE+gMg7abztKO9CPnQscEvCZGr85c2JEE6HB7QcxbA6IpDz0WI7WdA2WBM/c5lGWNE+gMg7abztKO9CPnQscEvCZGr85c2JEE6HB7QcxbA6IpDz0WI7WdA2WBM/c5lGWNE+gMg7abztKO9CPnQscEvP0OhWvdMO2k9oBXJJ5MV/g=</description></item><item><title>Quarkus项目配置方式详解</title><link>https://blog.greycode.top/posts/3ec6e900cdcb4bceba3b0be40c9aadbb/</link><pubDate>Mon, 12 Jul 2021 17:29:23 +0000</pubDate><guid>https://blog.greycode.top/posts/3ec6e900cdcb4bceba3b0be40c9aadbb/</guid><description>配置加载流程 Quarkus 可以从多个地方获取项目的配置，它读取配置优先级入下图，在下面的优先级中，一旦读取到某个配置，就不会再继续读取后面配置中的这个配置了。
0x1 System Properties 系统属性可以在启动期间通过 -D 标志传递给应用程序。
比如要设置 http 服务的运行端口，各个运行方式传递系统参数的方式如下：
Quarkus dev模式：mvn quarkus:dev -Dquarkus.http.port=8888 运行 jar 包：java -Dquarkus.http.port=8888 -jar quarkus-run.jar 运行 native-image：app-runner -Dquarkus.http.port=8888 0x2 Environment variables 环境变量的名字遵循 MicroProfile Config
Environment Variables Mapping Rules Some operating systems allow only alphabetic characters or an underscore, _, in environment variables. Other characters such as ., /, etc may be disallowed. In order to set a value for a config property that has a name containing such disallowed characters from an environment variable, the following rules are used.</description></item><item><title>Quarkus构建native-image遇到的问题及解决</title><link>https://blog.greycode.top/posts/5756337c1cea4b599e678a3380dcfe00/</link><pubDate>Fri, 09 Jul 2021 17:44:33 +0000</pubDate><guid>https://blog.greycode.top/posts/5756337c1cea4b599e678a3380dcfe00/</guid><description>本机构建 native-image 如果你本地安装了 Graal VM 的话，可以在项目目录下直接执行：
mvn clean package -Pnative 构建的时间比较长，构建完成后就会在 ./target 目录下生成一个二进制执行文件，一般名字是 quarkus-demo-1.0-runner，直接执行这个二进制文件就可以运行项目了。
➜ target: ./quarkus-demo-1.0-runner __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,&amp;lt; / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-07-09 16:54:10,812 INFO [io.quarkus] (main) quarkus-demo 1.0 native (powered by Quarkus 2.0.1.Final) started in 0.</description></item><item><title>使用Maven工具创建Quarkus项目</title><link>https://blog.greycode.top/posts/5870388109c640649633fa0bb2f5c9c1/</link><pubDate>Fri, 09 Jul 2021 15:22:39 +0000</pubDate><guid>https://blog.greycode.top/posts/5870388109c640649633fa0bb2f5c9c1/</guid><description>环境 我这边使用的是 Maven 3.8.1 版本，可以使用 Quarkus 官方提供的 io.quarkus:quarkus-maven-plugin:2.0.1.Final 插件来快速穿件 Quarkus 项目。
创建项目 使用以下 Maven 命令来创建一个新项目：
mvn io.quarkus:quarkus-maven-plugin:2.0.1.Final:create \ -DprojectGroupId=top.mjava \ -DprojectArtifactId=quarkus-demo \ -DprojectVersion=1.0 \ -DclassName=&amp;#34;top.mjava.demo.Application&amp;#34; 在执行命令的当前目录下会为项目生成和 ArtifactId 同名的文件夹，如果已存在该同名文件夹，则项目会创建失败。
在 src/main/docker 目录下还生成了 native 和 jvm 模式的 Dockerfile，构建镜像和运行容器的指令写在这些 Dockerfile 中。
命令描述 属性 默认值 描述 projectGroupId org.acme.sample 项目的 GroupId projectArtifactId 没有默认值，但是必填 项目的 ArtifactId projectVersion 1.0.0-SNAPSHOT 项目版本 platformGroupId io.quarkus 目标平台的组 ID。鉴于所有现有平台都来自 io.quarkus，实际上不会明确使用这一平台。但它仍然是一个选择。 platformArtifactId quarkus-universe-bom 目标平台 BOM 的工件 ID。为了使用本地构建的 Quarkus，它应该是 quarkus-bom。 platformVersion 如果未指定，将解析最新的。 您希望项目使用的平台版本。它还可以接受版本范围，在这种情况下，将使用指定范围中的最新版本。 className 如果省略则不创建 生成的资源的完全限定名称 path /hello 资源路径，仅在设置了 className 时生效。 extensions [] 要添加到项目的扩展列表（逗号分隔） 管理扩展 创建项目后就可以进入到项目文件夹，可以使用简短的命令来操作项目了，例如：mvn quarkus:[command]</description></item><item><title>开发SaaS应用的12条准则【转】</title><link>https://blog.greycode.top/posts/ffc3580d7e244b5282e3fdd7f3eb8e95/</link><pubDate>Wed, 07 Jul 2021 17:33:40 +0000</pubDate><guid>https://blog.greycode.top/posts/ffc3580d7e244b5282e3fdd7f3eb8e95/</guid><description>开发SaaS应用的12条准则【转】 原文地址：https://12factor.net/
简介 如今，软件通常会作为一种服务来交付，它们被称为网络应用程序，或软件即服务（SaaS）。12-Factor 为构建如下的 SaaS 应用提供了方法论：
使用标准化流程自动配置，从而使新的开发者花费最少的学习成本加入这个项目。 和操作系统之间尽可能的划清界限，在各个系统中提供最大的可移植性。 适合部署在现代的云计算平台，从而在服务器和系统管理方面节省资源。 将开发环境和生产环境的差异降至最低，并使用持续交付实施敏捷开发。 可以在工具、架构和开发流程不发生明显变化的前提下实现扩展。 这套理论适用于任意语言和后端服务（数据库、消息队列、缓存等）开发的应用程序。
背景 本文的贡献者参与过数以百计的应用程序的开发和部署，并通过 Heroku 平台间接见证了数十万应用程序的开发，运作以及扩展的过程。
本文综合了我们关于 SaaS 应用几乎所有的经验和智慧，是开发此类应用的理想实践标准，并特别关注于应用程序如何保持良性成长，开发者之间如何进行有效的代码协作，以及如何 避免软件污染 。
我们的初衷是分享在现代软件开发过程中发现的一些系统性问题，并加深对这些问题的认识。我们提供了讨论这些问题时所需的共享词汇，同时使用相关术语给出一套针对这些问题的广义解决方案。本文格式的灵感来自于 Martin Fowler 的书籍： Patterns of Enterprise Application Architecture ， Refactoring 。
读者应该是哪些人？ 任何 SaaS 应用的开发人员。部署和管理此类应用的运维工程师。
I. 基准代码 一份基准代码（Codebase），多份部署（deploy）
12-Factor应用(译者注：应该是说一个使用本文概念来设计的应用，下同)通常会使用版本控制系统加以管理，如Git, Mercurial, Subversion。一份用来跟踪代码所有修订版本的数据库被称作 代码库（code repository, code repo, repo）。
在类似 SVN 这样的集中式版本控制系统中，基准代码 就是指控制系统中的这一份代码库；而在 Git 那样的分布式版本控制系统中，基准代码 则是指最上游的那份代码库。
基准代码和应用之间总是保持一一对应的关系：
一旦有多个基准代码，就不能称为一个应用，而是一个分布式系统。分布式系统中的每一个组件都是一个应用，每一个应用可以分别使用 12-Factor 进行开发。 多个应用共享一份基准代码是有悖于 12-Factor 原则的。解决方案是将共享的代码拆分为独立的类库，然后使用 依赖管理 策略去加载它们。 尽管每个应用只对应一份基准代码，但可以同时存在多份部署。每份 部署 相当于运行了一个应用的实例。通常会有一个生产环境，一个或多个预发布环境。此外，每个开发人员都会在自己本地环境运行一个应用实例，这些都相当于一份部署。
所有部署的基准代码相同，但每份部署可以使用其不同的版本。比如，开发人员可能有一些提交还没有同步至预发布环境；预发布环境也有一些提交没有同步至生产环境。但它们都共享一份基准代码，我们就认为它们只是相同应用的不同部署而已。
II. 依赖 显式声明依赖关系（ dependency ）</description></item><item><title>vertx的web开发学习笔记</title><link>https://blog.greycode.top/posts/10ef2e95447c468cb55bd7bd0675c090/</link><pubDate>Mon, 05 Jul 2021 15:31:02 +0000</pubDate><guid>https://blog.greycode.top/posts/10ef2e95447c468cb55bd7bd0675c090/</guid><description>创建一个 Http 服务 // 创建路由 Router router = Router.router(vertx); // 创建 Http 服务 vertx.createHttpServer() // 绑定路由 .requestHandler(router) // 监听端口 .listen(8888) // Http 服务启动成功后调用 .onSuccess(server -&amp;gt; System.out.println(&amp;#34;HTTP server started on port &amp;#34; + server.actualPort()) ); Router 使用 直接使用上面的路由实例
创建请求路由 创建 Get 请求路由
router .get(&amp;#34;/test&amp;#34;) .respond( // 响应逻辑 ) // 或者 router .route(HttpMethod.GET, &amp;#34;/test&amp;#34;) .respond( // 响应逻辑 ) 创建 Post 请求路由
router .post(&amp;#34;/test&amp;#34;) .respond( // 响应逻辑 ) // 或者 router .post(HttpMethod.POST, &amp;#34;/test&amp;#34;) .</description></item><item><title>Java删除文件后电脑磁盘空间没有恢复</title><link>https://blog.greycode.top/posts/74caaafd610241a1b8ecdb5f3bb33ee4/</link><pubDate>Sat, 03 Jul 2021 17:12:41 +0000</pubDate><guid>https://blog.greycode.top/posts/74caaafd610241a1b8ecdb5f3bb33ee4/</guid><description>问题 当用一下命令删除文件后，电脑磁盘内存没有恢复，还是原来的大小
File folder = new File(&amp;#34;/tmp/file.mp4&amp;#34;) file.delete(); 解决 原来是 FileOutputStream 文件流忘了关了，导致一直占用这个资源。所以使用完后一定记得关文件流，使用下面的代码关闭文件流：
FileOutputStream fileOutputStream = new FileOutputStream(new File()); fileOutputStream.close(); Linux 里的文件被删除后，空间没有被释放是因为在 Linux 系统中，通过 rm 或者文件管理器删除文件将会从文件系统的目录结构上解除链接(unlink).然而如果文件是被打开的(有一个进程正在使用)，那么进程将仍然可以读取该文件，磁盘空间也一直被占用。
可以使用 lsof +L1 |grep delete 命令来查看状态为 deleted 的文件，状态为 deleted 为标记被删除，其实该文件并没有从磁盘中删除，类似windows下的回收站状态。
所以当进程结束后，磁盘空间就会被释放。
参考资料 http://www.cxyzjd.com/article/su4416160/78212934 https://www.jianshu.com/p/fcb80c878d04</description></item><item><title>程序员的酒后真言</title><link>https://blog.greycode.top/posts/a98d5ec3509f483e80919ca2e09bda1b/</link><pubDate>Tue, 29 Jun 2021 11:24:25 +0000</pubDate><guid>https://blog.greycode.top/posts/a98d5ec3509f483e80919ca2e09bda1b/</guid><description>转至：http://www.ruanyifeng.com/blog/2021/06/drunk-post-of-a-programmer.html
出至：https://old.reddit.com/r/ExperiencedDevs/comments/nmodyl/drunk_post_things_ive_learned_as_a_sr_engineer/
(1) 职业发展的最好方法是换公司。
(2）技术栈不重要。技术领域有大约 10-20 条核心原则，重要的是这些原则，技术栈只是落实它们的方法。你如果不熟悉某个技术栈，不需要过度担心。
(3）工作和人际关系是两回事。有一些公司，我交到了好朋友，但是工作得并不开心；另一些公司，我没有与任何同事建立友谊，但是工作得很开心。
(4）我总是对经理实话实说。怕什么？他开除我？我会在两周内找到一份新工作。
(5）如果一家公司的工程师超过 100 人，它的期权可能在未来十年内变得很有价值。对于工程师人数很少的公司，期权一般都是毫无价值。
(6）好的代码是初级工程师可以理解的代码。伟大的代码可以被第一年的 CS 专业的新生理解。
(7）作为一名工程师，最被低估的技能是记录。说真的，如果有人可以教我怎么写文档，我会付钱，也许是 1000 美元。
(8）网上的口水战，几乎都无关紧要，别去参与。
(9）如果我发现自己是公司里面最厉害的工程师，那就该离开了。
(10）我们应该雇佣更多的实习生，他们很棒。那些精力充沛的小家伙用他们的想法乱搞。如果他们公开质疑或批评某事，那就更好了。我喜欢实习生。
(11）技术栈很重要。如果你使用 Python 或 C++ 语言，就会忍不住想做一些非常不同的事情。因为某些工具确实擅长某些工作。
(12）如果你不确定自己想做什么东西，请使用 Java。这是一种糟糕的编程语言，但几乎无所不能。
(13）对于初学者来说，最赚钱的编程语言是 SQL，干翻所有其他语言。你只了解 SQL 而不会做其他事情，照样赚钱。人力资源专家的年薪？也许5万美元。懂 SQL 的人力资源专家？9万美元。
(14）测试很重要，但 TDD （测试驱动的开发）几乎变成了一个邪教。
(15） 政府单位很轻松，但并不像人们说的那样好。对于职业生涯早期到中期的工程师，12 万美元的年薪 + 各种福利 + 养老金听起来不错，但是你将被禁锢在深奥的专用工具里面，离开政府单位以后，这些知识就没用了。我非常尊重政府工作人员，但说真的，这些地方的工程师，年龄中位数在 50 岁以上是有原因的。
(16）再倒一杯酒。
(17）大多数头衔都无关紧要，随便什么公司都可以有首席工程师。
(18）手腕和背部的健康问题可不是开玩笑的，好的设备值得花钱。
(19）当一个软件工程师，最好的事情是什么？你可以结识很多想法相同的人，大家互相交流，不一定有相同的兴趣，但是对方会用跟你相同的方式思考问题，这很酷。
(20）有些技术太流行，我不得不用它。我心里就会很讨厌这种技术，但会把它推荐给客户，比如我恨 Jenkins，但把它推荐给新客户，我不觉得做错了。
(21）成为一名优秀的工程师意味着了解最佳实践，成为高级工程师意味着知道何时打破最佳实践。
(22）发生事故时，如果周围的人试图将责任归咎于外部错误或底层服务中断，那么是时候离开这家公司，继续前进了。
(23）我遇到的最好的领导，同意我的一部分观点，同时耐心跟我解释，为什么不同意我的另一部分观点。我正在努力成为像他们一样的人。
(24）算法和数据结构确实重要，但不应该无限夸大，尤其是面试的时候。我没见过药剂师面试时，还要测试有机化学的细节。这个行业的面试过程有时候很糟糕。
(25）做自己喜欢的事情并不重要，不要让我做讨厌的事情更重要。
(26）越接近产品，就越接近推动收入增长。无论工作的技术性如何，只要它接近产品，我都感到越有价值。
(27）即使我平时用 Windows 工作，Linux 也很重要。为什么？因为服务器是 Linux 系统，你最终在 Linux 系统上工作。
(28）人死了以后，你想让代码成为你的遗产吗？如果是那样，就花很多时间在代码上面吧，因为那是你的遗产。但是，如果你像我一样，更看重与家人、朋友和生活中其他人相处的时光，而不是写的代码，那就别对它太在意。
(29）我挣的钱还不错，对此心存感激，但还是需要省钱。
(30）糟糕，我没酒了。</description></item><item><title>免费全自动SEO优化</title><link>https://blog.greycode.top/posts/21324399fbdc41cba815d2069bb62168/</link><pubDate>Sun, 20 Jun 2021 22:27:32 +0000</pubDate><guid>https://blog.greycode.top/posts/21324399fbdc41cba815d2069bb62168/</guid><description>0x1 简介 怎么让各大站长快速收录你自己网站的链接？那就是主动的推送你自己网站的 URL 到各大站长上去。前几天我写了一个一键提交的工具，可以一键提交你的链接到各大站长上去。你也可以单独使用工具来推送你的 URL、批量 URL 文件、SiteMap 站点地图。工具的github地址是：https://github.com/greycodee/seo-tools
今天我再教大家如何让网站自动提交最新的 URL 到各大站长上去，让各大站长第一时间收录你的链接。
0x2 准备 在开始前，你需要准备如下东西：
IFTTT 账号
Github 账号
你个人网站开通 RSS 订阅
具体原理就是通过 IFTTT 订阅你网站的 RSS，然后当有新的网址发布后，IFTTT 会触发事件回调 Github，Github 收到回调后 Github Action 会进行运转，然后在里面使用工具进行推送。</description></item><item><title>百度、谷歌、必应三大站长API密钥申请流程</title><link>https://blog.greycode.top/posts/c334612cbdce41e79f7ea6a2f3f4c10c/</link><pubDate>Thu, 17 Jun 2021 21:40:11 +0000</pubDate><guid>https://blog.greycode.top/posts/c334612cbdce41e79f7ea6a2f3f4c10c/</guid><description>Google 索引 API 开通步骤 点击此链接，然后选择创建项目，点击继续。然后再点击转到“凭据”页面
到凭据页面先点击左侧的凭据选项，然后再点击管理服务账号
然后再点击创建服务账号
然后再填写相关信息，最后点击完成（第三步可不填）
点击图中的电子邮件，然后开始创建密钥，选择JSON格式，此时就会下载密钥文件到你电脑了
打开 Google Search Console，依次点击设置-&amp;gt;用户和权限-&amp;gt;拥有者账户右边三个点-&amp;gt;管理资源所有者-&amp;gt;添加所有者-&amp;gt;填入上面密钥中的client_email 的值
现在可以用作为服务帐号进行身份验证的步骤来使用密钥了
Bing 索引 API 开通步骤 点击导航栏的齿轮图标
点击 API 访问，然后点击 API 密钥，就可以得到 API 密钥了
然后就可以按照必应文档来进行API的调用了
Baidu 索引 API 开通步骤 百度就比较简单粗暴了，直接点击这个链接就可以直接得到 Token 了，这个页面下也有对应的 API 调用方法示例，这边就不再重复叙述了</description></item><item><title>后端服务器时间不一致问题解决手册</title><link>https://blog.greycode.top/posts/9afc3efaec15479bb5fbc6f670594a94/</link><pubDate>Tue, 15 Jun 2021 10:32:50 +0000</pubDate><guid>https://blog.greycode.top/posts/9afc3efaec15479bb5fbc6f670594a94/</guid><description>时区问题 一般快 8 小时，慢 8 小时的问题都是时区问题，直接把时区改成 CST 时区
编辑系统环境变量文件 /etc/profile
export TZ=&amp;#39;CST-8&amp;#39; Linux 系统时间不同步问题 Linux 系统时间比正常时间快几分中或慢几分钟，但是时区是正确的 CST 时区，这是就要用到 ntpdate 这个命令了
安装 #centos,redhat系列 yum install ntpdate #debian,ubuntu系列 apt install ntpdate #archlinux系列 pacman -S ntpdate 2、通过ntpdate命令从时钟服务器同步
我们这里选用中国ntp服务器cn.pool.ntp.org来作为时钟同步的来源。为能正常访问到cn.pool.ntp.org，你的Linux系统应该能访问外网才行。
执行命令如下：
ntpdate cn.pool.ntp.org 3、配置crontab自动执行同步
如果每次手动执行，显然是很麻烦的。这里，我们使用crontab定时任务来定期执行ntpdate同步命令，例如我们每10分钟或一小时执行一次，可以通过以下方式实现。
首先在命令终端中输入crontab -e命令，然后输入如下命令保存即可。
crontab -e 开始编辑文件内容，输入定时执行命令：
#每10分钟执行一次 */10 * * * * /usr/sbin/ntpdate cn.pool.ntp.org 或者
#每一个小时执行一次 0 * * * * /usr/sbin/ntpdate cn.pool.ntp.org 参考资料 https://linux265.com/news/6009.html</description></item><item><title>用Darabonba一键生成7种语言的代码</title><link>https://blog.greycode.top/posts/0d992236ec6b4e5ead15886d992ff84b/</link><pubDate>Sat, 05 Jun 2021 18:08:19 +0000</pubDate><guid>https://blog.greycode.top/posts/0d992236ec6b4e5ead15886d992ff84b/</guid><description>0x1 介绍 最近在看阿里的SDK的时候，突然看到了一个好玩的东西，这玩意叫 Darabonba。是一种 OpenAPI 应用的领域特定语言。可以利用它为任意风格的接口生成多语言的 SDK、代码示例、测试用例、接口编排等。现在阿里云的多语言 SDK 就是用这个生成的。下面是官方的介绍流程图。
0x2 安装 我们按照官方的步骤来安装它，因为是用 Nodejs 写的，所以可以用 npm 来安装它
sudo npm install -g @darabonba/cli 安装完成后可以在终端输入 dara ，如果输出版本号就是说明安装成功了
➜ dara The CLI for Darabonba 1.1.8 0x3 使用 安装完成后就可以使用了，首先创建一个文件夹来存放这个项目
mkdir demo &amp;amp;&amp;amp; cd demo 然后用 dara 命令来进行初始化模块，然后依次输入包名等信息。
➜ dara init package scope: demo package name: demo package version: 1.0.0 main entry: ./demo.dara 初始化完成后，我们就可以在 demo.dara 文件里进行 Darabonba DSL 表达式的编写里
比如我们编写一个经典的输出 hello world！
编写 Darabonba DSL 表达式 在 demo.</description></item><item><title>什么是HTTP协议？</title><link>https://blog.greycode.top/posts/0143cd7666cd44389fe6f565e10eee1a/</link><pubDate>Fri, 04 Jun 2021 11:44:00 +0000</pubDate><guid>https://blog.greycode.top/posts/0143cd7666cd44389fe6f565e10eee1a/</guid><description>版本介绍 HTTP 协议不用我多说了吧，大家都知道，现在我 web 开发一般都是使用 HTTP 协议来进行通信的。到目前为止，HTTP 进行了几次版本更新，HTTP 1.1 就是表示HTTP 的 1.1 版本。1.1 版本也是目前大部分网站所用的版本。
HTTP 0.9 发布时间：1991 年 简介：梦开始的地方，只接受GET一种请求方法，没有在通讯中指定版本号，且不支持请求头。由于该版本不支持POST方法，因此客户端无法向服务器传递太多信息。 HTTP 1.0 发布时间：1996 年 5 月 简介：这是第一个在通讯中指定版本号的HTTP协议版本。同时比 0.9 版本增加大量新特性。非持续连接，每次都要重新与服务器建立连接。 HTTP 1.1 发布时间：1997 年1月 简介：默认采用持续连接（Connection: keep-alive），能很好地配合代理服务器工作。还支持以管道方式在同时发送多个请求，以便降低线路负载，提高传输速度。同时这也是目前最流行的版本。 HTTP/1.1相较于HTTP/1.0协议的区别主要体现在：
缓存处理 带宽优化及网络连接的使用 错误通知的管理 消息在网络中的发送 互联网地址的维护 安全性及完整性 HTTP 2.0 发布时间：2015 年 5 月 简介：HTTP/2 是 HTTP 协议自 1999 年 HTTP 1.1 的改进版 RFC 2616 发布后的首个更新，主要基于 SPDY 协议。它由互联网工程任务组（IETF）的Hypertext Transfer Protocol Bis（httpbis）工作小组进行开发。该组织于 2014 年 12 月将 HTTP/2 标准提议递交至 IESG 进行讨论，于 2015 年 2 月 17 日被批准。 报文格式 请求报文 请求报文分为 4 个部分，分别是请求行、请求头、换行行、请求数据，每个部分的末尾都会带上回车符（CR，ASCII：0d）和换行符（LF，ASCII：0a）</description></item><item><title>使用Nacos实现网关动态路由</title><link>https://blog.greycode.top/posts/eef92888390b4e7c866fe7ada6a0b42b/</link><pubDate>Sat, 08 May 2021 22:47:54 +0000</pubDate><guid>https://blog.greycode.top/posts/eef92888390b4e7c866fe7ada6a0b42b/</guid><description>背景 网关作为一个主要的外部流量入口，其重启的次数当然是越少越好，所以不能有时候为了修改一个路由就重启整个网关服务，这样的话网关就不是一个高可用的网关。当然，有时候要新增或修改代码层面的自定义的过滤器时还是要重启网关的，所以我们能做的就是尽可能减少不必要的重启。这里就可以引入阿里巴巴开源的 Nacos 了。
什么是 Nacos？ Naocs 是阿里巴巴开源的一款微服务组件，它提供注册中心和配置中心来供我们使用。并且 Nacos 同时支持 AP 模式和 CP 模式来供我们选择使用。具体可以查看官方文档来进一步了解。
安装 Nacos 本地的话我这边建议直接用 Docker 来安装Nacos，省心省力。按照官方提供的方法，我们可以直接下载官方提供的 docker-compose 文件来启动 Nacos。
# 克隆项目 git clone https://github.com/nacos-group/nacos-docker.git ## 进入项目目录 然后启动 cd nacos-docker docker-compose -f example/standalone-mysql-5.7.yaml up 我这边是启动了一个使用 MySQL 5.7 的单机 Nacos，如果你想使用其他的数据库或者启动集群的话可以参照一下官方文档
待启动完成后，就可以用浏览器打开 http://localhost:8848/nacos 进入 Nacos的管理台了。默认的登陆账号密码都是 nacos
网关使用 Nacos 我这边 Spring Cloud 使用的版本号是 2020.0.2
Nacos 创建配置 在开始配置网关项目前，我们先在 Nacos 里创建一个配置，等下网关启动的时候就用这个配置。
server: port: 8989 spring: cloud: gateway: routes: - id: route-demo uri: https://baidu.com predicates: - Path=/** 在上面配置中，我们定义了项目启动端口为 8989，然后创建了一个路由，这个路由接收所有请求，然后转发到百度。</description></item><item><title>Dubbo项目双注册中心</title><link>https://blog.greycode.top/posts/c2e28aa517aa45768b679d93f248b5de/</link><pubDate>Fri, 07 May 2021 13:29:00 +0000</pubDate><guid>https://blog.greycode.top/posts/c2e28aa517aa45768b679d93f248b5de/</guid><description>🤔为什么要双注册中心？ 当前 Dubbo 版本注册粒度是以接口粒度来注册的，而 SpringBoot 是以服务为粒度来注册的。而且 Dubbo 有自己的注册中心（当然 Spring Cloud Alibaba Dubbo 的注册中心可以挂靠在 Spring 上）。所以当一个项目既要调用 Dubbo 服务，又要提供自己的 Web 接口给网关调用时，就要为该项目设置两个注册中心，一个 Dubbo，一个 SpringBoot的（当然可以注册到同一个注册中心上）。
🛠️创建一个 Dubbo 服务提供者 我们先创建一个 Dubbo 服务提供者，然后把它注册到 Zoookeeper 上。我这边用到是 2.7.10 版本到 Dubbo，不同 Dubbo 版本到配置有所差异化。
pom 依赖：
&amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.apache.dubbo&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;dubbo-spring-boot-starter&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;2.7.10&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.apache.dubbo&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;dubbo-dependencies-zookeeper&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;2.7.10&amp;lt;/version&amp;gt; &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt; &amp;lt;/dependency&amp;gt; 然后我们定义一个接口，返回一些文字，记得加上 @DubboService 注解，让 Dubbo 应用发现这个接口并注册到注册 Zookeeper 上。同时在启动类上面还要加上 @EnableDubbo 注解。当然你也可以用配置到方式来配置这些。
@DubboService public class DemoServiceImpl implements DemoService { @Override public String hello() { return &amp;#34;hello! This is Dubbo&amp;#39;s demo&amp;#34;; } } 定义好接口后，我们在配置文件加上如下配置：</description></item><item><title>快速搭建一个SpringGateway网关</title><link>https://blog.greycode.top/posts/9944cc0febd34a3ebd04eaa1564f4c3a/</link><pubDate>Fri, 07 May 2021 13:05:58 +0000</pubDate><guid>https://blog.greycode.top/posts/9944cc0febd34a3ebd04eaa1564f4c3a/</guid><description>☝️搭建脚手架 我们可以去 Spring initializer 网站或者用 IDEA 来快速创建出一个 Spring Cloud Gateway 项目。
这里我们选择的注册中心是 Zookeeper，你也可以自己选择其他的注册中心来注册你的项目，比如阿里巴巴的 Nacos 等。
配置完相关信息后，点击下面的 GENERATE 按钮就可以导出项目的 zip 压缩包，解压后用 IDE 打开。
打开后就是这个样子：
✌️配置路由 Ymal 方式配置 为了方便配置，我们把 application.properties 改成 application.yml 。
然后配置一个转发到百度到路由。
spring: cloud: gateway: routes: - id: route-demo uri: https://baidu.com predicates: - Path=/** 在配置中，我加来一个谓词 Path ，表示所有当请求都会匹配到这个路由下，然后转发到 uri 配置到网址里。所以当我们打开浏览器访问 [http://localhost:8080/](http://localhost:8080/) 是就会自动跳转到百度到首页。
Java 代码方式配置 除了用配置文件配置路由外，我们还可以用代码的方式来配置路由。
下面来展示一下代码方式配置的路由：
@Bean public RouteLocator routesConfig(RouteLocatorBuilder builder){ return builder.routes() .route(&amp;#34;route-demo&amp;#34;,r -&amp;gt; r.path(&amp;#34;/**&amp;#34;).uri(&amp;#34;https://baidu.com&amp;#34;)) .build(); } 这几行代码实现的是和上面配置一样的功能，当访问 [http://localhost:8080/](http://localhost:8080/) 时也会跳转到百度首页。</description></item><item><title>telnet使用smtp协议发送qq邮件</title><link>https://blog.greycode.top/posts/4dd3868b-e23e-4446-b7ec-fd95e98612f4/</link><pubDate>Tue, 23 Mar 2021 10:36:44 +0000</pubDate><guid>https://blog.greycode.top/posts/4dd3868b-e23e-4446-b7ec-fd95e98612f4/</guid><description>操作步骤 telnet命令调试QQ邮箱的smtp服务器
telnet smtp.qq.com 25 # 响应 Trying 183.3.225.42... Connected to smtp.qq.com. Escape character is &amp;#39;^]&amp;#39;. 220 newxmesmtplogicsvrsza5.qq.com XMail Esmtp QQ Mail Server. 使用EHLO命令，指示ESMTP会话开始。服务器可以在它对 EHLO 的响应中表明自己支持 ESMTP 命令
EHLO smtp.qq.com # 响应 250-newxmesmtplogicsvrsza5.qq.com 250-PIPELINING 250-SIZE 73400320 250-STARTTLS 250-AUTH LOGIN PLAIN 250-AUTH=LOGIN 250-MAILCOMPRESS 250 8BITMIME 使用AUTH关键字进行身份验证，这里使用AUTH LOGIN，然后输入Base64编码的用户名和QQ邮箱授权码
AUTH LOGIN // base64编码的`Username:` $ 334 VXNlcm5hbWU6 // base64编码的`Password:` $ 334 UGFzc3dvcmQ6 # 响应 235 Authentication successful 使用MAIL命令，通过标识邮件的发件人来标识邮件传输开始；以 MAIL FROM 的形式使用。
MAIL FROM:&amp;lt;211019847@qq.com&amp;gt; # 响应 250 OK.</description></item><item><title>【go库】钉钉机器人</title><link>https://blog.greycode.top/posts/47ca1795-f020-44a6-9847-02ef3955f6c9/</link><pubDate>Fri, 19 Mar 2021 14:18:58 +0000</pubDate><guid>https://blog.greycode.top/posts/47ca1795-f020-44a6-9847-02ef3955f6c9/</guid><description>钉钉机器人 go库
github地址：https://github.com/greycodee/dingbot
钉钉官方文档
快速开始 go get github.com/greycodee/dingbot 示例程序：
package main import ( &amp;#34;fmt&amp;#34; &amp;#34;github.com/greycodee/dingbot&amp;#34; &amp;#34;github.com/greycodee/dingbot/message&amp;#34; &amp;#34;time&amp;#34; ) func main() { bot:= dingbot.DingBot{ Secret: &amp;#34;你的加签秘钥&amp;#34;, AccessToken: &amp;#34;你的AccessToken【从钉钉机器人的url上获取】&amp;#34;, } msg := message.Message{ MsgType: message.TextStr, Text: message.Text_{ Content: &amp;#34;go-钉钉机器人测试&amp;#34;, }, } bot.Send(msg) } 消息支持 text类型 link类型 markdown类型 整体跳转ActionCard类型 独立跳转ActionCard类型 FeedCard类型 使用 发送Text消息 func send() { bot:= dingbot.DingBot{ Secret: &amp;#34;你的加签秘钥&amp;#34;, AccessToken: &amp;#34;你的AccessToken【从钉钉机器人的url上获取】&amp;#34;, } msg := message.Message{ MsgType: message.TextStr, Text: message.Text_{ Content: &amp;#34;go-钉钉机器人测试&amp;#34;, At: message.</description></item><item><title>go自定义库上传github下载不了问题</title><link>https://blog.greycode.top/posts/e1273901-26b1-4cfb-a55c-a9f5047855a5/</link><pubDate>Fri, 19 Mar 2021 11:39:50 +0000</pubDate><guid>https://blog.greycode.top/posts/e1273901-26b1-4cfb-a55c-a9f5047855a5/</guid><description>自定义库上传github标签规范 当自己写的库要上传到github时,标签号要符合vX.Y.Z的格式，例如v1.0.0
如果定义其他的标签格式，则go会下载不到，例如v1.0,此时如果用go get命令下载的话，则下载不到此版本</description></item><item><title>gitalk更换自定义代理</title><link>https://blog.greycode.top/posts/33f09b03-a5a7-4d66-93d3-7063905f9b81/</link><pubDate>Thu, 11 Mar 2021 12:10:04 +0000</pubDate><guid>https://blog.greycode.top/posts/33f09b03-a5a7-4d66-93d3-7063905f9b81/</guid><description>首先注册一个https://dash.cloudflare.com/ 的账号
然后创建一个Workers。
然后将代码复制到输入框里：
/* CORS Anywhere as a Cloudflare Worker! (c) 2019 by Zibri (www.zibri.org) email: zibri AT zibri DOT org https://github.com/Zibri/cloudflare-cors-anywhere */ /* whitelist = [ &amp;#34;^http.?://www.zibri.org$&amp;#34;, &amp;#34;zibri.org$&amp;#34;, &amp;#34;test\\..*&amp;#34; ]; // regexp for whitelisted urls */ blacklist = [ ]; // regexp for blacklisted urls whitelist = [ &amp;#34;.*&amp;#34; ]; // regexp for whitelisted origins function isListed(uri,listing) { var ret=false; if (typeof uri == &amp;#34;string&amp;#34;) { listing.forEach((m)=&amp;gt;{ if (uri.</description></item><item><title>Java的NIO编程-Channel</title><link>https://blog.greycode.top/posts/534d0985-a4a0-4239-ae81-d76378f64552/</link><pubDate>Mon, 01 Mar 2021 10:07:09 +0000</pubDate><guid>https://blog.greycode.top/posts/534d0985-a4a0-4239-ae81-d76378f64552/</guid><description>0x1 主要类型 在Java中有许多NIO Channel实现，本文只选最主要的四种Channel：
FileChannel：文件通道，用于文件的数据读写 SocketChannel：套接字通道，用于Socket套接字TCP连接的数据读写。 ServerSocketChannel：服务器嵌套字通道（或服务器监听通道），允许我们监听TCP连接请求，为每个监听到的请求，创建一个SocketChannel套接字通道。 DatagramChannel：数据报通道，用于UDP协议的数据读写。 0x2 使用 FileChannel 读取通道数据 首先在本地创建文件/home/zheng/channeltest，在里面编写内容：hello,world!
public class ChannelTest { public static void main(String[] args) throws IOException { // 创建输入流 File file = new File(&amp;#34;/home/zheng/channeltest&amp;#34;); FileInputStream fis = new FileInputStream(file); // 获取通道 FileChannel fileChannel = fis.getChannel(); // 创建缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int length = -1; // 读取通道数据到缓冲区 while ((length=fileChannel.read(byteBuffer))!=-1){ System.out.println(&amp;#34;缓冲区size：&amp;#34;+length); } fis.close(); fileChannel.close(); // 读取Buffer缓存数据 byteBuffer.flip(); StringBuilder str = new StringBuilder(); while (byteBuffer.</description></item><item><title>Java的NIO编程-Selector</title><link>https://blog.greycode.top/posts/2571330c-67ef-4d4c-8717-6c96768009c7/</link><pubDate>Mon, 01 Mar 2021 10:07:07 +0000</pubDate><guid>https://blog.greycode.top/posts/2571330c-67ef-4d4c-8717-6c96768009c7/</guid><description>0x1 监控 通道和选择器之间的关系，通过register（注册）的方式完成。调用通道的Channel.register（Selector sel, int ops）方法，可以将通道实例注册到一个选择器中。register方法有两个参数：第一个参数，指定通道注册到的选择器实例；第二个参数，指定选择器要监控的IO事件类型。
IO事件类型有：
可读：SelectionKey.OP_READ 可写：SelectionKey.OP_WRITE 连接：SelectionKey.OP_CONNECT 接收：SelectionKey.OP_ACCEPT 如果一下要监控多个事件的话可以用位或运算符来实现
int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE; 0x2 SelectionKey选择键 选择键的功能是很强大的。通过SelectionKey选择键，不仅仅可以获得通道的IO事件类型，比方说SelectionKey.OP_READ；还可以获得发生IO事件所在的通道；另外，也可以获得选出选择键的选择器实例。
常用方法 isAcceptable()：判断IO事件类型是否是SelectionKey.OP_ACCEPT isReadable()：判断IO事件是否是SelectionKey.OP_READ isConnectable():判断IO事件是否是SelectionKey.OP_CONNECT isWritable()：判断IO事件是否是SelectionKey.OP_WRITE 0x3 使用条件 并不是所有的Channel都可以使用Selector，判断一个通道能否被选择器监控或选择，有一个前提：判断它是否继承了抽象类SelectableChannel（可选择通道）。如果继承了SelectableChannel，则可以被选择，否则不能。
简单地说，一条通道若能被选择，必须继承SelectableChannel类。
FileChannel就没有继承SelectableChannel类，所以不能使用Selector
0x4 使用流程 使用选择器，主要有以下三步：
获取选择器实例； 将通道注册到选择器中； 轮询感兴趣的IO就绪事件（选择键集合）。 0x5 Demo 源码地址： https://github.com/GreyCode9/nio-demo/tree/main/src/io/selector</description></item><item><title>Java的NIO编程-Buffer</title><link>https://blog.greycode.top/posts/8d2049e3-3eb1-46ed-a44b-57398964eb21/</link><pubDate>Mon, 01 Mar 2021 10:07:05 +0000</pubDate><guid>https://blog.greycode.top/posts/8d2049e3-3eb1-46ed-a44b-57398964eb21/</guid><description>0x1 子类 Buffer是一个抽象类，所以一般使用他的子类来进行编程，常用的子类有：
ByteBuffer
IntBuffer
LongBuffer
CharBuffer
DoubleBufffer
FloatBuffer
ShortBuffer
MappedByteBuffer
0x2 属性 Buffer中有四个重要的属性，分别是：
capacity：Buffer类的capacity属性，表示内部容量的大小 position：Buffer类的position属性，表示当前的位置 limit：Buffer类的limit属性，表示读写的最大上限。 mark：暂存属性，暂时保存position的值，方便后面的重复使用position值。 0x3 方法 Buffer中几个重要的方法有：
allocate()：创建缓存区（BUffer创建缓存区不是用new，而是用这个方法来创建) put()：向缓冲器插入数据 filp()：翻转模式，将缓冲区改为读模式（缓冲区默认模式为写模式）。其实就改变了limit，position，mark属性的值。 get()：从缓冲区读取数据，从position位置开始读 rewind()：倒带（重复读取），就是将position的位置重置为0 mark()：mark()方法的作用就是将当前position的位置暂存起来，放在mark属性中。 reset()：将position重置为mark属性的位置。 clean()：清空缓存区，重置position，limit，mark属性为初始值</description></item><item><title>Java的NIO编程-Reactor模式</title><link>https://blog.greycode.top/posts/0702ff46-16cd-4520-9d33-0794cfda4b09/</link><pubDate>Mon, 01 Mar 2021 10:07:04 +0000</pubDate><guid>https://blog.greycode.top/posts/0702ff46-16cd-4520-9d33-0794cfda4b09/</guid><description>0x1 Reactor模型 0x1 单Reactor单线程</description></item><item><title>Disruptor-消费模式简介(池化)</title><link>https://blog.greycode.top/posts/c16646bf-1474-42a7-a5cd-84b99669062c/</link><pubDate>Mon, 01 Mar 2021 10:02:15 +0000</pubDate><guid>https://blog.greycode.top/posts/c16646bf-1474-42a7-a5cd-84b99669062c/</guid><description>并行模式(池化) 每个消费端有两个线程实例
disruptor.handleEventsWithWorkerPool(new A1Handler(),new A1Handler()); disruptor.handleEventsWithWorkerPool(new A2Handler(),new A2Handler()); 结果示例 可以看到每次执行的线程是不一样的
++++++++++++++++++++++++++++++++++++++++++++++++ ************************** DisruptorWorker-0 ************************** 1605100167571+A1Handler:10 ************************** DisruptorWorker-2 1605100167572+A2Handler:30 ************************** ++++++++++++++++++++++++++++++++++++++++++++++++ ************************** DisruptorWorker-1 1605100168572+A1Handler:11 ************************** ************************** DisruptorWorker-3 1605100168573+A2Handler:31 ************************** 串行模式（池化） 每个消费端有两个线程实例
disruptor.handleEventsWithWorkerPool(new A1Handler(),new A1Handler()) .then(new A2Handler(),new A2Handler()); 结果示例 ++++++++++++++++++++++++++++++++++++++++++++++++ ************************** DisruptorWorker-0 1605100492248+A1Handler:10 ************************** ************************** DisruptorWorker-2 1605100492249+A2Handler:30 ************************** ++++++++++++++++++++++++++++++++++++++++++++++++ ************************** DisruptorWorker-1 1605100493249+A1Handler:11 ************************** ************************** DisruptorWorker-3 1605100493249+A2Handler:31 **************************</description></item><item><title>Disruptor-缓存行填充</title><link>https://blog.greycode.top/posts/18e3fbd6-ff4b-4a0a-b82f-a547dbef8d0c/</link><pubDate>Mon, 01 Mar 2021 10:02:08 +0000</pubDate><guid>https://blog.greycode.top/posts/18e3fbd6-ff4b-4a0a-b82f-a547dbef8d0c/</guid><description>伪共享概念 CPU架构 常见的CPU架构如下图：
在某个CPU核心上运行一个线程时，他获取数据是先从L1缓存上面找，没有命中数据时，再从L2缓存上面找、还是没有命中时再从L3缓存上找，如果还没有的话就再从主内存里面找。找到后再一层一层的传递数据。
所以查找数据的顺序为：
L1 》L2 》 L3 》主内存
刷新缓存的顺序为：
主内存 》L3 》L2 》L1
缓存存储结构 在计算机缓存中，存储数据是以缓存行为单位的，不同的系统缓存行的大小也不一样，现在常见的64位操作系统，他每行可以存储64字节数据。比如Java中Long类型的数据占8个字节，所以一行可以存8个Long数据类型的数据。
所以当加载缓存行中任意一个数据时，其他在当前缓存行里的数据也会一起加载
线程数据共享 当线程共享一个变量时，每个线程的更改都会把最新数据刷新回主内存，如果处理器发现自己缓存行对应的内存地址呗修改，就会将当前处理器的缓存行设置无效状态，当处理器对这个数据进行修改操作的时候，会重新从系统内存中把数据库读到处理器缓存中（嗅探机制）。
伪共享 上面说的是共享一个缓存行的一个数据，这样是完全没问题的。可是当不同线程要使用一个缓存行里的不同数据时，这样就会出现一种伪共享的情况:
尽管变量a没有被其他线程更改，可以由于他和变量d在同一缓存行里，所以每次都会受变量d的影响,缓存都会被设置为无效状态，所以每次使用时都会从主内存里重新拉取。这样速度就会大大的打折扣。
RingBuffer的解决方法 在RingBuffer解决伪共享的方法就是缓存行填充
abstract class RingBufferPad { protected long p1, p2, p3, p4, p5, p6, p7; }</description></item><item><title>Disruptor-等待策略</title><link>https://blog.greycode.top/posts/ff51336d-70d4-449c-9214-fff2542bef1f/</link><pubDate>Mon, 01 Mar 2021 10:02:06 +0000</pubDate><guid>https://blog.greycode.top/posts/ff51336d-70d4-449c-9214-fff2542bef1f/</guid><description>BlockingWaitStrategy Disruptor默认策略
对EventProcessor使用等待条件的锁和条件变量的阻塞策略。
当吞吐量和低延迟不如CPU资源那么重要时，可以使用此策略。
LiteBlockingWaitStrategy BlockingWaitStrategy的变体，在无竞争的情况下尝试消除条件唤醒。 显示微基准测试的性能改进。
但是，由于我尚未完全证明锁省略码的正确性，因此应将这种等待策略视为实验性的。
BusySpinWaitStrategy 繁忙旋转策略，该繁忙旋转策略对EventProcessor的障碍使用繁忙的旋转循环。
此策略将使用CPU资源来避免可能导致延迟抖动的系统调用。 最好当线程可以绑定到特定的CPU内核时使用。
TimeoutBlockingWaitStrategy LiteTimeoutBlockingWaitStrategy TimeoutBlockingWaitStrategy的变体，在无竞争的情况下尝试消除条件唤醒。
PhasedBackoffWaitStrategy 在屏障上等待EventProcessor的分阶段等待策略。
当吞吐量和低延迟不如CPU资源那么重要时，可以使用此策略。 旋转，然后屈服，然后使用配置的后备WaitStrategy等待。
SleepingWaitStrategy 最初启动的休眠策略，然后使用Thread.yield（），最后在EventProcessor等待屏障时，休眠操作系统和JVM将允许的最小数量的nanos。
此策略是性能和CPU资源之间的良好折衷。 安静时段后可能会出现延迟峰值。 这也将减少对生产线程的影响，因为它不需要发出信号通知任何条件变量来唤醒事件处理线程。
YieldingWaitStrategy 在初始旋转后，使用Thread.yield（）的EventProcessor在屏障上等待。
如果其他线程需要CPU资源，则此策略将使用100％CPU，但比忙碌的自旋策略更容易放弃CPU。</description></item><item><title>Disruptor-消费模式简介(单个实例)</title><link>https://blog.greycode.top/posts/9a1bdb74-8ed3-4905-88bc-7b3b4e0a4af2/</link><pubDate>Mon, 01 Mar 2021 10:01:00 +0000</pubDate><guid>https://blog.greycode.top/posts/9a1bdb74-8ed3-4905-88bc-7b3b4e0a4af2/</guid><description>并行模式 并行模式下两个Handler同时执行，互不影响
disruptor.handleEventsWith(new A1Handler(),new B1Handler()); 结果示例 ++++++++++ 1605084168915+B1Handler:5 1605084168915+A1Handler:1 ++++++++++ 1605084169915+B1Handler:6 1605084169915+A1Handler:2 串行模式 串行模式下，Handler执行必须是从前往后，按顺序执行。
disruptor.handleEventsWith(new A1Handler()).then(new B1Handler()); // or disruptor.handleEventsWith(new B1Handler()).then(new A1Handler()); 结果示例 结果和handler放置的顺序有关，后面的handler要等前面的执行完才会执行
++++++++++ 1605084411462+A1Handler:1 1605084411467+B1Handler:5 ++++++++++ 1605084412463+A1Handler:2 1605084412463+B1Handler:6 // or ++++++++++ 1605084638285+B1Handler:5 1605084638289+A1Handler:1 ++++++++++ 1605084639286+B1Handler:6 1605084639286+A1Handler:2 菱形模式 菱形模式其实就是并行和串行的结合体，先并行执行，再串行执行
disruptor.handleEventsWith(new A1Handler(), new A2Handler()).then(new B1Handler()); 结果示例 B1Handler要等A1Handler和A2Handler全部执行完，它才会执行。
++++++++++ 1605085280283+A1Handler:1 1605085280283+A2Handler:3 1605085280287+B1Handler:5 ++++++++++ 1605085281283+A1Handler:2 1605085281283+A2Handler:4 1605085281283+B1Handler:6 链式模式 链式模式也是并行和串行的结合，并行模式执行串行模式
disruptor.handleEventsWith(new A1Handler()).then(new A2Handler()); disruptor.handleEventsWith(new B1Handler()).then(new B2Handler()); 结果示例 ++++++++++ 1605085843558+B1Handler:5 1605085843558+A1Handler:1 1605085843563+A2Handler:3 1605085843563+B2Handler:7 ++++++++++ 1605085844558+B1Handler:6 1605085844558+A1Handler:2 1605085844558+B2Handler:8 1605085844559+A2Handler:4 总结 所有的模式都可以根据并行和串行来衍生出各种模式，玩法多种多样。</description></item><item><title>Disruptor-实例化方法</title><link>https://blog.greycode.top/posts/b3025291-ae52-4d26-a70c-66a79bda07d7/</link><pubDate>Mon, 01 Mar 2021 09:58:05 +0000</pubDate><guid>https://blog.greycode.top/posts/b3025291-ae52-4d26-a70c-66a79bda07d7/</guid><description>创建一个事件实体 public class LongEvent{ private Long id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } } 创建一个事件实体工厂 public class LongEventFactory implements EventFactory&amp;lt;LongEvent&amp;gt; { public LongEvent newInstance() { return new LongEvent(); } } 创建两个事件处理类 /** * EventHandler&amp;lt;LongEvent&amp;gt; 是没有池化的实现方式,每个消费者中只有一个示例 * WorkHandler&amp;lt;LongEvent&amp;gt; 是池化的实现方式，每个消费者中可以以类似线程池的方式去执行这个事件 * 实际根据业务场景 实现其中一个接口就可以 */ public class A1Handler implements EventHandler&amp;lt;LongEvent&amp;gt; , WorkHandler&amp;lt;LongEvent&amp;gt; { public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception { long id = longEvent.</description></item><item><title>Disruptor-快速开始</title><link>https://blog.greycode.top/posts/a552f7f0-4cbe-4628-8fcf-02f8b8730b56/</link><pubDate>Mon, 01 Mar 2021 09:58:03 +0000</pubDate><guid>https://blog.greycode.top/posts/a552f7f0-4cbe-4628-8fcf-02f8b8730b56/</guid><description>创建一个事件实体 public class LongEvent{ private Long id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } } 创建一个事件实体工厂 public class LongEventFactory implements EventFactory&amp;lt;LongEvent&amp;gt; { public LongEvent newInstance() { return new LongEvent(); } } 创建两个事件处理类 /** * EventHandler&amp;lt;LongEvent&amp;gt; 是没有池化的实现方式,每个消费者中只有一个示例 * WorkHandler&amp;lt;LongEvent&amp;gt; 是池化的实现方式，每个消费者中可以以类似线程池的方式去执行这个事件 * 实际根据业务场景 实现其中一个接口就可以 */ public class A1Handler implements EventHandler&amp;lt;LongEvent&amp;gt; , WorkHandler&amp;lt;LongEvent&amp;gt; { public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception { long id = longEvent.</description></item><item><title>Disruptor-生产者发布方式</title><link>https://blog.greycode.top/posts/926f33f0-651d-471a-ad0c-b632fcce8c0f/</link><pubDate>Mon, 01 Mar 2021 09:57:58 +0000</pubDate><guid>https://blog.greycode.top/posts/926f33f0-651d-471a-ad0c-b632fcce8c0f/</guid><description>旧版本API发布方式 import com.lmax.disruptor.RingBuffer; public class LongEventProducer{ private final RingBuffer&amp;lt;LongEvent&amp;gt; ringBuffer; public LongEventProducer(RingBuffer&amp;lt;LongEvent&amp;gt; ringBuffer){ this.ringBuffer = ringBuffer; } public void onData(ByteBuffer bb){ long sequence = ringBuffer.next(); // Grab the next sequence try{ LongEvent event = ringBuffer.get(sequence); // Get the entry in the Disruptor // for the sequence event.set(bb.getLong(0)); // Fill with data } finally{ ringBuffer.publish(sequence); } } } 使用 RingBuffer&amp;lt;LongEvent&amp;gt; ringBuffer = disruptor.start(); LongEventProducer producer = new LongEventProducer(ringBuffer); ByteBuffer bb = ByteBuffer.</description></item><item><title>Dubbo的Telnet调试三部曲</title><link>https://blog.greycode.top/posts/dcf9d2d8-9c11-404a-874d-57834303dcac/</link><pubDate>Fri, 05 Feb 2021 14:04:54 +0000</pubDate><guid>https://blog.greycode.top/posts/dcf9d2d8-9c11-404a-874d-57834303dcac/</guid><description>第一步 找到Dubbo服务的IP地址，比如我的Dubbo服务地址是192.168.1.11
第二步 使用命令连接Dubbo服务
telnet 192.168.1.11 20880 第三步 直接调试方法
# 调试TestService类下的get方法 invoke com.example.test.service.TestService.get(1132359) 资料 https://dubbo.apache.org/zh/docs/v2.7/user/references/telnet/</description></item><item><title>Hexo使用UUID生成文章路径</title><link>https://blog.greycode.top/posts/79b5fe12-9c60-4b93-be92-dffb00fa39c7/</link><pubDate>Wed, 27 Jan 2021 17:51:05 +0000</pubDate><guid>https://blog.greycode.top/posts/79b5fe12-9c60-4b93-be92-dffb00fa39c7/</guid><description>教程 编写一个名为hexoN的脚本文件 #!/bin/bash uuid=$(sudo cat /proc/sys/kernel/random/uuid) echo $uuid hexo new $uuid 添加执行权限 chmod +x hexoN 在用户根目录的.zshrc(因为我用的是ohmyzsh，所以是这个文件，一般是.bashrc，也可直接加载系统文件/etc/profile中)追加一条 # 后面地址是存放这个脚本的文件夹路径 根据实际更改 export PATH=$PATH:/home/zheng/software/shell 执行命令是刚才追加的内容生效 source .zshrc 使用 到Hexo博客的根目录执行</description></item><item><title>使用GitHub Actions编译树莓派内核</title><link>https://blog.greycode.top/posts/github-actions-build-pi-kernerl/</link><pubDate>Tue, 26 Jan 2021 15:56:07 +0000</pubDate><guid>https://blog.greycode.top/posts/github-actions-build-pi-kernerl/</guid><description>仓库地址 仓库地址：https://github.com/GreyCode9/make-raspberrypi-kernel
创建秘钥 点击Github右上角头像 -&amp;gt; Settings -&amp;gt; Developer settings -&amp;gt; Personal access tokens -&amp;gt; Generate new token 或者直接点这个链接进入： https://github.com/settings/tokens
创建后保存这个秘钥(秘钥只显示一次)
创建仓库 创建仓库**make-raspberrypi-kernel**
然后点击仓库的Settings -&amp;gt; Secrets -&amp;gt;New repository secret
然后填入刚才生成的秘钥
创建Actions 接着点击Actions ,创建一个Actions，然后填入如下内容
name: Make RaspberryPi Kernel on: push: tags: - &amp;#39;v*&amp;#39; # 当推送的Tag为v开头的，就会触发构建 env: USE_SSH_CONFIG: true # 是否使用ssh连接进行 true:使用 false:不使用 jobs: build: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - name: pull RaspberryPi Kernel linux run: | cd ../ git clone https://github.</description></item><item><title>Git常用命令</title><link>https://blog.greycode.top/posts/git-commend-note/</link><pubDate>Wed, 16 Sep 2020 15:16:56 +0000</pubDate><guid>https://blog.greycode.top/posts/git-commend-note/</guid><description>Git简介 Git 是用于 Linux内核开发的版本控制工具。与常用的版本控制工具 CVS, Subversion 等不同，它采用了分布式版本库的方式，不必服务器端软件支持（wingeddevil注：这得分是用什么样的服务端，使用http协议或者git协议等不太一样。并且在push和pull的时候和服务器端还是有交互的。），使源代码的发布和交流极其方便。 Git 的速度很快，这对于诸如 Linux kernel 这样的大项目来说自然很重要。 Git 最为出色的是它的合并跟踪（merge tracing）能力。
git对于很多人来说,真的是又爱又恨,用的好可以提示开发效率;用不好,解决各种冲突就要累的你半死
git结构 网上有 我就不画了
workspace 相当于就是我们的本地电脑上的文件
Index 缓存区
Repository 本地仓库
Remote 远程仓库(github/gitlab/gitee)
git命令 git官方提供的命令多达几百个,可是我们日常却用不到这么多
所以我就整理了一下日常使用的命令
现在关注微信公招:灰色Code
回复关键字:git
就可以获取思维导图高清图片及导图源地址</description></item><item><title>Fegin和RestTemplate添加全局请求头</title><link>https://blog.greycode.top/posts/fegin-resttemplate-addheard/</link><pubDate>Wed, 16 Sep 2020 15:14:43 +0000</pubDate><guid>https://blog.greycode.top/posts/fegin-resttemplate-addheard/</guid><description>Fegin添加全局请求头 实现RequestInterceptor接口 /** * 实现RequestInterceptor接口的apply方法 */ @Configuration public class FeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Enumeration&amp;lt;String&amp;gt; headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String values = request.getHeader(name); requestTemplate.header(name, values); } } } } 在@FeginClient注释里configuration所填入的类文件中添加上面的拦截器
比如
// configuration指定的类为FeignConfig @FeignClient(name = &amp;#34;${TinyConfigServiceName}&amp;#34;,path=&amp;#34;/config&amp;#34;,configuration = FeignConfig.class) 在FeignConfig类中添加拦截器
@Configuration public class FeignConfig { @Bean public RequestInterceptor requestInterceptor(){ return new FeignRequestInterceptor(); } } RestTemplate添加全局请求头 编写拦截器,实现ClientHttpRequestInterceptor接口的intercept方法</description></item><item><title>浅谈MDC</title><link>https://blog.greycode.top/posts/mdc-test/</link><pubDate>Wed, 16 Sep 2020 15:10:47 +0000</pubDate><guid>https://blog.greycode.top/posts/mdc-test/</guid><description>MDC是什么？ MDC 全拼 Mapped Diagnostic Contexts，是SLF4J类日志系统中实现分布式多线程日志数据传递的重要工具；可利用MDC将一些运行时的上下文数据打印出来。目前只有log4j和logback提供原生的MDC支持；
简单使用 MDC里面提供的都是静态方法，所以可以直接调用
// 设置一个key MDC.put(&amp;#34;name&amp;#34;,&amp;#34;灰色Code&amp;#34;); // 获取一个key的值 MDC.get(&amp;#34;name&amp;#34;); // 删除一个key MDC.remove(&amp;#34;name&amp;#34;); // 清空MDC里的内容 MDC.clear(); // 获取上下文中的map Map&amp;lt;String,String&amp;gt; map = MDC.getCopyOfContextMap(); // 设置MDC的map MDC.setContextMap(map); 源码解析 MDC 通过阅读MDC的源码可以发现，它其实是调用了MDCAdapter的接口来实现的
MDCAdapter MDCAdapter接口有三个实现类，而MDC是调用了LogbackMDCAdapter里的方法(在MDC里有一个静态代码块，实例化了这个对象)
LogbackMDCAdapter 而LogbackMDCAdapter主要是用ThreadLocal在线程上下文中维护一个HashMap来实现的
总结 怎么样,实现原理是不是很简单，就这么短短几行代码，就实现了听起来很高大上的MDC。
所以简单来说，MDC就是利用ThreadLocal在线程中维护了一个HashMap，利用HashMap来存放数据</description></item><item><title>JDKproxy和Cglib初探</title><link>https://blog.greycode.top/posts/jdkproxy-cglib/</link><pubDate>Wed, 16 Sep 2020 15:09:47 +0000</pubDate><guid>https://blog.greycode.top/posts/jdkproxy-cglib/</guid><description>JDKproxy和Cglib初探 简介 在Java中，动态代理机制的出现，使得Java开发人员不用手工编写代理类，只要简单地制定一组接口及委托类对象，便能动态地获得代理类。动态代理在Java中有着广泛的应用，比如Spring AOP，Hibernate数据查询、测试框架的后端mock、RPC，Java注解对象获取等。
JDK原生动态代理(JDKProxy) JDKProxy只能对实现了接口的类生成代理，而不能针对普通类 。JDKProxy原生的反射API进行操作，在生成类上比较高效。
使用 interface TestInterface{ void test(); } class TestClass implements TestInterface{ @Override public void test(){ System.out.println(&amp;#34;JDK动态代理&amp;#34;); } } //主方法 public class JDKProxy { public static void main(String[] args) { TestClass testClass=new TestClass(); ProxyHandle proxyHandle=new ProxyHandle(testClass); //使用接口 TestInterface testClass1= (TestInterface) Proxy.newProxyInstance( testClass.getClass().getClassLoader(), testClass.getClass().getInterfaces(),proxyHandle); testClass1.test(); System.out.println(&amp;#34;代理类名称：&amp;#34;+testClass1.getClass()); } } //代理 class ProxyHandle implements InvocationHandler{ private Object originaObj; public ProxyHandle(Object o){ this.originaObj=o; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.</description></item><item><title>Java包装类缓存机制</title><link>https://blog.greycode.top/posts/java-base-data-pack/</link><pubDate>Wed, 16 Sep 2020 15:08:18 +0000</pubDate><guid>https://blog.greycode.top/posts/java-base-data-pack/</guid><description>面试题 首先,来看一道常见的面试题,下面代码运行后会输出什么?
上面代码运行后,最终会输出false和true;为什么会这样呢?
按道理来说,在Java中==是比较两个对象的地址,上面代码中i3和i4是两个不同的对象,理应也应该返回是false,怎么返回是true呢?让我们慢慢往下看
Integer的缓存机制 让我们来看看他的源代码.
当执行Integer i=128;这个语句时,Java会调用valueOf(int i)方法,然后自动装箱的方式,让其变成Integer i=new Integer(128),具体源码如下:
public static Integer valueOf(int i) { if (i &amp;gt;= IntegerCache.low &amp;amp;&amp;amp; i &amp;lt;= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; //装箱 return new Integer(i); } 从上面的源码中可以看到,在装箱之前会执行一个if语句,这个if语句就是判断传入的值是否在缓存内,如果在缓存内,就直接返回缓存内的值,如果不在缓存内,就装箱,在堆内创建一个新空间来存放.
//Integer包装类缓存源码 private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.</description></item><item><title>JVM运行时栈帧</title><link>https://blog.greycode.top/posts/java-jvm-stack-1/</link><pubDate>Wed, 16 Sep 2020 15:06:15 +0000</pubDate><guid>https://blog.greycode.top/posts/java-jvm-stack-1/</guid><description>在JVM中，每个线程都包含n个栈帧，每一个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。
栈帧的生命周期随着方法的创建而创建，随着方法的结束而销毁，无论方法是正常完成还是异常完成（抛出了在方法内未被捕获的异常）都算方法的结束。
在某条线程执行过程中的某个时间点上，只有目前正在执行的那个方法的栈帧是活动的。这个栈帧称为当前栈帧，这个栈帧对应的方法称为当前方法，定义这个方法的类称为当前类。对局部变量表和操作数栈的各种操作，通常都指的是对当前栈帧的局部变量表和操作数栈所进行的操作。
**注意:**栈帧是线程本地私有的数据，不可能在一个栈帧 之中引用另外一个线程的栈帧
局部变量表 局部变量表（Local Variables Table）是一组变量值的存储空间，用于存放方法参数和方法内部定义的局部变量。
存储方法 局部变量表的容量以变量槽（Variable Slot）为最小单位，一般在虚拟机中，一个Slot占用32位存储空间(这不是固定的，虚拟机可以自行改变每个槽占用空间的大小,但一般都是32位)。
Java虚拟机通过索引定位的方式使用局部变量表，索引值的范围是从0开始至局部变量表最大的变量槽数量。如果访问的是32位数据类型的变量，索引N就代表了使用第N个变量槽，如果访问的是64位数据类型的变量，则说明会同时使用第N和N+1两个变量槽。
eg: 在Java中，long在内存占64位，所以局部变量表用2个slot来存储
对于两个相邻的共同存放一个64位数据的两个变量槽，虚拟机不允许采用任何方式单独访问其中的某一个，《Java虚拟机规范》中明确要求了如果遇到进行这种操作的字节码序列，虚拟机就应该在类加载的校验阶段中抛出异常。
long和double的非原子性协定 在Java内存模型中，对于64位的数据类型（long和double），在模型中特别定义了一条宽松的规定：允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行，即允许虚拟机实现自行选择是否要保证64位数据类型的load、store、read和write这四个操作的原子性，这就是所谓的**“long和double的非原子性协定”（Non-Atomic Treatment of doubleand long Variables）**。
虽然有这个协定，但是，由于局部变量表(Local Variable Table)是建立在线程堆栈中的，属于线程私有的数据，无论读写两个连续的变量槽是否为原子操作，都不会引起数据竞争和线程安全问题。
初始值问题 我们已经知道类的字段变量有两次赋初始值的过程，一次在准备阶段，赋予系统初始值；另外一次在初始化阶段，赋予程序员定义的初始值。
但局部变量就不一样了，如果一个局部变量定义了但没有赋初始值，那它是完全不能使用的。所以不要认为Java中任何情况下都存在诸如整型变量默认为0、布尔型变量默认为false等这样的默认值规则。
eg： // 这个方法会报： // Error:(12, 28) java: variable y might not have been initialized public class JVMTest { public static void main(String[] args) { int y; int z=3; System.out.println(y+z); } } // 这个会正常输出 3； 因为int的初始值为0 public class JVMTest { private static int y; public static void main(String[] args) { int z=3; System.</description></item><item><title>JVM类加载过程</title><link>https://blog.greycode.top/posts/java-class-load-2/</link><pubDate>Wed, 16 Sep 2020 15:04:22 +0000</pubDate><guid>https://blog.greycode.top/posts/java-class-load-2/</guid><description>加载 通过一个类的全限定名(例如：java.lang.String)来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成一个代表这个类的java.lang.Class对象，作为方法区这个类的各种数据的访问入口。
对于数组类而言，情况就有所不同，数组类本身不通过类加载器创建，它是由Java虚拟机直接在内存中动态构造出来的。
从ZIP压缩包中读取，这很常见，最终成为日后JAR、EAR、WAR格式的基础。
从网络中获取，这种场景最典型的应用就是Web Applet。
运行时计算生成，这种场景使用得最多的就是动态代理技术，在java.lang.reflect.Proxy中，就是用了ProxyGenerator.generateProxyClass()来为特定接口生成形式为“*$Proxy”的代理类的二进制字节流。
由其他文件生成，典型场景是JSP应用，由JSP文件生成对应的Class文件。
从数据库中读取，这种场景相对少见些，例如有些中间件服务器（如SAP Netweaver）可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
可以从加密文件中获取，这是典型的防Class文件被反编译的保护措施，通过加载时解密Class文件来保障程序运行逻辑不被窥探。
验证 文件格式验证 是否以魔数0xCAFEBABE开头。 主、次版本号是否在当前Java虚拟机接受范围之内 常量池的常量中是否有不被支持的常量类型（检查常量tag标志）。 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。 CONSTANT_Utf8_info型的常量中是否有不符合UTF-8编码的数据。 ·Class文件中各个部分及文件本身是否有被删除的或附加的其他信息 &amp;hellip;&amp;hellip; 元数据验证 这个类是否有父类（除了java.lang.Object之外，所有的类都应当有父类）。 这个类的父类是否继承了不允许被继承的类（被final修饰的类）。 如果这个类不是抽象类，是否实现了其父类或接口之中要求实现的所有方法。 类中的字段、方法是否与父类产生矛盾（例如覆盖了父类的final字段，或者出现不符合规则的方法重载，例如方法参数都一致，但返回值类型却不同等）。 &amp;hellip;&amp;hellip; 字节码验证 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作，例如不会出现类似于“在操作栈放置了一个int类型的数据，使用时却按long类型来加载入本地变量表中”这样的情况。 保证任何跳转指令都不会跳转到方法体以外的字节码指令上。 保证方法体中的类型转换总是有效的，例如可以把一个子类对象赋值给父类数据类型，这是安全的，但是把父类对象赋值给子类数据类型，甚至把对象赋值给与它毫无继承关系、完全不相干的一个数据类型，则是危险和不合法的。 &amp;hellip;&amp;hellip; 符号引用验证 符号引用中通过字符串描述的全限定名是否能找到对应的类 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段。 符号引用中的类、字段、方法的可访问性（private、protected、public、）是否可被当前类访问。 &amp;hellip;&amp;hellip; 准备 准备阶段是正式为类中定义的变量（即静态变量，被static修饰的变量）分配内存并设置类变量初始值的阶段
例子：
// 变量value在准备阶段过后的初始值为0而不是123 // 因为这时尚未开始执行任何Java方法 value赋值为123的动作要到类的初始化阶段才会被执行 public static int value = 123 解析 解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程
符号引用（Symbolic References）：符号引用以一组符号来描述所引用的目标，符号可以是任何形式的字面量，只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关，引用的目标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布局可以各不相同，但是它们能接受的符号引用必须都是一致的，因为符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式中。
下面红框中的都属于符号引用
直接引用（Direct References）：直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的，同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用，那引用的目标必定已经在虚拟机的内存中存在。
初始化 参考：jvm类初始化</description></item><item><title>JVM中的双亲委派机制</title><link>https://blog.greycode.top/posts/java-class-load-1/</link><pubDate>Wed, 16 Sep 2020 15:02:34 +0000</pubDate><guid>https://blog.greycode.top/posts/java-class-load-1/</guid><description>四种类加载器 启动类加载器(Bootstrap Class Loader )：加载$JAVA_HOME/jre/lib目录下的jar包 拓展类加载器(Extension Class Loader)：加载$JAVA_HOME/jre/lib/ext目录下的jar包 应用程序类加载器(Application Class Loader)：加载ClassPath目录下的jar包 自定义类加载器(User Class Loader)：加载自定义指定目录下的jar包 双亲委派机制 如果一个类加载器收到了类加载的请求，它首先不会自己去尝试加载这个类，而是把这个请求委派给父类加载器去完成，每一个层次的类加载器都是如此，因此所有的加载请求最终都应该传送到最顶层的启动类加载器中，只有当父加载器反馈自己无法完成这个加载请求（它的搜索范围中没有找到所需的类）时，子加载器才会尝试自己去完成加载。
代码示例 当获取Bootstrap class loader的时候，输出了null，说明开发者无法通过引用操作启动类加载器
双亲委派机制的作用 每个加载器都只需要固定的加载自己管理范围内的类，这样的好处就是保证了Java体系的稳定，不然的话你自己定义一个String类的话，这样系统中就会有两个String类，如果没有双亲委派机制的话，系统就不知道到底该加载哪一个，这样系统就变得一片混乱了。
破坏双亲委派机制 双亲委派机制是Java设计者推荐给开发者们的类加载实现方式，并不是一个强制性约束的模型，所以也可以人为的破坏这个机制。
源码 源码在java.lang.ClassLoader有兴趣的可以去看下
可以看到，就这短短的几行代码，就实现了听起来很高大上的双亲委派机制，所以破坏双亲委派机制的话，就直接重写loadClass方法就可以了。</description></item><item><title>Java类初始化</title><link>https://blog.greycode.top/posts/java-class-init/</link><pubDate>Wed, 16 Sep 2020 15:01:03 +0000</pubDate><guid>https://blog.greycode.top/posts/java-class-init/</guid><description>代码结果？ 首先，我们来看看下面的代码的输出的结果，可以先试着想一下
//结果 Code 公众号 这时候有同学就会想，以前不是说类加载时，静态代码块都会加载的嘛！怎么Test1里的静态代码块没有加载呢？下面就来看看到底怎么回事
类的生命周期 了解类加载前，首先熟悉一下类的生命周期
这里注意几个点：
解析阶段可以在初始化阶段之后，这是为了支持Java语言的运行时绑定特性（也称为动态绑定或晚期绑定） 这些阶段通常都是互相交叉地混合进行的，会在一个阶段执行的过程中调用、激活另一个阶段。 初始化和实例化 我相信很多人跟我刚开始一样，搞不清他们两个的区别，搞不清new一个对象，到底是对这个对象进行了初始化还是实例化呢？
初始化：是完成程序执行前的准备工作。在这个阶段，静态的（变量，方法，代码块）会被执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次。
实例化：是指创建一个对象的过程。这个过程中会在堆中开辟内存，将一些非静态的方法，变量存放在里面。在程序执行的过程中，可以创建多个对象，既多次实例化。每次实例化都会开辟一块新的内存。
类的初始化 《Java虚拟机规范》中并没有对加载进行强制约束，这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段，《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”（而加载、验证、准备自然需要在此之前开始）：
遇到new、getstatic、putstatic或invokestatic这四条字节码指令时，如果类型没有进行过初始化，则需要先触发其初始化阶段。那到底什么时候能够生成这些指令呢？其实看下字节码就都明白了
使用java.lang.reflect包的方法对类型进行反射调用的时候，如果类型没有进行过初始化，则需要先触发其初始化。
当初始化类的时候，如果发现其父类还没有进行过初始化，则需要先触发其父类的初始化。
当虚拟机启动时，用户需要指定一个要执行的主类（包含main()方法的那个类），虚拟机会先初始化这个主类。
当使用JDK 7新加入的动态语言支持时，如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄，并且这个方法句柄对应的类没有进行过初始化，则需要先触发其初始化。
当一个接口中定义了JDK 8新加入的默认方法（被default关键字修饰的接口方法）时，如果有这个接口的实现类发生了初始化，那该接口要在其之前被初始化。
java.lang.invoke.MethodHandle 是JDK7中新加入类似反射功能的一个类
被动引用 对于以上这六种会触发类型进行初始化的场景，《Java虚拟机规范》中使用了一个非常强烈的限定语——“有且只有”，这六种场景中的行为称为对一个类型进行主动引用。除此之外，所有引用类型的方式都不会触发初始化，称为被动引用。
像文章一开始的代码，就属于被动引用，对于静态字段，只有直接定义这个字段的类才会被初始化，因此通过其子类来引用父类中定义的静态字段，只会触发父类的初始化而不会触发子类的初始化。
例子1&amp;ndash;对象数组 直接上图
以上代码执行后并不会输出灰色两个字，因为创建对象数组时并没有去初始化Test1这个类，而是用anewarray字节码指令去初始化了另外一个类，它是一个由虚拟机自动生成的、直接继承于java.lang.Object的子类。
拓展：数组越界检查没有封装在数组元素的访问类中，而是封装在数组访问的xaload,xastore字节码指令中
例子2&amp;ndash;final修饰的静态字段 被final修饰的静态字段 此时运行该代码时，只会输出灰色Code字样，Test1并没有触发初始化阶段。这是因为在编译阶段通过常量传播优化，已经将此常量的值灰色Code直接存储在ClassLoadTest类的常量池中，所以当ClassLoadTest类调用Test1里的value时，都变成了对自身常量池的调用，和Test1类没有任何关系。
没有final修饰的静态字段
没有使用final修饰的静态变量，字节码出现了getstatic，所以触发Test1的初始化阶段，此时运行结果将会输出灰色和灰色Code</description></item><item><title>创建线程的3种方式</title><link>https://blog.greycode.top/posts/create-thread-3/</link><pubDate>Mon, 22 Jun 2020 16:29:38 +0000</pubDate><guid>https://blog.greycode.top/posts/create-thread-3/</guid><description>Java线程状态变迁图 构造一个线程 在线程执行start()方法之前,首先需要初始化(NEW)一个线程,初始化的时候,可以设置线程名称,线程所属的线程组、线程优先级、是否是Daemon线程等信息。
Thread常见参数及设置方法:
//线程是否是守护线程 默认false private boolean daemon = false; //设置方法 Thread thread=new Thread(); thread.setDaemon(true); //线程名字 默认&amp;#34;Thread-&amp;#34; + nextThreadNum() private volatile String name; //设置方法 Thread thread=new Thread(); thread.setName(&amp;#34;myThread&amp;#34;); //不能设置为null,会报异常 //线程优先级 是否起作用和操作系统及虚拟机版本相关 private int priority; //设置方法 范围:1-10 默认5 myThread.setPriority(1); Thread源码构造方法 在Thread源码中,一共提供了9种构造方法.
从这些构造方法中,大致可以分为有Runnable构造参数的,和无Runnable构造参数两大类,无Runnable构造参数的就需要去继承Thread来重写run()方法(注:Thread也实现了Runnable接口),有Runnable构造参数的,就实现Runnable接口的run方法,然后通过构造参数,把实现Runnable接口的实例传入Thread.
无返回值的线程 可以看到,通过集成Thread类和实现Runnable接口的run()方法返回值都是void.这类是没有返回值的
方法一:继承Thread类创建一个线程 //继承Thread类,重写run方法 class MyThread extends Thread{ @Override public void run() { System.out.println(&amp;#34;继承Thread,重写run方法&amp;#34;); } } public class ThreadTest{ public static void main(String[] args){ MyThread myThread=new MyThread(); myThread.start(); } } 方法二:实现Runnable接口创建线程 //实现Runnable接口的run方法,然后以构造参数的形式设置Thread的target class MyRun implements Runnable{ @Override public void run() { System.</description></item><item><title>吐血整理Git常用命令</title><link>https://blog.greycode.top/posts/git-tool-command/</link><pubDate>Mon, 22 Jun 2020 16:27:25 +0000</pubDate><guid>https://blog.greycode.top/posts/git-tool-command/</guid><description>Git常用命令 Git简介 Git 是用于 Linux内核开发的版本控制工具。与常用的版本控制工具 CVS, Subversion 等不同，它采用了分布式版本库的方式，不必服务器端软件支持（wingeddevil注：这得分是用什么样的服务端，使用http协议或者git协议等不太一样。并且在push和pull的时候和服务器端还是有交互的。），使源代码的发布和交流极其方便。 Git 的速度很快，这对于诸如 Linux kernel 这样的大项目来说自然很重要。 Git 最为出色的是它的合并跟踪（merge tracing）能力。
git对于很多人来说,真的是又爱又恨,用的好可以提示开发效率;用不好,解决各种冲突就要累的你半死
git结构 网上有 我就不画了
workspace 相当于就是我们的本地电脑上的文件
Index 缓存区
Repository 本地仓库
Remote 远程仓库(github/gitlab/gitee)
git命令 git官方提供的命令多达几百个,可是我们日常却用不到这么多
所以我就整理了一下日常使用的命令
现在关注微信公招:灰色Code
回复关键字:git
就可以获取思维导图高清图片及导图源地址</description></item><item><title>创建一个自定义注解</title><link>https://blog.greycode.top/posts/apring-aop-ann/</link><pubDate>Mon, 22 Jun 2020 16:23:56 +0000</pubDate><guid>https://blog.greycode.top/posts/apring-aop-ann/</guid><description>前言 平时在用springBoot的使用，常常会用到@Service，@Compent等等注解，简化了我们的开发流程，提升了开发效率.那如何自己来写一个注解呢？下面就来介绍一下。
写一个注解 创建一个注解主要分两部分，一部分是创建注解类，一部分是创建一个切面类。
创建注解类 @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnn { String value() default &amp;#34;d&amp;#34;; } 创建注解类的关键字就是@interface，这个注解类设置了一个value变量，默认值为d；
在注解类上面还有@Target和@Retention注解，下面来说说创建注解类时需要用到的几个注解：
@Target 用来标记这个注解可以用于哪些地方，与ElementType枚举类搭配使用，那这个枚举类里面有什么内容呢？
public enum ElementType { /** 类，接口（包括注释类型）或枚举声明*/ TYPE, /** 字段声明（包括枚举常量）*/ FIELD, /** 方法声明*/ METHOD, /** 形式参数（形参-调用方法时传入的参数）声明 */ PARAMETER, /** 构造函数声明 */ CONSTRUCTOR, /** 局部变量声明 */ LOCAL_VARIABLE, /** 注释类型声明 */ ANNOTATION_TYPE, /** 包声明 */ PACKAGE, /** * 类型参数声明 * java8新特性： * @since 1.8 */ TYPE_PARAMETER, /** * 任何类型声明 * java8新特性： * @since 1.</description></item><item><title>JVM4种垃圾收集算法</title><link>https://blog.greycode.top/posts/jvm-gc-alg/</link><pubDate>Fri, 29 May 2020 10:31:30 +0000</pubDate><guid>https://blog.greycode.top/posts/jvm-gc-alg/</guid><description>简介 垃圾收集算法可以划分为“引用计数式垃圾收集”（Reference Counting GC）和“追踪式垃圾收集”（Tracing GC）两大类，这两类也常被称作“直接垃圾收集”和“间接垃圾收集”。
标记-清除算法 标记过程就是对象是否属于垃圾的判定过程(采用可达分析算法GC Roots) 算法分为**“标记”和“清除”**两个阶段：首先标记出所有需要回收的对象，在标记完成后，统一回收掉所有被标记的对象，也可以反过来，标记存活的对象，统一回收所有未被标记的对象。 缺点 执行效率不稳定，如果Java堆中包含大量对象，而且其中大部分是需要被回收的，这时必须进行大量标记和清除的动作，导致标记和清除两个过程的执行效率都随对象数量增长而降低； 第二个是内存空间的碎片化问题，标记、清除之后会产生大量不连续的内存碎片，空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 标记-复制算法 标记过程就是对象是否属于垃圾的判定过程(采用可达分析算法GC Roots)
它将可用内存按容量划分为大小相等的两块，每次只使用其中的一块。
当这一块的内存用完了，就将还存活着的对象复制到另外一块上面，然后再把已使用过的内存空间一次清理掉。
缺点 如果内存中多数对象都是存活的，这种算法将会产生大量的内存间复制的开销 代价是将可用内存缩小为了原来的一半,空间浪费未免太多了一点. 标记-整理算法 标记过程就是对象是否属于垃圾的判定过程(采用可达分析算法GC Roots) 在标记-清除的算法基础上改进,后续步骤不是直接对可回收对象进行清理，而是让所有存活的对象都向内存空间一端移动，然后直接清理掉边界以外的内存， 缺点 在有大量存活对象的老年代区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行,比标记-清除算法停顿时间长. 分代收集算法 现代商用虚拟机基于以上算法的优缺点,根据分代收集理论,在不同的区域采用了不同的收集算法.
老年代:新生代=2:1
新生代 堆大小默认比例:Eden:S0:S1=8:1:1
采用标记-复制算法
新生代分为Eden区和Survior区,而Survior区又分为From Survior区(S0)和To Survior区(S1).此区域采用标记-复制算法.每次Minor GC/Young GC时,会把Eden区存活的对象复制到S0区,然后清空Eden区,当S0区满时,Eden区和S0区存活的对象会复制到S1区,然后S0和S0进行交换,永远保持S1为空状态,当新生代的对象经过一定次数的Minor GC还未被回收时,就会把这个对象移到老年代.
老年代 采用标记-整理法或标记-清理法
当老年代Old区域满时,会触发Full GC,同时回收新生代和老生代的所有区域.回收后诺内存还是不足时,会引发OOM异常;</description></item><item><title>Java四种引用方法使用和对比</title><link>https://blog.greycode.top/posts/jvm-object-four-quote/</link><pubDate>Fri, 29 May 2020 10:22:07 +0000</pubDate><guid>https://blog.greycode.top/posts/jvm-object-four-quote/</guid><description>
强引用（Strongly Reference） 无论任何情况下，只要强引用关系还存在，垃圾收集器就永远不会回收掉被引用的对象。
回收时机:强引用关系不存在时
Object obj=new Object(); 软引用（Soft Reference） 软引用是用来描述一些还有用，但非必须的对象。只被软引用关联着的对象，在系统将要发生内存溢出异常前，会把这些对象列进回收范围之中进行第二次回收，如果这次回收还没有足够的内存，才会抛出内存溢出异常。
回收时机:发送内存溢出异常前
//软引用 SoftReference&amp;lt;Object&amp;gt; srf = new SoftReference&amp;lt;Object&amp;gt;(new Object()); //or Object obj=new Object(); SoftReference&amp;lt;Object&amp;gt; srf = new SoftReference&amp;lt;Object&amp;gt;(obj); obj=null; //这种方法一定要设置obj为null,否则这个对象除了软引用可达外,还有原来强引用也可达 弱引用（Weak Reference） 弱引用也是用来描述那些非必须对象，但是它的强度比软引用更弱一些，被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作，无论当前内存是否足够，都会回收掉只被弱引用关联的对象。
回收时机:下一次垃圾回收时
//弱引用 WeakReference&amp;lt;Object&amp;gt; wrf = new WeakReference&amp;lt;Object&amp;gt;(new Object()); //or Object obj=new Object(); WeakReference&amp;lt;Object&amp;gt; wrf = new WeakReference&amp;lt;Object&amp;gt;(new Object()); obj=null; 虚引用（Phantom Reference） 虚引用也称为“幽灵引用”或者“幻影引用”，它是最弱的一种引用关系。一个对象是否有虚引用的存在，完全不会对其生存时间构成影响，也无法通过虚引用来取得一个对象实例。
回收时机:随时
//虚引用 PhantomReference&amp;lt;Object&amp;gt; prf = new PhantomReference&amp;lt;Object&amp;gt;(new Object(), new ReferenceQueue&amp;lt;&amp;gt;()); //or Object obj=new Object(); PhantomReference&amp;lt;Object&amp;gt; prf = new PhantomReference&amp;lt;Object&amp;gt;(obj, new ReferenceQueue&amp;lt;&amp;gt;()); obj=null;</description></item><item><title>JVM判断对象是否还活着的两种方法</title><link>https://blog.greycode.top/posts/jvm-object-is-alive/</link><pubDate>Fri, 29 May 2020 10:18:30 +0000</pubDate><guid>https://blog.greycode.top/posts/jvm-object-is-alive/</guid><description>引用计数法 Java虚拟机并不是通过引用计数算法来判断对象是否存活的。
在对象中添加一个引用计数器，每当有一个地方引用它时，计数器值就加一；当引用失效时，计数器值就减一；任何时刻计数器为零的对象就是不可能再被使用的。
优点 原理简单,判定效率高 缺点 不能用于复杂的环境中,比如对象的互相引用问题 可达性分析算法 Java虚拟机使用此算法来判断对象是否存活
这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集，从这些节点开始，根据引用关系向下搜索，搜索过程所走过的路径称为“引用链”（Reference Chain），如果某个对象到GCRoots间没有任何引用链相连，或者用图论的话来说就是从GC Roots到这个对象不可达时，则证明此对象是不可能再被使用的。
Java中作为GC Roots的对象:
在虚拟机栈（栈帧中的本地变量表）中引用的对象，譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
在方法区中类静态属性引用的对象，譬如Java类的引用类型静态变量。
在方法区中常量引用的对象，譬如字符串常量池（String Table）里的引用。
在本地方法栈中JNI（即通常所说的Native方法）引用的对象。
Java虚拟机内部的引用，如基本数据类型对应的Class对象，一些常驻的异常对象（比如NullPointExcepiton、OutOfMemoryError）等，还有系统类加载器。
所有被同步锁（synchronized关键字）持有的对象。
反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
其他对象临时性地加入,共同构成GC Roots</description></item><item><title>JVM运行时数据区域</title><link>https://blog.greycode.top/posts/jvm-running-data-area/</link><pubDate>Tue, 26 May 2020 16:35:47 +0000</pubDate><guid>https://blog.greycode.top/posts/jvm-running-data-area/</guid><description>JVM运行时数据区域 程序计数器 线程私有
唯一一个没有规定 OutOfMemoryError 异常 的区域
它可以看作是当前线程所执行的字节码的行号指示器
如果线程正在执行的是一个Java方法，这个计数器记录的是正在执行的虚拟机字节码指令的地址；如果正在执行的是本地（Native）方法，这个计数器值则应为空（Undefined）
(摘自网上)我们想象下，CPU是怎么知道记住之前A线程，执行到哪一处的？
答案是，CPU根本就不会记住之前执行到哪里了，它只是埋头苦干；那是什么保证了切换线程的程序可以正常执行的；答案是 ： 程序计数器 ；程序计数器里面保存的是 当前线程执行的字节码的行号（看着像行号，其实是指令地址）；
那么，我们需要几个程序计数器呢？如果，我们只有一个的话，切换B线程以后，程序计数器里面保存的就是B线程所执行的字节码的行号了，再切换回A线程，就蒙圈了，不知道执行到哪里了，因为，程序计数器里面保存的是B线程当前执行的字节码地址 ；因此，我们可以想象出，要为每个线程都分配一个程序计数器，因此，程序计数器的内存空间是线程私有的 ；这样即使线程 A 被挂起，但是线程 A 里面的程序计数器，记住了A线程当前执行到的字节码的指令地址了 ，等再次切回到A线程的时候，看一下程序计数器，就知道之前执行到哪里了！
那么程序计数器，什么时候分配内存呢？我们试想下，一个线程在执行的任何期间，都会失去CPU执行权，因此，我们要从一个线程被创建开始执行，就要无时无刻的记录着该线程当前执行到哪里了！因此，线程计数器，必须是线程被创建开始执行的时候，就要一同被创建；
程序计数器，保存的是当前执行的字节码的偏移地址（也就是之前说的行号，其实那不是行号，是指令的偏移地址，只是为了好理解，才说是行号的，），当执行到下一条指令的时候，改变的只是程序计数器中保存的地址，并不需要申请新的内存来保存新的指令地址；因此，永远都不可能内存溢出的；因此，jvm虚拟机规范，也就没有规定，也是唯一一个没有规定 OutOfMemoryError 异常 的区域;
当线程执行的是本地方法的时候，程序计数器中保存的值是空（undefined）；原因很简单：本地方法是C++/C 写的，由系统调用，根本不会产生字节码文件，因此，程序计数器也就不会做任何记录
Java虚拟机栈 线程私有 如果线程请求的栈深度大于虚拟机所允许的深度，将抛出StackOverflowError异常； 如果Java虚拟机栈容量可以动态扩展，当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常；(HotSpot虚拟机的栈容量是不可以动态扩展的，以前的Classic虚拟机倒是可以。所以在HotSpot虚拟机上是不会由于虚拟机栈无法扩展而导致OutOfMemoryError异常——只要线程申请栈空间成功了就不会有OOM，但是如果申请时就失败，仍然是会出现OOM异常的) -Xss5m: 设置5m的栈容量 每个方法执行都会创建一个栈帧，栈帧包含局部变量表、操作数栈、动态连接、方法出口等 本地方法栈 线程私有
与Java虚拟机栈相似
与Java虚拟机栈区别: Java虚拟机栈为虚拟机执行Java方法（也就是字节码）服务，而本地方法栈则是为虚拟机使用到的本地（Native）方法服务。
Hot-Spot虚拟机直接就把本地方法栈和虚拟机栈合二为一
与虚拟机栈一样，本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常
Java堆 线程共享 所有的Java对象实例不一定都在Java堆上分配内存 Java堆既可以被实现成固定大小的，也可以是可扩展的，不过当前主流的Java虚拟机都是按照可扩展来实现的（通过参数-Xmx和-Xms设定）。 如果在Java堆中没有内存完成实例分配，并且堆也无法再扩展时，Java虚拟机将会抛出OutOfMemoryError异常。 Java堆是垃圾收集器(Garbage Collected)管理的内存区域 方法区 线程共享
用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分，但是它却有一个别名叫作**“非堆”（Non-Heap），目的是与Java堆区分开来**。
在JDK1.6及之前,使用永久代来实现方法区.
-XX:MaxPermSize 设置永久代内存上限 -XX:PermSize 设置永久代内存 JDK1.7把字符串常量池、类的静态变量(class statics)转移到了java heap,但是永久代还是存在,主要放一些类信息(运算时常量池)等.
JDK1.8彻底移除永久代,方法区采用本地内存中实现的元空间（Meta-space）来代替,将JDK1.7中永久代的信息移到了元空间,像字符串常量池和静态变量还是存在Java Heap中
如果方法区无法满足新的内存分配需求时，将抛出OutOfMemoryError异常。</description></item><item><title>JVM逃逸分析技术</title><link>https://blog.greycode.top/posts/jvm-javastack-escapeanalysis/</link><pubDate>Tue, 26 May 2020 16:02:00 +0000</pubDate><guid>https://blog.greycode.top/posts/jvm-javastack-escapeanalysis/</guid><description>逃逸分析技术的日渐成熟,促使所有的Java对象实例不一定都在Java堆上分配内存
简单来讲就是，Java Hotspot 虚拟机可以分析新创建对象的使用范围，并决定是否在 Java 堆上分配内存的一项技术。
使用 开启逃逸分析：-XX:+DoEscapeAnalysis 关闭逃逸分析：-XX:-DoEscapeAnalysis 显示分析结果：-XX:+PrintEscapeAnalysis 逃逸分析技术在 Java SE 6u23+ 开始支持,并默认设置为启用状态 逃逸程度 逸分析的基本行为就是分析对象动态作用域,从不逃逸、方法逃逸到线程逃逸，称为对象由低到高的不同逃逸程度。
方法逃逸 当一个对象在方法中被定义后，它可能被外部方法所引用，例如作为调用参数传递到其他地方中，称为方法逃逸。
/*StringBuffer sb是一个方法内部变量，上述代码中直接将sb返回，这样这个StringBuffer有可能被其他方法所 *改变，这样它的作用域就不只是在方法内部，虽然它是一个局部变量，称其逃逸到了方法外部。甚至还有可能被外部线 *程访问到，譬如赋值给类变量或可以在其他线程中访问的实例变量，称为线程逃逸。 */ public static StringBuffer craeteStringBuffer(String s1, String s2) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb; } //上述代码如果想要StringBuffer sb不逃出方法，可以这样写： public static String createStringBuffer(String s1, String s2) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb.toString(); } 线程逃逸 当一个对象在方法中被定义后，它可能被外部线程访问到，譬如赋值给可以在其他线程中访问的实例变量，这种称为线程逃逸。 逃逸分析优化 如果能证明一个对象不会逃逸到方法或线程之外（换句话说是别的方法或线程无法通过任何途径访问到这个对象），或者逃逸程度比较低（只逃逸出方法而不会逃逸出线程），则可能为这个对象实例采取不同程度的优化
栈上分配（Stack Allocations） 如果确定一个对象不会逃逸出线程之外，那让这个对象在栈上分配内存将会是一个很不错的主意，对象所占用的内存空间就可以随栈帧出栈而销毁。 由于复杂度等原因，HotSpot中目前暂时还没有做这项优化，但一些其他的虚拟机（如Excelsior JET）使用了这项优化。 栈上分配可以支持方法逃逸，但不能支持线程逃逸。 标量替换（Scalar Replacement） 若一个数据已经无法再分解成更小的数据来表示了，Java虚拟机中的原始数据类型（int、long等数值类型及reference类型等）都不能再进一步分解了，那么这些数据就可以被称为标量。相对的，如果一个数据可以继续分解，那它就被称为聚合量（Aggregate），Java中的对象就是典型的聚合量。 -XX:+EliminateAllocations 开启标量替换(jdk8默认开启) -XX:+PrintEliminateAllocations 查看标量的替换情况 如果把一个Java对象拆散，根据程序访问的情况，将其用到的成员变量恢复为原始类型来访问，这个过程就称为标量替换 假如逃逸分析能够证明一个对象不会被方法外部访问，并且这个对象可以被拆散，那么程序真正执行的时候将可能不去创建这个对象，而改为直接创建它的若干个被这个方法使用的成员变量来代替。 标量替换可以视作栈上分配的一种特例，实现更简单（不用考虑整个对象完整结构的分配），但对逃逸程度的要求更高，它不允许对象逃逸出方法范围内。 同步消除（Synchronization Elimination） 也叫锁消除</description></item><item><title>基于SpringCloud搭建Spring-security-oauth认证服务器</title><link>https://blog.greycode.top/posts/spring-security-oauth-server-demo/</link><pubDate>Tue, 19 May 2020 20:25:06 +0000</pubDate><guid>https://blog.greycode.top/posts/spring-security-oauth-server-demo/</guid><description>准备阶段 这里搭建一个用OAuth2.0密码模式认证的服务器，token存入redis，client存入Mysql；
所以事先要准备好：
Redis Mysql 并且Mysql执行Spring-security-oauth初始化Sql这个SQL，初始化Spring-security-oauth所需要的表。然后执行
-- 插入client_id和client_secret都为sunline的客户端 insert into oauth_client_details (client_id, client_secret, authorized_grant_types , autoapprove) values (&amp;#34;sunline&amp;#34;,&amp;#34; {bcrypt}$2a$10$G1CFd535SiyOtvi6ckbZWexQy.hW5x/I/fLBPiW/E4UmctCfKYbgG&amp;#34;,&amp;#34;password&amp;#34;,&amp;#34;true&amp;#34;); client_secret为new BCryptPasswordEncoder().encode(&amp;quot;sunline&amp;quot;)方法加密后，然后在加上{bcrypt}
开始搭建 导入pom依赖 &amp;lt;!--security-oauth--&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-cloud-starter-oauth2&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;!--redis--&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-data-redis&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;!--mysql--&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;mysql&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;mysql-connector-java&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;8.0.17&amp;lt;/version&amp;gt; &amp;lt;scope&amp;gt;compile&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt; 配置application.properties #datasource spring.datasource.url=jdbc:mysql://localhost:3307/grey_code?useUnicode=true&amp;amp;characterEncoding=UTF-8&amp;amp;autoReconnect=true&amp;amp;serverTimezone=Asia/Shanghai spring.datasource.username=zmh spring.datasource.password=zmh #redis spring.redis.host=127.0.0.1 spring.redis.port=6379 server.port=9991 server.servlet.context-path=/oauthServer 创建用户详情服务类 创建权限控制类 创建认证授权类 获取令牌 访问:/oauth/token就可以获取到令牌
{ &amp;#34;accessToken&amp;#34;: &amp;#34;e28f9a99-e60d-4693-b6c3-73e06a1d14f5ZMH10086&amp;#34;, &amp;#34;expiration&amp;#34;: &amp;#34;2020-05-19T21:11:39.883+0000&amp;#34;, &amp;#34;scope&amp;#34;: [ &amp;#34;all&amp;#34; ], &amp;#34;tokenType&amp;#34;: &amp;#34;bearer&amp;#34; } 访问资源 带上获取到的令牌</description></item><item><title>SSO单点登录和CAS框架</title><link>https://blog.greycode.top/posts/sso-and-cas/</link><pubDate>Thu, 14 May 2020 19:27:14 +0000</pubDate><guid>https://blog.greycode.top/posts/sso-and-cas/</guid><description>SSO单点登录 单点登录（英语：Single sign-on，缩写为 SSO），又译为单一签入，一种对于许多相互关连，但是又是各自独立的软件系统，提供访问控制的属性。当拥有这项属性时，当用户登录时，就可以获取所有系统的访问权限，不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议（LDAP）来实现，在服务器上会将用户信息存储到LDAP数据库中。相同的，单一退出（single sign-off）就是指，只需要单一的退出动作，就可以结束对于多个系统的访问权限。
优点 使用单点登录的好处包括：
降低访问第三方网站的风险（不存储用户密码，或在外部管理）。 减少因不同的用户名和密码组合而带来的密码疲劳。 减少为相同的身份重新输入密码所花费的时间。 因减少与密码相关的调用IT服务台的次数而降低IT成本。[1] SSO为所有其它应用程序和系统，以集中的验证服务器提供身份验证，并结合技术以确保用户不必频繁输入密码。
CAS框架 CAS 协议基于在客户端Web浏览器、Web应用和CAS服务器之间的票据验证。当客户端访问访问应用程序，请求身份验证时，应用程序重定向到CAS。CAS验证客户端是否被授权，通常通过在数据库对用户名和密码进行检查。如果身份验证成功，CAS一次性在客户端以Cookie形式发放TGT票据，在其有效期CAS将一直信任用户，同时将客户端自动返回到应用程序，并向应用传递身份验证票（Service ticket）。然后，应用程序通过安全连接连接CAS，并提供自己的服务标识和验证票。之后CAS给出了关于特定用户是否已成功通过身份验证的应用程序授信信息。
历史 CAS是由耶鲁大学[1]的Shawn Bayern创始的，后来由耶鲁大学的Drew Mazurek维护。CAS1.0实现了单点登录。 CAS2.0引入了多级代理认证（Multi-tier proxy authentication）。CAS其他几个版本已经有了新的功能。
2004年12月，CAS成为Jasig[2]的一个项目，2008年该组织负责CAS的维护和发展。CAS原名“耶鲁大学CAS”，此后被称为“Jasig CAS”。
2005年5月，CAS协议版本2发布，引入代理和服务验证。
2006年12月，安德鲁·W·梅隆基金会授予耶鲁大学第一届梅隆技术协作奖，颁发50000美元的奖金对耶鲁大学开发CAS进行奖励。[3]颁奖之时，CAS在“数以百计的大学校园”中使用。
2012年12月，JASIG与Sakai基金合并，CAS改名为Apereo CAS。
2016年11月，基于Spring Boot的CAS软件版本5发布。</description></item><item><title>OAuth2.0与JWT</title><link>https://blog.greycode.top/posts/oauth-and-jwt/</link><pubDate>Tue, 12 May 2020 14:59:43 +0000</pubDate><guid>https://blog.greycode.top/posts/oauth-and-jwt/</guid><description>OAuth2.0 OAuth2.0是一个授权协议，它允许软件应用代表资源拥有者去访问资源拥有者的资源。应用向资源拥有者请求令牌，并用这个令牌来访问资源拥有者的资源。
角色 客户端：相当于访问受保护资源的软件 授权服务器：授予客户端令牌的服务 资源拥有者：受保护的资源拥有者，有权决定将不将令牌授权给客户端 受保护的资源：除资源拥有者外，要访问此资源必须要有授权服务器颁发的有效的令牌 授权类型 授权码许可类型 隐式许可类型 客户端凭证许可类型 资源拥有者凭证许可类型(账号密码模式) 断言许可类型 JWT JWT全称：JSON Web Token，是一种令牌格式。其格式类似为xxxxx.yyyyy.zzzzz,分为三部分，每个部分都用Base64进行编码，之间用.分隔。
第一部分：为Header部分，标头通常由两部分组成：令牌的类型（即JWT）和所使用的签名算法，例如HMAC SHA256或RSA。
{ &amp;#34;alg&amp;#34;: &amp;#34;HS256&amp;#34;, &amp;#34;typ&amp;#34;: &amp;#34;JWT&amp;#34; } 第二部分：令牌的第二部分是有效负载，其中包含声明。 声明是有关实体（通常是用户）和其他数据的声明。 共有三种类型的声明：注册的，公共的和私有的三种声明。当然里面可以存放任何有效的字段信息（私有声明）。但是为了避免不同实现之间不兼容，可以准守JWT官方提供的声明字段。
注册声明：JWT官方提供的声明，参考资料:https://tools.ietf.org/html/rfc7519#section-4.1 公共声明：用户发邮件给JWT官方进行注册的声明字段，参考资料：https://tools.ietf.org/html/rfc7519#section-4.2 私有声明：完全用户自定义，参考资料https://tools.ietf.org/html/rfc7519#section-4.3 第三部分：为令牌签名部分，使用这个字段后，资源服务器只会接受签名正确的令牌。</description></item><item><title>fastDFS安装使用教程</title><link>https://blog.greycode.top/posts/linux-fastdfs-install/</link><pubDate>Thu, 07 May 2020 13:34:10 +0000</pubDate><guid>https://blog.greycode.top/posts/linux-fastdfs-install/</guid><description>FastDFS简介 FastDFS 是一个开源的高性能分布式文件系统（DFS）。 它的主要功能包括：文件存储，文件同步和文件访问，以及高容量和负载平衡。主要解决了海量数据存储问题，特别适合以中小文件（建议范围：4KB &amp;lt; file_size &amp;lt;500MB）为载体的在线服务。
FastDFS 系统有三个角色：跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端(Client)。
Tracker Server：跟踪服务器，主要做调度工作，起到均衡的作用；负责管理所有的 storage server和 group，每个 storage 在启动后会连接 Tracker，告知自己所属 group 等信息，并保持周期性心跳。
Storage Server：存储服务器，主要提供容量和备份服务；以 group 为单位，每个 group 内可以有多台 storage server，数据互为备份。
Client：客户端，上传下载数据的服务器，也就是我们自己的项目所部署在的服务器。
结构图
上传文件流程
安装环境 系统及软件版本 Git开源地址 Centos 7 # libfastcommon V1.0.43 https://github.com/happyfish100/fastdfs fastdfs V6.06 https://github.com/happyfish100/libfastcommon 我虚拟机装的Centos7的ip地址是172.16.54.137
安装前工作 关闭防火墙 为了方便，先关闭防火墙。线上环境安装可安装后开放对应端口。
service firewalld stop 下载所需安装包 libfastcommon wget https://github.com/happyfish100/libfastcommon/archive/V1.0.43.tar.gz -O libfastcommon.tar.gz fastDFS wget https://github.com/happyfish100/fastdfs/archive/V6.06.tar.gz -O fastdfs.tar.gz 安装fastDFS环境 解压安装libfastcommon tar -zxvf libfastcommon.tar.gz &amp;amp;&amp;amp; cd libfastcommon-1.0.43/ &amp;amp;&amp;amp; .</description></item><item><title>插入emoji到mysql时提示了一个表里不存在的字段的错误</title><link>https://blog.greycode.top/posts/mysql-utf8mb4-error/</link><pubDate>Wed, 29 Apr 2020 17:16:27 +0000</pubDate><guid>https://blog.greycode.top/posts/mysql-utf8mb4-error/</guid><description>1.问题描述 由于公司前端有需求，需要在tiny_user_info表的nickname这个字段里存入emoji表情，于是我熟练地将这个字段修改为utf8mb4，改好后测试插入一条带emoji数据。于是报了这个错误：
[2020-04-29 15:57:25] [HY000][1366] Incorrect string value: &amp;#39;\xF0\x9F\x98\x98&amp;#39; for column &amp;#39;user_name&amp;#39; at row 14 当时我就傻了，我这个表里也没有user_name这个字段啊，怎么会报这个字段错误,我明明修改的是nickname这个字段啊。于是google和百度搜了一圈，无解。
２.解决方案 试了好几种方法，删字段，重新建。删表，重新建。都不行。。。。。静下心来，于是打算从mysql服务器入手。进入到mysql对应库的文件夹，发现tiny_user_info这个表有三个文件
和常见的多了一个TRG文件。这是一个触发器文件，打开一看，发现了user_name字段。。。。。。
原来是同事在这个表里加了个触发器，当tiny_user_info里新增数据时，会触发新增到另一张表里，nickname的值同时会插入到另一张表的user_name字段，而他那张表的字段没有设置utf8mb4编码,所以导致插入失败。于是叫同事把他那张表设置一下utf8mb4编码后，就可以正常插入了。</description></item><item><title>【数据结构】手写平衡二叉树（AVL）</title><link>https://blog.greycode.top/posts/algorithm-avltree-01/</link><pubDate>Sat, 01 Feb 2020 15:56:00 +0000</pubDate><guid>https://blog.greycode.top/posts/algorithm-avltree-01/</guid><description>【数据结构】手写平衡二叉树（AVL） 积千里跬步，汇万里江河。每天进步一点点，终有一天将成大佬
本文源代码：手写AVL树
什么是平衡二叉树？ 平衡二叉树，又称为AVL树，当树不是空树时，它的左右两个子树的高度差的绝对值不超过1，并且左右两个子树都是一棵平衡二叉树。AVL树查找的时间复杂度为O(logN)。
平衡二叉树基本特点 左右子树深度差不能大于1 左边子树永远比根节点小 右边子树永远比根节点大 平衡二叉树基本结构及操作 左左结构——右旋 右右结构——左旋 左右结构——左子先左旋，然后整体右旋 右左结构——右子先右旋，然后整体左旋 代码实现 先创建一个内部类Node，来表示树的每个节点
public class AVLTree { private Node rootNode; //二叉树节点 private class Node{ public Node parent; //父 public Node left; //左子树 public Node right; //右子树 @NotNull public int data; //存放的数据 private int depth; //深度 private int balance; //平衡因子 //有参构造方法 public Node(int data){ this.data=data; this.depth=1; this.balance=0; } } } 插入数据 暴露一个方法给外部调用
/**添加数据方法*/ public void add(int data){ if (this.</description></item><item><title>【转】免费可商用，最值得收藏的10个插画素材网站</title><link>https://blog.greycode.top/posts/recommend-tool-1/</link><pubDate>Thu, 16 Jan 2020 09:24:52 +0000</pubDate><guid>https://blog.greycode.top/posts/recommend-tool-1/</guid><description>转自凯凯刘
现在插画风格的界面越来越多，网上提供的免费插图也越来越丰富。这些插图优点是表达的内容更丰富，包括：人物、商业、运动、自然、工作、幽默等等。也更适合产品类的宣传网站或者落地页。另外，矢量图的不失真在不同尺寸的显示效果上更胜一筹。花时间整理了当前全网那些优秀的10个免费插图网站，给做产品的人们节省点查找的时间，建议收藏以备后用。这些站点的素材都是免费下载可用的，而且可以免费商用。
IRA Design 网站：https://iradesign.io/ 介绍：可以将元素进行组合形成自己喜欢的图片，有png和svg格式
Absurd Design 网站：https://absurd.design/ 介绍：有些荒诞风格的矢量图，适用网站的落地页、APP等
Ouch! 网站：https://icons8.com/ouch 介绍：很多的免费图，各种分类
unDraw 网站：https://undraw.co/ 内容：开源的矢量图库，各种你能想到的基本都有
Pngtree 网站：https://pngtree.com/ 介绍：上百万的素材资源可下载
Drawkit 网站：https://www.drawkit.io/ 介绍：有免费的下载资源集合
Humaaans 网站：https://www.humaaans.com 内容：关于人物的插画图片站
Manypixels 网站：https://www.manypixels.co/gallery/ 介绍：建筑、人物、科技、天气、运动，商业等类型的插画
Lukaszadam 网站：https://lukaszadam.com/illustrations 介绍：一些有趣的小图标的插画
Pixabay 网站：https://pixabay.com/illustrations/search/ 介绍：收集了很多免费的插图素材，根据关键字可任意搜索</description></item><item><title>【源码解析】你真的了解ArrayDeque嘛？</title><link>https://blog.greycode.top/posts/java-arraydeque-source-1/</link><pubDate>Wed, 08 Jan 2020 14:00:51 +0000</pubDate><guid>https://blog.greycode.top/posts/java-arraydeque-source-1/</guid><description/></item><item><title>【源码解析】想了解LinkedList？看这篇文章就对了</title><link>https://blog.greycode.top/posts/java-linkedlist-source-1/</link><pubDate>Sun, 05 Jan 2020 00:21:43 +0000</pubDate><guid>https://blog.greycode.top/posts/java-linkedlist-source-1/</guid><description/></item><item><title>【源码解析】扒开ArrayList的外衣</title><link>https://blog.greycode.top/posts/java-arraylist-source-1/</link><pubDate>Fri, 03 Jan 2020 19:13:31 +0000</pubDate><guid>https://blog.greycode.top/posts/java-arraylist-source-1/</guid><description>积千里跬步，汇万里江河；每天进步一点点，终有一天将成大佬。
本文内容 当然ArrayList里的方法不止这些，本文主要讲一些常用的方法
方法变量 Arraylist里的方法变量主要有以下几个
构造方法 有参构造 传入数组的大小 代码实现 List&amp;lt;String&amp;gt; list=new ArrayList&amp;lt;&amp;gt;(5); 源码解析 传入一个list对象 其实这个就相当于把传入的list对象里的数据复制到新的ArrayList对象
代码实现 List&amp;lt;String&amp;gt; list=new ArrayList&amp;lt;&amp;gt;(Arrays.asList(&amp;#34;z&amp;#34;,&amp;#34;m&amp;#34;,&amp;#34;h&amp;#34;)); 这里用来Arrays工具类里的asList方法，它的源码里是直接返回一个List，有兴趣的可以去看看，这里就不介绍了
源码解析 无参构造 这个比较简单，直接赋值一个空数组
代码实现 List&amp;lt;String&amp;gt; list=new ArrayList&amp;lt;&amp;gt;(); 源码解析 add方法 add一般常用的有两个方法，一个就是add(E e)在尾部添加数据，一个就是add(int index,E element)在指定位置插入元素
add(E e) 这个是Arrayist的主要方法，平时用的也是最多的方法之一，所以源码比较复杂，比较长
代码实现 List&amp;lt;String&amp;gt; list=new ArrayList&amp;lt;&amp;gt;(); list.add(&amp;#34;灰灰HK&amp;#34;); 源码解析 ensureCapacityInternal(int minCapacity)确保数组容量充足 calculateCapacity(Object[] elementData, int minCapacity) 再回到ensureExplicitCapacity(int minCapacity)这个方法，这个方法先修改次数加1，然后判断size+1是不是比当前的数组容量大，如果比当前的数组容量大，则进行扩容操作，扩大容量为原数组的1.5倍 比如第二次调用add方法，此时size+1=2, elementData.length=10,为什么等于10呢？因为第一次默认把数组容量从0扩大到了10,这时size+1比elementData.length小，就不会进行扩容操作
grow(int minCapacity)扩容 这里调用Arrays.copyOf()方法进行复制操作，当进一步深入这个方法时，发现是由System.arraycopy()这个方法实现复制功能的，这个方法由native关键字修饰，表示不是由Java语言实现的，一般是c/cpp实现
小结 到这里，add的方法流程就走完了，其核心步骤：
每次添加元素时判断数组容量是否充足
第一次添加元素，把数组容量扩容到10
扩容时，除第一次，以后的每次扩容为原大小的1.5倍
扩容后调用System.arraycopy()方法把原数组的元素复制到扩容后的新数组
add(int index, E element) 该方法为在指定位置插入元素，该位置及后面所有元素后移
代码实现 List&amp;lt;String&amp;gt; list=new ArrayList&amp;lt;&amp;gt;(); list.</description></item><item><title>【图】用图片告诉你Java中的位运算</title><link>https://blog.greycode.top/posts/java-base-wei/</link><pubDate>Mon, 30 Dec 2019 22:17:30 +0000</pubDate><guid>https://blog.greycode.top/posts/java-base-wei/</guid><description>前言 ​ 虽然位运算在实际开发中并不常用,但是在各种算法中却常常见到它们的身影.因为是直接操作二进制的,所以机器执行起来就快很多,所以尽管实际业务中不常用,但如果你不想只做个码农,这个基础还是要掌握的;
讲位操作之前,就必须要知道原码、反码、补码
其中正数的原码=反码=补码
原码、反码、补码 在机器的内存中,一个负数的表示是这个负数的绝对值取原码,再取反码,再加一,最后出现的就是这个负数在内存中的表示的二进制数值
比如说-9在内存中的二进制码,这里用8位表示:
最后-9在内存中的二进制值为11110111
在二进制中,最高位为符号位,0代表正,1代表负
位运算 左移和右移 在Java中的int类型有4字节,一个字节有8位,所以这边用32位表示一个数
负数的左移和右移 这边负数表示是在内存中表示的二进制值
右移时:最高位补符号位1
左移时:末尾补0
正数的左移和右移 右移时:最高位补符号位0
左移时:末尾补0
无符号右移 无论是正数还是负数,右移最高位一律补0
&amp;amp;(位与) 当相对应的位都为1时,等于1,否则等于0
为了方便表示,接下来全部都用8位表示一个数
|(位或) 当相对应的位有一个为1时,等于1,否则等于0
^(异或) 当相对应的位不同时,等于1,相同时等于0
~(取反) 1等于0,0等于1
总结 含义 运算符 说明 左移 &amp;laquo; 末尾补0 右移 &amp;gt;&amp;gt; 负数:最高位补符号位1 正数:最高位补符号位0 无符号右移 &amp;gt;&amp;raquo; 无论是正数还是负数,右移最高位一律补0 &amp;amp;(位与) &amp;amp; 当相对应的位都为1时,等于1,否则等于0 |(位或) | 当相对应的位有一个为1时,等于1,否则等于0 ^(异或) ^ 当相对应的位 不同时,等于1 相同时,等于0 ~(取反) ~ 1等于0,0等于1 最后有个小技巧,向左位移几位就是乘以2的几次方,比如9向左移n位,就是
$$ 9向左移n位=9*2^n $$
向右移几位就是除以2的几次方然后向下取整,比如9向右移动n位,就是
$$ 9向右移n位=⌊9/2^n⌋ $$
注:⌊⌋是数学符号向下取整,例如:2.25向下取整是2; -2.25向下取整是-3; 具体的话可以看看这篇文章向上取整与向下取整函数;该技巧不适用无符号右移</description></item><item><title>设计模式之建造者模式【用好玩的故事讲清楚设计模式】</title><link>https://blog.greycode.top/posts/gof-builder/</link><pubDate>Fri, 27 Dec 2019 06:50:31 +0000</pubDate><guid>https://blog.greycode.top/posts/gof-builder/</guid><description>积千里跬步,汇万里江河;每天进步一点点,终有一天将成大佬
所有源代码都在这:https://github.com/z573419235/GofDemo
各位大佬记得点个星星哦
前言 建造者模式用于实例化一个比较复杂的实体类,当你实例化一个类时,它的构造参数比较多时,就可以用建造者模式来简化实例化过程;前几篇工厂模式的文章我们说道买车,那只是简单的区工厂买车,我们不关系工厂是怎么造出来的.可是实际工厂造一辆车需要有方向盘、发动机、车架、轮胎等部件,而且不同品牌的车的部件都是不同的,部件虽然不同,但是造车的方式基本都是差不多的步骤,这时候就可以用建造者模式来造一辆车了;
建造者（Builder）模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成
土豪朋友开车厂 土豪朋友上次买了车之后,发现造车卖还挺赚钱,于是决定涉足汽车领域,真是很有商业头脑啊,不愧是我的玉树临风,疯言疯语,语速惊人,人模狗样的土豪朋友啊. 一天,前去向他讨教汽车的知识,他给我讲了汽车的大致构成:
/** * 汽车 产品类 定义汽车的构成 * */ @Data public class Car { /** * 方向盘 * */ private String steering; /** * 发动机 * */ private String engine; /** * 车架 * */ private String frame; /** * 轮胎 * */ private String tire; /** * 展示一下汽车配置 * */ public String show() { return &amp;#34;{&amp;#34; + &amp;#34;steering=&amp;#39;&amp;#34; + steering + &amp;#39;\&amp;#39;&amp;#39; + &amp;#34;, engine=&amp;#39;&amp;#34; + engine + &amp;#39;\&amp;#39;&amp;#39; + &amp;#34;, frame=&amp;#39;&amp;#34; + frame + &amp;#39;\&amp;#39;&amp;#39; + &amp;#34;, tire=&amp;#39;&amp;#34; + tire + &amp;#39;\&amp;#39;&amp;#39; + &amp;#39;}&amp;#39;; } } 果真是大致啊,忽悠我不懂车是吧,就给我讲4个东西,这谁不知道啊,哼!</description></item><item><title>一个故事一个模式-原型模式</title><link>https://blog.greycode.top/posts/gof-prototype/</link><pubDate>Wed, 25 Dec 2019 23:34:48 +0000</pubDate><guid>https://blog.greycode.top/posts/gof-prototype/</guid><description>积千里跬步,汇万里江河;每天进步一点点,终有一天将成大佬
所有源代码都在这:https://github.com/z573419235/GofDemo
各位大佬记得点个星星哦
前言 前几天生病了,每天头昏脑胀的,诶,生病的时候才知道身体健康的重要性,以后还是要加强锻炼,身体是革命的本钱; 隔了差不多有五六天没写日志了,罪过罪过;好了,今天要说的是原型模式,原型模式在`Java`中核心秘密就是`clone`这个方法,通过重新`Object`中的`clone`方法.来达到原型模式;而要重新`clone`方法就必须要实现`Cloneable`这个接口,不实现这个接口的话就会报`java.lang.CloneNotSupportedException`异常; 我是鸣人 鸣人最喜欢的就是吃拉面,就算是上课的时候也是心心念念的想着一乐大叔的拉面 先来看看鸣人的原型实体类:
/** * @author zheng * * 我是鸣人实体类 */ @Data public class Naruto implements Cloneable{ /** * 姓名 * */ private String name=&amp;#34;鸣人&amp;#34;; /** * 年龄 * */ private int age=13; /** * 任务 * */ private String task; /** *爱好 * */ private ArrayList&amp;lt;String&amp;gt; hobby=new ArrayList&amp;lt;&amp;gt;(); /** * 构造方法 * */ public Naruto(){ this.hobby.add(&amp;#34;吃拉面&amp;#34;); this.hobby.add(&amp;#34;泡温泉&amp;#34;); } /** * 重写Object类的clone方法 * */ @Override public Naruto clone(){ Naruto naruto=null; try { naruto=(Naruto)super.</description></item><item><title>更新驱动到mysql-connector-java-8遇到的一些问题</title><link>https://blog.greycode.top/posts/mysql-update-driver-connector-8/</link><pubDate>Wed, 25 Dec 2019 17:07:12 +0000</pubDate><guid>https://blog.greycode.top/posts/mysql-update-driver-connector-8/</guid><description>更新驱动到mysql-connector-java-8遇到的一些问题 问题 POM &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;mysql&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;mysql-connector-java&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;8.0.16&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt; application.properties spring.datasource.driver-class-name=com.mysql.jdbc.Driver 项目是SpringBoot构建的,数据库版本是:MySQL5.7,用了mysql-connector-java-8来链接数据库,application.properties也配置成spring.datasource.driver-class-name=com.mysql.jdbc.Driver,中间遇到了几个问题;
问题一 描述 如上配置后,控制台报了一下错误:
Loading class `com.mysql.jdbc.Driver&amp;#39;. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver&amp;#39;. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary. 翻译过来后就是:
加载类 com.mysql.jdbc.Driver。 不推荐使用。 新的驱动程序类为 com.mysql.cj.jdbc.Driver。 通过SPI自动注册驱动程序，通常不需要手动加载驱动程序类。
解决 根据提示,解决方法有两种:
更改application.properties文件 spring.datasource.driver-class-name=com.mysql.jdbc.Driver //改成下面这样 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 去掉application.properties文件中的spring.datasource.driver-class-name,因为它说会通过SPI自动注册的; 问题二 描述 数据库的数据时间总是和实际时间差8个小时
解决 在数据库url添加serverTimezone=GMT%2B8
spring.datasource.url=jdbc:mysql://10.25.0.01:3307/db?useUnicode=true&amp;amp;autoReconnect=true&amp;amp;characterEncoding=UTF-8&amp;amp;serverTimezone=GMT%2B8 总结 mysql-connector-java5.X的版本驱动名是:com.mysql.jdbc.Driver; 6.X及以上版本的驱动名是:com.mysql.cj.jdbc.Driver</description></item><item><title>Docker迁移根目录导致mysql权限问题</title><link>https://blog.greycode.top/posts/docker-transfer-pit/</link><pubDate>Fri, 20 Dec 2019 15:04:16 +0000</pubDate><guid>https://blog.greycode.top/posts/docker-transfer-pit/</guid><description>问题描述 最近由于公司服务器硬盘老是爆满，导致经常要清硬盘空间．最后发现/var/lib/docker目录占了25G,以前分kvm分区的时候，他们分了两个区：根目录＂/＂,和＂/home＂目录，发现home目录使用几乎为零，于是准备迁移Docker的根目录：
迁移根目录我看的是这个文章：docker的存储目录迁移,　不过迁移的时候我没有使用rsync这个命令，而是使用cp -R;
文件复制过去后，按照教程，重新启动docker服务，可是其中mysql容器跑不起来了，报mysqld: Can&amp;rsquo;t create/write to file &amp;lsquo;/tmp/ibTCv7Rw&amp;rsquo; (Errcode: 13 - Permission denied)
期间按照网上的方法：说docker容器启动是添加&amp;ndash;privileged=true,设置/tmp目录的权限，关闭selinux，这些方法都没用！！！！！！
其中设置/tmp文件权限这个方法，我把里面的/tmp文件挂载出来后，设置了权限，报这个的问题是解决了，可是又出现了新的问题，又报Version: &amp;lsquo;5.7.27&amp;rsquo; socket: &amp;lsquo;/var/run/mysqld/mysqld.sock&amp;rsquo;
看来还是得从根源上解决问题啊！
我的解决办法 我想，既然是权限问题，那肯定是复制文件的时候权限丢失了，于是查了下cp命令保持权限的命令（cp -p）: 于是我又重新关闭的docker服务，然后删除了所有复制到home文件的目录，重新用cp -p -R /var/lib/docker /home/docker/lib/来重新复制了文件；
复制后，重启docker服务，启动docker容器，ok,一切正常；用docker info查看，看到已成功转移到/home下．</description></item><item><title>设计模式系列-模板方法模式</title><link>https://blog.greycode.top/posts/gof-taemplate-method/</link><pubDate>Fri, 20 Dec 2019 00:04:28 +0000</pubDate><guid>https://blog.greycode.top/posts/gof-taemplate-method/</guid><description>积千里跬步，汇万里江河．每天进步一点点，终有一天将成大佬
文前常规发言 模板方法的设计符合迪米特法则，也就是最少知道原则，他通过对一些重复方法的封装，减少类之间的耦合，让调用者也更省心，原来要调两三个方法才能实现的功能，现在调一个就可以了；就像我们伟大的祖国，现在也在推行这种模式呢．以前区办一些证明什么的，要跑三四个地方，还要保证这三四个地方都正常帮你办理，如果其中一个地方没办理，那么整个流程就都作废了．现在好了，提倡最多跑一次，只要去一个地方办一次手续就可以了，你只要知道这个地方能办好就行，其他的就不用烦心了；
阿狗卖电脑 阿狗是一个三十五岁没了头发的年轻小伙，当问及为什么没了头发，阿狗摸摸头，眼里充满了悔恨的泪水；要不是小时候没听大人的话，长大了也不至于做程序员啊－－－阿狗唉声叹气的说道．听到这里，我仿佛已经知道了答案．当我问他为什么现在改行卖电脑了，他说外面的世界很大，想趁年轻，多闯闯（实则是被公司裁员，被迫来卖电脑了）；
看看他的电脑店里都有什么
/** * 阿狗电脑店 * */ abstract class AGouShop { /** *显卡 * */ abstract void xianKa(); /** *cpu * */ abstract void cpu(); /** *电源 * */ abstract void dianYuan(); /** *主板 * */ abstract void zhuBan(); /** *硬盘 * */ abstract void yingPan(); /** *内存条 * */ abstract void neiCun(); /** *机箱 * */ abstract void jiXiang(); } 还不错，该有的都有了．当我们正在店里逛着时，来了两个顾客，阿猫和大牛，他们都来到阿狗店电脑店，挑选的电脑配件，准备组装电脑．
看看阿猫：
在看看大牛的：
再看看他们怎么组装的吧：</description></item><item><title>恍然大悟，数组和链表的区别</title><link>https://blog.greycode.top/posts/array-vs-linked/</link><pubDate>Wed, 18 Dec 2019 13:50:52 +0000</pubDate><guid>https://blog.greycode.top/posts/array-vs-linked/</guid><description>
积千里跬步，汇万里江河．每天进步一点点，终有一天将成大佬
文前发言 在Java中，很多地方都使用了数组和链表，还有两种组合的叫数组链表结构，就是常说的哈希表，HashMap底层的数据结构就是哈希表．远了，远了，这里不讲HashMap,这里讲数组和链表；
数组 数组是我们平时用的最多的数据结构，它的特点是查询数据快，插入数据慢，查询的时间复杂度是O(1),插入的时间复杂度是O(n).
牛＊一族去学校读书，学校有四人寝和五人寝，大牛，二牛，三牛，四牛一同住进了四人寝里，每天都五缺一；有一天，他们在游戏里认识了小牛，得知小牛也是他们学校的，于是邀请小牛和他们一起住，可是他们们寝室只能住四个人，这个怎么办呢？于是他们向学校(系统)申请，要求学校给他们一个新的六人寝(新的内存空间)，于是学校就给了他们新的六人寝，于是他们全部都搬去了六人寝里，小牛也办了进去，之后每天五黑，好不快活；
之后有其他学生看到牛＊他们的做法，于是也通通向学校申请；最后学校发现了一个问题：就是学生们为了住进新寝室，花费了大量的时间在从旧寝室到新寝室的路上(插入数据慢)
有的人会说，那一开始就安排大牛，二牛，三牛，四牛住５人寝不就好了吗？这样他们就不用搬了(这就相当于我们初始化数组时，给数组指定了一个大小)；这样的想法是好的，但是如果他们没有没有认识小牛，小牛也不会搬进去，这样他们四个人就一直住着５人寝，就造成了空间资源浪费；
有一天，老师去找进入新寝室的小牛谈话，一看得知小牛在４号床，一下就找到了小牛（查询数据快），问他在这个寝室住的习不习惯，小牛心想，每天都五黑，你说我习不习惯！！
链表 链表我们平时用的比较少，它的特点是:插入数据快，查询数据慢，查询的时间复杂度是：O(n)，插入的时间复杂度是：O(1)，它的特点是和数组相反的；
经过无数日夜的奋战，牛＊一寝人觉得是时候该出去玩玩了，自从小牛搬过来后，就一直没日没夜的五黑，都快不知道外面的世界长什么样子了；他们一行人准备去游乐园转转．
来到游乐园后，一群人像刚放出来的一样，对一切都充满了新鲜感，到处转悠．就在转悠的时候，细心的大牛发现了地上有一张纸条，打开一看，上面写着：＂少年，你渴望力量吗？想获得力量就来海盗船找我！＂，大牛赶紧找来其他小伙伴，一同前往；到了海盗船的地方，发现船上写着：＂力量源自摩天轮，请前往摩天轮＂，于是一群人就又前往摩天轮，在那里，终于过得了神秘力量－－－毒鸡汤：你的内心有多强大，你的力量就有多强大；小牛他们为了寻找这个力量，可谓费尽九牛二虎之力啊（查询数据慢）；
可以发现，每个元素存着下个元素的地址，所以如果要查找其中某个元素，就必须要从头开始，才能找到．这就比较慢了．但是，他们添加元素很快,元素可以随机出现在游乐园的某个地方，只要在新添加元素的前一个元素指明新元素的地址在哪里就可以了；
发个对比表格吧 时间复杂度对比表 数组 链表 插入 O(n) 慢 O(1) 快 删除 O(n) 慢 O(1) 快 查询 O(1) 快 O(n) 慢</description></item><item><title>设计模式系列-抽象工厂模式</title><link>https://blog.greycode.top/posts/gof-abstract-factory/</link><pubDate>Mon, 16 Dec 2019 21:17:23 +0000</pubDate><guid>https://blog.greycode.top/posts/gof-abstract-factory/</guid><description>积千里跬步，汇万里江河；每天进步一点点，终有一天将成大佬
突然开始的正文 紧接着上一章的工厂方法模式，其实抽象工厂的概念和工厂方法的概念都是差不多的，抽象工厂模式是对工厂方法模式的更高级，比如上次我们说的那个汽车工厂总部类AllCarFactory，本来他只定义了生产汽车这个方法，下面的各个品牌的汽车厂也只能生产这个汽车，现在由于市场需求，需要生产摩托车，然后AllCarFactory定义了一个生产摩托车的接口，这样这个接口下面的汽车厂就可以生产摩托车了．就在这时他们的生产模式也从工厂方法模式升级到了抽象工厂模式；
话不多说，看两个模式的类图你就明白了：
原本的工厂方法模式类图： 升级后的抽象工厂模式： 可以看到，抽象工厂只是比工厂方法模式多生产了一个产品，当抽象工厂模式的产品减到只有一个的时候，他就又回到了工厂方法模式；
好色的朋友买车了 上次我朋友看见我买车之后，得知是个小姐姐带我区买车的，于是他叫我联系了下那个小姐姐，说他也要买车，点名要叫小姐姐带他去，由于资金有限，他只卖了奔驰和五菱系列的产品，没有买莱斯莱斯的；看看他是怎么买的吧：
可以看到，由于要在一个工厂买两个东西，他是先找到了工厂，然后再一件一件的从工厂买．我们上次是一个工厂买一件东西，所以是直接去工厂买的；
措不及防的结束了 不是我不想讲，而是抽象工厂就是这样的东西．从上面可以看出，抽象工厂每当增加一个产品时，后面相关的的品牌工厂也全部要实现他这个产品，这就违背了开闭原则了．所以，在实际设计中，一个业务场景是稳定的,用抽象工厂是比较好的，因为一次设计,后面就不用改了,这样就不会违反开闭原则了．但是如果一个业务场景是稳定的是不稳定的，那么就不适合使用这个模式了，因为后期需要多次修改，这就违反了开闭原则，同时也及其难维护，应为你不知道修改了代码，到底会影响哪些功能；</description></item><item><title>设计模式系列-工厂模式</title><link>https://blog.greycode.top/posts/gof-factory-method/</link><pubDate>Sun, 15 Dec 2019 17:25:00 +0000</pubDate><guid>https://blog.greycode.top/posts/gof-factory-method/</guid><description>积千里跬步，汇万里江河．每天进步一点点，终有一天将成大佬
前言 工厂模式有一下三种
简单工厂模式 工厂方法模式 抽象工厂模式 其中简单工厂模式不在23中模式之中，更多的是一种编程习惯，而我们平常所说的工厂模式一般指的是工厂方法模式，抽象工厂在实际的业务开发中也用的比较少，因为它有时候违背了开闭原则．由于篇幅有限，抽象工厂本文就不讲了，以后单独讲；
简单工厂模式 简单工厂到底有多简单呢？简单到只有一个工厂，这个工厂相当于是万能工厂，你想要什么，只要和它说一声，它就会想方设法的去抱你创建，然后给你；举个买车的简单的例子：
当我要买车的时候，我选了这两种车．
/** * 创建一个汽车接口 * */ public interface Car { /** * 汽车能动 * */ void run(); } /** * 奔驰车 * */ public class Benz implements Car { @Override public void run() { System.out.println(&amp;#34;大奔开动了&amp;#34;); } } /** * 五菱神车 * */ public class Wuling implements Car { @Override public void run() { System.out.println(&amp;#34;五菱神车开动了&amp;#34;); } } 选是选好了，可是要怎么得到呢？是不是下意识的new一个出来？
//我要奔驰车 Benz　myCar=new Benz(); 如果是这样的话，就相当于自己亲手造了一辆奔驰车出来，因为是你自己new出来的嘛！！！！！</description></item><item><title>OOP程序七大原则</title><link>https://blog.greycode.top/posts/gof-oop-7-all/</link><pubDate>Sun, 15 Dec 2019 10:45:04 +0000</pubDate><guid>https://blog.greycode.top/posts/gof-oop-7-all/</guid><description>开闭原则 开闭原则相当于所有原则的祖先，主张对修改关闭，对拓展开放．
里氏替换原则 当两个类有继承关系时，子类不能修改父类的方法和变量. 里氏替换中的替换指的是：当有父类出现的地方，这个父类可以替换成子类，而且对程序没有影响，这就遵循了里氏替换原则；当替换成子类时对程序有影响，说明子类修改了父类的方法，就没有遵循里氏替换原则了；
依赖倒置原则 依赖倒置原则是对开闭原则的一个实现，也是主张对拓展开放，对修改关闭．它的核心思想是面对接口编程，不要面对具体实现编程．
这是一个遵守依赖倒置原则的UML图，原来的话当客户购买商品时,shopping这个方法要传入相应的网店进去，当要更改店铺时，就要修改Cusromer这个类里的shopping方法，而现在，只要定义一个Shop接口，所有的店铺都实现这个接口的方法，顾客类的shopping方法只要传入Shop这个接口类就可以了．然后具体实现的时候，要到哪里买，就传入哪一个网店就可以了，而不用修改Cusromer这个类的方法；
//代码来之＇C语言中文网＇ public class DIPtest { public static void main(String[] args) { Customer wang=new Customer(); System.out.println(&amp;#34;顾客购买以下商品：&amp;#34;); wang.shopping(new ShaoguanShop()); wang.shopping(new WuyuanShop()); } } //商店 interface Shop { public String sell(); //卖 } //韶关网店 class ShaoguanShop implements Shop { public String sell() { return &amp;#34;韶关土特产：香菇、木耳……&amp;#34;; } } //婺源网店 class WuyuanShop implements Shop { public String sell() { return &amp;#34;婺源土特产：绿茶、酒糟鱼……&amp;#34;; } } //顾客 class Customer { public void shopping(Shop shop) { //购物 System.</description></item><item><title>Jenkins教程-集成SonarQube</title><link>https://blog.greycode.top/posts/build-jenkins-sonarqube/</link><pubDate>Fri, 13 Dec 2019 15:38:02 +0000</pubDate><guid>https://blog.greycode.top/posts/build-jenkins-sonarqube/</guid><description>什么是SonarQube? 看看维基百科的说明： SonarQube与CI/CD架构图 Docker运行SonarQube 简单了解之后，开始安装SonarQube.这里用Docker安装
注：这里用mysql来存储SonarQube的数据，SonarQube7.9起已经不在支持mysql了，可以安装官方推荐的PostgreSQL
SonarQube 6.7.7 Docker-CE 19.03.1 Mysql 5.7 安装 直接运行这个docker命令来安装，网上其他的教程有什么挂载文件什么的，我试了都会安装失败，原因还是因为权限原因，因为SonarQube不是以root用户运行的，导致没权限读写挂载出来的文件夹．
注意：创建容器前一定要先保证你连的容器有对应的数据库
docker run -d --name sonarqube -p 9099:9000 -p 9092:9092 --link=dev_mysql:mysql -e SONARQUBE_JDBC_USERNAME=app -e SONARQUBE_JDBC_PASSWORD=app -e SONARQUBE_JDBC_URL=&amp;#34;jdbc:mysql://mysql:3306/sonar?useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;rewriteBatchedStatements=true&amp;amp;useConfigs=maxPerformance&amp;amp;useSSL=false&amp;#34; --restart=always sonarqube:6.7.7-community &amp;ndash;link=dev_mysql:mysql 这个命令我链接到了我的mysql容器，dev_mysql是容器的名字，mysql是在SonarQube容器里的别名，所以链接数据库时直接用mysql这个别名就可了．
SONARQUBE_JDBC_USERNAME ：数据库的账户
SONARQUBE_JDBC_PASSWORD ：数据库密码
访问 安装好后直接访问9099端口，登录的账户和密码默认都是admin．首页就是这个样子的．
Jenkins集成SonarQube Jenkins和SonarQube都是运行在Docker容器里的
下载和安装插件 直接下载最新版的，然后导入，导入的方法可以看插件导入方法
插件下载地址：https://updates.jenkins.io/download/plugins/sonar/ SonarQube生成Token 进入SonarQube管理界面
Administration-&amp;gt;Security-&amp;gt;Users
然后随便输入一个名字，点击生成，记下Token
添加全局凭证 类型选Secret text,然后Secret和ID输入框都填入刚才生成的Token
设置SonarQube servers 进入　系统管理-&amp;gt;系统设置-&amp;gt;SonarQube servers　设置好后点保存
因为我SonarQube和Jenkins安装在同一台机器不同的Docker容器里的,所以这里URL直接填SonarQube的Docker容器的IP和端口
安装SonarQube Scanner 下载压缩包 下载SonarQube Scanner压缩包：SonarQube Scanner 解压到Jenkins挂载出来的目录里 只有解压到挂载出来的Jenkins的目录里，Docker容器安装的Jenkins才能读取到,我这里是宿主机的/opt/jenkins挂载到了Jenkins容器里的/var/jenkins_home目录上，所以我只要解压到宿主机的/opt/jenkins目录中就可以了
Jenkins配置全局工具 进入　系统管理-&amp;gt;全局工具配置-&amp;gt;SonarQube Scanner 找到模块后点击新增SonarQube Scanner</description></item><item><title>Java8 Stream方法大全</title><link>https://blog.greycode.top/posts/java-8-stream-method/</link><pubDate>Tue, 10 Dec 2019 09:53:34 +0000</pubDate><guid>https://blog.greycode.top/posts/java-8-stream-method/</guid><description/></item><item><title>Java数组的几种初始化方式</title><link>https://blog.greycode.top/posts/java-array-init/</link><pubDate>Mon, 09 Dec 2019 10:22:12 +0000</pubDate><guid>https://blog.greycode.top/posts/java-array-init/</guid><description>一维数组 初始化容量 /** * 定义容量为5,初始值为0的int一维数组 */ int array[]=new int[5]; int[] array2=new int[5]; 初始化值 /** * 初始化一维容量为5的一维数组的值 */ int[] array10={1,2,3,4,5}; int aray12[]={1,2,3,4,5}; 二维数组 二维数组初始化时必须要声明行数,列数可随意
初始化容量 声明了列数的 /** * 初始化一个5行5列的二维数组 */ int[][] array3=new int[5][5]; int []array4[]=new int[5][5]; int array5[][]=new int[5][5]; 未声明列数的 此种方法初始化后如果要赋值的话要new一个数组,如果按照常规的方法赋值然后取值会报空指针异常
/** * 初始化一个5行空列的二维数组 */ int[][] array6=new int[5][]; int []arra7[]=new int[5][]; int array8[][]=new int[5][]; /** * 赋值方法 */ int[][] array6=new int[5][]; array6[0]=new int[]{1,2,3}; System.out.println(array6[0][0]); //输出:1 初始化值 /** * 初始化并赋值一个2行3列的二维数组 */ int[][] array13={{1,2,3},{4,5,6}}; int []array14[]={{1,2,3},{4,5,6}}; int array15[][]={{1,2,3},{4,5,6}}; 总结 ​ 其他像什么三维数组,多维数组初始化的方式都差不多,可以自己在IDE工具中试一下;</description></item><item><title>RestTemplate简单使用</title><link>https://blog.greycode.top/posts/spring-boot-resttemplate-example/</link><pubDate>Wed, 20 Nov 2019 17:32:18 +0000</pubDate><guid>https://blog.greycode.top/posts/spring-boot-resttemplate-example/</guid><description>前言 本文只讲常用的**GET** 和**POST**请求,其他类型的请求(如**PUT**，**PATCH**)请求方式都差不多，有兴趣的可以查看RestTemplate源码。 GET GET官方给了getForEntity和getForObject两种种方法，每个方法又有三个重载方法
官方源码接口 @Nullable &amp;lt;T&amp;gt; T getForObject(String url, Class&amp;lt;T&amp;gt; responseType, Object... uriVariables) throws RestClientException; @Nullable &amp;lt;T&amp;gt; T getForObject(String url, Class&amp;lt;T&amp;gt; responseType, Map&amp;lt;String, ?&amp;gt; uriVariables) throws RestClientException; @Nullable &amp;lt;T&amp;gt; T getForObject(URI url, Class&amp;lt;T&amp;gt; responseType) throws RestClientException; &amp;lt;T&amp;gt; ResponseEntity&amp;lt;T&amp;gt; getForEntity(String url, Class&amp;lt;T&amp;gt; responseType, Object... uriVariables) throws RestClientException; &amp;lt;T&amp;gt; ResponseEntity&amp;lt;T&amp;gt; getForEntity(String url, Class&amp;lt;T&amp;gt; responseType, Map&amp;lt;String, ?&amp;gt; uriVariables) throws RestClientException; &amp;lt;T&amp;gt; ResponseEntity&amp;lt;T&amp;gt; getForEntity(URI url, Class&amp;lt;T&amp;gt; responseType) throws RestClientException; 使用 API接口 首先我写了两个接口供RestTemplate调用</description></item><item><title>Nacos报[NACOS HTTP-POST]</title><link>https://blog.greycode.top/posts/nacos-post-error/</link><pubDate>Thu, 14 Nov 2019 23:50:58 +0000</pubDate><guid>https://blog.greycode.top/posts/nacos-post-error/</guid><description>问题 由于项目使用阿里的Nacos来管理项目的配置文件，今天所有使用Nacos的项目的日志都报[NACOS HTTP-POST] The maximum number of tolerable server reconnection errors has been reached这个错误。
解决方法 查阅资料后说是连接超过了最大重试次数。Nacos有个maxRetry这个配置参数，默认是3;可是和SpringCloud整合后在application文件中找不到这个参数，只好另寻方法；
由于项目都是Docker容器化的，先前出现过连接不到Nacos的问题,于是就查看了各个Docker容器的IP。
修正Nacos的地址 查阅后发现，是因为同事吧服务器重启了一遍，导致Docker服务也重启了，然后Docker容器里的IP全部都变了。因为同一台服务器上我们各个容器间的访问是通过Docker容器内部IP的，也就是172.16.x.x这个IP段。所以导致访问不到报错。
spring.cloud.nacos.config.server-addr=172.16.X.X //更改到最新nacos的地址</description></item><item><title>Jenkins插件版本太旧的更新方法</title><link>https://blog.greycode.top/posts/problem-jenkins-01/</link><pubDate>Tue, 12 Nov 2019 17:27:27 +0000</pubDate><guid>https://blog.greycode.top/posts/problem-jenkins-01/</guid><description>前言 Jenkins的插件好多都是互相依赖的，如果有的插件版本太低，而另一个插件就会导致用不了，就会出现下面的情况： Durable Task Plugin version 1.29 is older than required. To fix, install version 1.33 or later.
查看本地已安装版本 可以看到，本地安装的版本和刚才提示的一样，是1.29版本的，刚才提示说太旧了，要更新到1.33版本。
搜索插件 当你理所应当的去这个界面准备搜索这个插件并更新时。。。。你傻了，，怎么搜不到？？？WTF
不要慌，天无绝人之路，这里找不到，可以去另外的地方找。浏览器打开这个网站
Jenkins插件下载
进入后在输入框里输入你刚才要下载的插件: 选择对应的插件 然后点击右上角 下载刚才提示的1.33版本 下载完成后是一个hpi文件 导入插件 到插件管理界面，找到Upload Plugin 然后选择刚才下载的插件，点击导入 可以看到插件正在导入 导入完成后，重启Jenkins就OK了</description></item><item><title>Jenkins教程-Docker+GitLab持续部署持续集成</title><link>https://blog.greycode.top/posts/build-jenkins-ci-cd/</link><pubDate>Tue, 12 Nov 2019 17:27:02 +0000</pubDate><guid>https://blog.greycode.top/posts/build-jenkins-ci-cd/</guid><description>目录 Jenkins教程-搭建(Docker版)
Jenkins教程-创建Maven项目
Jenkins教程-Docker+GitLab持续部署持续集成
环境 地址 系统 安装的软件 主机１ 10.25.0.72 Centos 7 Docker　,　Jenkins(Docker版) 主机２ 10.25.0.50 Cnetos 7 Docker Jenkins所需添加插件 Git Parameter
GitLab
SSH
创建ssh登录凭据 这边选择Username with password,用账户密码来设置；然后在Username和Password输入框中分别输入10.25.0.50服务器的账号和密码。点击OK保存；
添加SSH配置 找到SSH remote hosts
设置你远程机器的ip和端口，然后选择刚配置好的凭证，点击save保存
配置Job 进入上篇文章创建好的Job,在此基础上进行改造
配置Git Parameter,来获取gitlab的Tag数据 配置触发器 点击最下面的Generate,生成秘钥。然后记下URL:http://172.16.54.131:8080/project/JenkinsTest 和生成的秘钥：60327d68d10f1f7621696edd42719d1c
添加构建完成后的动作 添加Execute shell 和Execute shell script on remote host using ssh Execute shell ： 执行Jenkins所在服务器的脚本 Execute shell script on remote host using ssh：登录远程服务器执行脚本 编写你要执行的脚本 由于是自定义的，内容我就不粘贴出来了.编写好后点击保存
开始构建 手动构建 选择你要构建的tag标签，点击Build开始构建并自动部署 自动构建 自动构建是当你push或打tag上传代码的时候，Jenkins就会自动构建部署</description></item><item><title>Jenkins教程-创建Maven项目</title><link>https://blog.greycode.top/posts/build-jenkins-mavne/</link><pubDate>Tue, 12 Nov 2019 17:26:19 +0000</pubDate><guid>https://blog.greycode.top/posts/build-jenkins-mavne/</guid><description>目录 Jenkins教程-搭建(Docker版)
Jenkins教程-创建Maven项目
Jenkins教程-Docker+GitLab持续部署持续集成
前期准备 本教程是和gitlab集成,所以要有gitlab仓库。注意：如果后期要弄自动部署的话,你Jenkins的地址gitlab必须能访问到。不然gitlab监听到事件就通知不了Jenkins了；
环境 Centos 7 Jenkins(Docker版) 所需插件 除了搭建Jenkins时安装的插件,还需安装的插件
Maven Integration 安装Maven 点击侧边栏的Manage Jenkins,然后点击Global Tool Configuration,进入全局工具设置 然后找到Maven,点击Add Maven,可以选择你要的Maven版本，然后设置一个名字。点击保存
创建Git登录凭证 点击侧边栏的凭证，然后按图点击 这边Kind有很多选项，这边选择Username with password,用账户密码来设置；然后在Username和Password输入框中分别输入gitlab的账号和密码。点击OK保存；
保存后就会出现你保存好的凭证；
创建JOB 创建Maven项目 输入你的gitlab项目地址，然后选择刚才配置的凭证 输入Maven打包命令，然后点击保存 开始构建 查看构建项目日志 第一次构建会比慢，因为他要下载maven相关构建的包
查看构建好的jar包 到此，构建maven项目已结束，可以下载这个jar包进行部署。后面会有自动构建部署的教程</description></item><item><title>Jenkins初始化界面插件安装失败解决方法</title><link>https://blog.greycode.top/posts/problem-jenkins-02/</link><pubDate>Thu, 07 Nov 2019 17:19:52 +0000</pubDate><guid>https://blog.greycode.top/posts/problem-jenkins-02/</guid><description>前言 在初始化安装界面可能由于网络问题会出现插件下载失败，就像下面这个界面
别着急，直接点击继续，先完成初始化步骤。
设置源 插件下载失败，一般都是网络的原因，只要更换到国内的软件源就可以了，点击Manage Jenkins 点击Correct 点击Advanced 下拉找到Update Site 然后把输入框的内容换成
https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/2.89/update-center.json 重新下载插件 然后重新下载刚才那些下载失败的插件,这里随机选一个 在刚才设置源的那个界面点击 Available，搜索插件，选择，点击install 插件正在安装 安装完全部插件后然后重启Jenkins，插件界面的报错信息才会消失;如果遇到插件下载不下来或搜不到，可以看这篇文章：Jenkins插件版本太旧的更新方法</description></item><item><title>Jenkins教程-搭建(Docker版)</title><link>https://blog.greycode.top/posts/build-jenkins-docker/</link><pubDate>Thu, 07 Nov 2019 17:19:34 +0000</pubDate><guid>https://blog.greycode.top/posts/build-jenkins-docker/</guid><description>目录 Jenkins教程-搭建(Docker版)
Jenkins教程-创建Maven项目
Jenkins教程-Docker+GitLab持续部署持续集成
环境 主机：172.16.54.131
系统：Cnetos 7
安装Docker-CE 检查Docker 首先检查本机是否安装Docker，如果安装了直接跳过安装Docker步骤
docker -v 如果出现Docker version 19.03.4, build 9013bf583a类似的信息，则说明已安装Docker
安装 本教程以centos7安装方式说明，其他系统安装方式会有不同 执行以下命令，安装Docker
yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo yum install docker-ce service docker start systemctl enable docker 第一条命令：为添加源做准备 使其支持存储 第二条命令：添加docker-ce软件源 第三条命令：安装docker-ce 第四条命令：启动docker服务 第五条命令：设置开启自启 安装Jenkins的Docker容器 创建文件夹 在创建容器前先在宿主机创建一个Jenkins的工作文件夹，用于持久化
mkdir /opt/jenkins //创建文件夹 chmod 7777 /opt/jenkins //授予权限 该文件夹一定要给权限，不然docker容器访问不了，容器会创建失败。
拉取官方镜像 docker pull jenkins/jenkins:lts 启动容器 docker run -d -p 8080:8080 -p 50000:50000 -u root -v /var/run/docker.</description></item><item><title>设计模式系例-单例模式</title><link>https://blog.greycode.top/posts/gof-singleton/</link><pubDate>Tue, 22 Oct 2019 21:16:37 +0000</pubDate><guid>https://blog.greycode.top/posts/gof-singleton/</guid><description>积千里跬步，汇万里江河．每天进步一点点，终有一天将成大佬
前言 网上说单例模式是所有模式中最简单的一种模式，巧的是我也这么认为。不过越简单的东西，往往坑就隐藏的越深，这边文章我会把我知道的几个坑所出来。
一.什么是单例模式 ​ 就如同他的名字一样，&amp;lsquo;单例&amp;rsquo;-就是只有一个实例。也就是说一个类在全局中最多只有一个实例存在，不能在多了，在多就不叫单例模式了。
1.白话小故事 ​ 程序员小H单身已久，每天不是对着电脑，就是抱着手机这样来维持生活。某日，坐在电脑前，突然感觉一切都索然无味。谋生想找一个对象来一起度过人生美好的每一天。
​ 于是精心打扮出门找对象，由于小H很帅，很快就找到了心仪的另一半&amp;ndash;小K。小H的心中永远只有小K一个人，而且发誓永远不会在找新对象。
小H和小K的关系就是单例模式，在小H的全局中只有一个小K对象，且无第二个，如果有第二个的话，他们之间的关系就出问题了。哈哈
2.用在哪里 ​ 单例模式一般用在对实例数量有严格要求的地方，比如数据池，线程池，缓存，session回话等等。
3.在Java中构成的条件 静态变量 静态方法 私有构造器 二.单例模式的两种形态 1.懒汉模式 线程不安全
public class Singleton { private static Singleton unsingleton; private Singleton(){} public static Singleton getInstance(){ if(unsingleton==null){ unsingleton=new Singleton(); } return unsingleton; } } 2.饿汉模式 线程安全
public class Singleton { private static Singleton unsingleton=new Singleton(); private Singleton(){} public static Singleton getInstance(){ return unsingleton; } } 调用 public class Test { public static void main(String[] args) { Singleton singleton1=Singleton.</description></item><item><title>RocketMQ集群搭建</title><link>https://blog.greycode.top/posts/rocketmq-cluster-build/</link><pubDate>Wed, 09 Oct 2019 20:55:36 +0000</pubDate><guid>https://blog.greycode.top/posts/rocketmq-cluster-build/</guid><description>本文只讲RocketMQ集群的搭建(异步复制)，具体理论知识后续会在写新文章详细介绍;
环境 JDK1.8 Centos7 主机-两台 centos7_1 :172.16.54.130 centos7_2 :172.16.54.128 软件资源 JDK1.8 :https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html RocketMQ4.5.2 :http://mirrors.tuna.tsinghua.edu.cn/apache/rocketmq/4.5.2/rocketmq-all-4.5.2-bin-release.zip 安装JDK 首先分别在两台主机上安装JDK1.8，具体安装方法这里就不说了，网上随便搜一搜都有；
配置RocketMQ 把下载的RocketMQ包分别上传到两台服务器上，然后用命令解压:
# unzip rocketmq-all-4.5.2-bin-release.zip 编写配置文件 这一步很重要，集群的搭建关键在于配置文件的编写，首先看看RocketMQ配置文件的解析:
#所属集群名字 brokerClusterName=rocketmq-cluster #broker名字，每队master和slave保持一致 brokerName=broker-a #0 表示 Master，&amp;gt;0 表示 Slave brokerId=0 #指定主机ip brokerIP1 = 主机IP #nameServer地址，分号分割 namesrvAddr=主机IP:9876;主机IP:9876 #在发送消息时，自动创建服务器不存在的topic，默认创建的队列数 defaultTopicQueueNums=4 #是否允许 Broker 自动创建Topic，建议线下开启，线上关闭 autoCreateTopicEnable=true #是否允许 Broker 自动创建订阅组，建议线下开启，线上关闭 autoCreateSubscriptionGroup=true #Broker 对外服务的监听端口 listenPort=10911 #删除文件时间点，默认凌晨 4点 deleteWhen=04 #文件保留时间，默认 48 小时 fileReservedTime=120 #commitLog每个文件的大小默认1G mapedFileSizeCommitLog=1073741824 #ConsumeQueue每个文件默认存30W条，根据业务情况调整 mapedFileSizeConsumeQueue=300000 #检测物理文件磁盘空间 diskMaxUsedSpaceRatio=88 #存储路径 storePathRootDir=/usr/local/rocketmq/store #commitLog 存储路径 storePathCommitLog=/usr/local/rocketmq/store/commitlog #消费队列存储路径存储路径 storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue #消息索引存储路径 storePathIndex=/usr/local/rocketmq/store/index #checkpoint 文件存储路径 storeCheckpoint=/usr/local/rocketmq/store/checkpoint #Broker 的角色 #- ASYNC_MASTER 异步复制Master #- SYNC_MASTER 同步双写Master #- SLAVE brokerRole=ASYNC_MASTER #刷盘方式 #- ASYNC_FLUSH 异步刷盘 #- SYNC_FLUSH 同步刷盘 flushDiskType=ASYNC_FLUSH #checkTransactionMessageEnable=false #abort 文件存储路径 abortFile=/usr/javawork/apache-rocketmq/store/abort #限制的消息大小 maxMessageSize=65536 以上配置可根据个人需求加入到自己的配置文件中；RocketMQ官方已经为我们创建好了简单的集群配置文件，进去解压后的文件夹，在进入到conf文件夹，可以看到里面有三个文件夹：</description></item><item><title>Vue Cli3-11创建项目慢的问题</title><link>https://blog.greycode.top/posts/vue-cli3-11-problem/</link><pubDate>Thu, 05 Sep 2019 16:33:23 +0000</pubDate><guid>https://blog.greycode.top/posts/vue-cli3-11-problem/</guid><description>前言 这几天刚学习vue，于是下载了最新的vue cli3.11来搭建项目，可是搭建的时候一直卡在下载插件见面，就是下面这张图。
网上查了说不能用国内的镜像，WTF，不是说国内的更快吗？好吧，我换！！！
下载nrm 看清楚哦，是nrm部署npm！！！nrm 是一个 npm 源管理器，允许你快速地在 npm 源间切换。执行以下命令安装。
sudo npm install -g nrm 测试nrm是否安装成功 nrm -V 如果输出版本号，则说明安装成功。
切换npm源 nrm ls 此命令会列出npm的所有源
可以看到我现在使用的是淘宝的源，现在把他切换到npm的源。
nrm use npm 再次创建vue项目 vue create rrr2 项目成功创建！！！</description></item><item><title>Docker下安装mysql并设置用户权限</title><link>https://blog.greycode.top/posts/docker-mysql/</link><pubDate>Tue, 03 Sep 2019 15:58:46 +0000</pubDate><guid>https://blog.greycode.top/posts/docker-mysql/</guid><description>环境 Ubuntu18.04
Docker19.03.1
Mysql5.7
Docker 拉取镜像 Docker拉取镜像默认是从DockerHub上面拉取，上面有各厂商提供的优质官方镜像，可以直接拉取使用。或者也可以用DockerFile自定义构建你自己的镜像。
sudo docker pull mysql:5.7 //拉取镜像到本地 注：上面mysql:5.7指的是拉取5.7版本的mysql，如果不加直接写mysql的话默认是拉取mysql的最新版本。
如果显示上面这样，说明已经拉取好了。
查看镜像 sudo docker images //查看本地镜像 创建容器 创建 sudo docker run -d -p 3306:3306 --name mysql5.7 -e MYSQL_ROOT_PASSWORD=root mysql:5.7 -d 指定容器运行于后台 -p 端口映射 主机端口:容器端口 &amp;ndash;name 自定义容器名字，方便记忆，不设置的话会随机生产 -e 容器环境变量 创建好的话会显示一串随机生产的id
查看创建好的容器 sudo docker ps -a -a 显示所有创建好的容器，如果不加只显示正在运行的容器 Mysql 进入容器 sudo docker exec -it mysql5.7 bash -i 打开STDIN，用于控制台交互 -t 分配tty设备，该可以支持终端登录 登录mysql mysql -uroot -p 注：然后输入刚才创建容器时的密码，就是MYSQL_ROOT_PASSWORD这个参数
创建测试数据库 create database test; 创建mysql用户 create user &amp;#39;zmh&amp;#39;@&amp;#39;%&amp;#39; identified by &amp;#39;zmh&amp;#39;; 注：&amp;quot;%&amp;ldquo;表示可以任意ip访问</description></item><item><title>树莓派安装docker</title><link>https://blog.greycode.top/posts/build-docker-pi/</link><pubDate>Fri, 30 Aug 2019 18:33:03 +0000</pubDate><guid>https://blog.greycode.top/posts/build-docker-pi/</guid><description>前言 和平常x86_64架构的电脑安装docker不同，树莓派是ARM架构的，所以安装步骤比较繁琐一点。
使用APT源安装docker 更新apt软件源及安装必备组件。为了确认所下载软件包的合法性，还需要添加软件源的 GPG 密钥。
$sudo apt-get update $sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ gnupg2 \ lsb-release \ software-properties-common $curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/raspbian/gpg | sudo apt-key add - 添加docker ce 软件源 首先执行以下一行命令，然后记一下输出的结果
$ echo $(lsb_release -cs) stretch 在/etc/apt/sources.list.d目录下新建文件docker.list
$ sudo vi /etc/apt/sources.list.d/docker.list 在文件里添加下面这行
deb [arch=armhf] https://download.docker.com/linux/raspbian $(lsb_release -cs) stable 把$(lsb_release -cs)改为刚才第一行输出的结果，比如我的输出的是stretch，改完后如下
deb [arch=armhf] https://download.docker.com/linux/raspbian stretch stable 保存，退出
安装docker ce 依次执行以下两行命令，即可完成安装
$ sudo apt-get update $ sudo apt-get install docker-ce 启动 $ service docker start 启动 $ service docker stop 停止 $ service docker status 状态 $ service docker restart 重启</description></item><item><title>JDK时区问题</title><link>https://blog.greycode.top/posts/problem-jdk-timezone/</link><pubDate>Tue, 27 Aug 2019 15:26:30 +0000</pubDate><guid>https://blog.greycode.top/posts/problem-jdk-timezone/</guid><description>今天碰到一个大坑，弄了快一个小时才解决掉；
一个管理台后端服务，用docker隔离了三个容器，oracle,nginx,tomcat;后发现管理台查出来的时间和现实时间相差8个小时，一查是linux时区问题；
于是改之,三台容器都输入一下代码 cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 测试了一下，发现问题docker容器的时区是正确了，可是问题并未得到解决，数据库时间还是慢了8个小时。
于是又查资料，换另外一种设置时区的方法； vi /etc/sysconfig/clock 在里面输入如下内容
ZONE=&amp;#34;Asia/Shanghai&amp;#34; UTC=false ARC=false 保存，重启，测试。。。。。发现还是一样,快疯了
第三种方法，设置TZ环境变量 设置环境变量可以在设置系统级别的/etc/profile ,也可以设置用户级别的home目录的.bashrc。由于用的是docker，防止变量重启失效，只能在.bashrc里设置。在.bashrc加入如下内容：
export TZ=&amp;#39;CST-8&amp;#39; 保存：然后执行
source .bashrc 使设置立即生效。
重启容器，测试，发现时间正常了。。。。哈哈哈哈
总结 上面问题出在jdk的new Date()方法，所以只要设置jdk所在的那个docker容器的变量就可以，不用每个都设置。jdk的new Date()方法每次调用都会去取环境变量TZ的时区，TZ是TimeZone的缩写，容器内部操作系统并未指定时区（TimeZone）信息，系统默认使用世界标准时（UTC+0),所以导致new Date()出来的数据存库会比当前时间慢8个小时；</description></item><item><title>Java的==和equals</title><link>https://blog.greycode.top/posts/java-equals/</link><pubDate>Tue, 20 Aug 2019 19:22:50 +0000</pubDate><guid>https://blog.greycode.top/posts/java-equals/</guid><description>在平常工作和学习中，我们一般用==和equals来比较两个对象或数据是否相等。但是什么时候用equals，什么时候用==一直都不怎么清楚，今天整理了下；
首先看看Java的栈空间和堆空间的地址引用 ==的说明 在Java中，==对比的是两个对象在空间里的地址是否一致,比如上图的s2==s3返回的是false，s5==s6返回的是为true。话不多说，上代码。
public class demo2 { public static void main(String[] args) { String s1=new String(&amp;#34;t1&amp;#34;); String s2=new String(&amp;#34;t2&amp;#34;); String s3=new String(&amp;#34;t2&amp;#34;); String s4=new String(&amp;#34;t3&amp;#34;); String s5=&amp;#34;t3&amp;#34;; String s6=&amp;#34;t3&amp;#34;; System.out.println(&amp;#34;s2==s3:&amp;#34;+(s2==s3)); System.out.println(&amp;#34;s5==s6:&amp;#34;+(s5==s6)); } } 结果： 这是因为==比的是在空间里的地址，s2和s3在堆里面是两个不同的对象，所以地址也不同，自然返回就是false。s5和s6是Java的基础数据类型，指向的是常量池里同一个引用，所以地址也相同，返回的就是true。
equals的说明 每个Object里的equals都不一样，我们看看String里的源码
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.</description></item><item><title>Java中String判断为空的4大方法比较</title><link>https://blog.greycode.top/posts/java-isnull-four/</link><pubDate>Tue, 20 Aug 2019 18:59:15 +0000</pubDate><guid>https://blog.greycode.top/posts/java-isnull-four/</guid><description>一.四大方法 public class demo1 { public static void main(String[] args) { String a=&amp;#34;&amp;#34;; String a2=new String(); System.out.println(a==&amp;#34;&amp;#34;); System.out.println(a2==&amp;#34;&amp;#34;); System.out.println(&amp;#34;------------------------------&amp;#34;); System.out.println(a==null); System.out.println(a2==null); System.out.println(&amp;#34;------------------------------&amp;#34;); System.out.println(a.length()&amp;lt;=0); System.out.println(a2.length()&amp;lt;=0); System.out.println(&amp;#34;------------------------------&amp;#34;); System.out.println(a.isEmpty()); System.out.println(a2.isEmpty()); System.out.println(&amp;#34;------------------------------&amp;#34;); } } 二.输出结果 可以看到用&amp;quot;==&amp;ldquo;判断的那组出现了不一致的情况</description></item><item><title>递归算法-获取json中指定key的所有值</title><link>https://blog.greycode.top/posts/algorithm-recursive-01/</link><pubDate>Sat, 17 Aug 2019 12:38:52 +0000</pubDate><guid>https://blog.greycode.top/posts/algorithm-recursive-01/</guid><description>今天在工作中遇到要解析json并获取json里所有指定key的值，再把key的值插入对应的数据映射表。于是写了一个递归算法来取值。
1.首先导入alibaba的fastjson，用来解析json。当然也可以用其他的解析包 &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;com.alibaba&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;fastjson&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;1.2.58&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt; 2.创建两个工具类方法，用来判断传入的是不是json对象或json数组 public static boolean isJSONObj(Object json){ return json instanceof JSONObject; } public static boolean isJSONArray(Object json){ return json instanceof JSONArray; } java中的instanceof也称为类型比较运算符，因为它将实例与类型进行比较。它返回true或false。
3.建立核心重载方法 public static void getJSONValue(JSONObject json,String k,List&amp;lt;String&amp;gt; list){ for (Object j:json.keySet()){ if(isJSONObj(json.get(j))){ //是对象 JSONObject j2= JSON.parseObject(json.get(j).toString()); getJSONValue(j2,k,list); }else if(isJSONArray(json.get(j))){ JSONArray j3=JSON.parseArray(json.get(j).toString()); //是数组 getJSONValue(j3,k,list); }else if(j==k){ //是字符串 list.add(json.get(j).toString()); } } } public static void getJSONValue(JSONArray json,String k,List&amp;lt;String&amp;gt; list){ for (Object j:json){ if(isJSONObj(j)){ //是对象 JSONObject j2= JSON.</description></item><item><title>Base64影响泰文字段取值问题</title><link>https://blog.greycode.top/posts/problem-java-base64/</link><pubDate>Wed, 14 Aug 2019 10:39:23 +0000</pubDate><guid>https://blog.greycode.top/posts/problem-java-base64/</guid><description>今天在工作中，图片要用base64上传，上传数据中还有泰文，然后和前端app联调时发现他们传的泰文这边竟然没存到库里，怀疑是app没有传值过来，于是一番操作 查看日志 what,日志里面竟然有他们传过来的泰文的值
对比ios和android的数据 发现日志里的数据都是一样的，但是android上传的数据全部传入了mysql数据库，ios的除了泰文，其他的也都传到了库里
确定问题 最后对比发现，android的泰文字段三放在base64字段前面的然后传上来的，ios是放在base64字段后面传上来的，怀疑问题在此处
修复bug 于是叫ios也和android一样，把上传字段的顺序调整了以下，把泰文的字段放在base64字段前面，然后上传。改了之后试了以下，，竟然解决了，2222333333
总结：暂时不知道具体什么原因，有可能是因为base64数据太长了，影响到泰文的字段存储了。</description></item></channel></rss>