游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

Go语言核心36讲(Go语言实战与应用二十一)学习笔记

  43bufio包中的数据类型(下)
  在上一篇文章中,我提到了bufio包中的数据类型主要有Reader、Scanner、Writer和ReadWriter。并着重讲到了bufio。Reader类型与bufio。Writer类型,今天,我们继续专注bufio。Reader的内容来进行学习。知识扩展问题:bufio。Reader类型读取方法有哪些不同?
  bufio。Reader类型拥有很多用于读取数据的指针方法,这里面有4个方法可以作为不同读取流程的代表,它们是:Peek、Read、ReadSlice和ReadBytes。
  Reader值的Peek方法的功能是:读取并返回其缓冲区中的n个未读字节,并且它会从已读计数代表的索引位置开始读。
  在缓冲区未被填满,并且其中的未读字节的数量小于n的时候,该方法就会调用fill方法,以启动缓冲区填充流程。但是,如果它发现上次填充缓冲区的时候有错误,那就不会再次填充。
  如果调用方给定的n比缓冲区的长度还要大,或者缓冲区中未读字节的数量小于n,那么Peek方法就会把所有未读字节组成的序列作为第一个结果值返回。
  同时,它通常还把bufio。ErrBufferFull变量的值(以下简称缓冲区已满的错误)
  作为第二个结果值返回,用来表示:虽然缓冲区被压缩和填满了,但是仍然满足不了要求。
  只有在上述的情况都没有出现时,Peek方法才能返回:以已读计数为起始的n个字节和表示未发生任何错误的nil。
  bufio。Reader类型的Peek方法有一个鲜明的特点,那就是:即使它读取了缓冲区中的数据,也不会更改已读计数的值。
  这个类型的其他读取方法并不是这样。就拿该类型的Read方法来说,它有时会把缓冲区中的未读字节,依次拷贝到其参数p代表的字节切片中,并立即根据实际拷贝的字节数增加已读计数的值。在缓冲区中还有未读字节的情况下,该方法的做法就是如此。不过,在另一些时候,其所属值的已读计数会等于已写计数,这表明:此时的缓冲区中已经没有任何未读的字节了。当缓冲区中已无未读字节时,Read方法会先检查参数p的长度是否大于或等于缓冲区的长度。如果是,那么Read方法会索性放弃向缓冲区中填充数据,转而直接从其底层读取器中读出数据并拷贝到p中。这意味着它完全跨过了缓冲区,并直连了数据供需的双方。
  需要注意的是,Peek方法在遇到类似情况时的做法与这里的区别(这两种做法孰优孰劣还要看具体的使用场景)。
  Peek方法会在条件满足时填充缓冲区,并在发现参数n的值比缓冲区的长度更大时,直接返回缓冲区中的所有未读字节。
  如果我们当初设定的缓冲区长度很大,那么在这种情况下的方法执行耗时,就有可能会比较长。最主要的原因是填充缓冲区需要花费较长的时间。
  由fill方法执行的流程可知,它会尽量填满缓冲区中的可写空间。然而,Read方法在大多数的情况下,是不会向缓冲区中写入数据的,尤其是在前面描述的那种情况下,即:缓冲区中已无未读字节,且参数p的长度大于或等于缓冲区的长度。
  此时,该方法会直接从底层读取器那里读出数据,所以数据的读出速度就成为了这种情况下方法执行耗时的决定性因素。
  当然了,我在这里说的只是耗时操作在某些情况下更可能出现在哪里,一切的结论还是要以性能测试的客观结果为准。
  说回Read方法的内部流程。如果缓冲区中已无未读字节,但其长度比参数p的长度更大,那么该方法会先把已读计数和已写计数的值都重置为0,然后再尝试着使用从底层读取器那里获取的数据,对缓冲区进行一次从头至尾的填充。
  不过要注意,这里的尝试只会进行一次。无论在这一时刻是否能够获取到数据,也无论获取时是否有错误发生,都会是如此。而fill方法的做法与此不同,只要没有发生错误,它就会进行多次尝试,因此它真正获取到一些数据的可能性更大。
  不过,这两个方法有一点是相同,那就是:只要它们把获取到的数据写入缓冲区,就会及时地更新已写计数的值。
  再来说ReadSlice方法和ReadBytes方法。这两个方法的功能总体上来说,都是持续地读取数据,直至遇到调用方给定的分隔符为止。
  ReadSlice方法会先在其缓冲区的未读部分中寻找分隔符。如果未能找到,并且缓冲区未满,那么该方法会先通过调用fill方法对缓冲区进行填充,然后再次寻找,如此往复。
  如果在填充的过程中发生了错误,那么它会把缓冲区中的未读部分作为结果返回,同时返回相应的错误值。
  注意,在这个过程中有可能会出现虽然缓冲区已被填满,但仍然没能找到分隔符的情况。
  这时,ReadSlice方法会把整个缓冲区(也就是buf字段代表的字节切片)作为第一个结果值,并把缓冲区已满的错误(即bufio。ErrBufferFull变量的值)作为第二个结果值。
  经过fill方法填满的缓冲区肯定从头至尾都只包含了未读的字节,所以这样做是合理的。
  当然了,一旦ReadSlice方法找到了分隔符,它就会在缓冲区上切出相应的、包含分隔符的字节切片,并把该切片作为结果值返回。无论分隔符找到与否,该方法都会正确地设置已读计数的值。
  比如,在返回缓冲区中的所有未读字节,或者代表全部缓冲区的字节切片之前,它会把已写计数的值赋给已读计数,以表明缓冲区中已无未读字节。
  如果说ReadSlice是一个容易半途而废的方法的话,那么可以说ReadBytes方法算得上是相当的执着。
  ReadBytes方法会通过调用ReadSlice方法一次又一次地从缓冲区中读取数据,直至找到分隔符为止。
  在这个过程中,ReadSlice方法可能会因缓冲区已满而返回所有已读到的字节和相应的错误值,但ReadBytes方法总是会忽略掉这样的错误,并再次调用ReadSlice方法,这使得后者会继续填充缓冲区并在其中寻找分隔符。
  除非ReadSlice方法返回的错误值并不代表缓冲区已满的错误,或者它找到了分隔符,否则这一过程永远不会结束。
  如果寻找的过程结束了,不管是不是因为找到了分隔符,ReadBytes方法都会把在这个过程中读到的所有字节,按照读取的先后顺序组装成一个字节切片,并把它作为第一个结果值。如果过程结束是因为出现错误,那么它还会把拿到的错误值作为第二个结果值。
  在bufio。Reader类型的众多读取方法中,依赖ReadSlice方法的除了ReadBytes方法,还有ReadLine方法。不过后者在读取流程上并没有什么特别之处,我就不在这里赘述了。
  另外,该类型的ReadString方法完全依赖于ReadBytes方法,前者只是在后者返回的结果值之上做了一个简单的类型转换而已。
  最后,我还要提醒你一下,有个安全性方面的问题需要你注意。bufio。Reader类型的Peek方法、ReadSlice方法和ReadLine方法都有可能会造成内容泄露。
  这主要是因为它们在正常的情况下都会返回直接基于缓冲区的字节切片。我在讲bytes。Buffer类型的时候解释过什么叫内容泄露。你可以返回查看。
  调用方可以通过这些方法返回的结果值访问到缓冲区的其他部分,甚至修改缓冲区中的内容。这通常都是很危险的。packagemainimport(bufiofmtstrings)funcmain(){comment:PackagebufioimplementsbufferedIO。Itwrapsanio。Readerorio。Writerobject,creatinganotherobject(ReaderorWriter)thatalsoimplementstheinterfacebutprovidesbufferingandsomehelpfortextualIO。basicReader:strings。NewReader(comment)fmt。Printf(Thesizeofbasicreader:d,basicReader。Size())size:300fmt。Printf(Newabufferedreaderwithsized。。。,size)reader1:bufio。NewReaderSize(basicReader,size)fmt。Println()fmt。Print(〔AboutPeekmethod〕)示例1。peekNum:38fmt。Printf(Peekdbytes。。。,peekNum)bytes,err:reader1。Peek(peekNum)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Peekedcontents(d):q,len(bytes),bytes)fmt。Printf(Thenumberofunreadbytesinthebuffer:d,reader1。Buffered())fmt。Println()fmt。Print(〔AboutReadmethod〕)示例2。readNum:38buf1:make(〔〕byte,readNum)fmt。Printf(Readdbytes。。。,readNum)n,err:reader1。Read(buf1)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Readcontents(d):q,n,buf1)fmt。Printf(Thenumberofunreadbytesinthebuffer:d,reader1。Buffered())fmt。Println()fmt。Print(〔AboutReadSlicemethod〕)示例3。fmt。Println(Resetthebasicreader。。。)basicReader。Reset(comment)fmt。Println(Resetthebufferedreader。。。)reader1。Reset(basicReader)fmt。Println()delimiter:byte(()fmt。Printf(Readslicewithdelimiterq。。。,delimiter)line,err:reader1。ReadSlice(delimiter)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Readcontents(d):q,len(line),line)fmt。Printf(Thenumberofunreadbytesinthebuffer:d,reader1。Buffered())fmt。Println()delimiterbyte(〔)fmt。Printf(Readslicewithdelimiterq。。。,delimiter)line,errreader1。ReadSlice(delimiter)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Readcontents(d):q,len(line),line)fmt。Printf(Thenumberofunreadbytesinthebuffer:d,reader1。Buffered())fmt。Println()示例4。fmt。Println(Resetthebasicreader。。。)basicReader。Reset(comment)size200fmt。Printf(Newabufferedreaderwithsized。。。,size)reader2:bufio。NewReaderSize(basicReader,size)fmt。Println()delimiterbyte(〔)fmt。Printf(Readslicewithdelimiterq。。。,delimiter)line,errreader2。ReadSlice(delimiter)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Readcontents(d):q,len(line),line)fmt。Printf(Thenumberofunreadbytesinthebuffer:d,reader2。Buffered())fmt。Println()fmt。Print(〔AboutReadBytesmethod〕)示例5。fmt。Println(Resetthebasicreader。。。)basicReader。Reset(comment)size200fmt。Printf(Newabufferedreaderwithsized。。。,size)reader3:bufio。NewReaderSize(basicReader,size)fmt。Println()delimiterbyte(〔)fmt。Printf(Readbyteswithdelimiterq。。。,delimiter)line,errreader3。ReadBytes(delimiter)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Readcontents(d):q,len(line),line)fmt。Printf(Thenumberofunreadbytesinthebuffer:d,reader3。Buffered())fmt。Println()示例6和示例7。fmt。Print(〔Aboutcontentsleak〕)showContentsLeak(comment)}funcshowContentsLeak(commentstring){示例6。basicReader:strings。NewReader(comment)fmt。Printf(Thesizeofbasicreader:d,basicReader。Size())size:len(comment)fmt。Printf(Newabufferedreaderwithsized。。。,size)reader4:bufio。NewReaderSize(basicReader,size)fmt。Println()peekNum:7fmt。Printf(Peekdbytes。。。,peekNum)bytes,err:reader4。Peek(peekNum)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Peekedcontents(d):q,len(bytes),bytes)fmt。Println()只要扩充一下之前拿到的字节切片bytes,就可以用它来读取甚至修改缓冲区中的后续内容。bytesbytes〔:cap(bytes)〕fmt。Printf(Theallofthecontentsinthebuffer:q,bytes)fmt。Println()blank:byte()fmt。Println(Setblanksintothecontentsinthebuffer。。。)for,i:range〔〕int{55,56,57,58,66,67,68}{bytes〔i〕blank}fmt。Println()peekNumsizefmt。Printf(Peekdbytes。。。,peekNum)bytes,errreader4。Peek(peekNum)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Peekedcontents(d):q,len(bytes),bytes)fmt。Println()示例7。ReadSlice方法也存在相同的问题。delimiter:byte(,)fmt。Printf(Readslicewithdelimiterq。。。,delimiter)line,err:reader4。ReadSlice(delimiter)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Readcontents(d):q,len(line),line)fmt。Println()lineline〔:cap(line)〕fmt。Printf(Theallofthecontentsinthebuffer:q,line)fmt。Println()underline:byte()fmt。Println(Setunderlinesintothecontentsinthebuffer。。。)for,i:range〔〕int{89,92,103}{line〔i〕underline}fmt。Println()peekNumsizefmt。Printf(Peekdbytes。。。,peekNum)bytes,errreader4。Peek(peekNum)iferr!nil{fmt。Printf(error:v,err)}fmt。Printf(Peekedcontents(d):q,len(bytes),bytes)}总结
  我们用比较长的篇幅介绍了bufio包中的数据类型,其中的重点是bufio。Reader类型。
  bufio。Reader类型代表的是携带缓冲区的读取器。它的值在被初始化的时候需要接受一个底层的读取器,后者的类型必须是io。Reader接口的实现。
  Reader值中的缓冲区其实就是一个数据存储中介,它介于底层读取器与读取方法及其调用方之间。此类值的读取方法一般都会先从该值的缓冲区中读取数据,同时在必要的时候预先从其底层读取器那里读出一部分数据,并填充到缓冲区中以备后用。填充缓冲区的操作通常会由该值的fill方法执行。
  在填充的过程中,fill方法有时还会对缓冲区进行压缩。在Reader值拥有的众多读取方法中,有4个方法可以作为不同读取流程的代表,它们是:Peek、Read、ReadSlice和ReadBytes。
  Peek方法的特点是即使读取了缓冲区中的数据,也不会更改已读计数的值。而Read方法会在参数值的长度过大,且缓冲区中已无未读字节时,跨过缓冲区并直接向底层读取器索要数据。
  ReadSlice方法会在缓冲区的未读部分中寻找给定的分隔符,并在必要时对缓冲区进行填充。
  如果在填满缓冲区之后仍然未能找到分隔符,那么该方法就会把整个缓冲区作为第一个结果值返回,同时返回缓冲区已满的错误。
  ReadBytes方法会通过调用ReadSlice方法,一次又一次地填充缓冲区,并在其中寻找分隔符。除非发生了未预料到的错误或者找到了分隔符,否则这一过程将会一直进行下去。
  Reader值的ReadLine方法会依赖于它的ReadSlice方法,而其ReadString方法则完全依赖于ReadBytes方法。
  另外,值得我们特别注意的是,Reader值的Peek方法、ReadSlice方法和ReadLine方法都可能会造成其缓冲区中的内容的泄露。
  最后再说一下bufio。Writer类型。把该类值的缓冲区中暂存的数据写进其底层写入器的功能,主要是由它的Flush方法实现的。
  此类值的所有数据写入方法都会在必要的时候调用它的Flush方法。一般情况下,这些写入方法都会先把数据写进其所属值的缓冲区,然后再增加该值中的已写计数。但是,在有些时候,Write方法和ReadFrom方法也会跨过缓冲区,并直接把数据写进其底层写入器。
  请记住,虽然这些写入方法都会不时地调用Flush方法,但是在写入所有的数据之后再显式地调用一下这个方法总是最稳妥的。packagemainimport(bufiobytesfmtstrings)funcmain(){comment:Writerimplementsbufferingforanio。Writerobject。IfanerroroccurswritingtoaWriter,nomoredatawillbeacceptedandallsubsequentwrites,andFlush,willreturntheerror。Afteralldatahasbeenwritten,theclientshouldcalltheFlushmethodtoguaranteealldatahasbeenforwardedtotheunderlyingio。Writer。basicWriter1:strings。Builder{}size:300fmt。Printf(Newabufferedwriterwithsized。。。,size)writer1:bufio。NewWriterSize(basicWriter1,size)fmt。Println()示例1。begin,end:0,53fmt。Printf(Writedbytesintothewriter。。。,endbegin)writer1。WriteString(comment〔begin:end〕)fmt。Printf(Thenumberofbufferedbytes:d,writer1。Buffered())fmt。Printf(Thenumberofunusedbytesinthebuffer:d,writer1。Available())fmt。Println(Flushthebufferinthewriter。。。)writer1。Flush()fmt。Printf(Thenumberofbufferedbytes:d,writer1。Buffered())fmt。Printf(Thenumberofunusedbytesinthebuffer:d,writer1。Available())fmt。Println()示例2。begin,end0,326fmt。Printf(Writedbytesintothewriter。。。,endbegin)writer1。WriteString(comment〔begin:end〕)fmt。Printf(Thenumberofbufferedbytes:d,writer1。Buffered())fmt。Printf(Thenumberofunusedbytesinthebuffer:d,writer1。Available())fmt。Println(Flushthebufferinthewriter。。。)writer1。Flush()fmt。Println()示例3。basicWriter2:bytes。Buffer{}fmt。Printf(Resetthewriterwithabytesbuffer(animplementationofio。ReaderFrom)。。。)writer1。Reset(basicWriter2)reader:strings。NewReader(comment)fmt。Println(Readdatafromthereader。。。)writer1。ReadFrom(reader)fmt。Printf(Thenumberofbufferedbytes:d,writer1。Buffered())fmt。Printf(Thenumberofunusedbytesinthebuffer:d,writer1。Available())}思考题
  今天的思考题是:bufio。Scanner类型的主要功用是什么?它有哪些特点?笔记源码
  https:github。comMingsonZhenggocoredemo

