2024年2月

1.无论sync.Mutex还是其衍生品都会提示不能复制,但是能够编译运行

加锁后复制变量,会将锁的状态也复制,所以 mu1 其实是已经加锁状态,再加锁会死锁.
所以此题的答案是 fatal error;

type MyMutex struct {
    count int
    sync.Mutex
}

func main() {
    var mu MyMutex
    mu.Lock()
    var mu1 = mu
    mu.count++
    mu.Unlock()
    mu1.Lock()
    mu1.count++
    mu1.Unlock()
    fmt.Println(mu.count, mu1.count)
}

2.defer对于有返回变量的函数,它的生命其可以保留到return之后.

func main() {
	fmt.Println(doubleScore(0)) //0
	fmt.Println(doubleScore(20.0)) //40
	fmt.Println(doubleScore(50.0)) //100
}
func doubleScore(source float32) (score float32) {
	defer func() {
		if score < 1 || score >= 100 {
			score = source //最终还能修改返回值
		}
	}()
	return source * 2
}

3.sync.Mutex不能重复加锁

var mu sync.Mutex
var chain string

func main() {
    chain = "main"
    A()
    fmt.Println(chain)
}
func A() {
    mu.Lock()
    defer mu.Unlock()
    chain = chain + " --> A"
    B()
}

func B() {
    chain = chain + " --> B"
    C()
}

func C() {
    mu.Lock() //A处还没有UnLock,此处又加锁,所以出现deadlock
    defer mu.Unlock()
    chain = chain + " --> C"
}

3.go的for语句几大特别之处

a.break到指定的层级

func main() {
OuterLoop:
	for i := 0; i < 2; i++ {
	InLoop:
		for j := 0; j < 5; j++ {
			switch j {
			case 2:
				fmt.Println(i, j)
				break InLoop
			case 3:
				fmt.Println(i, j)
				break OuterLoop
			}
		}
	}
}
b.类似于while
for true{
	//do sth.
}
for{
	//do sth.
}
c.三分号的用法和c++类似
for ; step > 0; step-- {
    fmt.Println(step)
}

for ; ; i++ {
    if i > 10 {
        break
    }
}


func main() {
    v := []int{1, 2, 3}
    for i, n := 0, len(v); i < n; i++ {
        v = append(v, i)
    }
    fmt.Println(v)
}

4.go的一些预定义字符

这些不是关键字,但最好不要拿去用,面试出这样的题的话,意义不大,只能拿来搞怪.

true /false

append 函数
append 函数用于向切片中添加元素,并返回新的切片。

make 函数
make 函数用于创建切片、映射和通道。

new 函数
new 函数用于分配内存并返回指向新分配的零值对象的指针。

len 函数
len 函数用于返回字符串、切片、映射、通道、数组等的长度。

cap 函数
cap 函数用于返回切片、数组、通道等的容量。

copy 函数
copy 函数用于复制切片中的元素。

delete 函数
delete 函数用于从映射中删除指定的键值对。

print 和 println 函数
print 和 println 函数用于打印输出信息。

panic 和 recover 函数
panic 函数用于引发运行时错误,recover 函数用于捕获并处理运行时错误。

close 函数
close 函数用于关闭通道。

5.有参返回类型可以直接return

以下等价,但是return的作用域内(代码块内)必须有ret变量的存在,
下图中ret被新的ret隐藏了,导致return所在作用域丢失ret.所以无法编译.

func add1(a, b int) (ret int) {
	ret = a + b
	return
}

func add2(a, b int) (ret int) {
	ret = a + b
	return ret
}

6.json.Unmarshal必须传入目标指针

虽然map的根本就是hmap指针,但是 reflect.ValueOf(v).Kind()获取到的不是指针,所以也必须传入指针.

func (d *decodeState) unmarshal(v any) error {
	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Pointer || rv.IsNil() {//!!
		return &InvalidUnmarshalError{reflect.TypeOf(v)}
	}

	d.scan.reset()
	d.scanWhile(scanSkipSpace).
	err := d.value(rv)
	if err != nil {
		return d.addErrorContext(err)
	}
	return d.savedError
}

7.slice因为切片底层公用数组,容易导致数据共享和不能内存不能及时释放

func get() []byte {
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0])
    return raw[:3]
}

func main() {
    data := get()
    fmt.Println(len(data), cap(data), &data[0])
}

8.go 语言中的可比较类型和不可比较类型

注意不可比较类型就三个:map slice func,可nil类型还包括chan,指针
但是chan,指针可以用于等值比较

操作符 变量类型
等值操作符 (==、!=) 整型、浮点型、字符串、布尔型、复数、
指针、管道、接口、结构体、数组
排序操作符 (<、<=、> 、 >=) 整型、浮点型、字符串
不可比较类型 map、slice、function

所以下面代码结果为false,同一类型指针是可以进行相等比较的

type foo struct{ Val int }

type bar struct{ Val int }

func main() {
	a := &foo{Val: 5}
	b := &foo{Val: 5}

	fmt.Print(a == b)
}

9.go每个case上的表达式都会执行,但只有一个chan可以读取或设置值

https://xie.infoq.cn/article/49526fb0dde758d663dfe0cd0
完整地介绍chan使用

func A() int {
	fmt.Println("A", GoID())
	time.Sleep(500 * time.Millisecond)

	return 1
}

func B() int {
	fmt.Println("B", GoID())
	time.Sleep(1000 * time.Millisecond)

	return 2
}

func GoID() uint64 {
	b := make([]byte, 64)
	b = b[:runtime.Stack(b, false)]
	b = bytes.TrimPrefix(b, []byte("goroutine "))
	b = b[:bytes.IndexByte(b, ' ')]
	n, _ := strconv.ParseUint(string(b), 10, 64)
	return n
}

func main() {
	ch := make(chan int, 1)
	go func() {
		select {
		case ch <- A():
			{
				fmt.Println("caseA")
			}
		case ch <- B():
			{
				fmt.Println("caseB")
			}
		default:
			ch <- 3
		}
	}()
	for v := range ch {
		fmt.Println(v)
	}
}

10.基础类型的map可以++,非基础类型不行.

