Go语言设计与实现 -- 反射

news2024/9/25 3:19:28

Go的反射有哪些应用?

  • IDE中代码的自动补全
  • 对象序列化
  • fmt函数的相关实现
  • ORM框架

什么情况下需要使用反射?

  • 不能明确函数调用哪个接口,需要根据传入的参数在运行时决定。
  • 不能明确传入函数的参数类型,需要在运行时处理任意对象。

反射对性能有消耗,而且可读性低,能不用就不要用反射。

如何比较两个对象完全相同?

Go中提供了一个函数可以实现这个功能:

func DeepEqual(x, y interface{}) bool

DeepEqual 函数的参数是两个 interface,实际上也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否是“深度”相等。

先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。

例如这个代码:

type MyInt int
type YourInt int

func main() {
   m := MyInt(1)
   y := YourInt(1)

   fmt.Println(reflect.DeepEqual(m, y))
}

这个代码的结果是false。

上面的代码中,m, y 底层都是 int,而且值都是 1,但是两者静态类型不同,前者是 MyInt,后者是 YourInt,因此两者不是“深度”相等。

来看一下源码:

func DeepEqual(x, y any) bool {
   if x == nil || y == nil {
      return x == y
   }
   v1 := ValueOf(x)
   v2 := ValueOf(y)
   if v1.Type() != v2.Type() {
      return false
   }
   return deepValueEqual(v1, v2, make(map[visit]bool))
}

首先查看两者是否有一个是 nil 的情况,这种情况下,只有两者都是 nil,函数才会返回 true

接着,使用反射,获取x,y 的反射对象,并且立即比较两者的类型,根据前面的内容,这里实际上是动态类型,如果类型不同,直接返回 false。

最后,最核心的内容在子函数 deepValueEqual 中。

然后我们来看一下deepValueEqual的源码:

// Tests for deep equality using reflected types. The map argument tracks
// comparisons that have already been seen, which allows short circuiting on
// recursive types.
func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
   if !v1.IsValid() || !v2.IsValid() {
      return v1.IsValid() == v2.IsValid()
   }
   if v1.Type() != v2.Type() {
      return false
   }

   // We want to avoid putting more in the visited map than we need to.
   // For any possible reference cycle that might be encountered,
   // hard(v1, v2) needs to return true for at least one of the types in the cycle,
   // and it's safe and valid to get Value's internal pointer.
   hard := func(v1, v2 Value) bool {
      switch v1.Kind() {
      case Pointer:
         if v1.typ.ptrdata == 0 {
            // go:notinheap pointers can't be cyclic.
            // At least, all of our current uses of go:notinheap have
            // that property. The runtime ones aren't cyclic (and we don't use
            // DeepEqual on them anyway), and the cgo-generated ones are
            // all empty structs.
            return false
         }
         fallthrough
      case Map, Slice, Interface:
         // Nil pointers cannot be cyclic. Avoid putting them in the visited map.
         return !v1.IsNil() && !v2.IsNil()
      }
      return false
   }

   if hard(v1, v2) {
      // For a Pointer or Map value, we need to check flagIndir,
      // which we do by calling the pointer method.
      // For Slice or Interface, flagIndir is always set,
      // and using v.ptr suffices.
      ptrval := func(v Value) unsafe.Pointer {
         switch v.Kind() {
         case Pointer, Map:
            return v.pointer()
         default:
            return v.ptr
         }
      }
      addr1 := ptrval(v1)
      addr2 := ptrval(v2)
      if uintptr(addr1) > uintptr(addr2) {
         // Canonicalize order to reduce number of entries in visited.
         // Assumes non-moving garbage collector.
         addr1, addr2 = addr2, addr1
      }

      // Short circuit if references are already seen.
      typ := v1.Type()
      v := visit{addr1, addr2, typ}
      if visited[v] {
         return true
      }

      // Remember for later.
      visited[v] = true
   }

   switch v1.Kind() {
   case Array:
      for i := 0; i < v1.Len(); i++ {
         if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
            return false
         }
      }
      return true
   case Slice:
      if v1.IsNil() != v2.IsNil() {
         return false
      }
      if v1.Len() != v2.Len() {
         return false
      }
      if v1.UnsafePointer() == v2.UnsafePointer() {
         return true
      }
      // Special case for []byte, which is common.
      if v1.Type().Elem().Kind() == Uint8 {
         return bytealg.Equal(v1.Bytes(), v2.Bytes())
      }
      for i := 0; i < v1.Len(); i++ {
         if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
            return false
         }
      }
      return true
   case Interface:
      if v1.IsNil() || v2.IsNil() {
         return v1.IsNil() == v2.IsNil()
      }
      return deepValueEqual(v1.Elem(), v2.Elem(), visited)
   case Pointer:
      if v1.UnsafePointer() == v2.UnsafePointer() {
         return true
      }
      return deepValueEqual(v1.Elem(), v2.Elem(), visited)
   case Struct:
      for i, n := 0, v1.NumField(); i < n; i++ {
         if !deepValueEqual(v1.Field(i), v2.Field(i), visited) {
            return false
         }
      }
      return true
   case Map:
      if v1.IsNil() != v2.IsNil() {
         return false
      }
      if v1.Len() != v2.Len() {
         return false
      }
      if v1.UnsafePointer() == v2.UnsafePointer() {
         return true
      }
      for _, k := range v1.MapKeys() {
         val1 := v1.MapIndex(k)
         val2 := v2.MapIndex(k)
         if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) {
            return false
         }
      }
      return true
   case Func:
      if v1.IsNil() && v2.IsNil() {
         return true
      }
      // Can't do better than this:
      return false
   case Int, Int8, Int16, Int32, Int64:
      return v1.Int() == v2.Int()
   case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
      return v1.Uint() == v2.Uint()
   case String:
      return v1.String() == v2.String()
   case Bool:
      return v1.Bool() == v2.Bool()
   case Float32, Float64:
      return v1.Float() == v2.Float()
   case Complex64, Complex128:
      return v1.Complex() == v2.Complex()
   default:
      // Normal equality suffices
      return valueInterface(v1, false) == valueInterface(v2, false)
   }
}

