func (u *User) Notify() error
,为指针方法集*User
func (u User) Notify() error
,为值方法集User
Admin
这种声明*User
指针方法集由所有具有*User
接收器和User
接收器类型的方法组成&
代表取变量的内存地址*
代表根据地址取得地址指向的值,*
是指针操作符,代表一个变量是指针类型,*
只能加在地址前方根据地址取出地址指向的值,_和&互相抵消,即 _&a = a
package main |
var a int |
结果
4 |
当接口仅包含一个方法时,这是 Go 中的约定,以-er 后缀命名接口
type Notifier interface { |
该函数SendNotification
接受实现了Notifier
接口的类型的任何值或指针(具体使用值还是指针见:实现接口规则)
func SendNotification(notify Notifier) error { |
如上代码,规定SendNotification
函数的入参对象必须实现Notifier
接口的所有方法(很明显不实现怎么会有.Notify()
方法呢),此函数可以为实现了接口的结构体(如 User)值或指针(如 bill)执行接口的特定方法
假如使用User
结构体调用SendNotification
方法时,如下 SendNotification(bill)
func main() { |
则User
结构体必须实现Notifier
接口的所有方法,如下
实现方式 1:(接收器为指针类型)
func (u *User) Notify() error { |
实现方式 2:(接收器为值类型)
func (u User) Notify() error { |
函数所接受的实现了接口的类型,取决于选用的方法集是 *User
指针类型(&User{"Bill", "bill@email.com"}
)还是 User
值类型(User{"Bill", "bill@email.com"}
)
User
值类型的方法集,不包含接收器类型为*User
类型的方法,见下方代码验证SendMsg
行会报错:cannot use (User literal) (value of type User) as Notifier value in argument to SendMsg: missing method Msg (Msg has pointer receiver)
User
值类型的方法集只包含 Notify()
方法,并不包含 Msg()
方法,所以传入SendMsg
中的 User
值类型并没有实现Notifier
接口的全部方法,无法调用接口中的方法type Notifier interface { |
*User
指针类型的方法集,包含接收器类型为User
和 *User
的方法,见下方代码验证SendNotification
行并不会报错*User
指针类型的方法集包含 Notify()
方法和 Msg()
方法,所以,传入SendNotification
中的 *User
指针类型实现了 Notifier
接口的全部方法type Notifier interface { |
结果
Notify: Sending User Email To Bill<bill@email.com> |
示例,普通调用方式:
book := Book{"Alice in Wonderland", "Lewis Carrol"} |
接口调用方式:
book := Book{"Alice in Wonderland", "Lewis Carrol"} |
接口调用方式时,接口成为方法实现和方法调用之间的桥梁,实现调用方法(通过调用接口中的方法实现调用方法)松耦合于实现方法
示例,通过调用接口的 String 方法,实现传递不同的实现执行不同的功能
package main |
结果
Book: Alice in Wonderland - Lewis Carrol |
Notify 的入参和传参类型必须全为指针或全不为指针
func Notify(u User) error
和 Notify(&User{"Bill", "bill@email.com"})
func Notify(u *User) error
和 Notify(User{"Bill", "bill@email.com"})
type User struct { |
结果
User: Sending User Email To Bill<bill@email.com> |
接收器类型(如(u *User))和调用者不允许指针和值混用
func (u User) Notify() error
和 (&User{"Bill", "bill@email.com"}).Notify()
func (u *User) Notify() error
和 (User{"Bill", "bill@email.com"}).Notify()
bill := User{"Bill", "bill@email.com"}
)调用func (u *User) Notify() error
,猜测 go 进行了自动转换bill
为指针类型Notify()
方法内部对u
的操作是否生效取决于定义的接收器是否为指针类型type User struct { |
结果
User: Sending User Email To Bill<bill@email.com> |
原文链接:https://www.ardanlabs.com/blog/2014/05/methods-interfaces-and-embedded-types.html
声明一个新类型,将 User
类型嵌入其中,我们称 Admin
为结构类型,称 User
为类型
type Admin struct { |
Admin
和 User
类型之间没有关系User
嵌入 Admin
,则 User
类型的方法成为外部类型 Admin
的方法,但是当方法被调用时,方法的接收者仍然是内部类型 User
,而不是外部类型 Admin
,如下仍可调用 SendNotification
,Admin
类型通过提升嵌入的 User
类型的方法来实现接口func main() { |
admin.Notify() |
User
充当字段名称,并且嵌入类型作为内部类型存在,因此下方调用方式也正确admin.User.Notify() |
给定一个结构类型 Admin
和 User
类型,如果 Admin
结构类型包含匿名字段 User
Admin
的方法集只包含接收者为 User
的提升方法*Admin
的方法集包含接收器为 *User
和接收器为 User
的提升方法如果外部类型包含满足接口的实现,则将使用它。否则,由于方法提升,任何实现接口的内部类型都可以通过外部类型使用
func [(obj Class)] FnName (p1 ParamType [, p2 ParamType]) ReturnType [, ReturnType] { |
// 具名函数 |
// 多个参数和多个返回值 |
// 读文件数据 |
每种类型对应的方法必须和类型的定义在同一个包中,因此是无法给 int 这类内置类型添加方法的(因为方法的定义和类型的定义不在一个包中)
栈
传递,函数的参数会以从右到左的顺序依次存入栈中;没有预分配内存,会产生多个内存分配,如果 append 的次数极多,则会产生大量的内存分配,是低效的
var s []int |
或
var s = []int{0, 1, 2, 3, 4} |
或使用 :
省略 var
s := []int{0, 1, 2, 3, 4} |
或,提前进行内存分配,假如有 10000 个数字将要 append 进切片,则提前内存分配会省去内存分配 10000 次的性能损耗 make([]int, 0, 10000)
s := make([]int, 0, 5) |
结果
[0 1 2 3 4] 5 6 |
var foo int = 200 |
var
语法:=
var usersMap map[string]*User
为var users map[string]*User
length := uint32(0x80)
代替var length uint32 = 0x80
,用来暗示事情很复杂,而不是遵循第一条规则thing := &Thing{}
或thing := Thing()
代替thing := new(Thing)
,避免关键字的不常见用法使用min, max := 0, 1000
代替
var min int |
导入标识符的名称(Content)包括其包名称(content),例如包 content 中的类型 Content 将被称为context.Context
,这使得context
无法在包中用作变量或类型
func WriteLog(context context.Context, message string) |
上方代码不会编译。这就是为什么context.Context
类型的本地声明传统上是ctx
func WriteLog(ctx context.Context, message string) |
base``common
或util
Get
函数在被另一个包引用net/http
时变为http.Get
strings
包中的Reader
类型变为strings.Reader
int 整数
int
在 32 位系统代表 int32,在 64 位系统代表 int64int8
范围 -128 ~ 127 math.MinInt8
~ math.MaxInt8
int16
范围 -32768 ~ 32767int32
范围 -2147483648 ~ 2147483647=-(2^32)/2-1 ~ (2^32)/2-1
int64
范围 -9223372036854775808 ~ 9223372036854775807uint 无符号版
uint
uint8
范围 0 ~ 255uint16
范围 0 ~ 65535uint32
范围 0 ~ 4294967295uint64
范围 0 ~ 18446744073709551615uintptr
一个大到足以存储指针值的未解释位的无符号整数,使用 unitptr 当需要对内存地址进行加减操作时(go 不会将它视为指针),从而当作数字来进行运算byte
范围 0 ~ 255 uint8 的别称rune
int32 的别称
float 小数
float32
范围 1.4e-45 ~ 3.4e38 math.MinFloat32
~math.MaxFloat32
float64
范围 4.9e-324 ~ 1.8e308math.MinFloat64
~math.MaxFloat64
complex 复数
complex64
complex128
注意:
byte(-70)=186
或byte(256 + int(-70))
(<0 时)byte
和rune
所有数字类型都是不同的,相互运算需要转换类型,包括 32 位系统时的int
和int32
也需要转换Hello, world
字符串底层数据和以下数组是完全一致var data = [...]byte{ |
s := "hello, world" |
for range
迭代这个含有损坏的 UTF8 字符串时,第一字符的第二和第三字节依然会被单独迭代到for i, c := range "\xe4\x00\x00\xe7\x95\x8cabc" { |
[]byte
转为 string,使用string([]byte)
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) |
结果
[123 34 78 97 109 101 34 58 34 87 101 100 110 101 115 100 97 121 34 44 34 65 103 101 34 58 54 44 34 80 97 114 101 110 116 115 34 58 91 34 71 111 109 101 122 34 44 34 77 111 114 116 105 99 105 97 34 93 125] |
package main |
结果
BenchmarkAddStringWithOperator-4 28661848 43.38 ns/op 0 B/op 0 allocs/op |
可见
+
运算符性能最高,且无内存分配(0 allocs/op
),每次分配 0 字节(0 B/op
)(推荐)join
性能次之,不过需要字符串数组Sprintf
性能最低,分配内存的次数等于变量的个数+1string 的定义
type stringStruct struct { |
uint8
(无符号版 0 ~ 255) 的一个别名,两者之间任何时候都是互相等价的,[]byte
是 byte 类型的数组[]byte
[]byte
可以是数组也可以是切片,Go 语言的切片这么灵活,想要用切片的特性就只能用[]byte
[]byte
也是同理。[]byte
[]byte
字符串数组newfoojson := []byte(`{"Ptr1":"Ptr1val","Ptr2":"Ptr2val"}`) |
定义
type Foo struct { |
新申明一个复杂接口体类型的变量
foo := Foo{ |
数组声明
arr1 := [3]int{1, 2, 3} |
后一种声明方式在编译期间就会被转换成前一种
[4]int 的内存表示只是四个按顺序排列的整数值
数组和切片都不是并发安全的
结构体 slice
type slice struct { |
s[1] = 2
等),但 append
这种修改长度的不会生效到旧切片,因为旧切片的 len 和 cap 是存储在结构体中func main() { |
数组初始值 [0 1 2 3 4] |
func makeslice(et *_type, len, cap int) slice
创建,函数返回的是 Slice
结构体,所以当 map 和 slice 作为函数参数时,在函数参数内部对 map 的操作会影响 map 自身;而对于 slice 却不一定会func main() { |
结果
cap 1, len 1, 0xc000128058 |
func main() { |
结果
cap 3, len 3, 0xc0000ae090 |
var s []int |
s := make([]int, 5, 5) |
[start:end]
),返回新的从 start(从 0 开始)开始到 end 结束但不包括 end 的指针,新切片索引从 0 开始,对新切片的值修改会同步到旧切片,长度修改(如a = append(a, 4)
)不会同步到旧切片a := s[1:2] |
func main() { |
初始值: |
s
的内存地址从始至终不会发生变化a
的内存地址开始未变化,当发生扩容时内存地址发生变化(0xc000010268
-> 0xc00000c3c0
),很明显地址变化后所有对 a
的操作对 s
均不会生效a
发生扩容前进行排序或值变化均会影响旧切片 s
,由于旧切片结构体中的 len 和 cap 均不变,所以,对 a
进行 append 不会对 s
生效,s
始终只取前三位a := []string{"John", "Paul"} |
或使用 copy
复制切片
func copy(dst, src []T) int |
copy 函数支持在不同长度的切片之间进行复制(它只会复制到较少数量的元素)
s := []int{0, 1, 2, 3, 4} |
结果
s [0 1 2 3 4] 5 5 |
var s3 []int
声明的值为 nil,s := []int{}
和 s2 := make([]int, 0)
声明的值为空数组func main() { |
结果
[] |
var a [length]int |
数组的长度是在编译时静态计算的,且无法在运行时动态扩缩容
func BenchmarkAppend(b *testing.B) { |
详见: Go test
package main |
结果:
[1024 1 2] |
modifySlice1
因为切片内部是指向具体数组的指针,所以函数内操作会在函数外部testSlice1
体现modifySlice2
由于 s 的 cap 为 4,第一次 append 时不会发生扩容,对新切片的修改会同步到外层旧切片,但长度变化不会生效到旧切片,所以旧切片变为了1024
,但没有2048
modifySlice3
由于 s 的 cap 为 4,第二次 append 时会发生扩容,函数 modifySolice3
内部使用的 s 和函数外部testSlice3
使用的 s 所使用的存储空间是完全不一样的,所以两次 append 和索引修改 s 都是在新内存地址上的修改,并不会体现到函数外部的 s 上modifySlice4
由于先使用了索引方式修改,已经生效到外部函数,所以结果是 [1024 1 2]
重新切片不会复制底层数组。完整数组将保存在内存中,直到不再被引用。
例如,将一个文件加载到内存中,并在其中搜索第一组连续数字,并将它们作为新切片返回
var digitRegexp = regexp.MustCompile("[0-9]+") |
返回的[]byte 指向包含整个文件的数组。由于切片引用了原始数组,所以只要切片保持在垃圾收集器周围,就无法释放数组;文件的几个有用字节将使整个内容保存在内存中,通过将数据复制到一个新的切片解决
func CopyDigits(filename string) []byte { |
func normal(s []int) { |
如果能确定访问到的 slice
的长度,可以先执行一次访问,让编译器去优化,防止每次下标检查耗费性能
map 本质上不是一个 hmap 的结构体,实际上是指向 hmap 结构体的一个指针
m := make(map[int]int) |
实际伪代码:
var m *hmap = &hmap{...} |
type bmap struct { |
runtime.GC()
清理内存,所以禁止在全局变量中使用 map,导致内存占用居高不下func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap
创建 map,函数返回的是*hmap
指针,所以当 map 和 slice 作为函数参数时,在函数参数内部对 map 的操作会影响 map 自身;而对 slice 却不会(slice 返回的是)m := make(map[int]int) |
轻量级线程
Goroutines 相对于线程的优势
ch <- 0
<- ch
chan struct{}
类型的异步 Channel — struct{}
类型不占用内存空间,不需要实现缓冲区和直接发送(Handoff)的语义;time.Sleep()
可以让出 cpuclosed
的channel
执行:read
可继续读取剩余数据write
会引起 panicvalue, ok := <- ch
ok 是 false 就代表已关闭for value := range ch {}
如果 channel 被关闭会跳出循环Channel
或sync.WaitGroup
可以解决下列示例中go loop()
还未执行,main
已经退出的问题func loop() { |
结果
0 1 2 3 4 5 6 7 8 9 |
通过Channel
解决
var complete chan int = make(chan int) |
通过sync.WaitGroup
解决
func main() { |
示例
var ch chan int = make(chan int) |
交替打印奇数和偶数示例
var ch = make(chan struct{}) |
结果
go1 1 |
缓冲信道不仅可以流通数据,还可以缓存数据
一个 WaitGroup 对象可以等待一组协程结束
func main() { |
结果
task: 0xc000006028 0xc000014098 0 |
wg.Add(delta int)
设置协程的个数,最终形成一组协程,一组协程的总数=delta 之和wg.Done()
wg.Wait()
且被阻塞,直到所有 worker 协程全部执行结束后继续很明显这种写法并不是并发执行四次打印,下方的写法才是:
func main() { |
结果
task: 0xc000006028 0xc000014098 0 |
task := task
不为 task
申请新内存时,将提示loop variable task captured by func literal
,且全部打印4.
,如下:task: 0xc000006028 0xc000014098 0 |
锁的特点就是慢
解决慢的方法
m.Unlock()
,此时由于不使用 defer 来调用m.Unlock()
,要注意中间不要有 return 之类导致死锁的跳出语句sync.RWMutex
切片复制时的注意事项在 slice 部分已经详细介绍,其实不仅限于切片,任何带有指针的类型都可能受到影响,见下方示例
type A struct { |
结果
Ptr1: &{ptr-str-1}0xc00003a240, Ptr2: &{ptr-str-2}0xc00003a250, Val: {val-str} |
可见由带指针类型的结构体复制生成新结构体时:
a.Ptr1.Str = "new-ptr-str1"
,所有指向此内存的指针均会受影响(包括旧结构体和复制后的新结构体)a.Ptr2 = &B{"new-ptr-str-2"}
,不会对旧结构体生效定义 Foo 类型,定义 MyRawMessage 类型反解析时自定义处理
type Foo struct { |
结果
UnmarshalJSON |
参照接口实现示例:src/encoding/json/stream.go
type RawMessage []byte |
Unmarshaler 接口定义:src/encoding/json/decode.go
type Unmarshaler interface { |
Marshaler 接口定义:src/encoding/json/encode.go
type Marshaler interface { |
自定义结构体JavaTime
序列化时的值为0
// JavaTime(time.Time) <==> time.Time(JavaTime) 互转 |
调用
package main |
结果
MarshalJSON |
由于时间 json encode 时被设置为 0,故 decode 时 year = 1970
encode:入参接收器返回[]byte 字符串
decode:入参[]byte 字符串返回到接收器
%s
打印字符串%v
打印值%#v
打印详细信息fmt.Printf("%#v\n", []byte("Hello, 世界")) |
[]byte{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c} |
0xe4, 0xb8, 0x96
对应中文世
,0xe7, 0x95, 0x8c
对应中文界
%T
打印类型fmt.Printf("%T\n", []byte("Hello, 世界")) |
[]byte |
%p
打印指针地址type A struct { |
指针类型可直接使用 %p
打印,非指针类型使用 &
取地址打印
结果
Ptr1: &{ptr-str-1}0xc00003a240 |
var m runtime.MemStats |
defer 语句指定在函数退出前执行的内容
func doClientWork(clientChan <-chan *rpc.Client) { |
从管道去取一个 RPC 客户端对象,并且通过 defer 语句指定在函数退出前关闭客户端。然后是执行正常的 RPC 调用 HelloService.Hello
,方法调用写法如下
func main() { |
切片
、哈希表
和 Channel
等;i := new(int) |
func main() { |
结果:
优秀! |
只能在 switch case 中使用,如
type FooInterface interface{} |
结果
<nil> is an unknown type. |
用于监测各个 Channel 的数据流动
监视三个信道的数据流出并收集数据到一个信道中
func foo(i int) chan int { |
结果
3 |
同时开启三个 Goroutine 并发执行,直到发送信号后阻塞 Goroutine,等待每个 Goroutine 逐个取走信号,然后一个一个全部发送到 c(这里是主线程收一个才会发下一个)
注意:死循环的 for 需放在 Goroutine 中,否则之后的代码不会被执行
判断 channel 是否阻塞(或者说 channel 是否已经满了)
package main |
结果
通道channel已经满啦,塞不下东西了! |
超时机制
package main |
结果
超时啦! |
ch := make(chan int, 3) |
func main() { |
结果:
55 |
注意如果 select 外层还有 for 循环,则 select 内部的循环只能 break select 本身,并不能 break for
func Reverse(s []byte) { |
func main() { |
结果
task: 0xc000006028 0xc000014098 0 |
key
task
内存地址无变化,每次循环覆盖内存中的值,所以一定要注意循环中取key
value
的地址的情况(取到的内存地址都是相同的),实际内存地址中存储的值为最后一个i
每次都申请新的内存,但内存并不增加,应该是实时回收了,见下方测试func main() { |
结果
409600 |
i
的内存地址不同,但内存并没有增加i
,使内存地址相同,内存占用是相同的,见下方测试func main() { |
结果
409600 |
func main() { |
修改为
ch := make(chan int, 3) |
注意:range 不等到信道关闭是不会结束读取的。也就是如果缓冲信道干涸了,那么 range 就会阻塞当前 goroutine,这时需要显式的关闭信道,防止死锁
switch
执行case
后跳出switch
默认地, Go 所有的 goroutines 只能在一个线程里跑
如果当前 goroutine 不发生阻塞,它是不会让出 CPU 给其他 goroutine 的
var m runtime.MemStats |
m.HeapInuse
单位 bytes
// go:notinheap
不受 gc 管控if g.stackguard0 <= sp
),g.stackguard0
)使满足条件,当执行到方法入口时就停下来了go run -race main.go |
示例:a.go
package main |
> go run -race a.go |
goroutine 7 运行到第 11 行和 main goroutine 运行到 13 行的时候触发竞争,而且 goroutine 7 是在第 12 行的时候创建的
GOPROXY https://goproxy.io,direct
map safe site:golang.org
ln -s [源文件或目录] [目标文件或目录] |
注意:第一个参数必须为绝对路径
mv now_name new_name |
passwd root |
alias site='cd /var/www/site/mycitsm/' |
yum -y install sudo |
-y
忽略确认
rm -rf dir |
先 ps -ef | more
分页看进程情况,如果知道进程名称 也可以用 ps -ef | grep
进程名称
kill -9 进程号 |
实时查看更新中的文件
tail -f file.log |
grep -r -I -l $'^\xEF\xBB\xBF' ./ |
然后用 notepad++去掉即可
find ./ -maxdepth 1 -name "@*" |
这个命令意思是,查找当前目录下以@开头的文件或者目录,搜索深度为一级也就是只在当前目录找,不进入子目录
查看磁盘剩余空间
df -hl |
查看该目录的大小
du -sh /usr/local/* |
查看 git 位置:
whereis git |
telnet 60.194.65.154 3341 |
uname -a |
centos 版本
cat /etc/redhat-release |
cat /etc/issue |
./configure --prefix=/usr/local/python3 |
/etc/rc.local |
用来下载 github 文件
export all_proxy=socks5://192.168.2.133:1092 |
centos6,service 命令依赖于 /etc/init.d/xxx
/etc/init.d/php-fpm restart |
centos7、Alibaba Cloud Liunx 2
systemctl restart php-fpm |
centos6 使用,串行启动需等待进程逐个启动
chkconfig nginx on/off |
/etc/init.d/nginx
文件2345
! /bin/sh |
chkconfig --add nginx
添加服务centos7 使用,多进程并发启动
systemctl enable nginx |
实际是在
/etc/systemd/system/multi-user.target.wants/php-fpm.service
建立了/lib/systemd/system/php-fpm.service
的软链接
PrivateTmp=true
将创建它自己的/tmp
子目录/tmp/systemd-private-*
并相应地更改它的命名空间/etc/init.d
目录下查找/lib/systemd/system
目录下查找systemctl daemon-reload
netstat -pan | grep 6379 | wc -l |
docker inspect nginx | grep Pid
sudo nsenter -n -t 6692
netstat -pan | grep 443 | wc -l
赋值
name="test" |
等号周围不能有空格
定义默认值
name=${1:-"test"} |
name 默认为$1,$1 未定义时为 test
只读
name="test" |
使用 readonly 把变量变为只读变量,第二次赋值会报错
将命令的结果赋值给变量
name=`command` |
删除
unset name |
读取文件收到的参数
server=$1 |
自定义的方法入参也是使用
$1
定义方法
get_ip() { |
函数的返回值必须为整数,用来标识方法是否执行成功,0 代表成功,非整数时会报
numeric argument required
,所以建议直接再方法中操作全局变量
局部变量
local name="test" |
默认情况下/sbin/
/usr/sbin
和 /usr/local/sbin
不在普通用户的 $PATH
中,这些文件往往需要管理员权限,建议只在 root 用户的~/.bashrc
中增加下列内容
export PATH=$PATH:/usr/local/sbin:/usr/local/bin |
source ~/.bashrc |
centos7:
yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm |
centos8:
yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm |
以 /sbin/nologin
用户执行指定命令
su www -s /bin/bash/ -c "git pull" |
-s
指定 shell-c
指定要执行的命令/bin
用于在/usr
安装分区之前可用的二进制文件,用于在非常早期的引导阶段或在引导单用户模式下需要可用的简单二进制文件,如cat
ls
,适用于所有用户/sbin
相上,需要拥有超级用户(root)权限的二进制文件/usr/bin
同 /bin
,但适用于一般系统范围的二进制文件/usr/sbin
同 /usr/bin
,需要拥有超级用户(root)权限的二进制文件/usr/local/bin
本地程序的二进制文件/usr/local/sbin
本地程序需要拥有超级用户(root)权限的二进制文件/sbin:/bin:/usr/sbin:/usr/bin
date +%F
2022-01-29date +"%F %H:%M:%S"
2022-01-29 10:59:07date +%Y-%m
2022-01date -d yesterday +%F
2022-01-28date -d "$(date +%Y%m)01 last month" +%Y%m
202112date -d "$(date +%Y%m)01 next month" +%Y%m
202202date +%F
2022-01-29date +"%F %H:%M:%S"
2022-01-29 10:59:07date +%Y-%m
2022-01date -v-1d +%F
2022-01-28date -v -1m -j "
date +%Y%m010000" +%Y%m
202112date -v +1m -j "
date +%Y%m010000" +%Y%m
202202 注意:+
号不能省略,-v 1m
代表当前月sh -x test.sh
记录调试脚本信息到日志
sh -x test.sh > /tmp/test.out 2>&1 |
ls -i demo.txt
查看文件名对应的 inode 号码inode 包含文件的元信息,具体来说有以下内容:
ls -i /etc
ln A B
创建 A 链接到 B,两个文件都指向相同的 inodeln -s A B
创建 A 链接到 B,两个文件使用不通的 inode,但文件 A 的内容是文件 B 的路径,无论打开哪一个文件,最终读取的都是文件 Bwatch -n 1 "cat test1.sh >> test.txt" |
-n 1
代表停留 1 秒
while true; do date; sleep 1; done |
循环的方式会有滚动的输出
]]>$name = 'post.name.value'; |
list($method, $name) = explode('.', $name, 2); |
int strtotime ( string$time [, int$now ] ) |
其值相对于 now 参数给出的时间,如果没有提供此参数则用系统当前时间。
string date ( string$format [, int$timestamp ] ) |
返回将整数 timestamp 按照给定的格式字串而产生的字符串。
如果没有给出时间戳则使用本地当前时间。换句话说,timestamp 是可选的,默认值为 time()。
|
5.4 以后使用 JSON_UNESCAPED_UNICODE
json_encode($arr, JSON_UNESCAPED_UNICODE); |
如果没有设置\(name,就把\)name 设置为 2
isset($name) or $name = 2; |
如果设置了\(name,就把\)name 设置为 2
isset($name) and $name = 2; |
htmlspecialchars
htmlspecialchars ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = TRUE ]]] ) : string |
字符 | 替换后 |
---|---|
& (& 符号) | & |
" (双引号) | ",除非设置了 ENT_NOQUOTES |
' (单引号) | 设置了 ENT_QUOTES 后, ' (如果是 ENT_HTML401) ,或者 ' (如果是 ENT_XML1、 ENT_XHTML 或 ENT_HTML5)。 |
< (小于) | < |
> (大于) | > |
strtolower()
strtoupper()
ucfirst()
ucwords()
EQ | EQUAL | 等于 |
---|---|---|
NQ | NOT EQUAL | 不等于 |
GT | GREATER THAN | 大于 |
LT | LESS THAN | 小于 |
GE | GREATER THAN OR EQUAL | 大于等于 |
LE | LESS THAN OR EQUAL | 小于等于 |
mb_convert_encoding
mb_convert_encoding ( string $str , string $to_encoding [, mixed $from_encoding = mb_internal_encoding() ] ) : string |
将 string 类型 str 的字符编码从可选的 from_encoding 转换到 to_encoding。
例子:
mb_convert_encoding($str, 'UTF-8'); |
或
//动态修改所有字符集到UTF-8 |
向上取整
echo ceil(164.1); // 165 |
向下取整
echo floor(3.14159); // 3 |
array_slice($data, $start, $limit); |
自动转换 \n
为 <br />
nl2br($str); |
apache 环境下,默认不能获取 Authorization 信息,需要配置 apache/config/httpd.conf 文件,加上以下内容即可
<IfModule mod_rewrite.c> |
详见:https://www.php.net/manual/zh/migration70.php
PHP 是一个弱类型的语言,不过在 PHP 7 中支持变量类型的定义,引入了一个开关指令declare(strict_type=1);
。这个指令一旦开启,就会强制当前文件下的程序遵循严格的函数传参类型和返回类型。不开启 strict_type,PHP 将会尝试转换成要求的类型;开启之后,PHP 不再做类型转换,类型不匹配就会抛出错误。
要使用严格模式,一个 declare 声明指令必须放在文件的顶部。这意味着严格声明标量是基于文件可配的。这个指令不仅影响参数的类型声明,还影响函数的返回值声明。
另外,在 PHP 7 中,很多致命错误以及可恢复的致命错误都被转换为异常来处理了。这些异常继承自 Error 类,此类实现了 Throwable 接口(所有异常都实现了这个基础接口)。
这也意味着,当发生错误的时候,以前代码中的一些错误处理的代码将无法被触发。因为在 PHP 7 版本中,已经使用抛出异常的错误处理机制了。(如果代码中没有捕获 Error 异常,就会引发致命错误)。
在 2013 年的时候,惠新宸和 Dmitry(PHP 语言内核开发者之一)就曾经在 PHP 5.5 的版本上做过一个 JIT(Just In Time,即时编译,一种软件优化技术)的尝试。
PHP 5.5 原来的执行流程是将 PHP 代码通过词法和语法分析编译成 opcode 字节码,然后 Zend 引擎读取这些 opcode 指令,逐条解析执行。他们在 opcode 环节后又引入了类型推断(TypeInf),然后通过 JIT 生成 ByteCodes 再执行。
采用这种技术优化,PHP 的效率在实际项目中并没有取得明显的提升,于是他们重新设计了 PHP 的底层语言结构。Zval 是存储 PHP 中变量的载体,是一个 C 语言实现的结构体(struct),PHP 5 的 Zval 在内存中占据 24 个字节,而在 PHP 7 中优化后的 Zval 只占 16 个字节,这样变量的存储变得非常简单和高效。
PHP 7 优化了数组的 HashTable 实现,PHP 5 的数组存储形式是一个支持双向链表的 HashTable,不仅支持通过数组的 key 来做 hash 映射访问元素,也能通过 foreach 以访问双向链表的方式遍历数组元素。
当我们通过 key 值访问一个元素内容的时候,有时需要 3 次的指针跳跃才能找对需要的内容。最重要的一点是,这些数组元素的存储是分散在各个不同的内存区域的,在 CPU 读取的时候,因为它们很可能不在同一级缓存中,导致 CPU 不得不到下级缓存甚至内存区域查找,从而引起 CPU 缓存命中下降,进而增加更多的耗时。
优化后的 Zend Array 最大的特点是整块的数组元素和 hash 映射表全部连接在一起,被分配在同一块内存中。如果是遍历一个整型的简单类型数组,效率会非常快,因为数组元素(Bucket)本身是连续分配在同一块内存里的,并且数组元素的 Zval 会把整型元素存储在内部,也不再有指针外链,全部数据都存储在当前内存区域内。
当然,最重要的是它能够避免 CPU 缓存命中率下降。
PHP 7 还改进了函数的调用机制,通过优化参数传递的环节减少了一些指令,提高执行效率。
详见:https://gywbd.github.io/posts/2014/12/php7-new-hashtable-implementation.html
Hashtable 的概念实际上非常简单:字符串的 键
先会被传递给一个 hash 函数(hashing function,中文也翻译为散列函数),然后这个函数会返回一个整数(我们把它叫做 hash 值),而这个整数就是"通常"的数组的索引(hashTable 的索引是键的散列)。
问题是对于两个不同的字符串,调用 hash 函数会得到同一个 hash 值,而现实情况是任意字符串都可以作为键,所以键会有无数个,而数组的大小必须是提前设定好的,因为 hash 值必须小于数组索引的最大值,所以可以生成的 hash 值必须是有限的。这样用有限的 hash 值表示无限的键,必然会导致冲突。我们把两个不同的键的 hash 值是一样的情况称为冲突,任何 Hashtable 算法都必须提供某种机制解决这种冲突。
有两种主要的处理冲突的方法:
typedef struct bucket { |
pListNext
中pListLast
中,还有一个指向列表开头 ( pListHead) 和列表结尾(pListLast)的指针,如下图pDataPtr
成员中(而不是单独分配的内存中),pData 然后指向 pDataPtr
,即 pData = &pDataPtr
总结:
pListNext
pListLast
typedef struct _hashtable { |
nTableSize - 1
的值总结:
h & (nTableSize - 1)
取余计算落到哪个桶中,而不是 h % nTableSize
的低效的方式struct _zval_struct { |
总结:
新 zval 相比 php5 没有 refcount 字段。这是因为新的 zval 将不会被单独分配,它会被直接嵌入到任何需要存放它的地方(例如,一个 hashtable bucket 中)。所以 zvals 将不再需要使用引用计数(refcounting),复杂数据类型例如字符串、数组、对象和资源(resources)仍需要使用。所以新的 zval 的设计将引用计数(包括跟垃圾回收相关的信息)从 zval 转移到了数组/对象/等中。这种方式有很多优点:
typedef struct _Bucket { |
typedef struct _HashTable { |
总结:
Bucket **arBuckets
)来保存分开分配的 buckets,这意味着需要更多的分配和释放操作(alloc/frees),需要为冗余信息及额外的指针分配内存如果智能代理在任意时间点所能感知的环境信息,完全满足做出最优决策所需,该环境就被称为是完全可观测的。类似纸牌游戏自己手里的牌,所有这些牌的瞬时状态足以做出最佳决策。感知器总是可以看到环境的全部STATE。类似跳棋,棋盘上基本显示了你所需了解的一切,因此它是完全可观测的。 ### 部分可观测(partially observable) 与完全可观测相对,需要智能代理自身的记忆,已做出可能的最佳决策。类似扑克牌游戏中,桌上的纸牌并非完全公开,记住过去的出牌情况,会帮助你做出更好的决策。感知器只能看到环境的一部分STATE,但记住过去的观测可以提供对当前不可观测的状态的额外信息。 > 感应器的瞬时输入值,是需要通过记忆判断做出动作,还是直接做出动作。
确定性环境指,在其中智能代理的行为,唯一的决定产生的结果。类似国际象棋中,完全不存在随机性,移动一颗棋子产生的效果完全是可预见的,移动同一颗棋,其结果将是一样的,称之为确定性。 ### 随机性的(stochastic) 相对于确定性环境,骰子游戏,例如双陆棋就是随机的。虽然走棋是确定性的,但取得一步棋的结果还涉及掷骰子,而你无法预测其结果,称之为随机性的。 > 判断从接收感应器数据,到做出动作,其状态是否为可预测的,不可预测为随机性。
一个离散的环境是指,其中只有有限多个行动选择,以及有限多个可感知的状态。类似国际象棋棋盘上的位置的数目是有限的,可以移动的步数也是有限的 ### 连续(continuous) 在连续的环境中,可采取的行动或可感知的状态,其空间可能是无限的。类似仍飞镖,将会有无限多个可以仍的角度,以及无限多种使之加速的方式 > 感应器收到数据后,是否有无数种方式进行下一步动作,无数则为连续。如转动任意度数的方向盘。
在良性的环境中,环境可能是随机的,但其不包含,可能与自身目标相抵触的目标。类似天气是良性,它可能是随机的,它可能会影响你的行动的结果,单它的存在并不是为了对抗你。 ### 对抗性(adversarial) 相对于良性的,类似许多游戏,像象棋,你的对手在那就是为了对抗你。事实证明,在对抗性的环境中,对手积极观察你的目标并与之对抗,这时试图找到良好的行动。相比于或许随机但并不对你造成破坏的良性环境而言,要困难的多。 > 是否有对抗
动作(s)->{a1, a2, a3...}
输入一个状态,输出一个可能的动作集合结果(s, a)->s1
输入一个状态和动作,输出新的状态。状态为出发地A,行动为驾驶,线路G107朝途径地B,然后在途径地B得到结果:新状态在途径地B。目标测试(s)->True|False
测试状态是否为goal(目的地S),输入一个状态,输出True或False。路径成本(s->s->s)->n
各个步骤成本的总和。步骤成本(s, a, s1)->n
通过状态,动作以及动作产生的结果状态,输出数字n,是该步骤的成本。在示例中可能是公里数或到达目的地S的分钟数。 状态空间:所有的状态的集合
状态分为三部分: - 边疆,未探索的路径末端的集合(frontier) - 未探索区域(unexplored) - 已探索区域(explored)
- 边疆为出发地A
- 方法删除边疆中的最后一个节点(出发地A),并返回节点的信息
- 判断节点是否为目标
- 得到节点状态的动作列表,循环动作列表
- 增加 被删除的节点(出发地A)通过动作到下一节点(途径地B)的结果 到边疆,边疆变为途径地B
未完待续...
]]>MACD
RSI
KDJ
市盈率
的释义、使用方式及注意事项。指数平滑异同移动平均线(英语:Moving Average Convergence / Divergence, 缩写:MACD,港澳台称为指数平滑异同移动平均线)
MACD 的意义和双移动平均线基本相同,即由快、慢均线的离散、聚合表示当前的多头空头状态和股价可能的发展变化趋势。
MACD 指标是由两线一柱组合起来形成,快速线为 DIF,慢速线为 DEA,柱状图为 MACD。
MACD 柱状图公式:MACD=2×(DIF-DEA)
离差值。快速指数移动平均线(EMA12)减去慢速指数移动平均线(EMA26)得到的离差值。
EMA(12):EMA(12)=前一日EMA(12)×11/13 + 今日收盘价×2/13
EMA(26):EMA(26)=前一日EMA(26)×25/27 + 今日收盘价×2/27
指数移动平均(英语:exponential moving average,EMA 或 EXMA)是以指数式递减加权的移动平均。
DIF 的 N 周期的平滑移动平均线,通常周期使用9 日。
根据离差值(DIF)计算其 9 日的 EMA,即离差平均值,是所求的 MACD 值。为了不与指标原名相混淆,此值又名 DEA 或 DEM。
今日 DEA(MACD):今日DEA=前一日DEA×8/10 + 今日DIF×2/10
注意事项:
相对强度指数(英语:Relative Strength Index,RSI),一种比较价格升降运动以表达价格强度的技术分析工具。
设每天向上变动为 U,向下变动为 D。
任何情况下,U 及 D 皆不可能为负数;若两天价格相同,则 U 及 D 皆等于零。
U 及 D 的平均值皆需用上"指数移动平均法(EMA)"(在 n 日内)。
所谓"相对强度",即 U 平均值及 D 平均值的比例: \[RS=\frac{EMA_{(U,n)}}{EMA_{(D,n)}}\]
\(EMA_{(U,n)}\):U 在 n 日内的指数平均值;
\(EMA_{(D,n)}\):D 在 n 日内的指数平均值。
相对强度指数: \[RSI=\left(1-{\frac {1}{1+RS}}\right)\cdot 100\%\] 或 \[RSI={\frac{EMA_{(U,n)}}{EMA_{(U,n)}+EMA_{(D,n)}}}\times 100\%\]
而当 n=14 时,指数最具代表性。
注意事项:
随机指标(KDJ),最早是以只判断股票的超买超卖的 KD 指标的形式出现,J 指标是个辅助指标。一般是根据统计学的原理,通过一个特定的周期(常为 9 日、9 周等)内出现过的最高价、最低价及最后一个计算周期的收盘价及这三者之间的比例关系,来计算最后一个计算周期的未成熟随机值 RSV(Raw Stochastic Value),然后根据平滑移动平均线(MACD)的方法来计算 K 值、D 值与 J 值,并绘成曲线图,在价格尚未上升或下降之前发出买卖信号的一种技术工具。
本质上是一个随机波动的观念,对于掌握中短期行情走势比较准确。
随机指数的典型背驰准确性颇高,看典型背驰区注意 D 线,而 K 线的作用只在发出买卖讯号。
- 当 K 值大于 D 值时,表明股价当前正处于上升趋势,因此,当 K 线从下向上交叉突破 D 线时,正是买进股票的时机。
- 反之,当 K 值小于 D 值时,表明股市当前处于下降趋势。因此,当 K 线从上向下交叉突破 D 线时,正是卖出股票的时机。
超买区:K 值在 80 以上,D 值在 70 以上,J 值大于 90 时为超买。股价有可能下跌。
超卖区:K 值在 20 以下,D 值在 30 以下为超卖区。股价有可能上涨。
徘徊区:KD 值处于 50 左右分三种情况。如在多头市场,50 是回挡下跌支撑线;如是空头市场,50 是反弹上涨压力线;如果在 50 左右徘徊,无意义。
当股价走势一峰比一峰高时,随机指标的 J 线却一峰比一峰低,或股价走势一波谷比一波谷低时,J 线却一波谷比一波谷高,这种现象称之为背驰。一般为市场转势的信号,此时是买卖股票的时机。
注意事项:
2021-01-26 更新
此部分转自知乎包总,禁止传播仅用作个人备忘
市盈率(Price Earnings Ratio,简称 P/E 或 PER),也称"本益比"、"股价收益比率"或"市价盈利比率(简称市盈率)"。它是指股票价格除以每股收益(EPS)的比率,或以公司市值除以年度净利润。
不同行业及相同行业不同公司的市盈率差别很大
假如包总有个面馆,每年除去杂七杂八的成本费用税收等,到手 10 万元的净利润,那么现在有人要买包总的面馆,包总开价 100 万,那么这个 100 万就是面馆的市值,100 万除以 10 万等于 10,这个 10 就是市盈率,也相当于购买面馆的回本年限,但不要单纯的理解为回本年限。
计算公式:归属于普通股股东的当期净利润/当期实际发行在外的普通股加权平均数
计算公式:当前总市值除以去年一年的总净利润。
或:当前股价(P)/每股收益(EPS),其中每股收益取去年的 12 个月的每股收益
计算公式:当前总市值除以去年一年的总净利润。
或:当前股价(P)/每股收益(EPS),其中每股收益取预测下一年的每股收益
计算方式:当前总市值除以前面四个季度的总净利润。去年一年
和前面四个季度
有什么区别,我就不需要多加解释了,简单点想就是PE-TTM
要比PE-LYR
更加准确一点,数据来得更新一些。所以,一般我们用的多的就是滚动市盈率。
或:当前股价(P)/每股收益(EPS),其中每股收益取最近 4 次的季报的每股收益的平均值
净利润=营业收入-营业成本-营业税金及附加-销售费用-管理费用-财务费用-资产减值损失-/+公允价值变动损益-/+投资收益+营业外收入-营业外支出-所得税费用等。
公允价值变动损益和投资收益可能是负的也可能是正的,看投资的是否升值情况
非经常性损益的净利润
(简称"扣非净利润")任何一家公司的股票如果定价合理的话,市盈率就会与收益增长率相等
这就是PEG估值法
的来源,计算公式:PEG=PE/公司盈利增长率。PEG=1 的时候是合理估值,小于 1 是低估,大于 1 是高估。
净利润
同比增长率也就是说如果未来三年公司盈利增长率
为 20%,则公司的市盈率
应该为 20,大于 20 则为高估,小于 20 则为低估。
以 2021-07-21 腾讯控股(HK00700)为例
可简单理解公司盈利增长率为 78.71(或每股收益增长率 77.99),即盈利增长率 78.71 > 市盈率(动) 22.28,所以公司暂未被高估(自认为...)
股息率(Dividend Yield Ratio),是一年的总派息额与当时市价的比例。 以占股票最后销售价格的百分数表示的年度股息,该指标是投资收益率的简化形式。 股息率是股息与股票价格之间的比率。在投资实践中,股息率是衡量企业是否具有投资价值的重要标尺之一。也可理解为每年的现金分红收益率。
主要实行市场 | A 股 | 港股 | 美股 |
---|---|---|---|
名称差异 | CAS | IFRS | GAAP |
利润表 | 利润表 | 总额收益表全面 | CONSOLIDATED STATEMENTS OF COMPREHENSIVE INCOME/ Consolidated Statements of Operations and Comprehensive Income |
利润总额 | 利润总额 | 税前溢利/除税前盈利 | Income before provision for income taxes/ Income before income taxes/ Income before tax |
净利润 | 净利润 | ||
利润总额 | 利润总额 | 除税前盈利/除税前溢利/税前溢利 | Income before provision for income taxes/ Income before income taxes/ Income before tax |
归母净利 | 归母净利 | 母公司持有者之应占溢利/ 本公司权益持有人应占期内盈利 | Net income attributable to ordinary shareholders |
综合收益 | 综合收益 | 期内全面收益总额/ 期内综合收益总额 | Total comprehensive income/ Comprehensive income |
CAS | IFRS | |
---|---|---|
息税折旧及摊销前利润/ EBITDA | × | √ |
经调整息税折旧及摊销前利润/ Adjusted EBITDA | × | √ |
非国际财务报告准则之归母净利/ Non-IFRS net income attributable to shareholers | × | √ |
经调整溢利净额/ Adjusted Non-IFRS net income | × | √ |
√ 表示有,× 表示没有
以腾讯控股(HK00700) 202103 季报为例
栏目 | 金额 | 同比 |
---|---|---|
除税后溢利 | 579.85 | 80.19% |
归属母公司所有者净利润 | 565.17 | 78.71% |
非控股权益应占净利润 | 14.68 | 164.62% |
它代表腾讯控股及其控股子公司,赚取了 579.85 亿元净利润,由于子公司并非全部由上市公司 100%持股,因此有 14.68 亿元利润是属于子公司其他参股股东的。去掉非控股股东的权益后,属于上市公司全体股东的净利润 565.17 亿元。由于永续债在国际会计准则归类于权益不是负债,这个数字还要扣除掉永续债的利息,这点要特别注意。则 565.17-永续债的利息 为腾讯控股(HK00700)的净利润。
以上,仅作为个人备忘使用。
]]>距地球 4.3 光年,周围有三颗恒星,因为引力作用会被拉来拉去,有时特别冷有时特别热,三体人进化出了脱水的技能,可以在恶劣的环境休眠,靠1379 号三体人时刻检测宇宙信号。
三体文明中最底层的角色,后来收到叶文洁的信息,1379 号三体人不想侵略地球,告诉叶文洁不要回答,随即也被三体人发现并抓住,但不被杀死,想其活到地球失去一切希望的那一天,杀人诛心。
1947 年叶文洁出生于一对科学家家庭,父亲在文革期间不愿说谎认罪,遭到四个中学生女红卫兵用皮带抽打致死,母亲也疯了,叶文洁参加建设兵团,山林都砍伐为荒漠,此时遇到白沐林,二人成为知己。但被白沐林陷害出卖进看守所,之后拒绝签字承认父亲说谎,又受到严重迫害,这让她反人类的种子长成参天大树。叶文洁在红岸计划中发现,可以用太阳做放大镜强化功率,便发出了地球的信息,由1379 号三体人收到。后被上级领导发现,趁丈夫杨卫宁和上级领导下悬崖查看设备时,锯断绳索,二人坠崖身亡。丈夫死后叶文洁决定将革命进行到底。文革结束后,得以平反,走出深山,负责发射基地的选址项目,在西北地区碰到了伊文思。
记者,欲向中央反映:建设兵团不是在建设而是在破坏环境。但长期干重活无法写好字,于是叶文洁帮助重新抄了一份,后上级追查下来推卸责任出卖叶文洁,称信都是叶文洁写的,导致叶文洁被关进看守所。
叶文洁父亲的学生,把恰好符合红岸基地条件的叶文洁从看守所救出,并带进红岸计划。
寻找外星人以求得科技飞跃。
国外富二代,富甲一方植树造林,拯救环境无望。和叶文洁是三体地球组织创始人。在一艘巨轮上秘密创建第二红岸基地。
三体人派出,来封锁人类的基础科学。超级微型机器人,可以被加速到接近光速,制造过程是通过把复合粒子中的质子展开到二维,搭建集成电路,再收缩回三维质子大小。逼死很多科学家。
杨冬的男朋友。
研究纳米材料的教授,通关三体游戏,打入三体地球组织,发现叶文洁是三体组织的精神领袖,成功破获三体组织后,知道了第二红岸基地邮轮,开始古筝计划。
调查科学家自杀的警察,从丁仪[1]处拿到了杨冬的遗书。和汪淼合力破获三体组织。后攻破第二红岸基地时想出使用纳米细丝切割游轮,叫作古筝计划。
一艘巨轮,伊文思和三体人联络的移动据点,后被攻破,伊文思死。
在巴拿马运河,使用纳米细丝切割第二红岸基地,拿到和三体人的聊天记录。计划胜利,伊文思死,知道了两个情报:
- 智子[1]还是超级监视器; - 三体人唯一害怕和想要杀死的人类:罗辑。 同时三体人通过古筝计划借人类的手消灭地球叛军。
三体剩余飞船以 1%光速前来,所以需要四百年。
杨冬的高中同学,教社会学,叶文洁在杨冬墓前为罗辑指出两条宇宙社会学的公理: - 生存是文明的第一需要; - 文明不断增长和扩张,但宇宙的物质总量保持不变。
太空军成员,外人看来坚信人类必胜,被冬眠以参加末日之战。
利用三体不懂说谎的弱点,选出四位有能力的大骗子,拥有最高权力调集地球资源。
刚卸任美国国防部秘书长,研究核弹,破壁人:粉丝的身份进入家中拆穿面壁阴谋:表面建立一只太空神风敢死队,其实装备球状闪电,被打死的变成幽灵态,开战前把自己人打死变成幽灵态死磕三体军队,这种自杀式的方式不被人们接受,最终泰勒饮弹自尽。
委内瑞拉现任总统,研究军队,八年后得到 3.5 亿吨恒星型氢弹,破壁人:氢弹放到水星上,目的不是防御地球,而是引爆水星,把水星推进太阳里,地球火星金星脱离轨道坠入太阳,和三体人同归于尽。破壁人指出造不出 100 万颗恒星型氢弹,也不会使水星脱离轨道,被公众无法接受冒险行为,回国后被无数石头砸死。
世界知名脑科学家,研究脑科学,八年后研究出超级计算机,和妻子造出思想钢印。185 年后和罗辑参加听证会,不料会上妻子承认自己是破壁人,思想钢印全被打了负号,培养的全是人类必败的逃亡主义军。希恩斯并没有被制裁。
没有破壁人,只要罗辑自己想不明白,他就是自己的破壁人,啥都不研究且享受生活,让史强找到梦中女神庄颜。之后政府强迫冬眠妻子和孩子送到未来,让罗辑做点什么,此时失落坠入冰河,明白叶文洁话的含义:向宇宙发射了任意一颗恒星坐标,等待其爆炸,后被戏称为"罗辑的咒语",不料被治疗冻伤的医务人员投基因病毒而被迫进入冬眠。
中央美院国画系刚毕业大学生,在卢浮宫蒙娜丽莎的微笑前和罗辑坠入爱河。
人类掌握可控核聚变,基因改造。飞船能达到光速 15%,比三体的还快。恒星并没有爆炸,被称为“罗辑的咒语”。大多数人生活在地下。一个世纪前就消灭了叛军组织。唤醒了罗辑和希恩斯,终止面壁者计划。由于思想钢印被加负号,唤醒大批军官任命执行舰长,包括章北海。
三体小型水滴探测器,水滴状,进入太阳系,派出全部 2015 搜恒星级战舰进行拦截,表面光滑无比,放大 1 千万倍还是光滑无比,直击战舰的核燃料仓,几乎毁灭所有战舰,称为末日之战。战后飞向太阳,发射干扰信号,无法通过太阳作为放大天线发射信号。
舰长是漂亮的女生东方延续,章北海是个伪装的很好的失败主义者,接过执行舰长指挥权后跑路了。追击它的四艘战舰:蓝色空间号、企业号、深空号、终极规律号,恰好躲过了水滴的毁灭。
追击自然选择号,五艘战舰开始寻找太阳系的新家园,但燃料只够一艘船,导致了黑暗战役:章北海在准备发动次声波氢弹前被终极规律号先行攻击,只有蓝色空间号抽干了空气躲过终极规律号的攻击,并集火摧毁了终极规律号,打扫战场后开启了寻找新家园的征程,后在威慑纪元被告知不要返航,并被万有引力号和两颗水滴追击,后通过四维碎块破坏水滴控制万有引力号,继续和万有引力号寻找新家园,并建立了四个世界。
量子号附近,和量子号在战斗中侥幸逃离,后攻击量子号,携带物资及尸体为食物飞往太阳系另一边。在威慑纪元收到地球人和三体已建立威慑的消息,返航地球,后被以一级谋杀和反人类罪被判刑,押送途中史奈德连线蓝色空间号不要返航。
杨冬的男朋友,已 83 岁,德高望重的科学家。选中第一个接触三体水滴探测器。
不会损伤船体,但会消灭船内所有生命,靠空气发挥杀伤力。
被诅咒的恒星被光粒击爆,罗辑恢复面壁者身份,提出黑暗森林法则:宇宙的总质量是恒定的,但宇宙无数外星生命在以指数增长,所以宇宙是一个生死局,不是你死就是我亡,一方的收益意味另一方损失,双方不存在合作的可能,或者自己文明的未来就是建立在他人文明的痛苦之上,只有损人利己才能活下去,其结果就是一方吃掉另一方,所以每个文明都必须为了生存而战,再弱小的文明都可能技术爆炸。为了防止技术爆炸最保险的方法就是发现了一个文明后,能消灭的就直接消灭,打不过的就绕着走,只要暴露自己的位置,就会被其他文明干掉。
罗辑无计可施,堕落后政府强迫其负责雪地项目:通过恒星氢弹和海王星的油膜物质制造太空尘埃,以便在其余 9 个水滴到达前可以提前观测到,被认为是没事找事的项目。后来人们明白罗辑的雪地项目是在逃避责任,项目被停止,罗辑的面壁人身份被废。落魄的罗辑自杀式对话三体人,指出手腕上的手表是检测生命体征的摇篮系统,摇篮系统会引爆雪地工程的 3614 颗恒星氢弹,形成巨大无比的油膜,通过太阳反射出三张简单的图案:三体星球的坐标。至此雪地项目成功,与三体人形成对立之势,进入威慑纪元,并要求:
三体人帮地球人建造引力波天线,可以不通过太阳直接向宇宙发射信号,人类帮智子制造了日本人的身体。
身患绝症,姐姐希望他早点安乐死,这时胡文赠予云天明 300 万,用这笔巨款通过联合国的群星计划买了一颗太阳系外的恒星匿名送给了程心。后安乐死时被程心阻止。
云天明大学同学,通过云天明的点子:矿泉水里塞野草做饮料,创业成功。
云天明的大学暗恋同学,提出 1%光速探测器的阶梯计划思路。当时并不知道送星星的人是云天明,后探测器偏离轨道,程心被冬眠到未来跟进阶梯计划。威慑纪元 61 年被唤醒,成为执剑人后,被三颗水滴攻击,后心灰意冷也移民到澳大利亚。
程心提出:只有辐射帆,引爆在轨道布置的核弹,使飞船不断加速。体积小只能放入被急冻的人脑,设想通过三体人恢复其中大脑为人形,来进行卧底,最终选择云天明的大脑,称为阶梯计划,用来探测三体舰队的情报。
程心在情报局工作时的局长,雷厉风行,口号:前进,前进,不择手段的前进。想造一枚 1%光速的探测器。
三体人面对威慑,表现出来的不是憋屈,不是不情愿,而是非常真诚,但又不停的寻找打破这种平衡的可能,差点搞定人类。
三体人帮助建立引力波飞船。
发现程心的恒星周围有两颗行星,其中一颗和地球相似,成为程心的好友和助手。当时已具备星际远航的能力,政府想买下行星而唤醒程心。
维德伪造艾 AA声音想杀死程心,维德想成为执剑人而程心会阻止他,不料话多被赶来的艾 AA和警察击断手臂。
4 个引力波天线:亚洲、北美、欧洲、太空中的万有引力号,执剑人掌握引力波天线向宇宙发射三体星坐标的权利,毁灭三体星,但地球也会暴露,罗辑是第一任执剑人,在这个温柔的年代,大家选择程心作为下一任执剑人,但只做了 15 分钟执剑人,被潜伏在太阳系的水滴攻击,但程心决定扔掉控制器,水滴击毁三座引力波天线。
因为三体人一直没有教给人类光速飞行技术,四年后三体新舰队会到达地球。三体人打破了两个文明脆弱的平衡,并要求一年内全部移民到澳大利亚,温柔乡的人们被屠杀 30 万。
一年后管理被智子切断食物等必需品的聚集在澳大利亚的 41 亿 6 千万人,10 亿人报名最终通过 500 万治安军。
蓝色空间号被万有引力号和水滴追击进入了四维碎块,看三维的物体就像看细节丰富的展开图,如果在四维空间拿走三维空间里一个人的心脏,不会有丝毫损伤,通过四维空间破坏水滴的内部,随后控制了万有引力号,商议后认为三体人已经占领地球撕毁和平协议,决定发送三体恒星的坐标,大移民后第六天三体星被光粒穿过,三体文明开始了星舰文明,从此进入了广播纪元。
三体星和地球长时间通讯,地球被摧毁只是时间问题,智子放弃移民计划,苦难结束,AA把程心公司打理成太空建筑业巨头,四年后人们观测到三体星的毁灭,威胁人类近三个世纪的三体世界被摧毁。
智子邀请程心和罗辑喝茶,罗辑问:在宇宙中存不存在一种安全声明,可以避免黑暗森林打击,智子回答有。三体人以平淡的茶会结束了 300 多年的对决。
当初阶梯计划一直被三体人关注,第一批三体舰队被罗辑威胁被迫离开太阳系后,去寻找了云天明的大脑,且被三体人复原了身体,云天明要见程心,但被三体人要求不允许提及三体文明,否则引爆程心的飞船。给程心讲了三个故事。程心说,宇宙很大生活更大,并邀请云天明在星星上见面。 通过反复解读三个故事得出:
避免被光粒打击开始此工程,以木星土星天王星和海王星为掩体,在背阳面建造太空城。
会在太空中留下印记,易被高级文明观测到;人权意识强,由于少部分人能离开,造光速飞船成为严重违法的行为。
明白光速飞船才是出路,告诉程心不要犯第二次错误,说服程心把公司资源给维德,但程心要求危害人类时收回权力,程心和AA进入冬眠等待成果。
群落在四大行星背阳面建成,地球成为太阳系联邦的普通城邦,如果被打击将无一幸免的地球只有 500 万人。唤醒沉睡 62 年的程心,劝服小有成就且被政府军包围的维德,维德恳求程心再考虑一下,无奈程心坚定,维德道:失去人性失去很多,失去兽性失去一切。程心选择了人性,被捕的维德被处死,程心想到未来看太阳系被打击后的世界,再次进入冬眠。
二向箔:宇宙规律之一。高级文明中很常见,一旦展开就会无限扩展,所以总有一天在一百多亿年前起码有十维的宇宙都会二维化。大神级文明在研究如何活在二维化的世界里和重启宇宙。宇宙经过星际战争变成现在的三维。
一艘任务是藏好自己清理低级文明的外星飞船发现太阳系,称为歌者的底层工作人员,把称为二向箔的武器扔向太阳系,像一张信用卡大小的纸条,越来越大,所有接触到的物体都遭到降维打击,从三维降到二维,成了一张画,只有光速才能逃离二向箔的引力,以此速度 8-10 天太阳系将变成没有厚度的画。
程心和AA被唤醒,在冥王星的太空博物馆,找到了快 200 岁的罗辑,罗辑留下了蒙娜丽莎的微笑,且决定留在博物馆,其余名画被程心和AA带到星环号飞船,用来保留地球文明。
使用和水滴一样的坚固材料,装备完整的生态系统和常规引擎,及曲率驱动引擎可进行光速航行,太阳系唯一的一艘光速飞船。由当初面壁者雷迪亚兹在水星上做爆炸试验炸出的大坑中及维德余党,后又有联邦政府悄悄研究的光速飞船。被同样在逃亡的人拦截攻击,但无效,如果当时程心没有阻止维德,光速飞船是可以批量生产的,程心还是犯了第二个错误,飞往云天明送的星星。
万有引力号研究员,五年前被唤醒,收到星环号的引力波信号,留在云天明送的恒星周围的一颗类似原始地球的蓝色行星上,等待程心和AA。后讲解宇宙中最厉害的武器是:宇宙规律。
关一帆的飞船,关一帆发现附近灰色行星有飞船踪迹,和认为是云天明的程心一同前往,但遇到了高等文明留下的死线。关一帆立即返航,这时收到AA的连线:云天明来了。见面说吧,遂关闭通讯。无奈此时死线破裂,他俩进入时间真空,16 天后才回到蓝星,但蓝星已经过了 1890 万年,程心永远错失了云天明。
极其的黑,任何东西进去都出不来,包括光,死线随时会破裂,破裂后会形成低光速带,称为黑域,在这片区域光会变得非常慢。
云天明送给程心小宇宙,可在里面躲过坍缩,去新的世界,有一扇由微亮细线画出来的门,是面积一立方千米的循环空间,在这里遇到了老朋友智子,这里时间非常慢,只需十年外面的宇宙就会重启。后收到大神级文明的广播:请把小宇宙归还到大宇宙,大宇宙因为偷走的质量会在膨胀中死去。后被归还,但留下了 5 公斤里面养着小鱼的生态球,随后乘坐小宇宙中融合了三体最强技术的飞船飞向大宇宙。
在浩瀚的宇宙中,永恒的只有死神。
]]>本文内容为摘抄名侦探拳头视频生成。禁止转载。
Match Extensions
Target Extensions
)暂只梳理常见项,待更新。注: 请结合Usage
中table、chain、rule-specification和options的位置阅读本文,更易食用。
iptables [-t table] -[ACD] chain rule-specification [options] |
filter
默认表值nat
当创建新连接的数据包时mangle
该表用于专门的数据包更改raw
该表主要用于与NOTRACK目标一起配置免除连接跟踪的功能。它在具有优先级的netfilter钩子处注册,因此在ip_conntrack或任何其他IP表之前被调用filter
表中的ChainINPUT
用于发往本地套接字的数据包FORWARD
用于通过盒子路由的数据包OUTPUT
用于本地生成的数据包nat
表中的ChainPREROUTING
用于在进入后立即更改数据包OUTPUT
用于在路由之前更改本地生成的数据包POSTROUTING
用于在即将外出时更改数据包mangle
表中的ChainPREROUTING
用于在路由之前更改传入的数据包OUTPUT
用于在路由之前更改本地生成的数据包INPUT
用于进入的数据包FORWARD
用于更改通过包装箱路由的数据包POSTROUTING
用于更改将要包装的数据包raw
表中的ChainPREROUTING
用于通过任何网络接口到达的数据包OUTPUT
用于由本地进程生成的数据包规则是以列表的方式被加进每条链。
每个包会从第一条规则开始检查,直至最后一条。假若包与其中一条规则吻合,相应的Target便会被执行,例如接纳(ACCEPT)或丢弃(DROP)包。
一旦有吻合的规则,这个包便会按照规则来处理,而不再被链内的其它规则所检查。假如包通过所有检查而不符合任何规则链内的任何一条规则,那这条链的缺省动作将会被执行,这就是所谓的缺省政策(-P, --policy
),可以设置为接纳(ACCEPT)或丢弃(DROP)封包。
防火墙规则指定数据包的标准和target(目标),如果数据包不匹配,则检查链中的下一个规则;如果匹配,则下一个规则由目标值指定,该值可以是用户定义chain的名称,也可以是特殊值: - ACCEPT
- DROP
- QUEUE
- RETURN
以及后面详细介绍的Target Extensions
-A, --append chain rule-specification
增加-I, --insert chain [rulenum] rule-specification
插入-D, --delete chain rule-specification/rulenum
删除-F, --flush [chain]
冲洗选定的链(如果没有指定chain,代表表中的所有链)。这等效于删除所有规则。-R, --replace chain rulenum rule-specification
替换-L, --list [chain]
列出所选链中的所有规则-Z, --zero [chain]
将选定的链(如果没有指定chain,代表表中的所有链)的数据包和字节计数器清零。同样指定-L,-- list(list)选项是合法的,以便在清除计数器之前立即查看它们。-N, --new-chain chain
通过给定名称创建一个新的用户定义链。该名称必须没有target。-X, --delete-chain [chain]
删除指定的用户自定义链,不能有规则引用该链,必须删除或替换引用规则,然后才能删除链。如果未提供任何参数,它将尝试删除表中的每个非内置链。-P, --policy chain target
为链的政策设置为某个Target,如iptables -P INPUT DROP
指将INPUT
链的缺省政策改为DROP
-E, --rename-chain old-chain new-chain
重命名链以下参数构成rule-specification(规则)规范(在添加,删除,插入,替换和追加命令中使用)。 - -p, --protocol [!] protocol
规则中要匹配的数据包的协议,protocol可以是tcp
udp
icmp
或all
,缺省all
,还允许/etc/protocols
中的协议名称,0
代表all
,!
代表反转,! tcp
代表匹配非tcp协议 - -s, --src, --source [!] address[/mask]
规则中要匹配的数据包的来源,address可以是网络名称、主机名、网络IP地址(带有/mask)或纯IP地址。该mask(掩码)可以是网络掩码,也可以是纯数字,如24
等效于255.255.255.0
- -d, --dst, --destination [!] address[/mask]
规则中要匹配的数据包的目的地,参考-s
- -j, --jump target
指定规则的目标,即,如果数据包匹配,该怎么办。target可以是用户定义的链(此规则所在的链除外),可以是立即确定数据包命运的特殊内置Target
之一(如ACCEPT
),也可以是Target Extensions
。如果在规则中省略了此选项(并且未使用-g),则匹配规则将不会影响数据包的命运,但是规则上的计数器将增加。 - -g, --goto chain
这指定处理应按用户指定的链继续进行。与--jump
选项不同,--goto
将return且不在此链中继续处理,而--jump
是在链中继续处理。 - -i, --in-interface [!] name
接收数据包所通过的接口的名称(仅适用于INPUT
,FORWARD
和PREROUTING
链的数据包),如-i eth0
。!
代表取反,+
结尾代表任何以该名称开头的接口都将匹配。如果省略此选项,则任何接口名称都将匹配。 - [!] -f, --fragment
这意味着该规则仅引用分段数据包的第二个片段和之后的片段,由于片段无法告知此类数据包(或ICMP类型)的源端口或目标端口,因此此类数据包将不匹配任何指定它们的规则,! -f
时该规则将仅匹配头片段或未分段的数据包。 - -c, --set-counters PKTS BYTES
这使管理员可以初始化规则的包和字节计数器(在INSERT,APPEND,REPLACE操作期间)。 - -v, --verbose
详细输出。此选项使list命令显示接口名称,规则选项(如果有)和TOS掩码。对于追加,插入,删除和替换,这将导致打印有关一个或多个规则的详细信息。 - -n, --numeric
数值输出。显示IP地址和端口号,默认情况下,程序将尝试将它们显示为主机名,网络名或服务。 - -x,--exact
显示数据包和字节计数器的确切值,此选项仅与-L
命令有关。 - --line-numbers
列出规则时,将行号添加到每个规则的开头,对应于该规则在链中的位置。 - --modprobe=command
在将规则添加或插入到链中时,请使用command
来加载任何必要的模块(Target
,Match Extensions
等)
扩展的数据包匹配模块,两种方式 - -p, --protocol
隐式地,如-p icmp
则会加载icmp
扩展 - -m, --match module
加载特定module模块
可以在一行中指定多个扩展匹配模块,详情见: Match Extensions
account
定义网络/mask中所有主机的帐户流量iptables -A FORWARD -m account --aname mynetwork --aaddr 192.168.0.0/24 |
icmp
如果指定了--protocol icmp
,则会加载此扩展名。它提供以下选项:--icmp-type [!] typename
这允许指定ICMP类型,该类型可以是数字ICMP类型,也可以是命令显示的ICMP类型名称之一。 如: iptables -p icmp -h
mport
此模块与一组源端口或目标端口匹配。最多可以指定15个端口。它只能与-p tcp或-p udp结合使用。--sports, --source-ports port[,port[,port...]]
如果源端口是给定端口之一,则匹配。--dports, --destination-ports port[,port[,port...]]
如果目标端口是给定端口之一,则匹配。--ports port[,port[,port...]]
如果源端口和目标端口彼此相等并且与给定端口之一相等,则匹配。multiport
此模块与一组源端口或目标端口匹配。最多可以指定15个端口。端口范围(port:port)被视为两个端口。它只能与-p tcp或-p udp结合使用。--sports, --source-ports [!] port[,port[,port:port...]]
如果源端口是给定端口之一,则匹配。--dports, --destination-ports [!] port[,port[,port:port...]]
如果目标端口是给定端口之一,则匹配。--ports [!] port[,port[,port:port...]]
如果源端口或目标端口等于给定端口之一,则匹配。osf
操作系统--log 1/0
--smart
如果存在,则OSF将使用一些智能来确定远程OS。仅当连接源位于我们的本地网络中时,OSF才会使用初始TTL。--netlink
如果存在,则OSF还将通过netlink NETLINK_NFLOG组1记录所有事件。--genre [!] string
通过被动指纹匹配操作系统类型iptables -I INPUT -j ACCEPT -p tcp -m osf --genre Linux --log 1 --smart |
set
该模块包含可以由ipset
定义的IP集。--set setname flag[,flag...]
其中的flag是src
dst
,并且最多可以包含六个iptables -A FORWARD -m set --set test src,dst |
state
与连接跟踪结合使用时,此模块允许访问此数据包的连接跟踪状态。--state state
其中state是要匹配的连接状态的逗号分隔列表INVALID
由于某种原因无法识别该数据包,,包括内存不足和与任何已知连接都不对应的ICMP错误ESTABLISHED
已建立连接NEW
已开始新连接,但还没有建立连接RELATED
正在开始新的连接,但与现有连接相关联,例如FTP数据传输或ICMP错误。string
该模块通过使用某些模式匹配策略来匹配给定的字符串。它需要> = 2.6.14的Linux内核。--algo bm|kmp
选择模式匹配策略。--from offset
设置从其开始寻找任何匹配的偏移量。如果未通过,则默认为0。--to offset
设置从其开始寻找任何匹配的偏移量。如果未通过,则默认为数据包大小。--string pattern
匹配给定的模式。--hex-string pattern
以十六进制表示法匹配给定的模式。tcp
加载tcp扩展--sport, --source-port [!] port[:port]
源端口。还可以使用port:port格式指定包含范围,如果省略第一个端口,则假定为"0",如果省略最后一个,则假定为"65535",如果第二个端口大于第一个,则将交换它们。
--dport, --destination-port [!] port[:port]
目标端口,参考--sport
--tcp-flags [!] mask comp
当TCP标志如指定时则匹配。mask是我们应该检查的标志,写为以逗号分隔的列表,comp是必须设置的以逗号分隔的标志列表。标志为:SYN ACK FIN RST URG PSH ALL NONE。
示例:
仅匹配设置了SYN标志且未设置ACK,FIN和RST标志的数据包 iptables -A FORWARD -p tcp --tcp-flags SYN,ACK,FIN,RST SYN
[!] --syn
仅匹配SYN位置为1,且ACK,RST和FIN位为零的TCP数据包。匹配后,阻止此类数据包进入接口,将阻止传入的TCP连接,但传出的TCP连接将不受影响。它等效于--tcp-flags SYN,RST,ACK,FIN SYN
。! --syn
代表含义反转。
--tcp-option [!] number
如果设置了TCP选项,则匹配。参见: TCP options Number列
--mss value[:value]
将TCP SYN或SYN/ACK数据包与指定的MSS值(或范围)匹配,以控制该连接的最大数据包大小。
udp
加载udp扩展--sport, --source-port [!] port[:port]
源端口,参考TCP部分--dport, --destination-port [!] port[:port]
目标端口,参考TCP部分DNAT
端口转发。该Target仅在nat表,PREROUTING和OUTPUT链以及仅从那些链中调用的用户定义的链中有效。它指定应修改数据包的目标地址(并且此连接中所有将来的数据包也将被修改),并且规则应停止检查。
--to-destination ipaddr[-ipaddr][:port-port]
它可以指定一个新的目标IP地址,一个IP地址(包括端值)范围和一个可选的端口范围(仅在规则还指定-p tcp或-p udp时才有效)。如果未指定端口范围,则将永远不会修改目标端口。 示例: iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 3124 -j DNAT --to-destination 192.168.100.10:3000 |
REDIRECT
: iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080 |
SNAT
源NAT,该Target仅在POSTROUTING链的nat表中有效。它指定应修改数据包的源地址(此连接中所有将来的数据包也将被修改),并且规则应停止检查。
--to-source ipaddr[-ipaddr][:port-port]
它可以指定一个新的源IP地址,一个IP地址(包括端值)范围和一个可选的端口范围(仅在规则还指定-p tcp或-p udp时才有效)。如果未指定端口范围,则低于512的源端口将被映射到低于512的其他端口:介于512和1023之间(包括512和1023之间)的那些端口将被映射到低于1024的端口,而其他端口将被映射到1024或更高版本。尽可能不发生端口更改。DNAT VS SNAT
SNAT
修改的是Source的地址,也就是要在路由后修改Source,故而配置POSTROUTING规则DNAT
修改的是Destination的地址,也就是在路由前修改Destination,故而配置PREROUTING规则,详情见规则链图。MASQUERADE
源NAT,该Target仅在POSTROUTING链的nat表中有效。它仅应与动态分配的IP(拨号)连接一起使用:如果您具有静态IP地址,则应使用SNAT。伪装等同于指定到数据包出接口的IP地址的映射,但是还具有当接口断开时会忘记连接的效果。当下一次拨号不太可能具有相同的接口地址(因此,无论如何建立的连接都会丢失)时,这是正确的行为。
--to-ports port[-port]
这指定了要使用的源端口范围,将覆盖默认的SNAT源端口选择启发式方法(请参见上文)。仅当规则还指定-p tcp或-p udp时,此选项才有效。SNAT VS MASQUERADE
这两个Target都在POSTROUTING链的nat表中提供源NAT(或SNAT)。
差异性:
--to-source
,因为它可以用于动态分配的IP地址。重要说明:仍然可以将MASQUERADE目标与静态IP 一起使用。请注意,这将增加额外的开销(用于计算)。
示例:
# stick to SNAT for static IP
iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -o eth0 -j SNAT --to-source ELASTIC_IP
# don't be lazy, simple but slower due to more overhead
iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -o eth0 -j MASQUERADE
MARK
这用于设置与数据包关联的netfilter标记值。它仅在mangle表中有效。例如,它可以与iproute2结合使用。
--set-mark mark
REDIRECT
跳转。 该Target仅在nat表,PREROUTING和OUTPUT链以及仅从那些链中调用的用户定义的链中有效。通过将目标IP更改为传入接口的主地址,它将数据包重定向到计算机本身(本地生成的数据包映射到127.0.0.1地址)。
--to-ports port[-port]
这指定了目标端口或端口范围(使用:),没有此端口,就永远不会更改目标端口。仅当规则还指定-p tcp或-p udp时,此选项才有效。REJECT
驳回。这用于响应匹配的数据包时发回错误数据包,否则,它等效于DROP,因此它是终止Target,结束规则遍历。该目标仅在INPUT,FORWARD和OUTPUT链以及仅从这些链调用的用户定义的链中有效。以下选项控制返回的错误包的性质:
--reject-with type
它返回适当的ICMP错误消息。type可以是REJECT
发送一个TCP RST在应答数据包。TCP RST数据包用于正常关闭打开的TCP连接。TARPIT
困住连接放到坑里。接受连接,但立即切换到持久状态(0字节窗口),在该状态中,远程端停止发送数据并要求每60-240秒继续一次。尝试关闭连接的尝试将被忽略,从而迫使远程端在12-24分钟内使连接超时。
错误想法:TARPIT
可用于浪费攻击者的资源,从而减慢攻击速度并降低其攻击其他主机的能力,用来处理(D)DoS。
考虑一下:
攻击者每秒创建数千个服务器连接,如果每个连接都被困在tarpit中,则这些连接将如何影响服务器。您的服务器将快速消耗其所有可用资源(或文件句柄),从而不再允许连接。这比仅关闭连接还差。暂时放下犯罪者(REJECT)比尝试占用他的资源(TARPIT)要好。
https://linux.die.net/man/8/iptables
开关防火墙 - 重启后生效 开启: chkconfig iptables on
关闭: chkconfig iptables off
service iptables start
关闭: service iptables stop
来自《鸟哥私房菜》
无Mangle的表更加常用,分析起来逻辑更加清晰。
]]>settings.json
,对于新手或者不喜欢折腾的朋友可能不太方便,所以就有了做一个主题插件的想法,以下为成品笔记。npm install -g yo generator-code |
yo code |
package.json
修改 icon
keywords
bugs
homepage
repository
badges
__metadata
会随着VSCode加载自动设置,前提插件在~/.vscode/extensions
下
详细修改见我的源码: vscode-theme-green-eyecare
json
文件确保json文件以
.color-theme.json
结尾,此类文件VSCode
会有输入提示
不确定某个地方的textmate scopes
写法时,可通过F1
或cmd+shift+p
打开快捷指令,输入: 开发人员: 检查编辑器标记和作用域
(英文名字: Inspet Editor Token And Scopes
)打开textmate scopes
提示,一般输入前几个字母就会出来结果,如下图
然后点击任意位置,就会有相应textmate scopes
的提示弹出,如下图
如果希望知道制作一个主题需要修改哪些textmate scopes
,欢迎参考我的源码: vscode-theme-green-eyecare
安装打包发布工具 npm i vsce -g
登录AzureDevOps,右上角创建Personal access tokens
,复制供下一步使用
生成时一定要注意,否则会报401
Organization
一定要选All accessible organizations
Scopes
一定要选Full access
新增发布者(会要求输入Personal access tokens
) vsce create-publisher 自定义
修改package.json
中的publisher
发布 vsce publish
package.json
中的version
vsce publish |
Enjoy!
]]>Closure Compiler
是Google的项目,可以用来压缩js代码,并且生成便于调试的source map
文件
只需将source map
文件名配置在压缩后的js文件中(我放在了文件末尾),浏览器会同时自动加载此文件,当min.js报错时,会准确的提示错误行数等信息
配置写法如下图
Github地址: Closure Compiler
官网地址:https://developers.google.com
官方提供了多种食用方式,如下简单介绍(我使用的compiler.jar
方式)
有一个简陋的WEB界面,适合快速使用,缺点是无法生成Source Map文件,最起码我没找到如何生成
地址: http://closure-compiler.appspot.com
贴入待压缩的代码,直接点Compile
,等待压缩完成,右侧会出现压缩后的效果,如下图
可见压缩效率还是很高的 - 原始大小: 72.18KB(服务器开启gzipped后大小: 16.05KB) - 压缩后大小: 43.77KB(服务器开启gzipped后大小: 12.18KB)
直接右键另存为default.js,下载到本地即可
缺点也是无法生成Source Map文件
大概介绍下常用的参数
必须
js_code
或 code_url
: 直接传js代码,或者代码url 如:https://raw.githubusercontent.com/vuejs/vue/dev/dist/vue.jsoutput_format
: 接口返回类型,支持xml
json
text
,推荐json
output_info
: 接口返回哪些信息,支持如下选项compiled_code
直接返回代码压缩结果warnings
返回异常errors
返回错误statistics
返回压缩比例等信息compilation_level
: 压缩和优化程度,支持如下选项WHITESPACE_ONLY
只从JavaScript中删除空格和注释SIMPLE_OPTIMIZATIONS
仅重命名局部变量(默认值)ADVANCED_OPTIMIZATIONS
最高级别的压缩选填
formatting
: 额外格式化要求,支持如下选项pretty_print
添加换行符和缩进,使代码易于阅读print_input_delimiter
编译多个文件时,每个文件添加一个分隔符(如: // Input 0)output_file_name
: 格式化后文件名language
: 检查代码中的错误时采用的ECMAScript版本API文档地址: https://developers.google.com
接口请求示例截图:
接口响应结果: {
"statistics": {
"originalSize": 342146,
"originalGzipSize": 90206,
"compressedSize": 118064,
"compressedGzipSize": 44353,
"compileTime": 11
},
"outputFilePath": "/code/jscd3ab9d60c123c4a2c7554ff5be0ca42b/default.min.js"
}
compiler.jar
此方法的前提是本地有java环境,环境安装方式我就不在这里细说了
优点是可以生成SourceMaps文件
jar包下载地址: compiler-latest.zip
基本用法 java -jar compiler.jar --js hello.js --js_output_file hello.min.js --create_source_map hello.min.js.map --source_map_format=V3
--js
指定待压缩的js文件 - --js_output_file
压缩后js文件名及位置 - --create_source_map
压缩后SourceMap的文件名及位置 - --source_map_format
SourceMap的版本
详细参数可查看help java -jar compiler.jar --help
最后需要在Chrome的DevTools->Settings,勾选Enable JavaScript source maps
和Enable CSS source maps
,如下图
npm install terser -g |
terser js/file1.js js/file2.js -o foo.min.js -c -m --source-map "filename='foo.min.js.map',root='http://foo.com/src',url='foo.min.js.map'" |
-c, --compress [options]
启用压缩器/指定压缩器选项:pure_funcs
(列出没有使用的可以安全删除的方法)-m, --mangle [options]
混淆--mangle-props [options]
指定混淆选项builtins
使用标准的JavaScript全局变量和DOM APIdebug
添加调试前缀和后缀keep_quoted
仅混淆未加引号的属性regex
仅混淆匹配的属性名reserved
不混淆的名称列表示例
-m reserved=['$','require','exports']
-b, --beautify [options]
美化preamble
前言,设置开头内容,您可以用它来插入备注、许可信息示例。quote_style
引号的样式wrap_iife
将IIFE括在括号中,注意:你可能需要压缩选项negate_iife
为true
wrap_func_args
将函数参数括在括号中示例
-b "preamble='/**\n * remarkable\n * Minified by Terser.\n * Original file: https://cdn.jsdelivr.net/npm/remarkable@2.0.0/dist/remarkable.js\n * @link https://github.com/jonschlinkert/remarkable\n * @version 2.0.0\n * @license MIT\n * \n */'"
-o, --output <file>
输出文件路径(默认输出到屏幕)--comments [filter]
在输出中保留版权备注false
省略所有备注/foo/
or /^!/
,仅保留匹配的备注--source-map [options]
启用source-mapbase
基础路径,根据输入文件的相对路径content
使用现有source-map文件,如果有配置//# sourceMappingURL
则可以使用inline
filename
输出source-map文件的名称及路径root
source-map的原始路径url
如果定义,则会增加到//# sourceMappingURL
示例
--source-map "filename='foo.min.js.map',root='http://foo.com/src',url='foo.min.js.map'"
]]>详情见Github主页: https://github.com/terser/terser
sm.ms
拉黑了(起初我调接口用的不是很正规的方法)小程序上传文件服务器要求域名备案,无奈sm.ms没有备案的马甲,改吧。
然后就有了下边这个流程,并记录下其中遇到的坑
Promise
代码,当伪代码看吧// 选择图片 |
const UPLOAD_PATH = 'upload/images/' |
const cloudResult = await wxcloudcallFunction({ |
const file = await cloud.downloadFile({ |
不知从何时,sm.ms接口出了v2版,上传图片需要token,而且旧版接口已废弃
// 取得smms token |
// 封装的request模块 |
在最后上传这里,出了很多问题,有以下几点要注意: - request模块的POST
multipart/form-data
方式,传参需要放到formData
中(而不是form
),否则Content-Type
会一直是x-www-form-urlencoded
,即使你在headers
设置了'Content-Type': 'multipart/form-data'
实际发出的请求头依然为application/x-www-form-urlencoded
。 - 搜索引擎搜到的上传File对象的代码基本都是下面这样 formData: {
smfile: fs.createReadStream(path)
}fs.createReadStream(path[, options])
- path <string> | <Buffer> | <URL>
- options <string> | <Object>
...path
支持路径
或者Buffer对象
或者URL
,然而当你直接把云存储下载得到的文件Buffer
对象作为入参时,像下边这样 formData: {
smfile: fs.createReadStream(file.fileContent)
}The "path" argument must be of type string without null bytes.
Buffer
非彼Buffer
,它期望的是缓冲区形式的路径,而不是数据缓冲区,详情见issues。 - 使用fs.writeFileSync(path, file.fileContent)
保存文件到云函数本地,再通过fs.createReadStream(path)
是不是就可以了? 然而,经测试,云函数文件环境为Read-only,并不能成功保存文件😂。 - 直接使用Buffer
对象上传时,像下边这样 formData: {
smfile: file.fileContent
}{"success":false,"msg":"Invalid file source"}
formData: {
smfile: {
value: file.fileContent,
options: {
filename: `图片${file_ext}`
}
}
}options
的方式手动提供"文件"相关的信息,request
的官方文档也有此方式的详细介绍。
好了,终于传上去了🏆🍻🍗
]]>PHP调用后台接口时,指定使用POST
的x-www-form-urlencoded
方式。
首先想到的是传个header
指定一下,搜索了一下发现的确是这样,设置CURLOPT_HTTPHEADER
即可 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
Google
一下这两者的区别吧。
post的两种Content-Type
header
的区别 - x-www-form-urlencoded 普通键值对 - form-data 要传输二进制(非字母数字)数据,例如文件
关于这两者的区别,stackoverflow有详细的解释,这里摘抄以下两段。
Summary; if you have binary (non-alphanumeric) data (or a significantly sized payload) to transmit, use multipart/form-data. Otherwise, use application/x-www-form-urlencoded.
摘要;如果您要传输二进制(非字母数字)数据(或有效载荷大小很大),请使用multipart/form-data
。否则,请使用application/x-www-form-urlencoded
。
The content type "application/x-www-form-urlencoded" is inefficient for sending large quantities of binary data or text containing non-ASCII characters. The content type "multipart/form-data" should be used for submitting forms that contain files, non-ASCII data, and binary data.
内容类型application/x-www-form-urlencoded
对于发送大量二进制数据或包含非ASCII字符的文本效率不高。内容类型multipart/form-data
应用于提交包含文件,非ASCII数据和二进制数据的表单。
重点来了
对于application/x-www-form-urlencoded
,发送到服务器的HTTP消息的主体实质上是一个巨大的查询字符串(键值对用=
分隔,多个键值对用&
分隔),即这种 title=aaa&name=bbb
x-www-form-urlencoded
的方式,CURLOPT_POSTFIELDS
应该传如下这种使用http_build_query()
转化后的内容 curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array('title' => 'aaa', 'name' => 'bbb')));
curl_setopt($ch, CURLOPT_POSTFIELDS, array('title' => 'aaa', 'name' => 'bbb'));
header
。
node-modules/.bin/hexo g
的简写形式(npx hexo g
)--inspect
Node.js 进程开始侦听调试客户端 > - 默认侦听127.0.0.1:9229
> - 支持--inspect=[host:port]
--inspect-brk
Node.js 进程开始侦听调试客户端,并停在第一行 > - 默认侦听127.0.0.1:9229
> - 支持--inspect-brk=[host:port]
很多时候,项目运行的命令基本都放在了package.json
.scripts
中,开启调试只需要为node增加--inspect-brk
参数即可,例如 {
"name": "hexo-site",
"version": "0.0.0",
"private": true,
"scripts": {
"debug": "node --inspect-brk=5858 index.js",
"build": "node index.js"
}
}--inspect-brk
指定项目运行时监听的VSCode端口,需要与VSCode中launch.json
.configurations.port
配置一致
{ |
request
必须
launch:调试整个项目时,attach:调试单个文件runtimeExecutable
必须
配置为npmruntimeArgs
必须
配置npm的参数数组,[0]:固定值run-script,[1]:package.json
.scripts
中的值,实际结果就是在执行npm run debug
port
必须
调试客户端运行的端口console
可选
使用VSCode内置终端stopOnEntry
可选
停在首行
例如,项目安装了hexo
npm install hexo
那么package.json
.scripts
使用如下内容,是否可以开启调试呢? {
"name": "hexo-site",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "hexo g --inspect-brk=5858"
}
}npm run build
时,即运行hexo g --inspect-brk=5858
。
此时你会发现VSCode的调试并没有正常连接,因为--inspect
参数是属于node的
所以需要把hexo g
转为node的形式,并附带--inspect
参数,那么问题来了。
只看结果,过程不看版: npx --node-arg=--inspect-brk=5858 hexo g
/usr/local
- 在Windows上,为%AppData%/npm
- 在Lixux系统上,为/usr/local/bin
> 通过此命令查看配置npm config get prefix
{prefix}/lib/node_modules
中{prefix}/bin
中{prefix}
,它将使用当前包的根目录// 查看包全局安装路径 |
node_moduels
目录下node_moduels/.bin
目录下但此时在项目下执行命令你会发现,命令不存在
正确的写法应该是 ./node_moduels/.bin/hexo g
npx 就是想解决这个问题,只需像下边这样调用就可以 npx hexo g
在package.json
.scripts
里直接使用hexo g
。
由于npm run
时会自动增加node_module/.bin
到当前命令所用的PATH变量中,详见:npm-run-script
我们在这里就要使用npx
解决问题
这时仔细一想,发现不对啊,我不是需要给node设置--inspect
参数才行吗,别急,npx
有一个--node-arg
参数 ,答案呼之欲出。 npx --node-arg=--inspect-brk=5858 hexo g
package.json
.scripts
内容如下 {
"name": "hexo-site",
"version": "0.0.0",
"private": true,
"scripts": {
"debug": "npx --node-arg=--inspect-brk=5858 hexo g",
"build": "hexo g"
}
}
VSCode的配置不变 {
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "hexo g",
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"runtimeExecutable": "npm",
"runtimeArgs": ["run-script", "debug"],
"port": 5858,
"stopOnEntry": true
}
]
}
点一下绿色的小三角,Debug起来吧!
补充一下 ERROR: --node-arg/-n can only be used on packages with node scripts.
npx
确实有bug,详情看这里npx -n on Windows broken since cmd-shim 3.x
可以追加./node_modules/.bin/
临时解决 npx --node-arg=--inspect-brk=5858 ./node_modules/.bin/hexo g
说来也奇怪,越是闲,越是懒得动起来,上个月“农药”打到星耀二了,作息时间都正常了(觉睡多了😅),大早上就能起来黑一把,而且发现大家都在线🤣
这个月才想起来更新更新小程序。
好了,入正题,下面直接贴了更新日志(懒得改了🙃)
A
新增 最近很火的订阅消息。我的
-开启/关闭消息推送
处开关控制。F
修复 一些已知问题。调起授权的截图如下(自行决定是否勾选不再询问)
小程序的其他图片就不放了,参考以前贴子 - Blog链接: V2EX小程序已发布20个版本了 - V站链接: V2EX小程序已发布20个版本了
最后放上小程序码
小程序名V2EXPlus
,欢迎搜索
谢谢
]]>前段时间写了个小程序(大概是去年年底),当时来V发了一贴,其他途径并没有推广。
目前版本趋于稳定,只是苦于每天只有大概400多次使用,恩没错,贼稳定,毫无增长迹象(如下图),特来开贴介绍一下新功能,顺便推广一下。
最近一直在使用此小程序的老哥们,跳过此帖即可。。。
个人中心
-消息推送
关闭消息推送功能全部
固定在列表页第一条技术 R2 问与答
固定在节点页最上方(老用户需要清除缓存才能显示,或者使用搜索)酷工作
需要使用搜索出来个人中心
的 敬请期待
,弹出清理缓存选项,会清除登录状态和节点开关配置创建的主题
还修复了很多已知问题
更多内容可以到个人中心
-更新日志
查看
然后偷偷安利一下博客:https://www.liu.app
以上
感谢大家
]]>因为哈希表对PHP来说太基础了,因此非常值得深入研究它是如何工作的。
英文原文:https://nikic.github.io/2012/03/28/Understanding-PHPs-internal-array-implementation.html
原文:http://www.hoohack.me/2016/02/15/understanding-phps-internal-array-implementation-ch
注:此文为转载,看到写的特别棒,所以转过来记录下
基本上,PHP里面的所有东西都是哈希表。不仅仅是在下面的PHP数组实现中,它们还用来存储对象属性,方法,函数,变量还有几乎所有东西。
因为哈希表对PHP来说太基础了,因此非常值得深入研究它是如何工作的。
记住,在C里面,数组是内存块,你可以通过下标访问这些内存块。因此,在C里面的数组只能使用整数且有序的键值(那就是说,你不能在键值0之后使用1332423442的键值)。C里面没有关联数组这种东西。
哈希表是这样的东西:它们使用哈希函数转换字符串键值为正常的整型键值。哈希后的结果可以被作为正常的C数组的键值(又名为内存块)。现在的问题是,哈希函数会有冲突,那就是说,多个字符串键值可能会生成一样的哈希值。例如,在PHP,超过64个元素的数组里,字符串"foo"和"oof"拥有一样的哈希值。
这个问题可以通过存储可能冲突的值到链表中,而不是直接将值存储到生成的下标里。
那么,现在哈希表的基本概念已经清晰了,让我们看看在PHP内部中实现的哈希表结构:
typedef struct _hashtable { |
nNumOfElements
标识现在存储在数组里面的值的数量。这也是函数count($array)
返回的值。
nTableSize
表示哈希表的容量。它通常是下一个大于等于nNumOfElements
的2的幂值。比如,如果数组存储了32元素,那么哈希表也是32大小的容量。但如果再多一个元素添加进来,也就是说,数组现在有33个元素,那么哈希表的容量就被调整为64。 这是为了保持哈希表在空间和时间上始终有效。很明显,如果哈希表太小,那么将会有很多的冲突,而且性能也会降低。另一方面,如果哈希表太大,那么浪费内存。2的幂值是一个很好的折中方案。
nTableMask
是哈希表的容量减一。这个mask用来根据当前的表大小调整生成的哈希值。例如,"foo"真正的哈希值(使用DJBX33A哈希函数)是193491849。如果我们现在有64容量的哈希表,我们明显不能使用它作为数组的下标。取而代之的是通过应用哈希表的mask,然后只取哈希表的低位。
hash | 193491849 | 0b1011100010000111001110001001& mask | & 63 | & 0b0000000000000000000000111111---------------------------------------------------------= index | = 9 | = 0b0000000000000000000000001001
nNextFreeElement
是下一个可以使用的数字键值,当你使用$array[] = xyz是被使用到。
pInternalPointer
存储数组当前的位置。这个值在foreach遍历时可使用reset(),current(),key(),next(),prev()和end()函数访问。
pListHead
和pListTail
标识了数组的第一个和最后一个元素的位置。记住:PHP的数组是有序集合。比如,['foo' => 'bar', 'bar' => 'foo']和['bar' => 'foo', 'foo' => 'bar']这两个数组包含了相同的元素,但却有不同的顺序。
arBuckets
是我们经常谈论的“哈希表(internal C array)”。它用Bucket **来定义,因此它可以被看作数组的bucket指针(我们会马上谈论Bucket是什么)。
pDestructor
是值的析构器。如果一个值从HT中移除,那么这个函数会被调用。常见的析构函数是zval_ptr_dtor。zval_ptr_dtor会减少zval的引用数量,而且,如果它遇到o,它会销毁和释放它。
最后的四个变量对我们来说不是那么重要。所以简单地说persistent标识哈希表可以在多个请求里存活,nApplyCount和bApplyProtection防止多次递归,inconsistent用来捕获在调试模式里哈希表的非法使用。
让我们继续第二个重要的结构:Bucket:
typedef struct bucket { |
h
是一个哈希值(没有应用mask值映射之前的值)。
arKey
用来保存字符串键值。nKeyLength
是对应的长度。如果是数字键值,那么这两个变量都不会被使用。
pData
及pDataPtr
被用来存储真正的值。对PHP数组来说,它的值是一个zval结构体(但它也在其他地方使用到)。不要纠结为什么有两个属性。它们两者的区别是谁负责释放值。
pListNext
和pListLast
标识数组元素的下一个元素和上一个元素。如果PHP想顺序遍历数组它会从pListHead这个bucket开始(在HashTable结构里面),然后使用pListNext bucket作为遍历指针。在逆序也是一样,从pListTail指针开始,然后使用pListLast指针作为变量指针。(你可以在用户代码里调用end()然后调用prev()函数达到这个效果。)
pNext
和pLast
生成我上面提到的“可能冲突的值链表”。arBucket数组存储第一个可能值的bucket。如果该bucket没有正确的键值,PHP会查找pNext指向的bucket。它会一直指向后面的bucket直到找到正确的bucket。pLast在逆序中也是一样的原理。
你可以看到,PHP的哈希表实现相当复杂。这是它使用超灵活的数组类型要付出的代价。
Zend Engine定义了大量的API函数供哈希表使用。低级的哈希表函数预览可以在zend_hash.h
文件里面找到。另外Zend Engine在zend_API.h
文件定义了稍微高级一些的API。
我们没有足够的时间去讲所有的函数,但是我们至少可以查看一些实例函数,看看它是如何工作的。我们将使用array_fill_keys
作为实例函数。
使用第二部分提到的技巧你可以很容易地找到函数在ext/standard/array.c
文件里面定义了。现在,让我们来快速查看这个函数。
跟大部分函数一样,函数的顶部有一堆变量的定义,然后调用zend_parse_parameters
函数:
zval *keys, *val, **entry; |
很明显,az
参数说明第一个参数类型是数组(即变量keys
),第二个参数是任意的zval(即变量val
)。
解析完参数后,返回数组就被初始化了:
/* Initialize return array */ |
这一行包含了array API里面存在的三步重要的部分:
1、Z_ARRVAL_P宏从zval里面提取值到哈希表。
2、zend_hash_num_elements提取哈希表元素的个数(nNumOfElements属性)。
3、array_init_size使用size变量初始化数组。
因此,这一行使用与键值数组一样大小来初始化数组到return_value
变量里。
这里的size只是一种优化方案。函数也可以只调用array_init(return_value)
,这样随着越来越多的元素添加到数组里,PHP就会多次重置数组的大小。通过指定特定的大小,PHP会在一开始就分配正确的内存空间。
数组被初始化并返回后,函数用跟下面大致相同的代码结构,使用while循环变量keys数组:
zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(keys), &pos); |
这可以很容易地翻译成PHP代码:
reset($keys); |
跟下面的一样:
foreach ($keys as $entry) { |
唯一不同的是,C的遍历并没有使用内部的数组指针,而使用它自己的pos变量来存储当前的位置。
在循环里面的代码分为两个分支:一个是给数字键值,另一个是其他键值。数字键值的分支只有下面的两行代码:
zval_add_ref(&val); |
这看起来太直接了:首先值的引用增加了(添加值到哈希表意味着增加另一个指向它的引用),然后值被插入到哈希表中。zend_hash_index_update
宏的参数分别是,需要更新的哈希表Z_ARRVAL_P(return_value)
,整型下标Z_LVAL_PP(entry)
,值&val
,值的大小sizeof(zval *)
以及目标指针(这个我们不关注,因此是NULL
)。
非数字下标的分支就稍微复杂一点:
zval key, *key_ptr = *entry; |
首先,使用convert_to_string
将键值转换为字符串(除非它已经是字符串了)。在这之前,entry
被复制到新的key
变量。key = **entry
这一行实现。另外,zval_copy_ctor
函数会被调用,不然复杂的结构(比如字符串或数组)不会被正确地复制。
上面的复制操作非常有必要,因为要保证类型转换不会改变原来的数组。如果没有copy操作,强制转换不仅仅修改局部的变量,而且也修改了在键值数组中的值(显然,这对用户来说非常意外)。
显然,循环结束之后,复制操作需要再次被移除,zval_dtor(&key)
做的就是这个工作。zval_ptr_dtor
和zval_dtor
的不同是zval_ptr_dtor
只会在refcount
变量为0时销毁zval变量,而zval_dtor
会马上销毁它,而不是依赖refcount
的值。这就为什么你看到zval_pte_dtor
使用"normal"变量而zval_dtor
使用临时变量,这些临时变量不会在其他地方使用。而且,zval_ptr_dtor
会在销毁之后释放zval的内容而zval_dtor
不会。因为我们没有malloc()
任何东西,因此我们也不需要free()
,因此在这方面,zval_dtor
做了正确的选择。
现在来看看剩下的两行(重要的两行^^):
zval_add_ref(&val); |
这跟数字键值分支完成后的操作非常相似。不同的是,现在调用的是符号表zend_symtable_update
而不是zend_hash_index_update
,而传递的是键值字符串和它的长度。
"正常的"插入字符串键值到哈希表的函数是zend_hash_update
,但这里却使用了zend_symtable_update
。它们有什么不同呢?
符号表简单地说就是哈希表的特殊的类型,这种类型使用在数组里。它跟原始的哈希表不同的是他如何处理数字型的键值:在符号表里,"123"和123被看作是相同的。因此,如果你在\(array["123"]存储一个值,你可以在后面使用\)array[123]获取它。
底层可以使用两种方式实现:要么使用"123"来保存123和"123",要么使用123来保存这两种键值。显然PHP选择了后者(因为整型比字符串类型更快和占用更少的空间)。
如果你不小心使用"123"而不是强制转换为123后插入数据,你会发现符号表一些有趣的事情。一个利用数组到对象的强制转换如下:
$obj = new stdClass; |
对象属性总是使用字符串键值来保存,尽管它们是数字。因此$obj->{123} = 'foo'
这行代码实际上保存'foo'变量到"123"下标里。当使用数组强制转换的时候,这个值不会给改变。但当$arr[123]
和$arr["123"]
都想访问123下标的值(不是已有的"123"下标)时,都抛出了错误。因此,恭喜,你创建了一个隐藏的数组元素。
很早前就想写个小程序
练练手,无奈辗转多个方案后,还是不知道写什么
女票都发话了,天天的农药农药,不努力上(挣)进(钱)
程序员通病,只求陶冶自身,等待伯乐,小钱不想挣,大钱挣不到
既然天天泡在V站,为啥不从这里下手???
浏览、登录、发言、分享什么的应该是必备功能,不造轮子
外链
,所以文章内链接跳转页面什么的都实现不了,包括301跳转
都不可以快速回复
头像
,进个人信息
页(1.0.11版已增加)@user
,快速显示最后一次评论内容抱着电脑啃代码的过程就...吧
二维码是重中之重
由于今天是发布的第一版,可能会有一些bug,欢迎大家指正,下方留言或者提Issues
@Livid L大,如有违规请立即指出
偷偷安利下Blog: liu.app
]]>VSCode 有特别多的精美主题,推荐两款我喜欢的主题。
暗色主题:One Dark Pro
亮色主题:Atom One Light
就在昨天突发奇想,每天几乎 4-6 个小时的时间是花在 IDE 上的,为何不使用一款护眼的主题呢(虽然暗色主题真的 B 格很高),在官网+Google 找了许久,尽然没有一款护眼绿的主题,我悲~
第一个想法就是动手写一个主题,于是 Google 了下,发现有个在线编辑主题的网站:tmThemeEditor,转念又一想,我其实只是想改下背景色,最好是能在某个喜欢的主题的基础上做一下简单的继承修改,于是找了找One Dark Pro
的源码,发现主题竟然就是个Json
文件这么简单,但是tmThemeEditor
只能下载为tmTheme
格式的 xml 文件,这种情况看来在线制作一个新的主题不但耗费精力,而且没法改为Json
格式使用啊(tmTheme 应该是古老的主题写法)。
继续 Google修改VSCode文字颜色的方法
,终于发现了新大陆,在User Settings
中增加如下代码,即可修改文字前景色
"workbench.colorCustomizations": { |
或者针对某个主题做修改
"workbench.colorCustomizations": { |
简直神技啊,这么简单,那再改下背景色试试
"workbench.colorCustomizations": { |
上边两个颜色都是Google
的Material Design
颜色,这下终于完美了,VSCode
我真的没白疼你啊,再附上官网主题颜色文档:Theme Color Reference,想改哪里都可以。
备注:
User Settings
打开方式:F1
或cmd+shift+p
打开快捷指令,输入setting
选择首选项:打开设置(json)
(英文: Preferences: Open Settings (JSON))即可,如下图
注意修改的时候建议使用亮色主题为基础,不然颜色差异会非常大。
最后附上我Material Design
版的精修结果:
全部粘贴到配置文件后会有修改项的备注
和颜色
的提示,非常方便修改:
"workbench.colorCustomizations": { |
Enjoy~
大家好像都比较关心我 IDE 的字体,哈哈,当时自己制作的混合版(Inconsolata + 中文造字工房悦圆)
注意:此字体仅供个人使用请勿商用
下载地址:https://pan.baidu.com/s/1Aglo2WSfE_rjOXm3LJzjVg
在 vscode
- 设置
中搜索 字体
,然后设置Editor: Font Family
为LZ YueYuan Inconsolata
即可(注意有空格),如下图
或者直接修改
settings.json
中的editor.fontFamily
为LZ YueYuan Inconsolata
也可以
周末抽时间写成了VSCode
插件,详情见这里《VSCode护眼绿主题插件开发》
插件地址: Green Eyecare Theme
只是测试了常用的几种语言Java
PHP
JavaScript
HTML
Python
欢迎大家帮忙测试,有问题请提Issues
注意: LZ YueYuan Inconsolata 字体还是需要自己安装和设置的
第一次写插件,轻拍
最近发现微软用于 Windows Terminal 的Cascadia Code
字体很是精美,贴一下同时用两款字体搭配Green Eyecare Theme主题的截图,觉得美爆了。
在 vscode
- 设置
中搜索 字体
,然后设置Editor: Font Family
为Cascadia Code, LZ YueYuan Inconsolata
即可(注意有空格),如下图
或者直接修改
settings.json
中的editor.fontFamily
为Cascadia Code, LZ YueYuan Inconsolata
也可以
截图字体大小为14
号,可根据个人喜好调整。
Chris Johnston
的SVN
插件,能够在gutter
中快速diff,并显示差异。在github中也找了好久,有说需要vscode.proposed.d.ts
,又有说需要配置"enableProposedApi": true
,却一直无法开启,资料也不是很多,无奈放弃。
后来偶然翻看SVN
插件的readme,发现里边竟然有开启的方式,如下:
打开<vscode path>\resources\app\product.json
找到extensionAllowedProposedApi
把希望开启的插件包名
追加到数组,例如SVN
插件的包名为johnstoncode.svn-scm
"extensionAllowedProposedApi": [ |
重启VSCode即可。
enjoy
]]>