这种差异造成诡异的心智负担.

func main() {
	m := make(map[string]int)
	m["foo"]++ //基础类型没有问题
	fmt.Println(m["foo"])

	m2 := map[string]Person{}
	m2["foo"] = Person{1}
	m2["foo"].Age = 22 //非法操作

}

type Person struct {
	Age int
}

11.一个接口是否==nil,要看其类型和数值是否同时为nil

打印则看数值

func Foo() error {
	var err *os.PathError = nil
	return err
}

func main() {
	err := Foo()
	fmt.Println(err) //nil,打印则看数值
	fmt.Println(err == nil) //false
}

func main() {
    x := interface{}(nil)
    y := (*int)(nil)
    a := y == x //类型不一样
    b := y == nil //y不是接口,所以直接看值.如果var y any = (*int)(nil)就变成接口了.最后接口就是false.
    _, c := x.(interface{}) //没有类型,断言失败
    println(a, b, c) //flase true false
}

12.在go里面0开始的数字是八进制

const (
	Decade = 010
)

func main() {
	fmt.Println(Decade) //8
}

13.字符串Trim操作注意

Trim结尾处的字符串,对应其他语言的TrimEnd

TrimSuffix
切勿使用TrimRight,会将第二个参数字符串里面所有的字符拿出来处理,只要与其中任何一个字符相等,便会将其删除

    fmt.Println(strings.TrimRight("ABBA", "BA")) //最后全部被删了
Trim以某段开始的字符串,对应其他语言的TrimStart

TrimPrefix

    fmt.Println(strings.TrimLeft("ABBAC", "BA")) //最后只会剩下C

14.关于for range

如果是chan则,没有k,只有v;
	for v := range ch {
		fmt.Println(v)
	}

15.关于切片两个冒号的说明

https://segmentfault.com/a/1190000018356577

注意

我们潜意识觉得cap就是底层数组的长度,但是在尽显冒号切片时,cap的长度是有max-low得到的,max的默认值为源切片(或源数组)的cap.

通过两个冒号创建切片,slice[x:y:z]切片实体[x:y]切片长度len = y-x,切片容量cap = z-x\

data := [...]int{0, 1, 2, 3, 4, 5, 6} //初始化一个数组
slice := data[1:4:5] // [low : high : max]  通过两个冒号创建切片

使用两个冒号[1:4:5] 从数组中创建切片,长度为4-1=3,也就是索引从1到3 的数据(1,2,3)

然后,后面是最大是5,即容量是5-1=4,即,创建的切片是长度为从索引为 1、2、3 的切片,底层数组为[ 1,2,3,4]

data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

data[:6:8] [0 1 2 3 4 5]         //默认low为0
data[5:] [5 6 7 8 9]             //省略 high、 max,默认值为源切片(或源数组)的cap
data[:3] [0 1 2]                 //low=0,max=默认值为源切片(或源数组)的cap
data[:] [0 1 2 3 4 5 6 7 8 9]    //10 10 全部省略。

一道题懂了,那真懂了.

func main() {
	a := []int{0, 1, 2, 3}
	s := a[1:3]  //1,2,[3]
	s2 := s[1:2] //2,[3]
	fmt.Println(cap(s), len(s))
	fmt.Println(cap(s2), len(s2))
	fmt.Println("--------------------------------")
	s2 = append(s2, 333)

	fmt.Println(a)
	fmt.Println(s)
	fmt.Println(s2)
}
3 2
2 1
--------------------------------
[0 1 2 333]
[1 2]
[2 333]

16.关于切片的一些手法

创建空切片
// 使用 make 创建空的整型切片
slice := make([]int, 0)
// 使用切片字面量创建空的整型切片
slice := []int{}

17.for range 常数

for i := range 10 {
		fmt.Println(i)
}

18.关于一切等值运算的根本是比较补码


func main() {
	count := 0
	for i := range [256]struct{}{} {
		m, n := byte(i), int8(i)
		if n == -n {
			count++
			fmt.Println("n==-n", n, -n, i)
		}
		if m == -m {
			count++
			fmt.Println("m==-m", m, -m, i)
		}
	}
	fmt.Println(count)

}

n==-n 0 0 0
m==-m 0 0 0
n==-n -128 -128 128
m==-m 128 128 128
4

-128 的补码:

首先,我们需要知道 -128 的原码。原码是表示数值的二进制形式,其中最高位表示符号位(0 表示正数,1 表示负数),其余位表示数值部分。对于 -128,其原码为 1000 0000。

接下来,我们求出 -128 的反码。反码是将原码的除符号位外的各位取反。对于 -128,其反码为 6666661 6666661。

最后,我们计算 -128 的补码。补码是在反码的末位加1,从而使后7位再次发生溢出,进位丢弃,符号位不变。因此,-128 的补码为 1000 0000。

需要注意的是,-128 是一个特殊的数,因为它的绝对值比最小的32位整数还要大1,所以在计算机中表示为 -128 的补码时,我们直接使用其反码加1的结果。

128的补码
128 的原码是 10000000。三码都是这个,所以也是 10000000。

19.多重赋值的优先级

多重赋值分为两个步骤,有先后顺序:

  • 计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式;
  • 赋值;
func main() {
    var a []int = nil
    a, a[0] = []int{1, 2}, 9
    fmt.Println(a)
}

解析:运行时错误。知识点:多重赋值。

19.sync.WaitGroup不能复制值,且需要在启动协程前Add
func main() {
    wg := sync.WaitGroup{}
    for i := 0; i < 5; i++ {
        go func(wg sync.WaitGroup, i int) {
            wg.Add(1)
            fmt.Printf("i:%d\n", i)
            wg.Done()
        }(wg, i)
    }
    wg.Wait()
    fmt.Println("exit")
}

20.chan的基本数据结构