这个代码的思路很清晰,就是分别递归调用deepValueEqual函数,一直递归到最进本的数据类型,比较int, string等可以直接得出true或者false,再一层层的返回,最终得到深度相等的比较结果。

Go语言是如何实现反射的?

interface,它是 Go 语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。

Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

type和interface

我们需要先介绍一下什么叫做静态类型,什么叫做动态类型。

静态类型

所谓的静态类型(即 static type),就是变量声明的时候的类型。

var age int   // int 是静态类型
var name string  // string 也是静态类型

它是你在编码时,肉眼可见的类型。

动态类型

所谓的 动态类型(即 concrete type,也叫具体类型)是 程序运行时系统才能看见的类型。

这是什么意思呢?

我们都知道 空接口 可以承接什么问题类型的值,什么 int 呀,string 呀,都可以接收。

比如下面这几行代码

var i interface{}   

i = 18  
i = "Go编程时光"  

第一行:我们在给 i 声明了 interface{} 类型,所以 i 的静态类型就是 interface{}

第二行:当我们给变量 i 赋一个 int 类型的值时,它的静态类型还是 interface{},这是不会变的,但是它的动态类型此时变成了 int 类型。

第三行:当我们给变量 i 赋一个 string 类型的值时,它的静态类型还是 interface{},它还是不会变,但是它的动态类型此时又变成了 string 类型。

从以上,可以知道,不管是 i=18 ,还是 i="Go编程时光",都是当程序运行到这里时,变量的类型,才发生了改变,这就是我们最开始所说的 动态类型是程序运行时系统才能看见的类型。

Go 语言中,每个变量都有一个静态类型,在编译阶段就确定了的,比如 int, float64, []int 等等。注意,这个类型是声明时候的类型,不是底层数据类型。

Go官方的博客里面就举过一个例子:

type MyInt int 
var i int 
var j MyInt

