• 关于

    不定类型可以做什么

    的搜索结果

问题

迷你书下载 精彩片段: 恶名昭著的指针究竟是什么:报错

指针——C语言的灵魂 为什么说C指针是C语言的灵魂? 买前必读: 迷你书下载 精彩片段: 恶名昭著的指针究竟是什么:报错 为什么说C指针是C语言的灵魂? 来...
kun坤 2020-06-09 15:10:04 4 浏览量 回答数 1

回答

1 写出下面代码输出内容。 package main import (    "fmt" ) funcmain() {     defer_call() } funcdefer_call() {     deferfunc() {fmt.Println("打印前")}()     deferfunc() {fmt.Println("打印中")}()     deferfunc() {fmt.Println("打印后")}()     panic("触发异常") } 考点:defer执行顺序 解答: defer 是后进先出。 panic 需要等defer 结束后才会向上传递。 出现panic恐慌时候,会先按照defer的后入先出的顺序执行,最后才会执行panic。 打印后 打印中 打印前 panic: 触发异常 2 以下代码有什么问题,说明原因。 type student struct {     Name string     Age  int } funcpase_student() {     m := make(map[string]*student)     stus := []student{         {Name: "zhou",Age: 24},         {Name: "li",Age: 23},         {Name: "wang",Age: 22},     }    for _,stu := range stus {         m[stu.Name] =&stu     } } 考点:foreach 解答: 这样的写法初学者经常会遇到的,很危险! 与Java的foreach一样,都是使用副本的方式。所以m[stu.Name]=&stu实际上一致指向同一个指针, 最终该指针的值为遍历的最后一个struct的值拷贝。 就像想修改切片元素的属性: for _, stu := rangestus {     stu.Age = stu.Age+10} 也是不可行的。 大家可以试试打印出来: func pase_student() {     m := make(map[string]*student)     stus := []student{         {Name: "zhou",Age: 24},         {Name: "li",Age: 23},         {Name: "wang",Age: 22},     }         // 错误写法     for _,stu := range stus {         m[stu.Name] =&stu     }          fork,v:=range m{               println(k,"=>",v.Name)     }           // 正确     for i:=0;i<len(stus);i++ {        m[stus[i].Name] = &stus[i]     }          fork,v:=range m{                println(k,"=>",v.Name)     } } 3 下面的代码会输出什么,并说明原因 func main() {     runtime.GOMAXPROCS(1)     wg := sync.WaitGroup{}     wg.Add(20)   for i := 0; i < 10; i++ {                  gofunc() {            fmt.Println("A: ", i)            wg.Done()         }()     }             for i:= 0; i < 10; i++ {                    gofunc(i int) {            fmt.Println("B: ", i)            wg.Done()         }(i)     }     wg.Wait() } 考点:go执行的随机性和闭包 解答: 谁也不知道执行后打印的顺序是什么样的,所以只能说是随机数字。 但是A:均为输出10,B:从0~9输出(顺序不定)。 第一个go func中i是外部for的一个变量,地址不变化。遍历完成后,最终i=10。 故go func执行时,i的值始终是10。 第二个go func中i是函数参数,与外部for中的i完全是两个变量。 尾部(i)将发生值拷贝,go func内部指向值拷贝地址。 4 下面代码会输出什么? type People struct{}func (p People)ShowA() {     fmt.Println("showA")     p.ShowB() } func(pPeople)ShowB() {     fmt.Println("showB") } typeTeacher struct {     People } func(t*Teacher)ShowB() {     fmt.Println("teachershowB") } funcmain() {     t := Teacher{}     t.ShowA() } 考点:go的组合继承 解答: 这是Golang的组合模式,可以实现OOP的继承。 被组合的类型People所包含的方法虽然升级成了外部类型Teacher这个组合类型的方法(一定要是匿名字段),但它们的方法(ShowA())调用时接受者并没有发生变化。 此时People类型并不知道自己会被什么类型组合,当然也就无法调用方法时去使用未知的组合者Teacher类型的功能。 showAshowB 5 下面代码会触发异常吗?请详细说明 func main() {     runtime.GOMAXPROCS(1)     int_chan := make(chanint, 1)     string_chan := make(chanstring, 1)     int_chan <- 1     string_chan <- "hello"     select {                case value := <-int_chan:        fmt.Println(value)           casevalue := <-string_chan:                   panic(value)     } } 考点:select随机性 解答: select会随机选择一个可用通用做收发操作。 所以代码是有肯触发异常,也有可能不会。 单个chan如果无缓冲时,将会阻塞。但结合 select可以在多个chan间等待执行。有三点原则: select 中只要有一个case能return,则立刻执行。 当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。 如果没有一个case能return则可以执行”default”块。 6 下面代码输出什么? funccalc(indexstring, a, bint) int {     ret := a+ b     fmt.Println(index,a, b, ret)     return ret } funcmain() {          a := 1     b := 2     defer calc("1", a,calc("10", a, b))    a = 0     defer calc("2", a,calc("20", a, b))    b = 1 } 考点:defer执行顺序 解答: 这道题类似第1题 需要注意到defer执行顺序和值传递 index:1肯定是最后执行的,但是index:1的第三个参数是一个函数,所以最先被调用 calc("10",1,2)==>10,1,2,3 执行index:2时,与之前一样,需要先调用calc("20",0,2)==>20,0,2,2 执行到b=1时候开始调用,index:2==>calc("2",0,2)==>2,0,2,2最后执行index:1==>calc("1",1,3)==>1,1,3,4 10 1 2 320 0 2 22 0 2 21 1 3 4 7 请写出以下输入内容 funcmain() {            s := make([]int,5)     s = append(s,1, 2, 3)     fmt.Println(s) } 考点:make默认值和append 解答: make初始化是由默认值的哦,此处默认值为0 [00000123] 大家试试改为: s := make([]int, 0) s = append(s, 1, 2, 3) fmt.Println(s)//[1 2 3] 8 下面的代码有什么问题? type UserAges struct {     ages map[string]int     sync.Mutex } func(uaUserAges)Add(name string, age int) {     ua.Lock()          deferua.Unlock()     ua.ages[name] = age } func(uaUserAges)Get(name string)int {           ifage, ok := ua.ages[name]; ok {                  return age     }         return-1 } 考点:map线程安全 解答: 可能会出现 fatal error: concurrent mapreadandmapwrite. 修改一下看看效果 func (ua *UserAges)Get(namestring)int {     ua.Lock()          deferua.Unlock()          ifage, ok := ua.ages[name]; ok {                   return age     }            return-1 } 9.   下面的迭代会有什么问题? func (set *threadSafeSet)Iter()<-chaninterface{} {     ch := make(chaninterface{})                  gofunc() {         set.RLock()                for elem := range set.s {            ch <- elem         }                   close(ch)         set.RUnlock()     }()      return ch } 考点:chan缓存池 解答: 看到这道题,我也在猜想出题者的意图在哪里。 chan?sync.RWMutex?go?chan缓存池?迭代? 所以只能再读一次题目,就从迭代入手看看。 既然是迭代就会要求set.s全部可以遍历一次。但是chan是为缓存的,那就代表这写入一次就会阻塞。 我们把代码恢复为可以运行的方式,看看效果 package main import (          "sync"     "fmt")//下面的迭代会有什么问题?type threadSafeSet struct {     sync.RWMutex     s []interface{} } func(set*threadSafeSet)Iter() <-chaninterface{} {     //ch := make(chan interface{}) // 解除注释看看!     ch := make(chaninterface{},len(set.s))    gofunc() {         set.RLock()        forelem,value := range set.s {            ch <- elem             println("Iter:",elem,value)         }       close(ch)         set.RUnlock()     }()     return ch } funcmain() {     th:=threadSafeSet{         s:[]interface{}{"1","2"},     }     v:=<-th.Iter()     fmt.Sprintf("%s%v","ch",v) } 10 以下代码能编译过去吗?为什么? package main import (   "fmt") typePeople interface {     Speak(string) string } typeStduent struct{} func(stu*Stduent)Speak(think string)(talk string) {     ifthink == "bitch" {         talk = "Youare a good boy"     } else {         talk = "hi"     }     return } funcmain() {     var peoPeople = Stduent{}     think := "bitch"    fmt.Println(peo.Speak(think)) } 考点:golang的方法集 解答: 编译不通过! 做错了!?说明你对golang的方法集还有一些疑问。 一句话:golang的方法集仅仅影响接口实现和方法表达式转化,与通过实例或者指针调用方法无关。 11 以下代码打印出来什么内容,说出为什么。 package main import (   "fmt") typePeople interface {     Show() } typeStudent struct{} func(stuStudent)Show() { } funclive()People {     var stuStudent     return stu } funcmain() {   if live() == nil {         fmt.Println("AAAAAAA")     } else {         fmt.Println("BBBBBBB")     } } 考点:interface内部结构 解答: 很经典的题! 这个考点是很多人忽略的interface内部结构。 go中的接口分为两种一种是空的接口类似这样: varininterface{} 另一种如题目: type People interface {     Show() } 他们的底层结构如下: type eface struct {      //空接口     _type _type        //类型信息     data  unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void)} typeiface struct {      //带有方法的接口     tab  itab          //存储type信息还有结构实现方法的集合     data unsafe.Pointer  //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void)} type_type struct {     size       uintptr //类型大小     ptrdata    uintptr //前缀持有所有指针的内存大小     hash       uint32  //数据hash值     tflag     tflag     align      uint8   //对齐     fieldalign uint8   //嵌入结构体时的对齐     kind       uint8   //kind 有些枚举值kind等于0是无效的     alg       *typeAlg //函数指针数组,类型实现的所有方法     gcdata    *byte   str       nameOff     ptrToThis typeOff }type itab struct {     inter  *interfacetype //接口类型     _type  *_type         //结构类型     link   *itab     bad    int32     inhash int32     fun    [1]uintptr     //可变大小方法集合} 可以看出iface比eface 中间多了一层itab结构。 itab 存储_type信息和[]fun方法集,从上面的结构我们就可得出,因为data指向了nil 并不代表interface 是nil, 所以返回值并不为空,这里的fun(方法集)定义了接口的接收规则,在编译的过程中需要验证是否实现接口 结果: BBBBBBB 12.是否可以编译通过?如果通过,输出什么? func main() {     i := GetValue() switch i.(type) {          caseint:                println("int")            casestring:                println("string")            caseinterface{}:                println("interface")            default:                 println("unknown")     } } funcGetValue()int {    return1 } 解析 考点:type 编译失败,因为type只能使用在interface 13.下面函数有什么问题? func funcMui(x,y int)(sum int,error){     returnx+y,nil } 解析 考点:函数返回值命名 在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。 如果返回值有有多个返回值必须加上括号; 如果只有一个返回值并且有命名也需要加上括号; 此处函数第一个返回值有sum名称,第二个未命名,所以错误。 14.是否可以编译通过?如果通过,输出什么? package mainfunc main() {    println(DeferFunc1(1)) println(DeferFunc2(1)) println(DeferFunc3(1)) }func DeferFunc1(i int)(t int) {     t = i   deferfunc() {         t += 3     }() return t } funcDeferFunc2(i int)int {     t := i  deferfunc() {         t += 3     }() return t } funcDeferFunc3(i int)(t int) {   deferfunc() {         t += i     }() return2} 解析 考点:defer和函数返回值 需要明确一点是defer需要在函数结束前执行。 函数返回值名字会在函数起始处被初始化为对应类型的零值并且作用域为整个函数 DeferFunc1有函数返回值t作用域为整个函数,在return之前defer会被执行,所以t会被修改,返回4; DeferFunc2函数中t的作用域为函数,返回1;DeferFunc3返回3 15.是否可以编译通过?如果通过,输出什么? funcmain() {    list := new([]int)     list = append(list,1)     fmt.Println(list) } 解析 考点:new list:=make([]int,0) 16.是否可以编译通过?如果通过,输出什么? package mainimport "fmt"funcmain() {     s1 := []int{1, 2, 3}     s2 := []int{4, 5}     s1 = append(s1,s2)     fmt.Println(s1) } 解析 考点:append append切片时候别漏了'…' 17.是否可以编译通过?如果通过,输出什么? func main() {     sn1 := struct {         age  int         name string     }{age: 11,name: "qq"}     sn2 := struct {         age  int         name string     }{age: 11,name: "qq"}  if sn1== sn2 {         fmt.Println("sn1== sn2")     }     sm1 := struct {         age int         m   map[string]string     }{age: 11, m:map[string]string{"a": "1"}}     sm2 := struct {         age int         m   map[string]string     }{age: 11, m:map[string]string{"a": "1"}}             if sm1 == sm2 {         fmt.Println("sm1== sm2")     } } 解析 考点:结构体比较 进行结构体比较时候,只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。 sn3:= struct {     name string     age  int } {age:11,name:"qq"} sn3与sn1就不是相同的结构体了,不能比较。 还有一点需要注意的是结构体是相同的,但是结构体属性中有不可以比较的类型,如map,slice。 如果该结构属性都是可以比较的,那么就可以使用“==”进行比较操作。 可以使用reflect.DeepEqual进行比较 if reflect.DeepEqual(sn1, sm) {     fmt.Println("sn1==sm") }else {     fmt.Println("sn1!=sm") } 所以编译不通过: invalid operation: sm1 == sm2 18.是否可以编译通过?如果通过,输出什么? func Foo(x interface{}) {    if x== nil {         fmt.Println("emptyinterface")                 return     }     fmt.Println("non-emptyinterface") }        funcmain() {           var x *int = nil     Foo(x) } 解析 考点:interface内部结构 non-emptyinterface 19.是否可以编译通过?如果通过,输出什么? func GetValue(m map[int]string, id int)(string, bool) {              if _,exist := m[id]; exist {                    return"存在数据", true     }            returnnil, false}funcmain() {     intmap:=map[int]string{    1:"a",        2:"bb",        3:"ccc",     }     v,err:=GetValue(intmap,3)     fmt.Println(v,err) } 解析 考点:函数返回值类型 nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。报:cannot use nil as type string in return argument. 20.是否可以编译通过?如果通过,输出什么? const (     x = iota     y     z = "zz"     k     p = iota) funcmain()  {     fmt.Println(x,y,z,k,p) } 解析 考点:iota 结果: 0 1 zz zz 4 21.编译执行下面代码会出现什么? package mainvar(     size :=1024     max_size = size*2) funcmain() {     println(size,max_size) } 解析 考点:变量简短模式 变量简短模式限制: 定义变量同时显式初始化 不能提供数据类型 只能在函数内部使用 结果: syntaxerror: unexpected := 22.下面函数有什么问题? package main const cl = 100 var bl   = 123 funcmain() {     println(&bl,bl)    println(&cl,cl) } 解析 考点:常量 常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用, cannot take the address of cl 23.编译执行下面代码会出现什么? package main funcmain() {     for i:=0;i<10;i++  {     loop:        println(i)     }    gotoloop } 解析 考点:goto goto不能跳转到其他函数或者内层代码 goto loop jumps intoblock starting at 24.编译执行下面代码会出现什么? package main import"fmt" funcmain() {      typeMyInt1 int      typeMyInt2 = int     var i int =9     var i1MyInt1 = i     var i2MyInt2 = i     fmt.Println(i1,i2) } 解析 考点:**Go 1.9 新特性 Type Alias ** 基于一个类型创建一个新类型,称之为defintion;基于一个类型创建一个别名,称之为alias。 MyInt1为称之为defintion,虽然底层类型为int类型,但是不能直接赋值,需要强转; MyInt2称之为alias,可以直接赋值。 结果: cannot use i (typeint) astype MyInt1 in assignment 25.编译执行下面代码会出现什么? package main import"fmt" typeUser struct { } typeMyUser1 User typeMyUser2 = User func(iMyUser1)m1(){     fmt.Println("MyUser1.m1") } func(iUser)m2(){     fmt.Println("User.m2") } funcmain() {     var i1MyUser1     var i2MyUser2     i1.m1()     i2.m2() } 解析 考点:**Go 1.9 新特性 Type Alias ** 因为MyUser2完全等价于User,所以具有其所有的方法,并且其中一个新增了方法,另外一个也会有。 但是 i1.m2() 是不能执行的,因为MyUser1没有定义该方法。 结果: MyUser1.m1User.m2 26.编译执行下面代码会出现什么? package main import"fmt" type T1 struct { } func(tT1)m1(){     fmt.Println("T1.m1") } type T2= T1 typeMyStruct struct {     T1     T2 } funcmain() {     my:=MyStruct{}     my.m1() } 解析 考点:**Go 1.9 新特性 Type Alias ** 是不能正常编译的,异常: ambiguousselectormy.m1 结果不限于方法,字段也也一样;也不限于type alias,type defintion也是一样的,只要有重复的方法、字段,就会有这种提示,因为不知道该选择哪个。 改为: my.T1.m1() my.T2.m1() type alias的定义,本质上是一样的类型,只是起了一个别名,源类型怎么用,别名类型也怎么用,保留源类型的所有方法、字段等。 27.编译执行下面代码会出现什么? package main import (           "errors"     "fmt") varErrDidNotWork = errors.New("did not work") funcDoTheThing(reallyDoItbool)(errerror) {     ifreallyDoIt {         result, err:= tryTheThing()         if err!= nil || result != "it worked" {            err = ErrDidNotWork         }     }    return err } functryTheThing()(string,error) {     return"",ErrDidNotWork } funcmain() {     fmt.Println(DoTheThing(true))     fmt.Println(DoTheThing(false)) } 解析 考点:变量作用域 因为 if 语句块内的 err 变量会遮罩函数作用域内的 err 变量,结果: 改为: func DoTheThing(reallyDoIt bool)(errerror) {     varresult string     ifreallyDoIt {         result, err =tryTheThing()         if err!= nil || result != "it worked" {            err = ErrDidNotWork         }     }    return err } 28.编译执行下面代码会出现什么? package main functest() []func() {     varfuns []func()     fori:=0;i<2;i++  {         funs = append(funs,func() {                       println(&i,i)         })     }    returnfuns } funcmain(){     funs:=test()            for_,f:=range funs{         f()     } } 解析 考点:闭包延迟求值 for循环复用局部变量i,每一次放入匿名函数的应用都是想一个变量。 结果: 0xc042046000 2 0xc042046000 2 如果想不一样可以改为: func test() []func()  {     varfuns []func()     fori:=0;i<2;i++  {         x:=i         funs = append(funs,func() {            println(&x,x)         })     }    returnfuns } 29.编译执行下面代码会出现什么? package main functest(x int)(func(),func()) {     returnfunc() {        println(x)     x+=10     }, func() {              println(x)     } } funcmain() {     a,b:=test(100)     a()     b() } 解析 考点:闭包引用相同变量* 结果: 100 110 30. 编译执行下面代码会出现什么? package main im port (   "fmt"     "reflect") funcmain1() {     deferfunc() {      iferr:=recover();err!=nil{           fmt.Println(err)        }else {           fmt.Println("fatal")        }     }()     deferfunc() {        panic("deferpanic")     }()     panic("panic") } funcmain() {     deferfunc() {        iferr:=recover();err!=nil{            fmt.Println("++++")            f:=err.(func()string)             fmt.Println(err,f(),reflect.TypeOf(err).Kind().String())         }else {            fmt.Println("fatal")         }     }()     deferfunc() {        panic(func()string {            return "defer panic"         })     }()     panic("panic") } 解析 考点:panic仅有最后一个可以被revover捕获 触发panic("panic")后顺序执行defer,但是defer中还有一个panic,所以覆盖了之前的panic("panic") 原文链接:https://blog.csdn.net/itcastcpp/article/details/80462619
剑曼红尘 2020-03-09 10:46:30 0 浏览量 回答数 0

问题

Apache Flink常见问题汇总【精品问答】

Apache Flink是由Apache软件基金会开发的开源流处理框架,其核心是用Java和Scala编写的分布式流数据流引擎。Flink以数据并行和流水线方式执行任意流数据程序,Flink的流水线运行时系统可以...
黄一刀 2020-05-19 17:51:47 11230 浏览量 回答数 2

回答

在PaaS层中一个复杂的通用应用就是大数据平台。大数据是如何一步一步融入云计算的呢?首先我们来看一下大数据里面的数据,就分三种类型,一种叫结构化的数据,一种叫非结构化的数据,还有一种叫半结构化的数据。 大数据拥抱云计算 在PaaS层中一个复杂的通用应用就是大数据平台。大数据是如何一步一步融入云计算的呢? 1 数据不大也包含智慧 一开始这个大数据并不大。原来才有多少数据?现在大家都去看电子书,上网看新闻了,在我们80后小时候,信息量没有那么大,也就看看书、看看报,一个星期的报纸加起来才有多少字?如果你不在一个大城市,一个普通的学校的图书馆加起来也没几个书架,是后来随着信息化的到来,信息才会越来越多。 首先我们来看一下大数据里面的数据,就分三种类型,一种叫结构化的数据,一种叫非结构化的数据,还有一种叫半结构化的数据。 结构化的数据:即有固定格式和有限长度的数据。例如填的表格就是结构化的数据,国籍:中华人民共和国,民族:汉,性别:男,这都叫结构化数据。 非结构化的数据:现在非结构化的数据越来越多,就是不定长、无固定格式的数据,例如网页,有时候非常长,有时候几句话就没了;例如语音,视频都是非结构化的数据。 半结构化数据:是一些XML或者HTML的格式的,不从事技术的可能不了解,但也没有关系。 其实数据本身不是有用的,必须要经过一定的处理。例如你每天跑步带个手环收集的也是数据,网上这么多网页也是数据,我们称为Data。数据本身没有什么用处,但数据里面包含一个很重要的东西,叫做信息(Information)。 数据十分杂乱,经过梳理和清洗,才能够称为信息。信息会包含很多规律,我们需要从信息中将规律总结出来,称为知识(Knowledge),而知识改变命运。信息是很多的,但有人看到了信息相当于白看,但有人就从信息中看到了电商的未来,有人看到了直播的未来,所以人家就牛了。如果你没有从信息中提取出知识,天天看朋友圈也只能在互联网滚滚大潮中做个看客。 所以数据的应用分这四个步骤:数据、信息、知识、智慧。 最终的阶段是很多商家都想要的。你看我收集了这么多的数据,能不能基于这些数据来帮我做下一步的决策,改善我的产品。例如让用户看视频的时候旁边弹出广告,正好是他想买的东西;再如让用户听音乐时,另外推荐一些他非常想听的其他音乐。 用户在我的应用或者网站上随便点点鼠标,输入文字对我来说都是数据,我就是要将其中某些东西提取出来、指导实践、形成智慧,让用户陷入到我的应用里面不可自拔,上了我的网就不想离开,手不停地点、不停地买。 很多人说双十一我都想断网了,我老婆在上面不断地买买买,买了A又推荐B,老婆大人说,“哎呀,B也是我喜欢的啊,老公我要买”。你说这个程序怎么这么牛,这么有智慧,比我还了解我老婆,这件事情是怎么做到的呢? 2 数据如何升华为智慧 数据的处理分几个步骤,完成了才最后会有智慧。 第一个步骤叫数据的收集。首先得有数据,数据的收集有两个方式: 第一个方式是拿,专业点的说法叫抓取或者爬取。例如搜索引擎就是这么做的:它把网上的所有的信息都下载到它的数据中心,然后你一搜才能搜出来。比如你去搜索的时候,结果会是一个列表,这个列表为什么会在搜索引擎的公司里面?就是因为他把数据都拿下来了,但是你一点链接,点出来这个网站就不在搜索引擎它们公司了。比如说新浪有个新闻,你拿百度搜出来,你不点的时候,那一页在百度数据中心,一点出来的网页就是在新浪的数据中心了。 第二个方式是推送,有很多终端可以帮我收集数据。比如说小米手环,可以将你每天跑步的数据,心跳的数据,睡眠的数据都上传到数据中心里面。 第二个步骤是数据的传输。一般会通过队列方式进行,因为数据量实在是太大了,数据必须经过处理才会有用。可系统处理不过来,只好排好队,慢慢处理。 第三个步骤是数据的存储。现在数据就是金钱,掌握了数据就相当于掌握了钱。要不然网站怎么知道你想买什么?就是因为它有你历史的交易的数据,这个信息可不能给别人,十分宝贵,所以需要存储下来。 第四个步骤是数据的处理和分析。上面存储的数据是原始数据,原始数据多是杂乱无章的,有很多垃圾数据在里面,因而需要清洗和过滤,得到一些高质量的数据。对于高质量的数据,就可以进行分析,从而对数据进行分类,或者发现数据之间的相互关系,得到知识。 比如盛传的沃尔玛超市的啤酒和尿布的故事,就是通过对人们的购买数据进行分析,发现了男人一般买尿布的时候,会同时购买啤酒,这样就发现了啤酒和尿布之间的相互关系,获得知识,然后应用到实践中,将啤酒和尿布的柜台弄的很近,就获得了智慧。 第五个步骤是对于数据的检索和挖掘。检索就是搜索,所谓外事不决问Google,内事不决问百度。内外两大搜索引擎都是将分析后的数据放入搜索引擎,因此人们想寻找信息的时候,一搜就有了。 另外就是挖掘,仅仅搜索出来已经不能满足人们的要求了,还需要从信息中挖掘出相互的关系。比如财经搜索,当搜索某个公司股票的时候,该公司的高管是不是也应该被挖掘出来呢?如果仅仅搜索出这个公司的股票发现涨的特别好,于是你就去买了,其实其高管发了一个声明,对股票十分不利,第二天就跌了,这不坑害广大股民么?所以通过各种算法挖掘数据中的关系,形成知识库,十分重要。 3 大数据时代,众人拾柴火焰高 当数据量很小时,很少的几台机器就能解决。慢慢的,当数据量越来越大,最牛的服务器都解决不了问题时,怎么办呢?这时就要聚合多台机器的力量,大家齐心协力一起把这个事搞定,众人拾柴火焰高。 对于数据的收集:就IoT来讲,外面部署这成千上万的检测设备,将大量的温度、湿度、监控、电力等数据统统收集上来;就互联网网页的搜索引擎来讲,需要将整个互联网所有的网页都下载下来。这显然一台机器做不到,需要多台机器组成网络爬虫系统,每台机器下载一部分,同时工作,才能在有限的时间内,将海量的网页下载完毕。 对于数据的传输:一个内存里面的队列肯定会被大量的数据挤爆掉,于是就产生了基于硬盘的分布式队列,这样队列可以多台机器同时传输,随你数据量多大,只要我的队列足够多,管道足够粗,就能够撑得住。 对于数据的存储:一台机器的文件系统肯定是放不下的,所以需要一个很大的分布 式文件系统来做这件事情,把多台机器的硬盘打成一块大的文件系统。 对于数据的分析:可能需要对大量的数据做分解、统计、汇总,一台机器肯定搞不定,处理到猴年马月也分析不完。于是就有分布式计算的方法,将大量的数据分成小份,每台机器处理一小份,多台机器并行处理,很快就能算完。例如著名的Terasort对1个TB的数据排序,相当于1000G,如果单机处理,怎么也要几个小时,但并行处理209秒就完成了。 所以说什么叫做大数据?说白了就是一台机器干不完,大家一起干。可是随着数据量越来越大,很多不大的公司都需要处理相当多的数据,这些小公司没有这么多机器可怎么办呢? 4 大数据需要云计算,云计算需要大数据 说到这里,大家想起云计算了吧。当想要干这些活时,需要很多的机器一块做,真的是想什么时候要就什么时候要,想要多少就要多少。 例如大数据分析公司的财务情况,可能一周分析一次,如果要把这一百台机器或者一千台机器都在那放着,一周用一次非常浪费。那能不能需要计算的时候,把这一千台机器拿出来;不算的时候,让这一千台机器去干别的事情? 谁能做这个事儿呢?只有云计算,可以为大数据的运算提供资源层的灵活性。而云计算也会部署大数据放到它的PaaS平台上,作为一个非常非常重要的通用应用。因为大数据平台能够使得多台机器一起干一个事儿,这个东西不是一般人能开发出来的,也不是一般人玩得转的,怎么也得雇个几十上百号人才能把这个玩起来。 所以说就像数据库一样,其实还是需要有一帮专业的人来玩这个东西。现在公有云上基本上都会有大数据的解决方案了,一个小公司需要大数据平台的时候,不需要采购一千台机器,只要到公有云上一点,这一千台机器都出来了,并且上面已经部署好了的大数据平台,只要把数据放进去算就可以了。 云计算需要大数据,大数据需要云计算,二者就这样结合了。 人工智能拥抱大数据 机器什么时候才能懂人心 虽说有了大数据,人的欲望却不能够满足。虽说在大数据平台里面有搜索引擎这个东西,想要什么东西一搜就出来了。但也存在这样的情况:我想要的东西不会搜,表达不出来,搜索出来的又不是我想要的。 例如音乐软件推荐了一首歌,这首歌我没听过,当然不知道名字,也没法搜。但是软件推荐给我,我的确喜欢,这就是搜索做不到的事情。当人们使用这种应用时,会发现机器知道我想要什么,而不是说当我想要时,去机器里面搜索。这个机器真像我的朋友一样懂我,这就有点人工智能的意思了。 人们很早就在想这个事情了。最早的时候,人们想象,要是有一堵墙,墙后面是个机器,我给它说话,它就给我回应。如果我感觉不出它那边是人还是机器,那它就真的是一个人工智能的东西了。 让机器学会推理 怎么才能做到这一点呢?人们就想:我首先要告诉计算机人类的推理的能力。你看人重要的是什么?人和动物的区别在什么?就是能推理。要是把我这个推理的能力告诉机器,让机器根据你的提问,推理出相应的回答,这样多好? 其实目前人们慢慢地让机器能够做到一些推理了,例如证明数学公式。这是一个非常让人惊喜的一个过程,机器竟然能够证明数学公式。但慢慢又发现其实这个结果也没有那么令人惊喜。因为大家发现了一个问题:数学公式非常严谨,推理过程也非常严谨,而且数学公式很容易拿机器来进行表达,程序也相对容易表达。 教给机器知识 因此,仅仅告诉机器严格的推理是不够的,还要告诉机器一些知识。但告诉机器知识这个事情,一般人可能就做不来了。可能专家可以,比如语言领域的专家或者财经领域的专家。 语言领域和财经领域知识能不能表示成像数学公式一样稍微严格点呢?例如语言专家可能会总结出主谓宾定状补这些语法规则,主语后面一定是谓语,谓语后面一定是宾语,将这些总结出来,并严格表达出来不就行了吗?后来发现这个不行,太难总结了,语言表达千变万化。 人工智能这个阶段叫做专家系统。专家系统不易成功,一方面是知识比较难总结,另一方面总结出来的知识难以交给计算机。因为你自己还迷迷糊糊,觉得似乎有规律,就是说不出来,又怎么能够通过编程教给计算机呢? 算了,教不会你自己学吧 于是人们想到:机器是和人完全不一样的物种,干脆让机器自己学习好了。
茶什i 2019-12-31 13:13:50 0 浏览量 回答数 0

回答

自己顶一下。。求解释 如果容器发现一个beanclass是一个FactoryBean实现类,他仍然会是实例化这个FactoryBean,对他进行注入,但实际的bean对象则是是由这个FactoryBean实例来创建,除了创建实例的方法,FactoryBean本身有方法返回他创建的实例的类型和表示创建的实例是否是单例。 更多细节,建议你在自己实现的FactoryBean里面打上断点调试,沿着调用栈查看Spring的源代码比口头问好很多。回复<aclass='referer'target='_blank'>@雅典娜拉:eclipse上,FactoryBean的那三个方法随便一个查他们的调用者就有结果了...刚才怕他是反射所以说要debug,结果直接调用就更省事了回复<aclass='referer'target='_blank'>@逝水fox:额,,,好厉害,,,能告诉我你是从哪个类开始找起的么?回复<aclass='referer'target='_blank'>@雅典娜拉:其实用IDE的话很好找的,调试都不需要"iif(beanInstanceinstanceofFactoryBean&&!isFactoryType){"org.springframework.beans.factory.support.StaticListableBeanFactory的代码里面回复<aclass='referer'target='_blank'>@逝水fox:恩,好吧,我试试,不过它的类结构很复杂。。。不过你也为我提供了一种新的思路,多谢指点回复<aclass='referer'target='_blank'>@雅典娜拉:你问的是Spring的实现细节,觉得他是特殊处理,那就打断点调试看什么东西调用你的FactoryBean来生成bean实例的,再在周围找找就会找到特殊处理的证据,细节这些东西看别人说还不如自己看源代码调试看得明白。<preclass="brush:java;toolbar:true;auto-links:false;">publicclassPropertiesFactoryBeanextendsPropertiesLoaderSupportimplementsFactoryBean<Properties>,InitializingBean<imgsrc="http://www.oschina.net/js/ke/plugins/emoticons/images/28.gif"alt=""/>我也说不清楚等别人解答 应该是做特殊处理了。返回的时候都给的是<spanstyle="background-color:#F6F6F6;font-family:'CourierNew',Arial;font-size:9pt;line-height:1.5;">Properties<preclass="brush:java;toolbar:true;auto-links:false;">publicclassPropertiesFactoryBeanextendsPropertiesLoaderSupportimplementsFactoryBean<Properties>,InitializingBean{privatebooleansingleton=true;privatePropertiessingletonInstance;<preclass="brush:java;toolbar:true;auto-links:false;">publicClass<Properties>getObjectType(){returnProperties.class;} 不要一味的套规则,要理解原理,你可以去了解下spring的bean创建方式,我个人理解的也许和别人常说的pring的bean几种创建方式可能不一样,我理解的有三种方式 1、构造方法 2、工厂bean(静态方法、实例方法、FactoryBean接口) 3、代理(使用jdk代理或者类似CGlib库。例如:事务处理,spring通过aop方式产生bean的代理对象)恩。。。你的回答对我帮助很大,再次感谢!恩,其实我也一直在尝试着去理解原理,要不然也就不会问这个问题了。多谢指点! <preclass="brush:java;toolbar:true;auto-links:false;">@Resource('&properties')privatePropertiesFactoryBeanproperties; 这样子说不定能得到~之所以叫FactoryBean,就因为是用它来生成Bean的,默认情况下,如果一个Bean是FactoryBean,Spring是会返回其生成的Bean,而不是工厂本身,如果想要得到工厂本身,需要在ID前加&. 上面这样子不知道行不行,但<spanstyle="font-family:Helvetica,Tahoma,Arial,sans-serif;font-size:14px;line-height:25.1875px;background-color:#FFFFFF;">factory.getBean('&properteis')应该是可以获取到FactoryBean的。 一般也很少会用到FactoryBean。 前面加了个“&”有啥特殊作用吗?<divclass="ref"> 引用来自“梦无涯”的答案<divclass=ref_body> <preclass="brush:java;toolbar:true;auto-links:false;">@Resource('&properties')privatePropertiesFactoryBeanproperties; 这样子说不定能得到~之所以叫FactoryBean,就因为是用它来生成Bean的,默认情况下,如果一个Bean是FactoryBean,Spring是会返回其生成的Bean,而不是工厂本身,如果想要得到工厂本身,需要在ID前加&. 上面这样子不知道行不行,但<spanstyle="font-family:Helvetica,Tahoma,Arial,sans-serif;font-size:14px;line-height:25.1875px;background-color:#FFFFFF;">factory.getBean('&properteis')应该是可以获取到FactoryBean的。 一般也很少会用到FactoryBean。 哦。原来如此。。多谢指点这个是工厂bean实现了FactoryBean<Properties>,实际的返回对象是getObject方法Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。 当配置文件中<bean>的class属性配置的实现类是FactoryBean时,通过getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。 <arel="nofollow">http://wenku.baidu.com/link?url=N0nQvLfRJ2w-InrUHOiXL_dvl5ecsxg0k7GCH6Eh8EEBCYAGQyyU3-tJ8Qwt21VLD3VJxHVLCKppopIVZGGH4yCNPcNJnfqgV09oNTWiLbG 这个文章可以帮助你理解的!!!谢谢
爱吃鱼的程序员 2020-06-22 14:51:44 0 浏览量 回答数 0

回答

前辈,你们在吗? @中山野鬼 @stxy0509  能帮我看看这个问题吗? ###### @stxy0509###### 老实说,你@我之前,我就看过你这个问题,但是我觉得挺棘手的,因为我自己觉得我也搞不定,所以没敢开腔,但是,被你@了,不站出来似乎有点乌龟了…… 1 “在mes1和mes2的信息被输出到stdout时都在其尾部被加上了一个换行符”,这个确实是不应该的,我在Windows下用vs2010试验过,并没有这样。至于linux为什么这样,我也不知道。 2 “stdout是行缓存的(在本机试验过也确实如此)”,那你是如何证明这一点的呢?h或许你真的证明了,那我只能说linux的行为对我来说有点费解。 你觉得fclose()另外一个文件,会强迫stdout输出吗?可是为了实际证明这一点,你把fclose()注释掉,试一下。或许我说的是错的。 3 “input file 的buffer 好像与stdout终端的buffer没有什么直接的联系”,肯定是这样的,要联系也是程序让他们有联系,否则不乱套了? 4 文件以"r"模式打开,而且文件是全缓冲,我的理解是缓冲区中的内容不是自动就填充的,而是你使用fread()或其他读的函数,系统会将读取的内容自动放到这个缓冲区,并返回你的读取函数。如果下次读的数据还是这个缓冲去范围内的数据,就直接从这个缓冲去复制,不直接读文件了。但是如果不在范围,还是要实际读取文件的,从而再次更新缓冲区。 5 根据我知道的资料,fflush()是把缓冲区中的数据刷新到磁盘(真正的写入)。对于以读模式打开的文件,应该不需要同步,因为根本没有也不能修改文件; 6 文件可以被同时打开为读和写。很多时候都是一个线程写文件,而另一个线程从这个文件读取,这算是线程通讯的一种办法。只要文件指针(读写位置)保持正确就可以了。实际上,在windows下面文件要想这样,必须打开时共享读写,不能独占式访问。但是简单的c库函数做不到这一点,能不能,听天由命了(要看读写这个文件的其他进程是否设置共享)。 7 “如果可以,那么它们申请的应该是两个不同的缓存空间吗?",我要说的是一个FILE *指针对应一个缓冲区,如果只用一个,那不乱套,你写乱我的,我搞乱你的。多从常识,原理角度考虑问题,常识,原理应该这样,那么程序也应该这样。否则,程序也会出现问题的。 希望对你有帮助! ###### FILE结构体只维护了一个缓冲区。如果你先对一个文件调用fgets函数来到一个特定的位置,这时你想要写文件的话,你需要先fseek(fp,0,SEEK_CUR)来清空缓冲,然后才能调用写函数。 可以去看unix环境高级编程,前几章写得非常明确。 ###### 关于第一个问题,我用vim去创建mest1跟mest2的话就会出现跟楼主一样的换行,但如果 printf hello > mest1 printf marry! > mest2 这样创建的话是不会出现换行的。 当我用hexdump去看用vim创建的mest1的时候发现文件末尾出现了奇怪的东西,所以应该是文件编辑器问题,不关代码的事吧 ###### 问题解决了,总结一下: 1. 问题的根本原来出现在vim这个文本编辑器身上,就像@杨同学 所说的那样。我百度了一下,原来vim会自动在其创建的文本中的每一行末尾自动添加一个换行符(如果该行末尾本身没有换行符的话)。如果想去掉这个自动换行,可以在vim的命令mode中依次敲入“set noeol”和"set binary"(后面这个"set binary" 默认是off的,打开它好像可能会引起一些其他的问题。。这个有待研究)。敲完命令后":w"保存可以看到提示的文本字符数正好是自己输入的个数(而没有额外得被加上了1个换行符)。 2. stdout 和 stdin 默认是行缓存的。可以通过下面的代码证明stdout是行缓存的: printf("hello"); while(1); 编译此文件,运行,可以看到并没有输出,而是一直停在了某个地方 (while(1)的作用),因为此时的数据还在stdout的buffer中,而且程序并没有退出main(),所以不能flush buffer中的内容到stdout终端。但若为printf("hello\n"),则会因stdout的buffer遭遇到了一个换行符而将buffer的内容flush到stdout终端,故在运行程序时会看到相应的“hello”输出。 3. input file 的buffer(通过“r”模式用fopen打开文件所生成的buffer),和 output file 的buffer(通过“w”模式用fopen打开文件所生成的buffer)不是同一个东西, 他们是独立的。当调用fclose()函数时(如fflush(fp),fp为指向某个文件的文件指针,类型为FILE *),其会调用fflush函数来“刷新”fp所指向文件的缓存区。而这个“刷新”是有两层含义的。对于input file 的buffer 来说,这会丢弃缓存区中尚未被读取的内容,而对于output file 来说,这会使得buffer中的内容写入到相应的输出文件中(写入disk或相应的设备文件,通过调用相应的system call)。 http://man7.org/linux/man-pages/man3/fflush.3.html 的description有说到这个问题。所以源码中的“fclose(fp)”并不会强迫stdout输出,他只是flush了input file 的buffer,并释放了fp这个文件指针以供下个input file使用。 4. 驻留在disk上面的文件一般为全缓存的。当以“r”模式用fopen打开此类文件时,system会为这个文件建立一个input buffer,而此时应该还不会立马将文件的内容读至buffer,而是等到真正要由该文件读取数据时才会通过system call将文件的数据填满这个buffer,然后再由这个buffer读取数据。 5. 文件可以同时被读和写("rw")。但这个要注意的东西很多,还没学到。。        最后,非常感谢 @stxy0509 @优游幻世 和 @杨同学 。能抽时间看完我这累赘的叙述并且还给出了自己的看法,让我解决了一个大疑惑。一个人的能力和经验是有限的,大家不同的看法才能引起brain storm,更快地解决问题。有你们的支持,我才能走得更远。Thanks a lot~######和库有关。查一下相关库的说明。包括stdout。c标准里,对这些具体的操作方式,没有规定的。各家按照自己喜好来。本身c语言跨平台时,要注意的,就是这些。###### 引用来自“中山野鬼”的答案 和库有关。查一下相关库的说明。包括stdout。c标准里,对这些具体的操作方式,没有规定的。各家按照自己喜好来。本身c语言跨平台时,要注意的,就是这些。 感谢野鬼前辈~我会注意的。
kun坤 2020-06-07 20:08:36 0 浏览量 回答数 0

问题

ECS-CentOS  /etc/fstab格式简介

/etc/fstab 格式简介 <file system>     <dir>     <type>      <options>       <dump>      ...
ethnicity 2019-12-01 21:03:38 10993 浏览量 回答数 1

回答

在校生要找到好工作,主要靠几个光环,学校光环、竞赛光环、项目光环、实习光环。其中项目经验尤为重要。有些同学就有疑问了: “我校招没offer,没有项目经验,是不是要报个培训班?” “我转行计算机,是不是应该报个班?” “我也想自学,可怎么学啊,选哪个方向啊?” 对于有些同学,当我还在想办法劝他自学时,给我贴出了培训班的广告词,真可谓,人有多大胆,口号就有多不要脸: “0基础入学,三个月包就业” “毕业月入不过万,不收学费” “从前是你找工作,接下来是工作找你” 当我推荐某些同学去培训时,又给咔咔咔亮出了几个帖子,说培训出来的受歧视啊、有些同学培训出来还是找不到工作啊,等等。 其实,选择自学还是培训是看自身情况而定,无论选择自学还是培训,都只是入门的一种手段,各有优劣势,本文就详细说说自学/培训怎么选,选择以后怎么办,记得帮我点赞哦。   目录: 自学还是培训,怎么选? 自学怎么学? 培训班到底在培训什么? 有些企业歧视培训班学员,培训班的问题到底出在哪? 一些建议 一、自学还是培训,怎么选? 无论你是什么学历、有没有计算机基础,这些都不是决定你适合自学的条件,具备如下三个条件的人都可以选择自学: (1)、时间充足 如果说从零基础靠自学达到找工作的水平,需要多久呢?我觉得至少一年,有的人可能需要两年。所以,如果你是大一、大二、大三的学生,你还有时间,可以选择自学。如果你是已经工作的,想转行计算机,可以边工作边学习,这个过程会比较辛苦,但也不是绝对不可行。 对于大四的同学,以就业为导向,建议你去培训。不可否认,培训是最快入门的方式,对于时间不足的同学而言,培训是最优解。同样地,如果你是已工作的,不存在财务压力,我同样建议你去培训,工作后的时间很珍贵,比不上在校期间有大把时间可以浪费,如果做好了必转的决心,以最快速度转行才是最优解。 (2)、自控力强 能管得住自己,自己定的目标能想尽一切办法实现的同学,真不多,能占人群中1/4已经不错了。 有些人学了半小时就会累,休息一会,就成这样: 我见过太多的半途而废的同学,也见过太多自己安慰自己式的学习方式,但就业就是一个试金石,你这段时间的努力有没有回报,去找工作的时候,就水落石出。 如果在自律这方面不太行的话,可以看下这篇文章,《启舰:你是怎么变自律的?》,找到自已的驱动器,完成自己的梦想。 (3)、具备高中以上学历 计算机本身是数学家发明的,或多或少会用到一些基本的数学知识、经常用到的很多算法都是数学知识的延伸,没有基本的数学功底,自学确实很难。 至于英语阅读能力还好说,只要会用有道词典,不会的去搜去看,总会读懂的,而且入门级的文献和视频中文版的资料已足够你入门,英语应该不是太大的问题。 如果你这三点都满足,恭喜你,你具有了自学的基础,可以选择自学。 二、自学怎么学? 1、选定一个方向 首先,我们选择方向的目的是什么?不就是为了找份工作吗?那直接到招聘类网站去搜下相关的岗位数量及要求不就好了,哪个数量多,自己也喜欢,那就选这个即可。 其次,如果是大三、大四即将毕业的同学,想知道最近哪个岗位好找工作的话。还可以看看很多培训机构的培训内容,现在很多培训机构都声称保就业,真的以为,培训几个月能培训出朵花来吗?不可能的,编程是个需要长期训练的活,几个月的培训,仅是入门而已,入门的水平能保证找份工作,就靠的是这个岗位门槛低,需求大,好找工作。 如果实在不知道选什么,我帮你找几个方向:python、java后端、Html5就业岗位都挺多,就业门槛低,相对好就业,如果也有其它方向推荐,大家可以留言。 2、找到几套视频教材 在入门时强烈不建议跟着书学 第一,不一定能看得懂 第二,书本的知识不成体系,入门有入门的书,进阶有进阶的书,实战有实战的书,需要自己去选择,本身就不是一件易事。 第三,视频可以看到老师的操作,而书本全靠自己摸 现在某某培训班的入门、进阶、实战的系列视频不要太好找,找到这么两套视频,对比着看,或者跟着一套视频深入看,来得更容易。人家培训班安排好的路线跟着学,不懂的自己搜,就已经排除了自已给自己安排路线的难点,况且人家本身就是面向就业的,培训出来的同学能保就业,只要你能跟着学通学会,自然找到工作也不是问题。 我精心整理了计算机各个方向的从入门、进阶、实战的视频课程和电子书,都是技术学习路上必备的经验,跟着视频学习是进步最快的,而且所有课程都有源码,直接跟着去学!!! 只要关注微信公众号【启舰杂谈】后回复你所需方向的关键字即可,比如『Android』、『java』、『ReactNative』、『H5』、『javaweb』、『面试』、『机器学习』、『web前端』、『设计模式』等关键字获取对应资料。(所有资料免费送,转发宣传靠大家自愿) 视频内容非常多,总共2184G、一千六百多册电子书,九百多套视频教程,涉及43个方向。我整理了很长时间,有些资料是靠买的,希望大家能最快的提升自己。帮我点个赞吧。 启舰:全网2184G计算机各方向视频教程/电子书汇总(持续更新中)​   3、自学,除了知识,你还能学到什么? 自学的缺点很明显: 第一:速度慢,所有进度完全靠自己把控,没有氛围 第二:遇到问题需要自己解决,无人请教 那优点恰恰是从这些缺点中磨练出来的,进度靠自己把握,完全磨练了你的意志力。而所有问题靠自己解决,恰恰培养了你的解决问题的能力。 而这些能力都是培训班教不出来的、无法速成的。而这些能力却是真正的开发高手所必备的 问题定义、分析与设计阶段,这是最需要智商、创造力和经验的阶段,真正的开发高手,就是在这一阶段体现出远超普通人的水平,而在这一阶段所需要的能力,对不起,培训班教不出来,也无法速成,只能靠人自己的努力,慢慢地培养和增强。 4、自学建议 (1)、多做笔记、多复习 刚开始学习时,很难,真的很难。很多东西听不懂,很多东西需要自己搜,自己定的进度很可能完不成。 没关系,坚持下去,都是这么过来的。我刚开始自学的时候,也是无数次想死的冲动…… 学会做笔记,把自己学到的东西及时记下来,形成目录,在后面用到的时候,根据笔记再去看一遍,刚开始经常会出现,听得懂,跟着学会,自己弄就不会的现象。这都是正常的,技术本就是个熟能生巧的过程。 多动手,多总结,就慢慢熟练了。 (2)、多写代码!听得懂、看得懂,并没什么用 入门级知识,本就是语法和框架的熟悉过程,说到底就是工具的使用方法熟悉的过程。既然是工具,那就必然要多用。熟能生巧,指的是用的熟。很多同学看的懂,听的会,自己一下手就问题百出,就是练的少! (3)、听不懂,搜一下,再不懂就放过 刚学的时候很多概念听不懂,没关系,自己搜一下,能理解了就理解,理解不了就算。听一遍就行,学到后面的时候,你就懂些了回头,再看看那些知识,基本上你都懂了。 (4)、多写注释 刚开始的时候,很多逻辑弄不懂,没关系,自己把代码拆解,并对其加以注释,这样,你在反过来再看这些代码时,能很快弄懂它的逻辑。你要知道,你后面学习时还是会碰到这些知识的,而在只看一遍的情况下是不可能记得住的,到时候,你还是会返回来复习这些知识的。 增加注释,看起来浪费时间,其实是整理代码逻辑的过程。浑浑噩噩敲出来的代码,自己都不明白什么意思的话,其实相当于没有真正学会。 三、培训班到底在培训什么? 去培训的主要原因,说到底还是因为自己啥都不会。但不会与不会间是有区别的。 对于科班出身的,上学又好好学了的同学,虽然他们没有系统的编程知识,没有项目经验,但他们有计算机基础,他懂得操作系统原理、数据结构与算法等原理性知识。 而对于跨专业和在玩了四年的同学而言,那才是真正的零基础。 而对于培训机构而言,它的责任就是让你实现从0到1的入门过程,而有经验的老鸟都知道,编程入门仅仅是知识的堆积,并没有什么技巧性可言。所有的语法和框架运用,简单来说,就是学会编程套路,学习工具使用。 而培训机构的责任,就是把这些套路教给你。只要你不太笨,经过几个月的强化训练,大部分人都能学得会。 所以,培训班教你的就是工具的使用,目的,就是以最快的速度塞给你,助你找到工作。 四、有些企业歧视培训班学员,培训班的问题到底出在哪? 培训机构有着熟练的授课体系,老师手把手答疑,让你在学习路上没有一丁点的思考时间,为的就是以最快的速度让你达标,好结课,开始下一波培训。 1、问题就出在速度上。 认知科学的研究成果表明,知识的消化与吸收,职业技能的学习与精通,本质上是在大脑神经元之间建立连接,重塑大脑结构的过程,这个过程的时间可以缩短,但不能无限地缩短。另外,不同的人,拥有不同的背景和基础,在学习与掌握相同的知识与职业技能时,所花的时间是不一样的。 而培训机构才不管这些,他的目的就是挣钱,以最快的速度挣钱,能在三天内把所有内容塞给你绝不用四天,只要最终能糊弄住面试官,让学员找到一份工作就可以了。 所以,必然会出现下面的现象: 对于原来有一些基础的,学习能力较强的同学,在学习之前已经有较扎实的基础,所以在培训期间能够自己构建成技术体系,知识吸收相对较好: 而另一些学员,则会出现消化不良的情况: 2、培训后遗症 对于软件开发而言,所有的软件开发都大致分为两个阶段: 1、分析、定义、设计阶段。这个阶段是需要有解决问题、分析问题的能力。而这个能力培训班培训不出来,只能是慢慢增强。 2、语法、工具的使用,将设计的内容实现出来。这一块就比较机械了,工具嘛,学一学都能会,培训班在这一块的效率是很高效的,它们多半能在较短的时间内,教会学员特定编程语言(比如Python)特定工具(比如Git)与特定技术的使用(比如Spring MVC),并且传授给他们一些开发的“套路”(比如分层架构与设计模式),从而将学员成功地培养成为一个能够“搬砖”的软件工人,即初级程序员。 培训班一般都会选择门槛低、就业岗位多的方向进行培训,对于这类岗位,人才缺口大,只要能直接上手写代码的初级程序员,都很容易找到一份工作。这也就是为什么培训班多半会收学生五位数的学费,而学生也愿意支付的根本原因。 (1)、解决问题能力差,动不动就得人教 经过几个月饭来张口、衣来伸手的填鸭式集训,有些人在工作后,却依然认为,当他遇到问题时,从来不想着自己搜搜资料解决,而是依赖同事帮他答疑! 自学能力差、解决问题能力差,是很多人找到了工作,过不了试用期的根本原因。 (2)、培训效果立竿见影,却又很快遗忘 任何的知识都是一样,短时间内填鸭式学到的知识,在一段时间不用后,就会遗忘。这就是有些同学刚从培训班出来时,能找到份工作,当学到的东西在工作中几个月用不到时,就很快忘记,总觉得自己还是啥都不会的原因。 永远要记住:学历不行靠实力,实力不行靠态度!!! 当我们初入职场,尽心尽责地把自己的工作做完做好的同时,千万不要忘记像海绵一样,以最快的速度给自己充水。 像培训完的同学,在校期间已经做了很多的笔记,工作之余,多复习,重新练,利用时间将它理解,真正内化为自己的本领。 对于自学的同学,多找进阶性书籍和视频去看,以最快的速度提升自己。 文末我整理了计算机各个方向的从入门、进阶、实战的视频课程和电子书,都是技术学习路上必备的经验,跟着视频学习是进步最快的,而且所有课程都有源码,直接跟着去学!!! 五、一些建议 1、非科班同学建议 对于非科班转行计算机的同学,有太多的知识需要补足,如果你靠的是自学,需要强有力的自律能力,只要时间还够,是可以靠自学的,在跟着视频学的时候,哪里听不懂及时去搜相关的资料去补足。 刚开始自学时,即便是科班出身也是有想死的冲动的,大家都一样。我也是靠自学过来的,很多的东西不会,很多的东西听不懂。没关系,多做笔试,多搜资料,把不会的弄会,你会发现,学习起来越来越容易。 所有的困难只不过是纸老虎,坚持过去就成功了。 如果你是通过培训找到了一份工作,你需要比别人更努力补充计算机知识,基础知识的缺乏,会使你很难在这条路上走很远,所有的大神,都是自学能力很强的人,你想,你也可以。 2、所有开发方向都必须从C++开始? 经常会有要校生问我:我要做H5开发,是不是要先学C++? 其实,各个语言之间是没有任何关联的,完全都是有各自的语法体系和开发工具的,简单来讲,他们都是不同类型的工具。 你学会一种工具,只会对另一种类似的工具更容易上手,而不是完全不用学。所以,想学哪个方向,直接去学就行了,没必须先从C++入手迂回一下,纯属浪费时间。 但,如果你还在上学,现在正在学C++,那我还是建议你好好学,必须C语言语法更接近低层编译器原理,学会了它,对理解低层分配、释放、编译机制都是很有用的,但就以工作为导向而言,如果你不从事C++相关工作,是没必要学的。 3、培训出来人人工资过万? 有个男生非常沮丧的找我,自己是专科毕业,培训完,小公司不想进,大点的公司进不去,给的工资也不高,问我怎么办? 上面我们已经讲到,对于不同程度的同学,在培训出来的结果是不一样的,你要分清,你培训完的情况是属于这种: 还是这种? 对于没有名校光环的同学,建议以先就业为主。 别看培训班招你的时候给你洗脑,培训完人人过万,但能不能过万,最终靠的是自己,而不是培训班。 认请自己的情况,可以先就业,再优化自己履历,而进步步高升。 4、建议不要暴露自己的培训经历 你百度、知乎搜一下,遍地的培训歧视,很多公司根本不要培训出来的同学. 业界对培训有偏见,因为写代码是一个逐渐学习、熟练的过程,经过几个月集中的培训,虽然看起来什么都接触到了,但真正能内化为自己知识的部分其实不多。在工作中并不能熟练运用,仅是入门水平而已。 而且大家普遍认为参加培训的主要原因是因为,大学中没好好学,临近毕业了,催熟一把。不然,谁会花这几万块钱呢?对普通家庭而言,其实也并不是个小数目了。 有一个外包公司的朋友,技术总监,招人时培训公司出来都不要,原因就是干活能力不行。当然这仅代表个例,但大家需要注意的是,业界并不认为培训是一件光彩的事,千万不要搞错了!!! 5、培训班防骗三十六计 现在太多的培训机构,一个个把自己吹的天花乱坠,我也建议过小伙伴去培训,但小孩子交完钱培训一个月就退费了,深感自己好心做了坏事,这里建议大家培训市场,鱼龙混杂,一定要提前做好防骗准备。 谎言之所以真实是因为年青的心太不甘寂寞,太急于求成! 从网上找了,培训班防骗三十六计,供大家参考: “借刀杀人”:培训班间竞争激烈,彼此勾心斗角,正好为我所用。去培训班甲问乙如何,到培训班乙打听甲。Ha.Ha..,狗咬狗开始了,一时间内幕迭报:乙设备不全,很多实验不能做;甲的那个号称CCIE的老师只过了笔试,没过实验室,假的! “声东击西”:与甲约好星期六考察学校,结果星期X跑去(1=< X <= 5)。   “你怎么来了?”   “我星期六有事,所以提前来看看……” “抛砖引玉”:有时候,拿不定注意或者培训班在外地,实地考察有难度,何不到论坛发个帖子征求意见,要是能得到已经培训过的前辈的释疑,那你绝对是不虚此帖了! “假痴不癫”:有时候你可能偶然拥有一些内幕消息,不如试试他们的诚实度。   “听说你们的教师是CCIE!”   “那当然,技术首屈一指,……”   此时此刻,看着乙那得意样样的小样,不知是好笑,还是可气。不过记住:一个没有诚信的公司是什么都干的出来的! “反间计”:一个卑鄙的培训班后面一般都有一个卑鄙的流氓大亨,他不仅千方百计的从学员那里榨取钱财,对自己的手下也不会心慈手软,本着人们内部矛盾的原则发展一个或多个间谍。 “走为上计”:经过一番打探,知道他们都不是东西,还犹豫什么?宁缺毋滥,走人! 最后,如论怎么选,自终也只是入门阶段,为了找到一份工作。对于初入职场的你们,给一条最终建议:学校不行靠实力,实力不行靠态度。记得帮我点赞哦。 ———————————————— 版权声明:本文为CSDN博主「启舰」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/harvic880925/article/details/103413853
问问小秘 2020-01-07 10:55:15 0 浏览量 回答数 0

回答

X-Engine是阿里云数据库产品事业部自研的联机事务处理OLTP(On-Line Transaction Processing)数据库存储引擎。作为自研数据库POLARDB的存储引擎之一,已经广泛应用在阿里集团内部诸多业务系统中,包括交易历史库、钉钉历史库等核心应用,大幅缩减了业务成本,同时也作为双十一大促的关键数据库技术,挺过了数百倍平时流量的冲击。 为什么设计一个新的存储引擎 X-Engine的诞生是为了应对阿里内部业务的挑战,早在2010年,阿里内部就大规模部署了MySQL数据库,但是业务量的逐年爆炸式增长,数据库面临着极大的挑战: 极高的并发事务处理能力(尤其是双十一的流量突发式暴增)。 超大规模的数据存储。 这两个问题虽然可以通过扩展数据库节点的分布式方案解决,但是堆机器不是一个高效的手段,我们更想用技术的手段将数据库性价比提升到极致,实现以少量资源换取性能大幅提高的目的。 传统数据库架构的性能已经被仔细的研究过,数据库领域的泰斗,图灵奖得主Michael Stonebreaker就此写过一篇论文 《OLTP Through the Looking Glass, and What We Found There》 ,指出传统关系型数据库,仅有不到10%的时间是在做真正有效的数据处理工作,剩下的时间都浪费在其它工作上,例如加锁等待、缓冲管理、日志同步等。 造成这种现象的原因是因为近年来我们所依赖的硬件体系发生了巨大的变化,例如多核(众核)CPU、新的处理器架构(Cache/NUMA)、各种异构计算设备(GPU/FPGA)等,而架构在这些硬件之上的数据库软件却没有太大的改变,例如使用B-Tree索引的固定大小的数据页(Page)、使用ARIES算法的事务处理与数据恢复机制、基于独立锁管理器的并发控制等,这些都是为了慢速磁盘而设计,很难发挥出现有硬件体系应有的性能。 基于以上原因,阿里开发了适合当前硬件体系的存储引擎,即X-Engine。 X-Engine架构 全新架构的X-Engine存储引擎不仅可以无缝对接兼容MySQL(得益于MySQL Pluginable Storage Engine特性),同时X-Engine使用分层存储架构。 因为目标是面向大规模的海量数据存储,提供高并发事务处理能力和降低存储成本,在大部分大数据量场景下,数据被访问的机会是不均等的,访问频繁的热数据实际上占比很少,X-Engine根据数据访问频度的不同将数据划分为多个层次,针对每个层次数据的访问特点,设计对应的存储结构,写入合适的存储设备。 X-Engine使用了LSM-Tree作为分层存储的架构基础,并进行了重新设计: 热数据层和数据更新使用内存存储,通过内存数据库技术(Lock-Free index structure/append only)提高事务处理的性能。 流水线事务处理机制,把事务处理的几个阶段并行起来,极大提升了吞吐。 访问频度低的数据逐渐淘汰或是合并到持久化的存储层次中,并结合多层次的存储设备(NVM/SSD/HDD)进行存储。 对性能影响比较大的Compaction过程做了大量优化: 拆分数据存储粒度,利用数据更新热点较为集中的特征,尽可能的在合并过程中复用数据。 精细化控制LSM的形状,减少I/O和计算代价,有效缓解了合并过程中的空间增大。 同时使用更细粒度的访问控制和缓存机制,优化读的性能。 技术特点 利用FPGA硬件加速Compaction过程,使得系统上限进一步提升。这个技术属首次将硬件加速技术应用到在线事务处理数据库存储引擎中,相关论文 《FPGA-Accelerated Compactions for LSM-based Key Value Store》 已经被2020年的顶级会议FAST'20接收。 通过数据复用技术减少数据合并代价,同时减少缓存淘汰带来的性能抖动。 使用多事务处理队列和流水线处理技术,减少线程上下文切换代价,并计算每个阶段任务量配比,使整个流水线充分流转,极大提升事务处理性能。相对于其他类似架构的存储引擎(例如RocksDB),X-Engine的事务处理性能有10倍以上提升。 X-Engine使用的Copy-on-write技术,避免原地更新数据页,从而对只读数据页面进行编码压缩,相对于传统存储引擎(例如InnoDB),使用X-Engine可以将存储空间降低至10%~50%。 Bloom Filter快速判定数据是否存在,Surf Filter判断范围数据是否存在,Row Cache缓存热点行,加速读取性能。 LSM基本逻辑 LSM的本质是所有写入操作直接以追加的方式写入内存。每次写到一定程度,即冻结为一层(Level),并写入持久化存储。所有写入的行,都以主键(Key)排序好后存放,无论是在内存中,还是持久化存储中。在内存中即为一个排序的内存数据结构(Skiplist、B-Tree、etc),在持久化存储也作为一个只读的全排序持久化存储结构。 普通的存储系统若要支持事务处理,需要加入一个时间维度,为每个事务构造出一个不受并发干扰的独立视域。例如存储引擎会对每个事务定序并赋予一个全局单调递增的事务版本号(SN),每个事务中的记录会存储这个SN以判断独立事务之间的可见性,从而实现事务的隔离机制。 如果LSM存储结构持续写入,不做其他的动作,那么最终会成为如下结构。 这种结构对于写入是非常友好的,只要追加到最新的内存表中即完成,为实现故障恢复,只需记录Redo Log,因为新数据不会覆盖旧版本,追加记录会形成天然的多版本结构。 但是如此累积,冻结的持久化层次越来越多,会对查询会产生不利的影响。例如对同一个key,不同事务提交产生的多版本记录会散落在各个层次中;不同的key也会散落在不同层次中。读操作需要查找各个层并合并才能得到最终结果。 因此LSM引入了Compaction操作解决这个问题,Compaction操作有2种作用: 控制LSM层次形状 一般的LSM形状都是层次越低,数据量越大(倍数关系),目的是为了提升读性能。 通常存储系统的数据访问都有局部性,大量的访问都集中在少部分数据上,这也是缓存系统能有效工作的基本前提。在LSM存储结构中,如果把访问频率高的数据尽可能放在较高的层次上,存放在快速存储设备中(例如NVM、DRAM),而把访问频率低的数据放在较低层次中,存放在廉价慢速存储设备中。这就是X-Engine的冷热分层概念。 合并数据 Compaction操作不断的把相邻层次的数据合并,并写入更低层次。合并的过程实际上是把要合并的相邻两层或多层的数据读出来,按key排序,相同的key如果有多个版本,只保留新的版本(比当前正在执行的活跃事务中最小版本号新),丢掉旧版本数据,然后写入新的层,这个操作非常耗费资源。 合并数据除了考虑冷热分层以外,还需要考虑其他维度,例如数据的更新频率,大量的多版本数据在查询的时候会浪费更多的I/O和CPU,因此需要优先进行合并以减少记录的版本数量。X-Engine综合考虑了各种策略形成自己的Compaction调度机制。 高度优化的LSM X-Engine的memory tables使用了无锁跳表(Locked-free SkipList),并发读写的性能较高。在持久化层如何实现高效,就需要讨论每层的细微结构。 数据组织 X-Engine的每层都划分成固定大小的Extent,存放每个层次中的数据的一个连续片段(Key Range)。为了快速定位Extent,为每层Extents建立了一套索引(Meta Index),所有这些索引,加上所有的memory tables(active/immutable)一起组成了一个元数据树(Metadata Tree),root节点为Metadata Snapshot,这个树结构类似于B-Tree。 X-Engine中除了当前的正在写入的active memory tables以外,其他结构都是只读的,不会被修改。给定某个时间点,例如LSN=1000,上图中的Metadata Snapshot 1引用到的结构即包含了LSN=1000时的所有的数据的快照,因此这个结构被称为Snapshot。 即便是Metadata结构本身,也是一旦生成就不会被修改。所有的读请求都是以Snapshot为入口,这是X-Engine实现Snapshot级别隔离的基础。前文说过随着数据写入,累积数据越多,会执行Compaction操作、冻结memory tables等,这些操作都是用Copy-on-write实现,即每次都将修改产生的结果写入新的Extent,然后生成新的Meta Index结构,最终生成新的Metadata Snapshot。 例如执行一次Compaction操作会生成新的Metadata Snapshot,如下图所示。 可以看到Metadata Snapshot 2相对于Metadata Snapshot 1并没有太多的变化,仅仅修改了发生变更的一些叶子节点和索引节点。 事务处理 得益于LSM的轻量化写机制,写入操作固然是其明显的优势,但是事务处理不只是把更新的数据写入系统那么简单,还要保证ACID(原子性、一致性、隔离性、持久性),涉及到一整套复杂的流程。X-Engine将整个事务处理过程分为两个阶段: 读写阶段 校验事务的冲突(写写冲突、读写冲突),判断事务是否可以执行、回滚重试或者等锁。如果事务冲突校验通过,则把修改的所有数据写入Transaction Buffer。 提交阶段 写WAL、写内存表,以及提交并返回用户结果,这里面既有I/O操作(写日志、返回消息),也有CPU操作(拷贝日志、写内存表)。 为了提高事务处理吞吐,系统内会有大量事务并发执行,单个I/O操作比较昂贵,大部分存储引擎会倾向于聚集一批事务一起提交,称为Group Commit,能够合并I/O操作。但是一组事务提交的过程中,还是有大量等待过程的,例如写入日志到磁盘过程中,除了等待落盘无所事事。 X-Engine为了进一步提升事务处理的吞吐,使用流水线技术,把提交阶段分为4个独立的更精细的阶段: 拷贝日志到缓冲区(Log Buffer) 日志落盘(Log Flush) 写内存表(Write memory table) 提交返回(Commit) 事务到了提交阶段,可以自由选择执行流水线中任意一个阶段,只要流水线任务的大小划分得当,就能充分并行起来,流水线处于接近满载状态。另外这里利用的是事务处理的线程,而非后台线程,每个线程在执行的时候,选择流水线中的一个阶段执行任务,或者空闲后处理其他请求,没有等待,也无需切换,充分利用了每个线程的能力。 读操作 LSM处理多版本数据的方式是新版本数据记录会追加在老版本数据后面,从物理上看,一条记录不同的版本可能存放在不同的层,在查询的时候需要找到合适的版本(根据事务隔离级别定义的可见性规则),一般查询都是查找最新的数据,总是由最高的层次往低层次找。 对于单条记录的查找而言,一旦找到便可以终止,如果记录在比较高的层次,例如memory tables,很快便可以返回;如果记录已经落入了很低的层次,那就得逐层查找,也许Bloom Filter可以跳过某些层次加快这个旅程,但毕竟还是有很多的I/O操作。X-Engine针对单记录查询引入了Row Cache,在所有持久化的层次的数据之上做了一个缓存,在memory tables中没有命中的单行查询,在Row Cache之中也会被捕获。Row Cache需要保证缓存了所有持久化层次中最新版本的记录,而这个记录是可能发生变化的,例如每次flush将只读的memory tables写入持久化层次时,就需要恰当的更新Row Cache中的缓存记录,这个操作比较微妙,需要精心的设计。 对于范围扫描而言,因为没法确定一个范围的key在哪个层次中有数据,只能扫描所有的层次做合并之后才能返回最终的结果。X-Engine采用了一系列的手段,例如SuRF(SIGMOD'18 best paper)提供range scan filter减少扫描层数、异步I/O与预取。 读操作中最核心的是缓存设计,Row Cache负责单行查询,Block Cache负责Row Cache的漏网之鱼,也用来进行范围扫描。由于LSM的Compaction操作会一次更新大量的Data Block,导致Block Cache中大量数据短时间内失效,导致性能的急剧抖动,因此X-Engine做了很多的优化: 减少Compaction的粒度。 减少Compaction过程中改动的数据。 Compaction过程中针对已有的缓存数据做定点更新。 Compaction Compaction操作是比较重要的,需要把相邻层次交叉的Key Range数据读取合并,然后写到新的位置。这是为前面简单的写入操作付出的代价。X-Engine为优化这个操作重新设计了存储结构。 如前文所述,X-Engine将每一层的数据划分为固定大小的Extent,一个Extent相当于一个小而完整的排序字符串表(SSTable),存储了一个层次中的一个连续片段,连续片段又进一步划分为一个个连续的更小的片段Data Block,相当于传统数据库中的Page,只不过Data Block是只读而且不定长的。 回看并对比Metadata Snapshot 1和Metadata Snapshot 2,可以发现Extent的设计意图。每次修改只需要修改少部分有交叠的数据,以及涉及到的Meta Index节点。两个Metadata Snapshot结构实际上共用了大量的数据结构,这被称为数据复用技术(Data Reuse),而Extent大小正是影响数据复用率的关键,Extent作为一个完整的被复用的物理结构,需要尽可能的小,这样与其他Extent数据交叉点会变少,但又不能非常小,否则需要索引过多,管理成本太大。 X-Engine中Compaction的数据复用是非常彻底的,假设选取两个相邻层次(Level1, Level2)中的交叉的Key Range所涵盖的Extents进行合并,合并算法会逐行进行扫描,只要发现任意的物理结构(包括Data Block和Extent)与其他层中的数据没有交叠,则可以进行复用。只不过Extent的复用可以修改Meta Index,而Data Block的复用只能拷贝,即便如此也可以节省大量的CPU。 一个典型的数据复用在Compaction中的过程可以参考下图。 可以看出数据复用的过程是在逐行迭代的过程中完成的,不过这种精细的数据复用带来另一个副作用,即数据的碎片化,所以在实际操作的过程中也需要根据实际情况进行分析。 数据复用不仅给Compaction操作本身带来好处,降低操作过程中的I/O与CPU消耗,更对系统的综合性能产生一系列的影响。例如c、Compaction过程中数据不用完全重写,大大降低了写入时空间的增大;大部分数据保持原样,数据缓存不会因为数据更新而失效,减少合并过程中因缓存失效带来的读性能抖动。 实际上,优化Compaction的过程只是X-Engine工作的一部分,更重要的是优化Compaction调度的策略,选什么样的Extent、定义compaction任务的粒度、执行的优先级等,都会对整个系统性能产生影响,可惜并不存在什么完美的策略,X-Engine积累了一些经验,定义了很多规则,而探索更合理的调度策略是未来一个重要方向。 适用场景 请参见X-Engine最佳实践。 如何使用X-Engine 请参见使用X-Engine引擎。 后续发展 作为MySQL的存储引擎,持续地提升MySQL系统的兼容能力是一个重要目标,后续会根据需求的迫切程度逐步加强原本取消的一些功能,例如外键,以及对一些数据结构、索引类型的支持。 X-Engine作为存储引擎,核心的价值还在于性价比,持续提升性能降低成本,是一个长期的根本目标,X-Engine还在Compaction调度、缓存管理与优化、数据压缩、事务处理等方向上进行深层次的探索。 X-Engine不仅仅局限为一个单机的数据库存储引擎,未来还将作为自研分布式数据库POLARDB分布式版本的核心,提供企业级数据库服务。
游客yl2rjx5yxwcam 2020-03-08 13:24:40 0 浏览量 回答数 0

回答

看了下 servo 中 dom 节点的处理方式,构造了个例子 use std::cell::{Cell, RefCell}; struct Element { flag: Cell<i32>, id: Cell<i32>, children: RefCell<Vec<RefCell<Element>>>, } impl Element { fn new() -> Element { Element { flag: Cell::new(0), id: Cell::new(1), children: RefCell::new(Vec::new()), } } fn add_child(&self, child: Element) { self.flag.set(self.flag.get() + 1); self.children.borrow_mut().push(RefCell::new(child)); } fn before_remove(&self) { self.flag.set(0); } fn after_remove(&self) { self.id.set(0); } fn remove_children(&self) { self.before_remove(); for e in self.children.borrow().iter() { e.borrow().remove_children(); } self.after_remove(); } } fn main() { let e1 = Element::new(); let e2 = Element::new(); let e3 = Element::new(); let e4 = Element::new(); let e5 = Element::new(); let e6 = Element::new(); let e7 = Element::new(); e6.add_child(e7); e5.add_child(e6); e4.add_child(e5); e3.add_child(e4); e2.add_child(e3); e1.add_child(e2); e1.remove_children(); println!("..."); } Element 中所有的字段用 Cell/RefCell 包装后,所有的成员方法都使用 &self 不可变借用来调用 remove_children 应该是能还原你原始的问题了。所有要嵌套/递归修改自己的地方都只用 borrow() 不可变借用就行。 而其他的基础类型字段,如 i32, vec 由于不会嵌套修改,可以 borrow_mut() 修改完马上还回来,保证一句话修改完就行。 children 我给简写了,实际上由于要再被其他人引用,应该在外面再包一层 Rc 的。 大致搜了下有类似需求的一些项目,处理方式都是类似这样的。 ######回复 @DogFeet :c++里,这些地方相对较少,而变更对象时,改其它对象是很多的,在有交互的代码里可以说是大量,成本不一样######回复 @templar : 不需要把每个修改都封装到单独的函数里面。至于说所有路径都测试到,C++也一样有这个问题,如果你在 Objects 的迭代中某个 object.update 中修改了 Objects 的布局,一样可能会导致迭代器出问题,这些只是你在 C++ 中习惯了,知道 update 中有些事情其实也是不能做的。######回复 @DogFeet : borrow_mut在运行中检查,实际中很难所有路径都测试到,所以rust还是太累了,暂时还是不用了,不过这个问题也算是有解决方法了,谢谢了######回复 @DogFeet : 那得很注意,把每个修改都封到单独的函数里,行是行,就是太费劲了~~~######回复 @templar : 转换成你那个例子就是 update 用 &self 调用,只需要 borrow 借用,update 中又一次借用自己也是用 borrow,由于 life 字段被 Cell 包起来了,所以可以通过 &self 修改。而多次 borrow 并不会有什么问题。这样就解决你原来的问题了。###### 已经用上rust了!  赞一个, ######用Recell+Mutex试下。###### 如果是多个线程要修改你的objects,则用Arc 如果只有一个线程多个地方要修改objects,则用Rc ######回复 @DogFeet : 搞不定~~求个示例代码。。。这个问题就是持有一个μt T的情况下,调用其成员函数,在该成员函数里,又需要修改自已或别的对象,就是也要获得μt T,在游戏服务器里,放技能这类逻辑,都会修改多个对象的属性###### @templar .而且用Rc了一般就不会再传引用了,你什么时候见过到处 &shared_ptr的。。。。。全局变量的问题,Rust是不推荐的,所以你要写传统语言中常见的单例模式会发现很难。###### @templar Rc没有你说的这种要求,都说了就是非线程安全版的shared_ptr######回复 @DogFeet : 看文档,rc是用于不可变量的,要获得μt还是得传入μt RC<T>,一层层往上推,又得全是μt,而且还要求rc是unique的,可能你说的可行吧,但现在暂时没什么兴趣了,写一个全局变量都这么费劲,哪天有精神了再弄吧,多谢回答######回复 @templar : Rc 就是用来解决你多个地方要共享修改一个元素的,等同于 C++ 非线程安全版 shared_ptr###### 引用来自“DogFeet”的评论 如果是多个线程要修改你的objects,则用Arc 如果只有一个线程多个地方要修改objects,则用Rc  #[derive(Debug)] pub struct OBJ {     x : i32, } fn main() {     let mut x1 = Rc::new(OBJ { x: 100 } );     let mut x2 = x1.clone();     x1.x = 101;     x2.x = 102;     println!("{:?}",x1);     println!("{:?}",x2); } src\main.rs(105,5): error : cannot assign to immutable field src\main.rs(106,5): error : cannot assign to immutable field std::rc::Rc A reference-counted pointer type over an immutable value. 要获得&mut T,还是需要 fn get_mut(rc: &mut Rc<T>) -> Option<&mut T>[−] Unstable Returns a mutable reference to the contained value if the Rc<T> is unique. Returns None if the Rc<T> is not unique. ###### 我想可能rust不适合其它语言那种N层嵌套调用,可能得写成比较扁平的类,Github上的开源,也大都如此,比如技能这个,可能就得把技能拉出来,跟OBJ同层,用RefCell只临时borrow_mut,不过这样又得导致很多其它的不便,也是挺恶心的 ###### fn main() { let x1 = Rc::new(RefCell::new(OBJ { x: 100 } )); let x2 = x1.clone(); x1.borrow_mut().x = 101; x2.borrow_mut().x = 103; println!("{:?}",x1); println!("{:?}",x2); } 所有使用你那个HashMap的地方拿到的都是一个类似上面的 x1, x2 的对象,不用保存引用,可变引用的哦,也不会传染到上层哦。 我比较好奇的是你的 get_global_objects () 打算怎么实现,要实现这种类似单例类会很麻烦的。 ######https://github.com/Kimundi/lazy-static.rs 这个宏可以简化全局变量######Global是有办法的,参照os.Stdout,比较烦一点,不过可以用宏实现######两次borrow_mut会panic的,在N层嵌套里,要想保持单一个borrow几乎不可能的,一个技能出发一个效果,影响N个对象,N个对象上的反应器又会触发其它效果,完全不可控,这个我在最上面说过不行######我说的两次borrow_mut会panic的,是指先持一个mut,调用其内部函数,在函数里,可能需要borrow自已,或其它对象,一层层函数调下去,需要borrow的对象是动态的,不可知的 你没仔细看我的问题 impl for OBJ { fn update(&mut self) { // 这里borrow另一个,可能是自已,或别人 // 假如那个对象已经被borrow了,就panic了 } } fn main() {     let x1 = Rc::new(RefCell::new(OBJ { x: 100 } ));     let x2 = x1.clone();       x1.borrow_mut().update()       println!("{:?}",x1);     println!("{:?}",x2); } ######不过RefCell可以解决HashMap的引用了,不必都用iter_mut了###### 引用来自“templar”的评论 我想可能rust不适合其它语言那种N层嵌套调用,可能得写成比较扁平的类,Github上的开源,也大都如此,比如技能这个,可能就得把技能拉出来,跟OBJ同层,用RefCell只临时borrow_mut,不过这样又得导致很多其它的不便,也是挺恶心的 如果你能做到扁平的,那就可以直接用 &, &mut 来borrow了,用完直接还掉,就没上面的问题了呀。 但事实上很难做到的,我也是做游戏的。比如常见的触发器需求,有时候触发器关联了2个对象,这个触发器需要引用这2个对象吧,C++可能就直接用到智能指针了(有的解决方案是用ID做句柄,不过也有麻烦的地方,要手动解除ID的关联,除非能保证ID不回环)。 所有的对象都被 Objects borrow 了,触发器再 borrow 肯定要出事,这种需求很难通过做平来避免的。######打包成事件扔到顶层循环处理,会勉强可以,不过很不自然,会导致别的问题###### 引用来自“templar”的评论我说的两次borrow_mut会panic的,是指先持一个mut,调用其内部函数,在函数里,可能需要borrow自已,或其它对象,一层层函数调下去,需要borrow的对象是动态的,不可知的 你没仔细看我的问题 impl for OBJ { fn update(&mut self) { // 这里borrow另一个,可能是自已,或别人 // 假如那个对象已经被borrow了,就panic了 } } fn main() {     let x1 = Rc::new(RefCell::new(OBJ { x: 100 } ));     let x2 = x1.clone();       x1.borrow_mut().update()       println!("{:?}",x1);     println!("{:?}",x2); } 你说的这个我还没考虑到。 如果是这样,那确实很麻烦,按照多人共享用 Rc 的策略,如果 Objects 中的 entry 也需要多人共享,那把 entry 也用 Rc 包起来?不过这样,entry.update(&mut self) 就得改写成 entry_update(e: Rc<...>) 了,这样也确实很丑。 或者是模仿 Rc, Arc 这种用 unsafe 包装内部可变性的方式,把 entry 的 update 用 unsafe 包起来,外面拿 immutable borrow 的 entry 也能通过调用其成员函数来做到修改内部状态。就像 Rc 的 clone。不过这种感觉也不是解决方法。 我再问问其他看看 ######unsafe也试过了,在unsafe里不能把获得的μt T作为参数调用其它安全函数
kun坤 2020-06-06 14:56:09 0 浏览量 回答数 0

问题

“木兰抄袭Python”官方致歉!从“红芯”到“木兰”,国产套壳何时休?

近日,中科院计算所下属团队号称完全自主设计、开发和实现的编程语言“木兰”被推到了舆论的中心,体验过后开发者们纷纷表示:这就是个套壳的Python。随着事件的发酵,项目负责人刘雷终于道歉...
茶什i 2020-01-20 12:00:45 1642 浏览量 回答数 1
阿里云企业服务平台 陈四清的老板信息查询 上海奇点人才服务相关的云产品 爱迪商标注册信息 安徽华轩堂药业的公司信息查询 小程序定制 上海微企信息技术相关的云产品 国内短信套餐包 ECS云服务器安全配置相关的云产品 天籁阁商标注册信息 开发者问答 阿里云建站 自然场景识别相关的云产品 万网 小程序开发制作 视频内容分析 视频集锦 代理记账服务 北京芙蓉天下的公司信息查询