ype hchan struct {
  qcount   uint  // 队列中的总元素个数
  dataqsiz uint  // 环形队列大小,即可存放元素的个数
  buf      unsafe.Pointer // 环形队列指针
  elemsize uint16  //每个元素的大小
  closed   uint32  //标识关闭状态
  elemtype *_type // 元素类型
  sendx    uint   // 发送索引,元素写入时存放到队列中的位置

  recvx    uint   // 接收索引,元素从队列的该位置读出
  recvq    waitq  // 等待读消息的goroutine队列
  sendq    waitq  // 等待写消息的goroutine队列
  lock mutex  //互斥锁,chan不允许并发读写
}

21.cap 关于 cap 函数适用下面哪些类型?

A. 数组;
B. channel;
C. map;
D. slice;

func main() {
	c := make(chan int, 2)
	c <- 11
	fmt.Println(len(c)) //1
	fmt.Println(cap(c)) //2
}

22.启动一个goroutine的前提是,还有数

func main() {
    runtime.GOMAXPROCS(1)

    go func() {
        for i:=0;i<10 ;i++  {
            fmt.Println(i)
        }
    }()

    for {}
}

23.接收器方法在defer时也会优先求值

type Slice []int

func NewSlice() Slice {
    return make(Slice, 0)
}
func (s *Slice) Add(elem int) *Slice {
    *s = append(*s, elem) //!! 注意无名类型的特别之处.
    fmt.Print(elem)
    return s
}
func main() {
    s := NewSlice()
    defer s.Add(1).Add(2)
    s.Add(3)
}
//1 3 2

24.闭包引用相同变量,那么他们的作用效果都在这个变量上

func test(x int) (func(), func()) {
    return func() {
        println(x)
        x += 10
    }, func() {
        println(x)
    }
}

func main() {
    a, b := test(100)
    a()
    b()
}
// 100 110

25.range相当于一个函数,会对传入的数据进行拷贝.

因此,下面切片和数组的场景会产生不同的值

type T struct {
	n int
}

func main() {
	ts := [2]T{}
	//ts := make([]T, 2)
	for i, t := range ts {
		switch i {
		case 0:
			t.n = 3
			ts[1].n = 9 //改变了ts,但t的数据,源头是range产生的拷贝
		case 1:
			fmt.Print(t.n, " ")
		}
	}
	fmt.Print(ts)
}

// 数组 0 [{0} {9}]
// 切片 9 [{0} {9}]


//--------------
func main() {
     var a = []int{1, 2, 3, 4, 5}
     var r = make([]int, 0)
     for i, v := range a { //这里产生了新的副本,所以下面的添加操作不影响遍历.
         if i == 0 {
             a = append(a, 6, 7)
         }
        r = append(r, v)
    }
    fmt.Println(r)
}

25.关于defer func recover panic

  • panic会将恐慌压入一个后进先出的栈里面;
  • recover 必须内置于defer func 内才有效;
  • recover从panic栈中取值;
func main() {
    defer func() {
        fmt.Print(recover())
    }()
    defer func() {
        defer func() {
            fmt.Print(recover())
        }()
        panic(1)
    }()
    defer recover() //这里的recover没有效果,因为没有在defer func内部
    panic(2)
}

上面的代码panic依次入站 2 ,1,出栈为1 ,2,所以结果 1 2

func main() {
    defer func() {
        fmt.Print(recover())
    }()
    defer func() {
        defer fmt.Print(recover()) //首先参数需要求值,所以在当前执行func完毕时要打印2
        panic(1) //压入栈中,当前有没有机会了,上级会捕捉到.
    }()
    defer recover() //这里的recover没有效果,因为没有在defer func内部
    panic(2)
}

结果为 2 1

26. 当闭包含有外层变量时,会让外面的变量一直活着

func F(n int) func() int {
    return func() int {
        n++
        return n
    }
}

func main() {
    f := F(5) //参数为5
    defer func() {
        fmt.Println(f())
    }()
    defer fmt.Println(f()) //需要立即求值,所以最后打印6,参数变成为了6
    i := f() //参数变成了7,
    fmt.Println(i)//打印7
}

//最后结果768

27.关于for,range对枚举器的赋值

func main() {
	var k = 9
	for k = range []int{} { //没有数据,所以对k的赋值,没能执行
	}
	fmt.Println(k)

	for k = 0; k < 3; k++ {
	}
	fmt.Println(k) //很明显上面导致k变成了3

	for k = range (*[3]int)(nil) { //一个[三个数据的数组]的指针,虽然是nil,但是在go里面这是合法的,最终k变成2
	}
	fmt.Println(k)
}

28.关于继承的本质问题



type T struct{}

func (*T) foo() {
}

func (T) bar() {
}

type S struct {
    *T //注意指针也可以,也即,没有名称只有类型,那么我们就可以通过语法糖的方式,好像调用自己的方法一样调用
}

func main() {
    s := S{}
    _ = s.foo
    s.foo() //这个是没有问题的.虽然就nil,但因为没有发生调用nil,所以没有问题
    _ = s.bar // (* s.T).bar,但指针s.T为nil,解引用失败报错
}

//---------------这是可以的
type X struct {}
func (x *X) test()  {
   println(x)
}
func main() {
    var a *X
    a.test()//nil,不是null
    X{}.test() //这里有问题,右值不可寻址
}
//--------------------------------------

type Father struct{ Name string }
type Monther struct {
    Name string
}

func (m Monther) World() {

}

type Child struct {
    Father
    Monther
}

func (entity Father) Hello() {

}
func (entity Father) World() {

}

func main() {
    child := Child{}
    child.World() //二义性语法糖就用不了,需要指明Father or Monther的方法
}

//-----------------------


- 匿名字段的本质字段名是
S *S
S S

- 然后调用他们对应的接收器方法。
- 如果T为指针,则全有,不问上面的两种情况了

- 能不能改变原有数据,取决于接收器是不是指针类型
 