尽管 i,j 的底层类型都是 int,但我们知道,他们是不同的静态类型,除非进行类型转换,否则,i 和 j 不能同时出现在等号两侧。j 的静态类型就是 MyInt

反射跟interface{}的关系十分密切,因此我们需要先学习一下interface{}的原理。

interface{}

非空interface{}

type iface struct {
   tab  *itab
   data unsafe.Pointer
}

type itab struct {
	inter *interfacetype
	_type *_type
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

type _type struct {
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	tflag      tflag
	align      uint8
	fieldAlign uint8
	kind       uint8
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
	// gcdata stores the GC type data for the garbage collector.
	// If the KindGCProg bit is set in kind, gcdata is a GC program.
	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}

itab主要由具体类型_type和结构类型interfacetype组成。

我们可以用一张图来理顺中间的关系:

img

空interface{}

type eface struct {
   _type *_type
   data  unsafe.Pointer
}

相比 ifaceeface 就比较简单了。只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。data 描述了具体的值。

接口变量可以存储任何实现了接口定义的所有方法的变量。

Go中常见的接口ReaderWriter接口:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}
var r io.Reader
tty, err := os.OpenFile("./", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

首先声明 r 的类型是 io.Reader,注意,这是 r 的静态类型,此时它的动态类型为 nil,并且它的动态值也是 nil

之后,r = tty 这一语句,将 r 的动态类型变成 *os.File,动态值则变成非空,表示打开的文件对象。这时,r 可以用<value, type>对来表示为: <tty, *os.File>

img

注意看上图,此时虽然 fun 所指向的函数只有一个 Read 函数,其实 *os.File 还包含 Write 函数,也就是说 *os.File 其实还实现了 io.Writer 接口。因此下面的断言语句可以执行:

var w io.Writer
w = r.(io.Writer)

之所以用断言,而不能直接赋值,是因为 r 的静态类型是 io.Reader,并没有实现 io.Writer 接口。断言能否成功,看 r 的动态类型是否符合要求。

这样,w 也可以表示成 <tty, *os.File>,仅管它和 r 一样,但是 w 可调用的函数取决于它的静态类型 io.Writer,也就是说它只能有这样的调用形式: w.Write()w 的内存形式如下图:

img

最后,我们再来一个赋值:

var empty interface{}
empty = w

由于 empty 是一个空接口,因此所有的类型都实现了它,w 可以直接赋给它,不需要执行断言操作。

img

从上面的三张图可以看到,interface包含三部分的信息:_type是类型信息,*data指向实际类型的实际值,itab包含实际类型的信息,包括大小,包路径,还包含绑定在类型上的各种方法。

反射的基本函数

reflect包里面定义了一个接口和一个结构体,reflect.Type是一个接口,reflect.Value是一个结构体,它们提供很多函数来存储在接口里面的类型信息。

reflect.Type 主要提供关于类型相关的信息,所以它和 _type 关联比较紧密;reflect.Value 则结合 _typedata 两者,因此程序员可以获取甚至改变类型的值。

reflect包中提供了两个基础的关于反射的函数来获取上述的接口和结构体:

func TypeOf(i interface{}) Type 
func ValueOf(i interface{}) Value

TypeOf函数用来提取一个接口中值的类型信息。由于它的输入参数是一个空的interface{},调用这个函数的时候,实参会先被转化为interface{}类型。这样,实参的类型信息,方法集,值信息都存储到interface{}变量里了。

func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

这里的 emptyInterface 和上面提到的 eface 是一回事(字段名略有差异,字段是相同的),并且在不同的源码包:前者在 reflect 包,后者在 runtime 包。 eface.typ 就是动态类型。

type emptyInterface struct {
    typ  *rtype
    word unsafe.Pointer
}

然后toType函数只是做了一个类型转换而已:

func toType(t *rtype) Type {
   if t == nil {
      return nil
   }
   return t
}

注意看,返回值Type实际上是一个接口,定义了很多方法,用来获取类型的相关的各种信息,而*rtype实现了Type接口。

type Type interface {
   // Methods applicable to all types.

   // 此类型的变量对齐后占用的字节数
   Align() int

   // 如果是struct自动,对齐后占用的字节数
   FieldAlign() int

    // 返回类型方法集李的第`i`(传入的参数)个方法
   Method(int) Method


   // 通过名称获取方法
   MethodByName(string) (Method, bool)


   // 获取类型方法集里导出的方法个数
   NumMethod() int

   // 类型名称
   Name() string

    // 返回类型所在的路径,如: encoding/base64
   PkgPath() string

   // 返回类型的大小,和unsafe.Sizeof功能类似
   Size() uintptr

   // 返回类型的字符串表示形式
   String() string

   // 返回类型的类型值
   Kind() Kind

   // 类型是否实现了接口 u
   Implements(u Type) bool

   // 是否可以赋值给 u
   AssignableTo(u Type) bool


   // 是否可以类型转换成 u
   ConvertibleTo(u Type) bool

   // 类型是否可以比较
   Comparable() bool


   // 类型占据的位数
   Bits() int


   // 返回通道的方向,只能是chan类型调用
   ChanDir() ChanDir


   // 返回类型是否是可变参数,只能是func类型调用
   IsVariadic() bool

   // 返回内部子元素类型, 只能由类型Array, Chan, Map, Ptr, or Slice调用
   Elem() Type

   // 返回结构体类型的第i个字段,只能是结构体类型调用
   // 如果i超过了字段数,就会panic
   Field(i int) StructField

   // 返回嵌套的结构体的字段
   FieldByIndex(index []int) StructField

   // 通过字段名获取字段
   FieldByName(name string) (StructField, bool)

   // 返回名称符合func函数的字段
   FieldByNameFunc(match func(string) bool) (StructField, bool)

   // 获取函数类型的第i个参数的类型
   In(i int) Type

   // 返回map的key类型,只能由类型map调用
   Key() Type

   // 返回Array的长度,只能由Array调用
   Len() int

   // 返回类型字段的数量,只能由类型Struct调用
   NumField() int

   // 返回函数类型的输入参数个数
   NumIn() int

   // 返回函数类型的返回值个数
   NumOut() int

   // 返回函数类型的第i个值的类型
   Out(i int) Type

   // 返回类型结构体的相同部分
   common() *rtype
    
   // 返回类型结构体的不同部分
   uncommon() *uncommonType
}

可见Type定义了非常多的方法,通过它们可以获取到类型的所有信息。

注意到Type方法集的倒数第二个方法common返回的rtype类型,它和_type是一回事,而且源代码里面也注释了,两边要保持同步。

type rtype struct {
    size       uintptr
    ptrdata    uintptr
    hash       uint32
    tflag      tflag
    align      uint8
    fieldAlign uint8
    kind       uint8
    alg        *typeAlg
    gcdata     *byte
    str        nameOff
    ptrToThis  typeOff
}

所有的类型都会包含 rtype 这个字段,表示各种类型的公共信息;另外,不同类型包含自己的一些独特的部分。

比如下面的 arrayTypechanType 都包含 rytpe,而前者还包含 slice,len 等和数组相关的信息;后者则包含 dir 表示通道方向的信息。

// arrayType represents a fixed array type.
type arrayType struct {
    rtype `reflect:"array"`
    elem  *rtype // array element type
    slice *rtype // slice type
    len   uintptr
}

// chanType represents a channel type.
type chanType struct {
    rtype `reflect:"chan"`
    elem  *rtype  // channel element type
    dir   uintptr // channel direction (ChanDir)
}

注意到,Type 接口实现了 String() 函数,满足 fmt.Stringer 接口,因此使用 fmt.Println 打印的时候,输出的是 String() 的结果。另外,fmt.Printf() 函数,如果使用 %T 来作为格式参数,输出的是 reflect.TypeOf 的结果,也就是动态类型。例如:

fmt.Printf("%T", 3) // int

TypeOf函数讲完了,我们接下来来看一下ValueOf函数。返回值reflect.Value表示interface{}里面存储的实际变量,它能提供实际变量的各种信息。

源码如下:

func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }

   // ……
    return unpackEface(i)
}