不要对千元机有偏见,2021年这3款手机很流畅,使用两三年也点击关注,每天精彩不断!导读:不要对千元机有偏见,2021年这3款手机很流畅,使用两三年也不卡!众所周知,智能手机是一种消费类的电子数码产品,其更新换代的速度是非常快的,……相互宝分摊金额暴涨400倍,人数骤减2000万!你还在继续使你知道阿里巴巴公司旗下的相互宝么?一直以来,由于国内保险公司的鱼龙混杂,导致很多人一谈到保险,就会心生抵触情绪,然而阿里巴巴旗下的支付宝在2018年的时候,面向公众推出了……假如人口数量增长到500亿,地球会变成什么样?正如一山不容二虎,若生态环境的极限无法容纳500亿人,那么必定会通过爆发矛盾来减少人口从而使得人口低于生态容纳的极限。爆发的矛盾可能会是:大规模疾病,全球饥荒,气候异常,大规模……6月2日华为发布会,鸿蒙系统掀翻概念股事件:华为方面宣布,华为将于6月2日发布搭载鸿蒙操作系统的华为MatePadPro和全新HUAWEIMPencil第二代手写笔。这是继2019年11月华为发布第一代MatePa……华为已获鸿蒙商标转让,网友识大体,在华为手上更能发挥价值6月1日,在华为即将举办鸿蒙操作系统发布会的前一天,天眼查App显示,注册号为38307327的鸿蒙商标已被惠州市契贝科技有限公司转让于华为技术有限公司。同时转让的还有HONG……请问朋友们,我想学习家电维修,有必要从收音机原理开始研究吗?最好从元器件学起。先学认识零件,再学怎么量元器件,色环电阻要记牢,管子类型极性,阻值,容量换算一定要死记硬背,滚瓜烂熟才行。要学电器不限于家电维修,这类基本功必须张嘴就来,一看……Spring框架使用自己的注解Transactional控制Transactional注解,使用注解的属性控制事务(隔离级别,传播行为,超时)属性:1。propagation:事务的传播行为,他使用的Propagation类的枚举……征集!事关河南省区块链典型应用案例及应用场景需求中宏网河南4月24日电(记者李天慧)4月24日,记者从河南省工业和信息化厅获悉,为贯彻落实省委十一届二次全体(扩大)会议暨省委经济工作会议精神,抢抓区块链发展机遇,加快推进区块……市值高达243亿美元Lyft成功IPO盖世汽车讯据外媒报道,网约车初创公司Lyft星期四(3月28日)进行首次公开募股(IPO)时的市值达到243亿美元,在投资者需求强劲的情况下,所筹集的资金超过设定的目标。……美团启动2022全国网上年货节北京商报讯(记者魏蔚)1月10日,美团联合平台百万商户启动2022全国网上年货节活动,围绕吃住行游购多种场景,推出满减红包、年货5折等优惠,涵盖外卖、美食、酒店、生鲜即时零售等……vivo加入平板电脑行列,主打与手机的协同性,明年上半年推出万物互联已然是大势所趋了,现在很多厂商都在努力地布局抢夺市场,尤其是头部的华为、小米,在这方面做得相当不错,从手机、智能穿戴设备再到笔记本电脑、智能电视等,几乎是一应俱全,比如……U盘做操作系统图文教程,U盘做操作系统图解教学(推荐阅读)现如今,U盘除了存取数据以外,还可以用来做操作系统,借助一些U盘启动制作工具(例如大白菜、老毛桃等等)将U盘制作成启动盘,就可以用这个U盘做操作系统了,尤其适合没有操作系统的新……
AstahProfessionalformac(uml建模工AstahProfessional是Mac上一款全新的轻量级UML建模工具。软件集思维导图和UML建模于一体,采用100纯JAVA构建,兼容性强,不仅能够实现分布式建模、项目合……5199元的精致极品iPhone13mini网友入手几天,4精致极品iPhone13mini网友入手几天,为何忍痛出手:这位网友前几天刚入手的iPhone13mini,忍不住和大家分享一下体验,主要问题还是在实用性方面已不符合目前……除了外观,平台三大件都一样,懂车的人都会选换壳车?最近发现,身边的朋友入手了一辆亚洲龙。我问他,为什么会选择这辆车,他自信满满地回答:雷克萨斯是丰田的换壳车,选择亚洲龙无疑就是一台雷克萨斯ES,还省下来将近十万元,懂车的人都会……我菜,我不懂设计,但我能做出好PPTiSlide插件体验谈一、前言依稀记得我第一次做PPT的手足无措,满世界找模板,找矢量图,赚虚拟币下载资料。参加工作以后逃不过月度报告,又是满世界找,我才发现好看的素材基本都收人民币了,当年被……哈苏80岁生日,发布奢华907X周年纪念版套装1941年,第一台HK7相机诞生,哈苏由此开启传奇影像之路,历经八十载,留下无数珍贵影像印记与动人故事。2021年,哈苏以907X周年纪念版套装,铭记为卓越影像而生的80……原子同学博战场以强根基,积跬步以至千里原子同学与同学们并肩作战,凭借优质的教学方式与资深教师、教研团队,指导学生参与国内外游学营、国际科创竞赛,在FLL、FRC、NOI、IYRC等权威赛事及竞赛课程中屡获多个奖项与……HUAWEIHiCar让华为手机用户中的宝马车主Hi起来2020年5月数据显示,华为手机在中国大陆智能手机市场中占有率已经超过50,而宝马车主使用华为手机的比例已经远超苹果。宝马车主都知道,苹果有一个CarPlay功能非常好用,就是……操作简单功能强大,哪里酸痛点哪里甫士筋膜枪评测Part1前言运动后我们很多时候都会肌肉酸痛的现象,而对于运动恢复我们会采用拉伸的方式,这样简单方便效果还可以。不过在运动圈流行了一款运动恢复神器筋膜枪。筋膜枪这个词听着……团战必须赢,网速不能慢!怎么办?网速救星来了排位升级有三宝,眼疾、手快、网速好!想要火力全开大秀神操作,奈何网速不给力队友轻松五杀抢风头!游戏团战明明胜利在望,但你死我活的重要关头,网络一卡……车宽近1。9米,搭同级罕见按摩座椅,9。59万起全新AX7实随着国产品牌在整车制造水平以及技术研发上的崛起,现阶段家用SUV领域已不再是多年前单一比拼性价比的时候了,不难发现市场竞争的加剧导致很多上市新车不仅要兼顾性价比,产品力明显比之……极具探索意义的超高屏占比,Amazfit跃我GTR3Pro外10月12日,在Amazfit新品发布会上,华米科技正式宣布品牌焕新,公布了新的品牌Logo、品牌标语以及品牌中文名跃我。作为Amazfit跃我六周年向上而生的新起点,第三代A……64。8亿!小米618终极战报雷军小米铁军,三年决胜中国第一小米正式公布618终极战报,可谓战功累累,笔者看了了一下,几乎都是冠军,都是第一。这里笔者简单说几个地方,大家也可以直接看一张图了解一下(见文末)。小米在618旗舰,全平……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网