func main() {
   var v T
   t := &v
   fmt.Printf("%p\n", t.S)
   t.SPtr(1) //因为t指针类型,所以拥有S,*S的接收器方法
   t.SPtr(1)
   fmt.Println(t.S.Age)
}
type S struct {
   Age int
}
type T struct {
   S
}
func (s S) SVal(dd int) {
   s.Age = 10
   fmt.Printf("%p\n", &s)
}
func (s *S) SPtr(dd int) {
   fmt.Printf("%p\n", s)
   s.Age = 100
}
func (t T) TVal(dd int) {
}
func (t *T) TPtr(dd int) {
}
func methodSet(arg interface{}) {
   argType := reflect.TypeOf(arg)
   fmt.Println(argType.NumMethod())
   for i := 0; i < argType.NumMethod(); i++ {
      m := argType.Method(i)
      fmt.Printf("%s: %v\n", m.Name, m.Type)
   }
}
//----------
type Fragment interface {
        Exec(transInfo *TransInfo) error
}
type GetPodAction struct {
}
func (g GetPodAction) Exec(transInfo *TransInfo) error {
        ...
        return nil
}
指针接收器拥有值接口器的方法
var fragment Fragment = new(GetPodAction)


图片名称

####29.关于map的怪异之处,面试时尤其注意

func main() {
	var m map[int]bool // nil
	var a = m[123]
	fmt.Println(a)//false,原因很简单,nil不是null,map不仅允许读取没有的key,还允许读取nil的map
}

30.关于切片

  • 关于切片之前的理解是有问题的,我们切片的对象是底层真实的数组长度(即:容量)
  • low,high,max high默认是slice的长度,max则为容量
func main() {
	x := make([]int, 2, 10)
	a := x[6:10]
	b := x[6:] //x[6:2],所以这里要报错
	c := x[2:] //2-2=0,所以最后是空的
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
}


31.不可比较类型 slice map func

func main() {

	var x interface{}
	var y interface{} = []int{3, 5}
	_ = x == x
	_ = y == x //注意虽然slice是不可以比较的类型,但是它可以和any nil比较
	println("end")
	_ = y == y //2 slice是不可以比较的
}

32.对于接收器方法的表达,我们都很陌生

type N int

func (n N) test() {
	fmt.Println(n)
}

func main() {
	var n N = 10
	fmt.Println(n)

	n++
	//func (N).test()
	f1 := N.test
	f1(n)

	n++
	//var f2 func(*N)
	f2 := (*N).test
	f2(&n)
}

33.读写nil类型的chan都会永远阻塞

func main() {
    var ch chan int
    select {
    case v, ok := <-ch:
        println(v, ok)
    default:
        println("default") 
    }
}

34.:=操作符不能给结构体字段赋值

type foo struct {
    bar int
}

func main() {
    var f foo
    f.bar, tmp := 1, 2
}

35.go中所有的变量申明了就必须用,但常量除外,常量不能取地址

func main() {
    const x = 123
    const y = 1.23
    fmt.Println(x)
}

36.byte在go中是uint8的别名,他们完全等价

type byte = uint8

func test(x byte)  {
    fmt.Println(x)
}

func main() {
    var a byte = 0x11 
    var b uint8 = a
    var c uint8 = a + b
    test(c)
}

37.关于给切片加索引赋值的注意项

  • 字面量初始化切片时候,可以指定索引,没有指定索引的元素,其索引=前一个索引+1,
  • 空缺的索引的位置,数据就是零值
var x = []int{2: 2, 3, 0: 1}

func main() {
    fmt.Println(x) // [1 0 2 3]
}

38.关于select

A. select机制用来处理异步IO问题;//输入输出,要读取或写入chan

B. select机制最大的一条限制就是每个case语句里必须是一个IO操作;

C. golang在语言级别支持select关键字;

39.指针类型的map和slice均不能使用索引

40.chan的写入方负责关闭,不然造成泄露

func main() {
     ch := make(chan int, 100)
     // A
     go func() {              
         for i := 0; i < 10; i++ {
             ch <- i
         }
     }()
     // B
    go func() {
        for {
            a, ok := <-ch
            if !ok {
                fmt.Println("close")
                return
            }
            fmt.Println("a: ", a)
        }
    }()
    close(ch)
    fmt.Println("ok")
    time.Sleep(time.Second * 10)
}

41. chan的几个特性

A. 给一个 nil channel 发送数据,造成永远阻塞

B. 从一个 nil channel 接收数据,造成永远阻塞

C. 给一个已经关闭的 channel 发送数据,引起 panic

D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值

42.类型转换的方式和c系有差异需要注意

B.
type MyInt int
var i int = 1
var j MyInt = (MyInt)i //c系的方式,在go里面是非法的

C.

type MyInt int
var i int = 1
var j MyInt = MyInt(i)//在才是正确的方式.

43.关于for range枚举器,不同版本有不同定义

type Foo struct {
bar string
}
func main() {
s1 := []Foo{
{"A"},
{"B"},
{"C"},
}
s2 := make([]*Foo, len(s1))
for i, value := range s1 {
s2[i] = &value
}
fmt.Println(s1[0], s1[1], s1[2])
fmt.Println(s2[0], s2[1], s2[2])
}
1.22输出:
{A} {B} {C}
&{A} &{B} &{C}

1.22之前
s2 的输出是 &{C} &{C} &{C}

43.关于defer nil方法

func f(n int) (r int) {
	defer func() {
		r += n
		recover() //这里捕捉了nil方法的调用
	}()

	var f func()

	defer f() //f是一个nil,所以没有机会执行下面的方法
	f = func() {
		fmt.Println("f called")
		r += 2
	}
	return n + 1
}

func main() {
	fmt.Println(f(3)) //7
}

https://www.topgoer.cn/docs/gomianshiti/mian28

1 前言

​ 本文通过一个立方体贴图的例子,讲解三维纹理贴图的应用,案例中使用 6 张不同的图片给立方体贴图,图片如下。

img

​ 读者如果对 libGDX 不太熟悉,请回顾以下内容。

2 立方体贴图

​ 本节将使用 Mesh、ShaderProgram、Shader 实现立方体贴图,OpenGL ES 的实现见博客 →
立方体贴图(6张图)
,本节完整代码资源见 →
libGDX Mesh立方体贴图(6张图)

​ DesktopLauncher.java

package com.zhyan8.game;

import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;

public class DesktopLauncher {
	public static void main (String[] arg) {
		Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();
		config.setForegroundFPS(60);
		config.setTitle("CubeChartlet");
		new Lwjgl3Application(new CubeChartlet(), config);
	}
}