// 分解 eface
func unpackEface(i interface{}) Value {
    e := (*emptyInterface)(unsafe.Pointer(&i))

    t := e.typ
    if t == nil {
        return Value{}
    }

    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f}
}

从源码看,比较简单:将先将 i 转换成 *emptyInterface 类型, 再将它的 typ 字段和 word 字段以及一个标志位字段组装成一个 Value 结构体,而这就是 ValueOf 函数的返回值,它包含类型结构体指针、真实数据的地址、标志位。

Value 结构体定义了很多方法,通过这些方法可以直接操作 Value 字段 ptr 所指向的实际数据:

// 设置切片的 len 字段,如果类型不是切片,就会panic
 func (v Value) SetLen(n int)

 // 设置切片的 cap 字段
 func (v Value) SetCap(n int)

 // 设置字典的 kv
 func (v Value) SetMapIndex(key, val Value)

 // 返回切片、字符串、数组的索引 i 处的值
 func (v Value) Index(i int) Value

 // 根据名称获取结构体的内部字段值
 func (v Value) FieldByName(name string) Value

 // ……

// 用来获取 int 类型的值
func (v Value) Int() int64

// 用来获取结构体字段(成员)数量
func (v Value) NumField() int

// 尝试向通道发送数据(不会阻塞)
func (v Value) TrySend(x reflect.Value) bool