​ CubeChartlet.java

package com.zhyan8.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;

public class CubeChartlet extends ApplicationAdapter {
	private PerspectiveCamera mCamera;
	private ShaderProgram mShaderProgram;
	private Mesh mMesh;
	private Texture[] mTextures;
	private Vector3 mRotateAxis; // 旋转轴
	private int mRotateAgree = 0; // 旋转角度
	Matrix4 mModelMatrix; // 模型变换矩阵

	@Override
	public void create() {
		initCamera();
		initShader();
		initMesh();
		initTextures();
		mRotateAxis = new Vector3(0.5f, 1f, 1f);
		mModelMatrix = new Matrix4();
	}

	@Override
	public void render() {
		Gdx.gl.glClearColor(0.455f, 0.725f, 1.0f, 1.0f);
		Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT | GL30.GL_DEPTH_BUFFER_BIT);
		Gdx.gl.glEnable(GL30.GL_DEPTH_TEST);
		mShaderProgram.bind();
		transform();
		renderCube();
	}

	@Override
	public void dispose() {
		mShaderProgram.dispose();
		mMesh.dispose();
	}

	private void renderCube() {
		for (int i = 0; i < mTextures.length; i++) { // 给每个面都贴图
			// mShaderProgram.setUniformi("u_texture", 0); // 设置纹理单元
			mTextures[i].bind(0);
			mMesh.render(mShaderProgram, GL30.GL_TRIANGLE_FAN, i * 4, 4);
		}
	}

	private void initCamera() { // 初始化相机
		mCamera = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		mCamera.near = 0.3f;
		mCamera.far = 1000f;
		mCamera.position.set(0f, 0f, 4f);
		mCamera.lookAt(0, 0, 0);
		mCamera.update();
	}

	private void initShader() { // 初始化着色器程序
		String vertex = Gdx.files.internal("shaders/square_chartlet_vertex.glsl").readString();
		String fragment = Gdx.files.internal("shaders/square_chartlet_fragment.glsl").readString();
		mShaderProgram = new ShaderProgram(vertex, fragment);
	}

	private void initMesh() { // 初始化网格
		float[] vertices = Model.vertices;
		short[] indices = Model.indices;
		VertexAttribute vertexPosition = new VertexAttribute(Usage.Position, 3, "a_position");
		VertexAttribute texCoords = new VertexAttribute(Usage.TextureCoordinates, 2, "a_texCoord0");
		mMesh = new Mesh(true, vertices.length / 5, indices.length, vertexPosition, texCoords);
		mMesh.setVertices(vertices);
		mMesh.setIndices(indices);
	}

	private void initTextures() {
		mTextures = new Texture[Model.texturePaths.length];
		for (int i = 0; i < mTextures.length; i++) {
			mTextures[i] = new Texture(Gdx.files.internal(Model.texturePaths[i]));
		}
	}

	private void transform() { // MVP矩阵变换
		mRotateAgree = (mRotateAgree + 2) % 360;
		mRotateAxis.x = mRotateAgree / 180f - 1;
		mRotateAxis.y = (float) Math.sin(mRotateAgree / 180f * Math.PI * 0.7f);
		mRotateAxis.z = (float) Math.cos(mRotateAgree / 180f * Math.PI * 0.5f);
		mModelMatrix.idt(); // 模型变换矩阵单位化
		mModelMatrix.rotate(mRotateAxis, mRotateAgree);
		Matrix4 mvpMatrix = mModelMatrix.mulLeft(mCamera.combined);
		mShaderProgram.setUniformMatrix("u_mvpTrans", mvpMatrix);
	}
}

​ Model.java

package com.zhyan8.game;

public class Model {
    private static float r = 1.0f;

    public static String[] texturePaths = new String[] {
            "textures/a1.png", "textures/a2.png", "textures/a3.png",
            "textures/a4.png", "textures/a5.png", "textures/a6.png"
    };

    public static float[] vertices = new float[] {
            // 前面
            r, r, r, 0f, 0f, // 0
            -r, r, r, 1f, 0f, // 1
            -r, -r, r, 1f, 1f, // 2
            r, -r, r, 0f, 1f, // 3
            // 后面
            r, r, -r, 0f, 0f, // 4
            -r, r, -r, 1f, 0f, // 5
            -r, -r, -r, 1f, 1f, // 6
            r, -r, -r, 0f, 1f, // 7
            // 上面
            r, r, r, 0f, 0f, // 8
            r, r, -r, 1f, 0f, // 9
            -r, r, -r, 1f, 1f, // 10
            -r, r, r, 0f, 1f, // 11
            // 下面
            r, -r, r, 0f, 0f, // 12
            r, -r, -r, 1f, 0f, // 13
            -r, -r, -r, 1f, 1f, // 14
            -r, -r, r, 0f, 1f, // 15
            // 右面
            r, r, r, 0f, 0f, // 16
            r, r, -r, 1f, 0f, // 17
            r, -r, -r, 1f, 1f, // 18
            r, -r, r, 0f, 1f, // 19
            // 左面
            -r, r, r, 0f, 0f, // 20
            -r, r, -r, 1f, 0f, // 21
            -r, -r, -r, 1f, 1f, // 22
            -r, -r, r, 0f, 1f // 23
    };

    public static short[] indices = new short[] {
            0, 1, 2, 3, // 前面
            4, 5, 6, 7, // 上面
            8, 9, 10, 11, // 右面
            12, 13, 14, 15, // 后面
            16, 17, 18, 19, // 下面
            20, 21, 22, 23 // 左面
    };
}

​ square_chartlet_vertex.glsl

#version 300 es

in vec3 a_position;
in vec2 a_texCoord0;

uniform mat4 u_mvpTrans; // MVP矩阵变换

out vec2 v_texCoord0;

void main() {
    gl_Position = u_mvpTrans * vec4(a_position, 1.0);
    v_texCoord0 = a_texCoord0;
}

​ square_chartlet_fragment.glsl

#version 300 es
precision mediump float; // 声明float型变量的精度为mediump

in vec2 v_texCoord0;

uniform sampler2D u_texture;

out vec4 fragColor;

void main() {
    fragColor = texture(u_texture, v_texCoord0);
}

​ 运行效果如下。

img

​ 声明:本文转自
【libGDX】Mesh立方体贴图(6张图)

定义主机和组

在使用ansible来批量管理主机的时候,通常我们需要先定义要管理哪些主机或者主机组,而这个用于管理主机与主机组的文件就叫做Inventory,也叫做主机清单,该文件默认位于/etc/ansible/hosts(如果是pip安装则没有)
当然我们也可以通过修改ansible的配置文件来修改默认使用的主机清单

主机的定义

  • 主机部分可以使用域名,主机名,IP地址来定义;使用前2者时,需要主机能够解析到对应的IP地址
[devops@node1 ~]$ cat hosts
node1
node2
node1.example.com
node2.example.com
192.168.200.100
192.168.200.200

我们可以看到,现在主机清单里面是写了node1(主机名),node1.example.com(域名),192.168.200.100(IP地址),这三种定义方式都是可以的,但是你要想管理的话,主机名和域名必须是有对应的解析的
这种方式是一个个定义,我们还可以指定范围,比如

[devops@node1 ~]$ cat hosts
192.168.200.[1:10]
# 通过命令来查看当前被定义的主机是哪些
# ansible all --list就是列出主机清单内的全部主机
[devops@node1 ~]$ ansible all --list
  hosts (10):
    192.168.200.1
    192.168.200.2
    192.168.200.3
    192.168.200.4
    192.168.200.5
    192.168.200.6
    192.168.200.7
    192.168.200.8
    192.168.200.9
    192.168.200.10

可以看到,他确实是我们定义的那样从1-10这个IP地址范围

主机组的定于

主机的定义就是直接讲被管理的节点写入到主机清单内,而主机组的定义是需要加上一个[组名]的

[devops@node1 ~]$ cat hosts
[webserver]
node1
node2

中括号里面写的就是组名,下面的内容就是这个组内有哪些主机
同样我们可以通过组名来查看组内有哪些主机

# 注意,我之前写的是ansible all --list
# 这里写的是webserver,也就是主机组的名字,所以他只会列出这个主机组内的成员
[devops@node1 ~]$ ansible webserver --list
  hosts (2):
    node1
    node2

如果有多个主机组,那么就可以写多个中括号,一个主机可以隶属于不同主机组

[devops@node1 ~]$ cat hosts
[webserver]
node1
node2
[sqlserver]
node2
node3
[devops@node1 ~]$ ansible all --list
  hosts (3):
    node1
    node2
    node3

这里不知道你有没有疑问,那我主机和主机组同时定义的时候,位置是随意的吗?不是!
单个主机只能写在主机组之前,如果你写在主机组之后,不管你空多少行,他都会认为你这个主机是属于这个组内的,来看例子

现在我想定义一个单个主机192.168.1.1

从截图我们可以看到,这个192.168.1.1与主机组sqlserver之前是空了很多行的,我们通过命令来查一下看看

# 我们直接来查sqlserver主机组内有哪些成员
[devops@node1 ~]$ ansible sqlserver --list
  hosts (3):
    node2
    node3
    192.168.1.1

看到了吗,sqlserver主机组内是包含这个主机的,那我们将这个主机提前到所有主机组之前

我们再来查一下

[devops@node1 ~]$ ansible sqlserver --list
  hosts (2):
    node2
    node3
# 通过这个命令可以查到不属于任何主机组的主机
[devops@node1 ~]$ ansible ungrouped --list
  hosts (1):
    192.168.1.1

现在他被定义成了一个单个主机

主机组的嵌套

主机组的嵌套是什么呢?我们可以这样来想象,假设你现在管理2个机房的服务器,现在你需要对机房1的所有服务器进行软件升级,这个时候你怎么去选择机房1呢?我们可以通过主机嵌套来解决

[devops@node1 ~]$ cat hosts
192.168.1.1

[webserver]
node1
node2


[sqlserver]
node2
node3

[MachineRoom:children]
webserver
sqlserver

就是这样定义的,[MachineRoom:children],也就是说MachineRoom下面有哪些孩子嘛,它下面有webserver组和sqlserver组,这2个组就代表着机房1,所以我需要对机房1进行操作的话我就可以直接选择Machine这个组就可以了

[devops@node1 ~]$ ansible MachineRoom --list
  hosts (3):
    node1
    node2
    node3

选择主机和组

上面我们说到了定义主机和主机组,定义完了之后我们如何去选择呢?上面也提到了一些,选择我们来看如果更精准的选择

匹配主机

# 或者执行ansible all --list-hosts是一样的
[devops@node1 ~]$ ansible all --list
  hosts (4):
    192.168.1.1
    node1
    node2
    node3
  1. 匹配指定的主机或组
# 匹配单个主机
[devops@node1 ~]$ ansible 192.168.1.1 --list
  hosts (1):
    192.168.1.1
# 配置主机组
[devops@node1 ~]$ ansible webserver --list
  hosts (2):
    node1
    node2
# 匹配多个主机,这种方式可以选择多个,只需要用逗号隔开就行
[devops@node1 ~]$ ansible 192.168.1.1,webserver --list
  hosts (3):
    192.168.1.1
    node1
    node2
# 匹配不属于任何主机组的主机
[devops@node1 ~]$ ansible ungrouped --list
  hosts (1):
    192.168.1.1

使用通配符匹配

先改一个hosts文件内容

[devops@node1 ~]$ cat hosts
192.168.1.1
node1.example.com
node2.example.com

[webserver]
node1
node2

[sqlserver]
node2
node3

使用通配符匹配

# 匹配所有以.example.com结尾的主机
[devops@node1 ~]$ ansible *.example.com --list
  hosts (2):
    node1.example.com
    node2.example.com
# 匹配所有.example.com结尾的主机但是不匹配node2开头的主机,这种情况需要使用引号,如果不使用引号终端会将!当作历史命令给执行的
[devops@node1 ~]$ ansible '*.example.com,!node2*' --list
  hosts (1):
    node1.example.com

配置文件优先级