// 通过参数列表 in 调用 v 值所代表的函数(或方法
func (v Value) Call(in []Value) (r []Value) 

// 调用变参长度可变的函数
func (v Value) CallSlice(in []Value) []Value

另外,通过 Type() 方法和 Interface() 方法可以打通 interfaceTypeValue 三者。Type() 方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。Interface() 方法可以将 Value 还原成原来的 interface。

img

总结一下:TypeOf() 函数返回一个接口,这个接口定义了一系列方法,利用这些方法可以获取关于类型的所有信息; ValueOf() 函数返回一个结构体变量,包含类型信息以及实际值。

img

上图中,rtye 实现了 Type 接口,是所有类型的公共部分。emptyface 结构体和 eface 其实是一个东西,而 rtype 其实和 _type 是一个东西,只是一些字段稍微有点差别,比如 emptyface 的 word 字段和 eface 的 data 字段名称不同,但是数据型是一样的。

反射的三大的定律

根据 Go 官方关于反射的博客,反射有三大定律:

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.
  3. To modify a reflection object, the value must be settable.

第一条是最基本的:反射是一种检测存储在 interface 中的类型和值机制。这可以通过 TypeOf 函数和 ValueOf 函数得到。

第二条实际上和第一条是相反的机制,它将 ValueOf 的返回值通过 Interface() 函数反向转变成 interface 变量。

前两条就是说 接口型变量反射类型对象 可以相互转化,反射类型对象实际上就是指的前面说的 reflect.Typereflect.Value

第三条不太好懂:如果需要操作一个反射变量,那么它必须是可设置的。反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑。所以第二种情况在语言层面是不被允许的。

举一个经典例子:

var x float64 = 3.4

v := reflect.ValueOf(x)

v.SetFloat(7.1) // Error: will panic.

执行上面的代码会产生 panic,原因是反射变量 v 不能代表 x 本身,为什么?因为调用 reflect.ValueOf(x) 这一行代码的时候,传入的参数在函数内部只是一个拷贝,是值传递,所以 v 代表的只是 x 的一个拷贝,因此对 v 进行操作是被禁止的。

可设置是反射变量 Value 的一个性质,但不是所有的 Value 都是可被设置的。

就像在一般的函数里那样,当我们想改变传入的变量时,使用指针就可以解决了。

var x float64 = 3.4

p := reflect.ValueOf(&x)

fmt.Println("type of p:", p.Type())

fmt.Println("settability of p:", p.CanSet())

输出是这样的:

type of p: *float64

settability of p: false

p 还不是代表 xp.Elem() 才真正代表 x,这样就可以真正操作 x 了:

v := p.Elem()

v.SetFloat(7.1)

fmt.Println(v.Interface()) // 7.1