ansible的配置文件也是有优先级的,他一般会存在4个地方

    1. ANSIBLE_CONFIG:首先,Ansible命令会检查这个环境变量以及指向的配置文件,优先级最高
    1. ./ansible.cfg:当前目录下的ansible.cfg,如果ANSIBLE_CONFIG环境变量不存在,那么就会使用这个
    1. ~/.ansible.cfg:当前用户家目录下的一个隐藏文件,如果当前目录下没有ansible.cfg文件,就会检查这个隐藏文件是否存在
    1. /etc/ansible/ansible.cfg:默认的配置文件,如果以上所有的配置文件都不存在,则会使用这个

配置文件详解

配置文件段

ansible.cfg的配置迷人分为十段:
[defaults] 通用配置项目
[inventory] 与主机清单相关配置
[privilege_escalation] 特权升级相关配置
[paramiko_connection] 使用paramiko连接的相关配置
[ssh_connection] 使用openssh连接的相关配置
[persistent_connection] 持久连接的配置项
[accelerate] 加速模式相关配置
[selinux] selinux相关配置
[color] ansible命令输出的颜色相关配置
[diff] 是否再运行时打印diff (变更前与变更后的差异)

配置文件参数说明

[defaults]
inventory = /etc/ansible/hosts
ask_pass = false
remote_user = root
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = False

这些配置我们一行行来看

  1. inventory: 定义默认使用的主机清单,当前是使用的/etc/ansible/hosts,可以将他修改成你想用的那个文件
  2. remote_user:ansible在操作远程主机时,使用远程主机上的哪个用户身份,默认是root,为了安全可以使用一个普通用户
  3. ask_pass:在操作远程主机时,登录时是否输入密码,默认为true。如果使用密钥认证,将这里设置为false
  4. become:是否提权
  5. become_method:如果使用提权,将以何种方式提权,默认时sudo
  6. become_user:提权到哪个用户,默认提权到root
  7. become_ask_pass:提权是否需要输入密码,默认为False

配置案例

前置条件,配置hosts映射,所有节点都要做

主机 IP 主机名
node1 192.168.100.210 node1
kvm 192.168.100.220 node2
echo "192.168.100.210 node1" >> /etc/hosts
echo "192.168.100.220 node2" >> /etc/hosts

需求,使用普通用户devops去操作主机,devops使用密钥验证,可以免密提权到root

1. 在节点上创建一个普通用户devops,并设置密码123

[root@node1 ~]# useradd devops
[root@node1 ~]# echo 123|passwd --stdin devops
[root@kvm ~]# useradd devops
[root@kvm ~]# echo 123|passwd --stdin devops

2. 配置sudo提权

[root@node1 ~]# cat /etc/sudoers.d/devops 
devops ALL=(root) NOPASSWD:ALL
[root@kvm ~]# cat /etc/sudoers.d/devops 
devops ALL=(root) NOPASSWD:ALL

3. 配置管理节点免密登录到被管节点