fmt.Println(x) // 7.1

关于第三条,记住一句话:如果想要操作原变量,反射变量 Value 必须要 hold 住原变量的地址才行。

参考自:码神桃花源的博客。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/354908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1.TCP、UDP区别、TCP/IP七层、四层模型、应用层协议(计网)

文章目录1.OSI 七层模型是什么&#xff1f;每一层的作用是什么&#xff1f;2.TCP/IP 四层模型是什么&#xff1f;每一层的作用是什么&#xff1f;应用层&#xff08;Application layer&#xff09;传输层&#xff08;Transport layer&#xff09;网络层&#xff08;Network lay…

百度工程师带你探秘C++内存管理

一、概述 ptmalloc是开源GNU C Library(glibc)默认的内存管理器&#xff0c;当前大部分Linux服务端程序使用的是ptmalloc提供的malloc/free系列函数&#xff0c;而它在性能上远差于Meta的jemalloc和Google的tcmalloc。服务端程序调用ptmalloc提供的malloc/free函数申请和释放内…

Datawhale组队学习:大数据 D2——分布式文件系统(HDFS)

妙趣横生大数据 Day2三、Hadoop 分布式文件系统(HDFS)1. 分布式文件系统2. HDFS 简介3. HDFS 体系结构4. HDFS存储原理数据冗余存储数据存储策略数据错误与恢复5. HDFS数据读写过程读写过程HDFS故障类型和其检测方法HDFS编程实验1. 本地和集群文件间操作2. 基本文件操作3. Hado…

Java基本语法【未完待续】

目录 一、注释方式 1、单行注释 // 2、多行注释 /*...*/ 3、文档注释 /**....*/ 二、标识符和关键字 三、数据类型 拓展及面试题讲解 1、整数拓展 进制 二进制0b 八进制0 十六进制0x 2、字符拓展 编码Unicode表 2字节 0~65536 3、字符串拓展 4、布尔值拓展 四、类型…

CleanMyMac X软件下载及详细功能介绍

mac平台的知名系统清理应用CleanMyMac在经历了一段时间的测试后&#xff0c;全新设计的X正式上线。与CleanMyMac3相比&#xff0c;新版本的UI设计焕然一新&#xff0c;采用了完全不同的风格。使用Windows电脑时&#xff0c;很多人会下载各类优化软件&#xff0c;而在Mac平台中&…

jsp高校教职工管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 jsp 高校教职工管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助mvc模式 serlvetdaobean方式开发&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式 开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#…

tomcat安装和配置

目录 1.下载tomcat 2.解压tomcat压缩包 3.配置端口号 4.启动 命令行窗口日志乱码的解决 5.验证 tomcat 如果已经安装配置过jdk&#xff0c;则向下执行&#xff0c;若无&#xff0c;请先安装jdk。 1.下载tomcat 路径&#xff1a; Apache Tomcat - Apache Tomcat 9 Sof…

Windows 系统从零配置 Python 环境,安装CUDA、CUDNN、PyTorch 详细教程

文章目录1 配置 python 环境1.1 安装 Anaconda1.2 检查环境安装成功1.3 创建虚拟环境1.4 进入/退出 刚刚创建的环境1.5 其它操作1.5.1 查看电脑上所有已创建的环境1.5.2 删除已创建的环境2 安装 CUDA 和 CUDNN2.1 查看自己电脑支持的 CUDA 版本2.2 安装 CUDA2.3 安装 CUDNN2.4 …

LabVIEW中CPU和内存使用情况在NI分布式系统管理器中不可见

LabVIEW中CPU和内存使用情况在NI分布式系统管理器中不可见想使用NI分布式系统管理器监测网络连接实时控制器的CPU和内存使用情况。从左侧窗口的树中选择了感兴趣的实时目标&#xff0c;然后通过选择视图自动视图来确保启用自动查看。希望看到CPU/内存选项卡&#xff0c;但它有显…

算法导论【在线算法】—The Ski-Rental Problem、The Lost Cow Problem、The Secretary Problem

算法导论【在线算法】The Ski-Rental Problem问题描述在线算法证明The Lost Cow Problem问题描述在线算法类似问题—寻宝藏The Secretary Problem问题描述在线算法The Best Possible kThe Ski-Rental Problem 问题描述 假设你正在上滑雪课。每节课结束后&#xff0c;你决定&a…

【Element】el-table 表格

目录 ElementUI 表格分页&#xff08;每页20条&#xff09; 表格分页&#xff08;全部数据&#xff09; 表格排序&#xff08;全部数据&#xff09; 表格排序&#xff08;默认&#xff09; 两个el-table冲突 加载数据前显示“ 暂无数据 ” 表格项为路由 表头样式 树形…

Homebrew 安装遇到的问题

Homebrew 安装遇到的问题 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录Homebrew 安装遇到的问题前言一、安装二、遇到的问题1.提示 zsh: command not found: brew三、解决问题前言 使用 Homebrew 能够 安装 Apple&#xff08;或您的 Linux 系统&#…

React 合成事件理解

1 事件三个阶段 捕获、目标、处理 &#xff08;具体百度&#xff0c;后面有空补全&#xff09;2import React from "react";class Test extends React.Component {parentRef;childRef;constructor(props) {super(props);this.parentRef React.createRef();this.chil…

cmd 窗口、记事本打开后一片空白且几秒钟后闪退的问题解决方案汇总

前言 前段时间&#xff0c;电脑忽然出现了问题&#xff0c;首先是通过 微软应用商店 Microsoft Store 下载安装的 Snipaste 截图软件崩溃&#xff0c;不过将其卸载后&#xff0c;通过电脑管家下载后又可以正常使用了。 之后就是突然发现&#xff0c;记事本文本文档不能使用了…

分享112个HTML娱乐休闲模板,总有一款适合您

分享112个HTML娱乐休闲模板&#xff0c;总有一款适合您 112个HTML娱乐休闲模板下载链接&#xff1a;https://pan.baidu.com/s/15uBy1SVSckPPMM55fiudeQ?pwdkqfz 提取码&#xff1a;kqfz Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 Bootstrap视频网站模板 …

Terraform基础入门 (Infrastructure as Code)

文章目录前言介绍Terraform 术语Terraform 如何工作关于provider安装开启本地缓存demo1(dockernginx)demo2(dockerzookeeperkafka)参考资料前言 像写代码一样管理基础设施。 Terraform 使用较为高级的配置文件语法来描述基础设施&#xff0c;这个特性让你对配置文件进行版本化…

Ubuntu升级cmake

目录 1、下载cmake安装包 2、开始安装 3、查看cmake版本 参考链接&#xff1a; https://blog.csdn.net/qq_27350133/article/details/121994229 1、下载cmake安装包 cmake安装包下载&#xff1a;download | cmake 我们根据自身需求下载所需版本的cmake安装包&#xff0c;这…

万字干货 | 荔枝魔方基于云原生的架构设计与实践

近年来&#xff0c;荔枝集团在国内和海外的业务迅速发展&#xff0c;业务数据规模也是成几何式地增长&#xff0c;海量数据的计算分析场景、业务智能算法应用需求随之而生&#xff0c;为了快速地满足业务发展的需要&#xff0c;我们面临着诸多的技术挑战。技术挑战工程问题资源…

计算机如何思考与图灵完备

图灵完备是针对一套数据操作规则而言的概念,数据操作规则可以是一门编程语言,也可以是计算机实现里面的指令集,比如C/C++是图图灵完备的,通用CPU也是图灵完备的,但是GPU却不一定是图灵完备的。说白了图灵完备定义了一套规则,当这套规则可以实现图灵迹模型里的全部功能时,…

Unreal Engine09:自定义Pawn运动组件

写在前面 这里介绍一下如何为Pawn定制自定义的运动组件。 一、新建一个运动组件C类 需要在Editor中新建类&#xff0c;不要直接Visual Studio中新建&#xff1b;Pawn的运动组件继承自UPawnMovementComponent&#xff0c;需要勾选显示所有类才能找到&#xff1b; 初始时&…