[root@node1 ~]# su - devops
[devops@node1 ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/devops/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/devops/.ssh/id_rsa.
Your public key has been saved in /home/devops/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:eUVPHb1laerU/LB+EDBFHu8nrtjz9jE91cZ2SditO8Y devops@node1.example.com
The key's randomart image is:
+---[RSA 3072]----+
|            .o=o+|
|           .o+o=*|
|            .+B+=|
|         . . o+Bo|
|        S . o o=@|
|         .   +o=*|
|             .E=.|
|           o.oo.=|
|          . o+.o.|
+----[SHA256]-----+
# 将公钥发送到被管节点
[devops@node1 ~]$ ssh-copy-id node1
[devops@node1 ~]$ ssh-copy-id node2
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/devops/.ssh/id_rsa.pub"
The authenticity of host 'node2 (192.168.100.220)' can't be established.
ECDSA key fingerprint is SHA256:sH7gqZEak7Xap0VARUzaZJXrr2y4RE2ds40WKMoCspw.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
devops@node2's password: # 这里输入密码
Now try logging into the machine, with:   "ssh 'node2'"

4. 配置

# 1. 将配置文件拷贝到普通用户家目录下
[devops@node1 ~]$ cp -r /etc/ansible/ .
[devops@node1 ~]$ ls
ansible
# 2. 进入目录修改配置
[devops@node1 ~]$ cd ansible/
[devops@node1 ansible]$ ls
ansible.cfg  hosts  roles
# 最后将配置文件修改成这样就行
[devops@node1 ansible]$ cat ansible.cfg
[defaults]
inventory      = ./hosts
sudo_user      = devops
ask_sudo_pass = False
ask_pass      = False
host_key_checking = False
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False

5. 写主机清单

[devops@node1 ansible]$ cat hosts 
node1
node2

6. 验证配置是否生效

[devops@node1 ansible]$ ansible all --list
  hosts (2):
    node1
    node2
[devops@node1 ansible]$ ansible all -m shell -a 'whoami'
node2 | CHANGED | rc=0 >>
root
node1 | CHANGED | rc=0 >>
root

我们可以看到,node1和node2上都执行成功了,并且返回的结果时root,说明提权也是配置正确的
这就是ansible的基本配置了

概述

通义灵码是阿里云开发的一个编码助手,基于AI大模型,提供代码智能生成,智能问答等功能,旨在加快编码,提高开发效率。

功能

  • 帮你续写代码
  • 帮你写代码注释
  • 帮你写单元测试
  • 解释代码
  • 生成代码
  • 代码优化
  • 答疑解惑
  • 排查异常原因

特性

  • 多语言支持:java,python,go,JavaScript等
  • 多IDE支持:VSCode,JetBrains IDES
  • 多网络环境:支持极速本地模型、云端大模型两种代码补全模型,支持一键切换,满足不同网络环境、不同补全强度的场景诉求
  • 标准版面向个人开发者免费使用,企业版面向企业用户(当前免费,支持1000个授权),面向保密单位的私有化部署
  • 不存储用户代码

安装

准备工作

  • VSCode编辑器(1.75.1 及以上)
  • JetBrains IDEs 任意一款 (2020.3 及以上)
  • 阿里云账号,注册地址:
    https://www.aliyun.com/

VSCode安装

  • 直接在左侧插件市场搜索
    通义灵码
    或者
    TONGYI
    点击安装即可
  • 离线安装,通过在
    插件市场网站
    (
    https://marketplace.visualstudio.com/
    )搜索关键词获取

IDEA安装

  • 插件市场安装:file--->settings--->plugins,点击Marketplace ,然后输入
    通义灵码
    或者
    TONGYI
    点击安装即可
  • 离线安装:先下载安装包(
    https://tongyi.aliyun.com/lingma/download),然后file---
    >settings--->plugins,点击齿轮,选择 Install Plugin from Disk

安装完成后,在右侧工具栏将出入同义灵码对话框入口图标,同时在编辑器右键菜单中有一个常用功能的快捷入口。如图:

使用指南

以IDEA为例

续写代码

安装插件之后,通义灵码会扫面整个项目的代码,分析并学习。当我们在编写代码的时候给出续写建议。

  • 根据注释续写
  • 根据代码上下文续写

如下示例演示了续写功能:

代码本意是通过判断 param.getAdCode() 是否有值,没有则使用param的经纬度结合行政区划电子围栏计算出param所属行政区划编码。

当我们写完注释信息“通过事件经纬度结合行政区划电子围栏进行计算”后,给出了提示.

可以通过快捷键 ALT+] 切换到其它的提示(如果它可以给出多个提示的情况下)。

可以通过快捷键 TAB 选择一个提示,此时续写完成了。

可以通过快捷键 ESC 取消提示,此时退出续写。

根据代码上下文续写:

代码注释

通义灵码实时检测我们编写的方法名称,并自动给出可能的方法注释信息

  • 当我们输入java doc 注释关键字的时候提示
  • 当我们选中一个方法,右键调出通义灵码菜单的时候选中
    代码注释

如下示例演示了写注释的功能:

当我们编写完方法名称和参数定义后,给出了方法注释的提示。

写单元测试

在IDEA编辑器界面右键有一个通义灵码的菜单,选中方法,点击菜单中的生成单元测试代码,将唤起对话框,并生成相关的测试代码样例,如图:

解释代码含义

选中需要被解释的代码,右键点击解释代码,将唤起对话框,并生成相关的解释结果。如图:

这个功能的好处:

  • 便于理解别人写的代码
  • 变相解决了程序员不爱写注释的问题
  • 对于特别拗口的逻辑,可以借助通义灵码进行分析

优化代码

选中需要优化的代码,右键点击生成优化建议,将唤起对话框,并生成相关的优化建议。

问答

在IDEA中安装了通义灵码插件后,右侧(maven图标位置)将有一个对话框唤起的入口,在此对话框中可以进行智能问答和搜索。

  • 智能问答类似 ChatGPT ,可以发起各种问答
  • 搜索,搜索阿里云开发者社区、github、StackOverflow中的相关内容
  • 无需离开IDE,专属的开发者搜索引擎

排查异常

仅java

当程序发生异常,控制台中的异常log中将嵌入通义灵码图标,点击图标将打开对话框,在对话框中将给出异常可能的原因,并提供相应的修复建议。如图:

此处是因为JDBC连接信息不正确导致MP自动配置发生异常。当点击图标后,通义灵码给出了分析和解决办法,并对结果提供中英翻译功能。

总结

  • 确有帮助,提高了开发人员的工作效率
  • 对于编码规范,帮助很大
  • 通义灵码也会给出错误的信息,不可全信
  • 缺点:占机器资源

引用

前言:

在经过漫长的技术沉淀,终于又为 .Net 及 .Net Core 的微服务系列框架贡献当中的一个重要组件。

Taurus.DistributedLock is a distributed lock for .net or .net core.【支持:Redis、MemCache、Database、Local、File 五类锁】

1、开源地址:

https://github.com/cyq1162/Taurus.DistributedLock

# Taurus.DistributedLock 分布式锁,使用 .Net Core 示例:


2、以 Nuget 中引入运行包:Taurus.DistributedLock


3、进行编码:

1、引入名称空间:

using Taurus.Plugin.DistributedLock;

2、配置相关项(示例用代码进行配置,也可以在配置文件中配置):

1、Database 锁配置:
DLockConfig.Conn
= "server=.;database=mslog;uid=sa;pwd=123456";//由数据库链接决定启用什么链接 DLockConfig.TableName = "taurus_lock";2、Redis 锁配置:
DLockConfig.RedisServers
= "127.0.0.1:6379";3、MemCache 锁配置:
DLockConfig.MemCacheServers
= "192.168.100.666666:11211";

3、根据需要获得对应锁类型:

var dsLock = DLock.File;//Get File Lock
var dsLock = DLock.Local;//Get Local Lock
var dsLock = DLock.Database;//Get DataBase Lock
var dsLock = DLock.Redis;//Get Redis Lock
var dsLock = DLock.MemCache;//Get MemCache Lock

4、进行锁、并释放锁:

 string key = "myLock";bool isOK = false;try{
isOK
= dsLock.Lock(key, 30000);if(isOK)
{
Console.Write(
"- OK -" +);
}
}
finally{if(isOK)
{
dsLock.UnLock(key);
}
}

更详细使用见开源地址:/demo 运行示例,运行界面:

总结:

分布式锁,最早在是去年,因为前面两个开源框架:
Taurus.DTC 分布式事务框架

Taurus.DTS 分布式任务框架
中需要用到分布式锁。

考虑到
CYQ.Data
ORM框架内部已经实现了分布式缓存(Redis、Memcached),而分布式锁依赖于分布式缓存, 所以花了些时间,集成在
CYQ.Data
框架内部实现了,默认实现时并没有数据库类型。

后来想让Taurus.DistributedLock 分布式锁框架独立,也让 CYQ.Data 单纯一些,分布式锁从 CYQ.Data 版本移除了 。

同时独立的分布式锁框架,增加了数据库类型的锁,数据库类型锁目前支持(Mssql、Mysql、Oracle、Sybase、Postgres、DB2、FireBird、Sqlite、DaMeng(达梦)、KingBaseES(人大金仓))。