前端技术
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
[错误解读数据因日期格式不匹配]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
Saiku
...序号一:引言 在进行数据分析时,数据格式问题是一个常见的挑战。其中,日期格式不匹配就是其中之一。这可能会导致数据的错误解读,甚至影响到整个分析的结果。今天,我们将围绕"Date Format Mismatch: Dimension Field's Date Format Not as Expected"这个主题,一起学习如何在Saiku中解决这个问题。 序号二:什么是日期格式? 首先,我们需要明确的是,什么是日期格式?简单来说,日期格式就是在电脑系统中用于表示日期的一种特定的字符串模式。比如说,你看到的“yyyy-MM-dd”这种格式,其实就是大家日常生活中经常会碰到的一种日期写法。它具体表示的是年份有四位数,月份和日期各是两位数,像这样“2023-02-28”,就代表了2023年2月28日这个日子啦。 序号三:为什么会出现日期格式不匹配的问题? 那么,为什么在数据分析过程中会遇到日期格式不匹配的问题呢?这主要是因为不同的软件或工具可能对同一日期有着不同的处理方式,或者用户输入的日期格式与期望的格式不符。 序号四:在Saiku中如何解决日期格式不匹配的问题? 在Saiku中,我们可以利用其内置的日期格式转换功能来解决这个问题。以下是一些基本的操作步骤: 1. 打开Saiku,选择你需要修改的维度字段。 2. 点击该字段右侧的下拉菜单,选择“设置”选项。 3. 在弹出的窗口中,找到并点击“日期”标签。 4. 在这里,你可以看到当前的日期格式。要是这个日期格式不合你的心意,那就轻轻松松地按一下那个“选择日期格式”的小按钮,然后按照它的贴心提示,输入你心目中的理想格式就一切搞定了! 5. 最后,记得点击右上角的“保存”按钮,确认你的更改。 让我们通过一个具体的例子来演示一下这个操作。想象一下,我们手头上有个叫“Sales”的数据字段,它现在显示的日期样式是“日/月/年”,比方说“12/03/2023”这样的格式。不过呢,我们现在想要把它变一变,换成更加横平竖直的“年-月-日”形式,就像“2023-03-12”这样子的。具体的操作如下: 1. 打开Saiku,选择“Sales”字段。 2. 点击右侧的下拉菜单,选择“设置”选项。 3. 在弹出的窗口中,切换到“日期”标签。 4. 现有的日期格式是“dd/MM/yyyy”,我们需要将其更改为“yyyy-MM-dd”。点击“选择日期格式”按钮,在弹出的窗口中输入“yyyy-MM-dd”,然后点击“确定”。 5. 最后,别忘了点击右上角的“保存”按钮,确认我们的更改。 现在,“Sales”字段的日期格式已经成功地从“dd/MM/yyyy”更改为“yyyy-MM-dd”。 总结: 通过本文,我们了解了日期格式的重要性以及在Saiku中解决日期格式不匹配问题的基本方法。只要我们把日期格式设定对了,就等于给那些因为日期格式不对而惹来的各种小麻烦提前打上了“封印”,让它们没机会来烦咱们。对了,你知道吗?虽然Saiku这个工具自带了贼方便的日期格式转换功能,但是在实际用起来的时候呢,我们还是得灵活应变,根据具体的需求和实际情况,时不时地给它调整、优化一下才更靠谱。
2023-08-28 23:56:56
67
柳暗花明又一村-t
SeaTunnel
...quet/CSV文件格式解析错误的深度探索与实战 1. 引言 在数据集成和ETL的世界里,SeaTunnel(原名Waterdrop)作为一款强大的实时、批处理开源大数据工具,深受开发者喜爱。嘿,你知道吗?当你在捣鼓Parquet或者CSV这些不同格式的文件时,有时候真的会冒出一些让人措手不及的解析小插曲来呢!本文将深入探讨这类问题的成因,并通过丰富的代码实例演示如何在SeaTunnel中妥善解决这些问题。 2. Parquet/CSV文件解析常见问题及其原因 2.1 数据类型不匹配 Parquet和CSV两种格式对于数据类型的定义和处理方式有所不同。比如,你可能会遇到这么个情况,在CSV文件里,某个字段可能被不小心认作是文本串了,但是当你瞅到Parquet文件的时候,嘿,这个同样的字段却是个整数类型。这种类型不匹配可能导致解析错误。 python 假设在CSV文件中有如下数据 id,name "1", "John" 而在Parquet文件结构中,id字段是int类型 (id:int, name:string) 2.2 文件格式规范不一致 Parquet和CSV对空值、日期时间格式等有着各自的约定。如CSV中可能用“null”、“N/A”表示空值,而Parquet则以二进制标记。若未正确配置解析规则,就会出现错误。 3. 利用SeaTunnel解决文件格式解析错误 3.1 配置数据源与转换规则 在SeaTunnel中,我们可以精细地配置数据源和转换规则以适应各种场景。下面是一个示例,展示如何在读取CSV数据时指定字段类型: yaml source: type: csv path: 'path/to/csv' schema: - name: id type: integer - name: name type: string transform: - type: convert fields: - name: id type: int 对于Parquet文件,SeaTunnel会自动根据Parquet文件的元数据信息解析字段类型,无需额外配置。 3.2 自定义转换逻辑处理特殊格式 当遇到非标准格式的数据时,我们可以使用自定义转换插件来处理。例如,处理CSV中特殊的空值表示: yaml transform: - type: script lang: python script: | if record['name'] == 'N/A': record['name'] = None 4. 深度思考与讨论 处理Parquet和CSV文件解析错误的过程其实也是理解并尊重每种数据格式特性的过程。SeaTunnel以其灵活且强大的数据处理能力,帮助我们在面对这些挑战时游刃有余。但是同时呢,我们也要时刻保持清醒的头脑,像侦探一样敏锐地洞察可能出现的问题。针对这些问题,咱们得接地气儿,结合实际业务的具体需求,灵活定制出解决问题的方案来。 5. 结语 总之,SeaTunnel在应对Parquet/CSV文件格式解析错误上,凭借其强大的数据源适配能力和丰富的转换插件库,为我们提供了切实可行的解决方案。经过实战演练和持续打磨,我们能够更溜地玩转各种数据格式,确保数据整合和ETL过程一路绿灯,畅通无阻。所以,下次你再遇到类似的问题时,不妨试试看借助SeaTunnel这个好帮手,让数据处理这件事儿变得轻轻松松,更加贴近咱们日常的使用习惯,更有人情味儿。
2023-08-08 09:26:13
76
心灵驿站
转载文章
...4.用参数初始化表对数据成员初始化 5.构造函数的重载 (1)含义 (2)【例3.3】 (3)说明 6.使用默认参数值的构造函数 (1)含义 (2)格式 (3)【例3.4】 (4)说明 二、析构函数 1.含义 2.执行析构函数的时机 3.特征 4.【例3.5】包含构造函数和析构函数的C++程序 三、调用构造函数和析构函数的顺序 1.同一类存储类别的对象 2.全局范围内定义的对象 3.局部自动对象 4.静态局部对象 5.例 四、对象数组 1.含义 2.【例3.6】 五、对象指针 1.指向对象的指针 2.指向对象成员的指针 (1)含义 (2)指向对象公有数据成员的指针 (3)指向对象成员函数的指针 (4)【例3.7】有关对象指针的使用方法 3.this指针 六、共用数据的保护 1.常对象 2.常对象成员 (1)常数据成员 (2)常成员函数 3.指向对象的常指针 4.指向常对象的指针变量 5.对象的常引用 (1)含义 (2)格式 (3)【例3.8】对象的引用 6.const型数据小结 编辑 七、对象的动态建立与释放——动态建立对象 八、对象的赋值和复制 1.对象的赋值 (1)含义 (2)【例3.9】对象的赋值 (3)说明 2.对象的复制 (1)含义 (2)【例】用复制对象的方法创建Box类的对象(用默认复制构造函数) (3)说明 九、静态成员 1.静态数据成员 (1)定义格式 (2)特性 (3)说明 (4)【例3.10】引用静态数据成员 2.静态成员函数 (1)含义 (2)【例3.11】关于引用非静态成员和静态成员的具体方法 (3)【例】具有静态数据成员的point类 (4)静态成员函数举例 (5)具有静态数据、函数成员的Point类 (6)静态成员函数、静态数组及其初始化 十、友元 1.友元函数 (1)含义 (2)格式 (3)【例3.12】将普通函数声明为友元函数 (4)友元成员函数 2.友元类 十一、类模板 1.含义 2.定义类模板的格式 3.在类模板外定义成员函数的语法 4.使用类模板时,定义对象的格式 5.【例3.14】声明类模板,实现两个整数、浮点数和字符的比较,求出大数和小数 前言 通过第二章的学习,已经对类和对象有了初步了解。本章将对类和对象进行进一步讨论。 一、构造函数 如果定义一个变量,而程序未对其进行初始化的话,这个变量的值是不确定的,因为C和C++不会自觉地去为它赋值。与此相似,如果定义一个对象,而程序未对其数据成员进行初始化的话,这个对象的值也是不确定的。 1.对象的初始化 在定义一个类时,不能对其数据成员赋初值,因为类是一种类型,系统不会为它分配内存空间。在建立一个对象时,需要对其数据成员赋初值。如果一个数据成员未被赋初值,则它的值是不确定的。因为系统为对象分配内存时,保持了内存单元的原状,它就成为数据成员的初值。这个值是随机的。 C++提供了构造函数机制,用来为对象的数据成员进行初始化。在前面的学习中一直未讲这个概念,其实如果你未设计构造函数,系统在创建对象时,会自动提供一个默认的构造函数,而它只为对象分配内存空间其他什么也不做。 如果类中的所有数据成员是公有的,可以在定义对象时对其数据成员初始化。例如: class Time{public:int hour;int minute;int sec;};Time t1{15,36,26}; 在一个打括号内顺序列出各个公有数据成员的值,在两个值之间用逗号分隔。注意这只能用于数据成员都是共有的情况。 在前面的例子里,是用成员函数对对象的数据成员赋初值,如果一个类定义了多个对象,对每个对象都要调用成员函数对数据成员赋初值,那么程序就会变得繁琐,所以用成员函数为数据成员赋初值不是一个好办法。 2.构造函数的作用 构造函数用于为对象分配空间和进行初始化,它属于某一个类,可以由系统自动生成。也可以由程序员编写,程序员根据初始化的要求设计构造函数及函数参数。 构造函数是一种特殊的成员函数,在程序中不需要写调用语句,在系统建立对象时由系统自觉调用执行。 构造函数的特点: 构造函数的名字与它的类名必须相同 它没有类型,也不返回值 它可以带参数,也可以不带参数 include <iostream>using namespace std;class Time {public:Time() {hour = 0;minute = 0;sec = 0;}void set_time();void show_time();private:int hour;int minute;int sec;};int main() {Time t1;t1.set_time();t1.show_time();Time t2;t2.show_time();return 0;}void Time::set_time() {cin >> hour;cin >> minute;cin >> sec;}void Time::show_time() {cout << hour << ":" << minute << ":" << sec << endl;} 在类Time中定义了构造函数Time,它与所在的类同名。在建立对象时自动执行构造函数,该函数的作用是为对象中的每个数据成员赋初值0。注意只有执行构造函数时才能为数据成员赋初值。 程序运行时首先建立对象t1,并对t1中的数据成员赋初值0,然后执行t1.set_time函数,从键盘输入新值给对象t1的数据成员,再输出t1的数据成员的值。接着建立对象t2,同时对t2中的数据成员赋初值0,最后输出t2的数据成员的初值。程序运行情况如下: 也可以在类内声明构造函数然后在类外定义构造函数。将程序修改为Time();然后在类外定义构造函数: Time::Time() {hour = 0;minute = 0;sec = 0;} 关于构造函数的使用,说明如下: 什么时候调用构造函数?当函数执行到对象定义语句时建立对象,此时就要调用构造函数,对象就有了自己的作用域,对象的生命周期开始了。 构造函数没有返回值,因此不需要在定义中声明类型。 构造函数不需要显式地调用,构造函数是在建立对象时由系统自动执行的,且只执行以此。构造函数一般定义为public。 在构造函数中除了可以对数据成员赋初值,还可以使用其他语句。 如果用户没有定义构造函数,C++系统会自动生成一个构造函数,而这个函数体是空的,不执行初始化操作。 3.带形参数的构造函数 (1)含义 可以采用带形参数的构造函数,在调用不同对象的构造函数时,从外边将不同的数据传递给构造函数,实现不同对象的初始化。 构造函数的首部的一般格式为:构造函数名(类型 形参1,类型 形参2,……)。在定义对象时指定实参,定义对象的格式为:类名 对象名(实参1,实参2,……)。 (2)【例3.2】 有两个长方柱,其长、宽、高分别为:(1)12,25,30(2)15,30,21编写程序,在类中用带参数的构造函数,计算它们的体积。 分析:可以在类中定义一个计算长方体体积的成员函数计算对象的体积。 include<iostream>using namespace std;class Box{public:Box(int,int,int); //声明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(heightwidthlength);}int main(){Box box1(12,25,30); //定义对象box1cout<<"box1体积="<<box1.volume()<<endl;Box box2(15,30,21); //定义对象box2cout<<"box2体积="<<box2.volume()<<endl;return 0;} 【注】 带形参的构造函数在定义对象时必须指定实参 用这种方法可以实现不同对象的初始化 4.用参数初始化表对数据成员初始化 C++提供了参数初始化表的方法对数据成员初始化。这种方法不必再构造函数内对数据成员初始化,在函数的首部就能实现数据成员初始化。 函数名(类型1 形参1,类型2 形参2): 成员名1(形参1),成员名2(形参2){ } 功能:执行构造函数时,将形参1的值赋予成员1,将形参2的值赋予成员2,形参的值由定义对象时的实参值决定。此时定义对象的格式依然是带实参的形式:类名 对象名(实参1,实参2); 例:定义带形参初始化表的构造函数 Box::Box(int h,int w,int len):height(h),width(w),length(len){}//定义对象:Box box1(12,25,30);//……Box box2(15,30,21); 5.构造函数的重载 (1)含义 构造函数也可以重载。一个类可以有多个同名构造函数,函数参数的个数、参数的类型各不相同。 (2)【例3.3】 在【例3.2】的基础上定义两个构造函数,其中一个无参数,另一个有参数 include <iostream>using namespace std;class Box {public:Box();Box(int h, int w, int len): height(h), width(w), length(len) {}int volume();private:int height;int width;int length;};Box::Box() {height = 10;width = 10;length = 10;}int Box::volume() {return (height width length);}int main() {Box box1;cout << "box1 体积" << box1.volume() << endl;Box box2(15, 30, 25);cout << "box2 体积" << box2.volume() << endl;return 0;} (3)说明 不带形参的构造函数为默认构造函数,每个类只有一个默认构造函数,如果是系统自动给的默认构造函数,其函数体是空的 虽然每个类可以包含多个构造函数,但是创建对象时,系统仅执行其中一个 6.使用默认参数值的构造函数 (1)含义 C++允许在构造函数里为形参指定默认值,如果创建对象时,未给出相应的实参时,系统将用形参的默认值为形参赋值。 (2)格式 函数名(类型 形参1=常数,类型 形参2=常数,……); (3)【例3.4】 将【例3.3】中的构造函数改用带默认值的参数,长、宽、高的默认值都是10 include <iostream>using namespace std;class Box {public:Box(int w = 10, int h = 10, int len = 10);int volume();private:int height;int width;int length;};Box::Box(int w, int h, int len) {height = h;width = w;length = len;}int Box::volume() {return (height width length);}int main() {Box box1;cout << "box1 体积" << box1.volume() << endl;Box box2(15);cout << "box2 体积" << box2.volume() << endl;Box box3(15, 30);cout << "box3 体积" << box3.volume() << endl;Box box4(15, 30, 20);cout << "box4 体积" << box4.volume() << endl;return 0;} (4)说明 如果在类外定义构造函数,应该在声明构造函数时指定默认参数值,再定以函数时不再指定默认参数值 在声明构造函数时,形参名可以省略。例如:Box(int 10,int 10,int 10); 如果构造函数的所有形参都指定了默认值,在定义对象时,可以指定实参也可不指定实参。由于不指定实参也可以调用构造函数,因此全部形参都指定了默认值的构造函数也属于默认构造函数。为了避免歧义,不允许同时定义不带形参的构造函数和全部形参都指定默认值的构造函数。 不能同时使用重载构造函数和带默认值的构造函数 二、析构函数 1.含义 析构函数也是个特殊的成员函数,它的作用与构造函数相反,当对象的生命周期结束时,系统自动调用析构函数,收回对象占用的内存空间。 2.执行析构函数的时机 在一个函数内定义的对象当这个函数结束时,自动执行析构函数释放对象 static局部对象要到main函数结束或执行exit命令时才自动执行析构函数释放对象 全局对象(在函数外定义的对象)当main函数结束或执行exit命令时自动执行析构函数释放对象 如果用new建立动态对象,用delete时自动执行析构函数释放对象 3.特征 以~符号开始后跟类名 析构函数没有数据类型、返回值、形参。由于没有形参所以析构函数不能重载。一个类只有一个析构函数 如果程序员没有定义析构函数,C++编译系统会自动生成一个析构函数 【注】析构函数除了释放对象(资源)外,还可以执行程序员在最后一次适用对象后希望执行的任何操作。例如输出有关的信息。 4.【例3.5】包含构造函数和析构函数的C++程序 include <iostream>include <string>using namespace std;class Student {public:Student(int n, string nam, char s) {num = n;name = nam;sex = s;cout << "Constructor called." << endl;}~Student() {cout << "Destructor called." << endl;}void display() {cout << "num:" << num << endl;cout << "name:" << name << endl;cout << "sex:" << sex << endl;}private:int num;string name;char sex;};int main() {Student stud1(10010, "wang_li", 'f');stud1.display();Student stud2(10011, "zhang_han", 'm');stud2.display();return 0;}//main函数前声明的类其作用域是全局的 三、调用构造函数和析构函数的顺序 1.同一类存储类别的对象 一般情况下,调用析构函数的次序与调用构造函数的次序恰好相反:最先调用构造函数的对象,最后调用析构函数;最后调用构造函数的对象,最先调用析构函数。可简记为:先构造的后析构,后构造的先析构。它相当于一个栈,后进先出。 2.全局范围内定义的对象 在全局范围内定义的对象(在所有函数之外定义的对象),在文件中的所有函数(包括主函数)执行前调用构造函数。当主函数结束或执行exit函数时,调用析构函数。 3.局部自动对象 如果定义局部自动对象(在函数内定义对象),在创建对象时调用构造函数。如多次调用对象所在的函数,则每次创建对象时都调用构造函数。在函数调用结束时调用析构函数。 4.静态局部对象 如果在函数中定义静态局部对象,则在第一次调用该函数建立对象时调用构造函数,但在主函数结束或调用exit函数时才调用析构函数。 5.例 void fun(){student st1; //定义局部自动对象static student st2; //定义静态局部对象...} 对象st1是每次调用函数fun时调用构造函数。在函数fun结束时调用析构函数。 对象st2是第一次调用函数fun时调用构造函数,在函数fun结束时并不调用析构函数,到主函数结束时才调用析构函数 四、对象数组 1.含义 类是一种特殊的数据类型,它当然是C++的合法类型,自然可以定义对象数组。在一个对象数组中各个元素都是同类对象。例如一个班级有50个同学,每个学生有学号、年龄、成绩等属性,可以为这个班级建立一个对象数组,数组包括了50个元素:student std[50];。 可以这样建立构造函数:student::student(int 1001,int 18,int 60);。 在建立数组时,同样要调用构造函数。上面的数组有50个元素,要调用50次构造函数。如果构造函数有多个参数,C++要求:在等号后的花括号中为每个对象分别写出构造函数并指定实参。格式为: student st[n]={ student(实参1,实参2,实参3); …… student(实参1,实参2,实参3); }; 假定对象有三个数据成员:学号、年龄、成绩。下面定义有三个学生的对象数组: student st[3]={ student(1001,18,87); student(1002,19,76); student(1003,18,80); };//构造函数带实参 在建立对象数组时,分别调用构造函数,对每个对象初始化。每个元素的实参用括号括起来,实参的位置与构造函数形参的位置一一对应,不会混淆。 2.【例3.6】 include <iostream>using namespace std;class Box {public:Box(int h = 10, int w = 12, int len = 15): height(h), width(w), length(len) {} //int volume();private:int height;int width;int length;};int Box::volume() {return (height width length);}int main() {Box a[3] = {Box(10, 12, 15), Box(15, 18, 20), Box(16, 20, 26)};cout << "a[0]的体积是" << a[0].volume() << endl;cout << "a[1]的体积是" << a[1].volume() << endl;cout << "a[2]的体积是" << a[2].volume() << endl;return 0;}//每个数组元素是一个对象 五、对象指针 指针的含义是内存单元的地址,可以指向一般的变量,也可以指向对象。 1.指向对象的指针 对象要占据一片连续的内存空间,CPU实际都是按地址访问内存,所以对象在内存的其实地址是CPU确定对象在内存中位置的依据。这个起始地址称为对象指针。 C++的对象也可以参加取地址运算:&对象名。运算的结果是该对象的起始地址,也称对象的指针,要用与对象类型相同的指针变量保存运算的结果。 C++中定义对象的指针变量与定义其他的指针变量相似,格式如下:类名 变量名表。类名表示对象所属的类,变量名按标识符规则取名,两个变量名之间用逗号分隔。定义好指针变量后,必须先给赋予合法的地址后才能使用。 例如定义如下一个类: class Time {public:Time() {hour = 0;minute = 0;sec = 0;}void set_time();void show_time();private:int hour;int minute;int sec;};void Time::set_time() {cin >> hour;cin >> minute;cin >> sec;}void Time::show_time() {cout << hour << ":" << minute << ":" << sec << endl;} 在此基础上,有如下语句: Time pt; //定义pt是指向Time类对象的指针Time t1; //定义Time类对象t1pt=&t1; //将对象t1的地址赋予pt 程序在此基础上就可以用指针变量访问对象的成员。 (pt).hour;pt->hour;(pt).show_time();pt->show_time(); 2.指向对象成员的指针 (1)含义 对象由成员组成。对象占据的内存区是各个数据成员占据的内存区的总和。对象成员也有地址,即指针。这指针分指向数据成员的指针和指向成员函数的指针。 (2)指向对象公有数据成员的指针 定义数据成员的指针变量:数据类型 指针变量名(这里的数据类型是数据成员的数据类型) 计算公有数据成员的地址:&对象名.成员名 Time t1;int p1; //定义一个指向整型数据的指针变量p1=&t1.hour; //假定hour是公有成员cout<<p1<<endl; (3)指向对象成员函数的指针 定义指向成员函数的指针变量:数据类型(类名::变量名)(形参表); 数据类型是成员函数的类型;类名是对象所属的类;变量名按标识符取名;形参表:指定成员函数的形参表(形参个数、类型) 取成员函数的地址:&类名::成员函数名 给指针变量赋初值:指针变量名=&类名::成员函数名; 用指针变量调用成员函数:(对象名.指针变量名)([实参表]); 对象名:指定调用成员函数的对象;:明确其后的是一个指针变量;实参表:与成员函数的形参表对应,如无形参,可以省略实参表 (4)【例3.7】有关对象指针的使用方法 include <iostream>using namespace std;class Time {public:Time(int, int, int);int hour;int minute;int sec;void get_time();};Time::Time(int h, int m, int s) {hour = h;minute = m;sec = s;}void Time::get_time() {cout << hour << ":" << minute << ":" << sec << endl;}int main() {Time t1(10, 13, 56);int p1 = &t1.hour; //定义指向数据成员的指针p1cout << p1 << endl;t1.get_time(); //调用成员函数Time p2 = &t1; //定义指向对象t1的指针p2p2->get_time(); //用对象指针调用成员函数void(Time::p3)(); //定义指向成员函数的指针p3 = &Time::get_time; //给成员函数的指针赋初值(t1.p3)(); //用指向成员函数的指针调用成员函数return 0;} 【注】代码的34,35行可合并为:void(Time::p3)=&Time::get_time; 3.this指针 一个类的成员函数只有一个内存拷贝。类中不论哪个对象调用某个成员函数,调用的都是内存中同一个成员函数代码。例如Time类一个成员函数: void Time::get_time(){cout<<hour<<":"<<minute<<":"<<sec<<endl;}t1.get_time();t2.get_time(); 当不同对象的成员函数访问数据成员时,怎么保证访问的就是指定对象的数据成员?其实每个成员函数中都包含一个特殊的指针,他的名字是this指针。它是指向本类对象的指针。当对象调用成员函数时,它的值就是该对象的起始地址。所以为了区分不同对象访问成员函数,语法要求的调用成员函数的格式是:对象名.成员函数名(实参表)。从语法上明确是对象名所指的对象调用成员函数。This指针是隐式使用的,在调用成员函数时C++把对象的地址作为实参传递给this指针。例如成员函数定义如下: int Box::volume(){return(heightwidthlength);} C++编译成: int Box::volume(this){return(this->heightthis->widththis->length);} 对于计算长方体体积的成员函数volume,当对象调用它时,就把对象地址给this指针,编译程序将的地址作为实参调用成员函数:a.volume(&a);。实际上函数是计算(this->height)(this->width)(this->length),这时就等价计算(a.height)(a.width)(a.length)。 可以用(this)表示调用成员函数的对象。(this)就是this所指的对象。如前面的计算长方体体积的函数中return语句可以写成:return((this).height(this).width(this).length);注意,this两侧的括号不能省略。 C++通过编译程序,在对象调用成员函数时,把对象的地址赋予this指针,用this指针指向对象,实现了用同一个成员函数访问不同对象的数据成员。 六、共用数据的保护 如果既希望数据在一定范围内共享,又不愿它被随意修改,从技术上可以把数据指定为只读型的。C++提供const手段,将数据、对象、成员函数指定为常量,从而实现了只读要求,达到保护数据的目的。 1.常对象 定义格式: const 类名 对象名(实参表);或 类名 const 对象名(实参表); 把对象定义为常对象,对象中的数据成员就是常变量,在定义时必须带实参作为数据成员的初值,在程序中不允许修改常对象的数据成员值。 如果一个常对象的成员函数未被定义为常成员函数(除构造函数和析构函数外),则对象不能调用这样的函数。 const Time t1(10,16,36);t1.get_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
544
转载
JSON
在深入理解了JSON格式的规范,特别是其对于键值对和分隔符的严格要求之后,我们可以进一步探讨JSON在现代数据交换与存储中的实际应用及其重要性。近日(根据实际日期),GitHub宣布推出增强型JSON模式验证功能,允许开发者在API设计阶段即对JSON数据结构进行详细的定义和检查,有效预防因格式错误导致的应用崩溃或性能下降问题。 此外,随着JSON-LD(JSON for Linking Data)标准的发展,JSON不再仅限于纯数据表示,而是逐渐成为语义网络和知识图谱构建的关键工具。JSON-LD通过引入上下文信息,使得JSON数据具有更强的互操作性和可机器解析性,为人工智能、大数据分析等领域提供了有力支持。 同时,针对JSON安全问题,业界专家提醒开发者关注JSON注入攻击的风险。这种攻击手法利用了服务端对用户提交JSON数据解析时的漏洞,通过精心构造的恶意JSON内容实现非法操作。因此,开发人员在处理JSON数据时,不仅要遵循正确的语法格式,还应采用适当的安全策略,如输入验证、最小权限原则等,确保JSON数据交互的安全可靠。 综上所述,JSON作为主流的数据交换格式,在不断优化和完善的过程中,正持续影响着软件工程、数据分析以及信息安全等多个领域的实践与发展。从遵守基本语法规范到紧跟前沿技术趋势,全面理解和掌握JSON的应用与防护至关重要。
2023-06-16 09:53:24
301
算法侠
Python
...正则表达式的复杂模式匹配、条件语句和环视等高级特性,还结合当下大数据处理、网络爬虫及数据分析等领域的需求,提供了丰富的实战案例。 例如,文中详述了如何利用正则表达式高效解析JSON和XML数据结构,这对于提升数据分析效率至关重要。此外,作者还分享了在抓取网页内容时,如何精准提取特定标签内的信息,展示了正则表达式在Web scraping任务中的关键作用。同时,文章讨论了正则表达式在文本清洗过程中过滤特殊字符、标准化日期格式以及识别电子邮件、URL等常见字符串模式的实践方法。 对于希望更深入理解并有效应用Python正则表达式的开发者来说,这篇深度解读与实战指导相结合的文章无疑是极具时效性和针对性的延伸阅读材料,它将帮助读者应对更为复杂的文本处理挑战,提高开发效率,并助力实现项目目标。
2023-01-25 14:35:48
282
键盘勇士
Mongo
...SON)是一种二进制格式,用于在MongoDB中存储和传输数据。它类似于JSON,但增加了对二进制数据、日期和其他特殊数据类型的支持,使得MongoDB能够高效地处理复杂的数据结构,并在数据库内部以紧凑的形式存储数据。 Strict Mode(严格模式) , 在MongoDB 5.0及更高版本中,严格模式是一项可选配置,旨在增强集合级别的数据验证能力。启用严格模式后,MongoDB将在插入或更新文档时,强制执行字段类型和schema的匹配,如果数据类型不一致,则操作将失败,从而防止因字段类型不匹配导致的错误和数据异常。 数据清洗 , 数据清洗是数据分析过程中的一个重要环节,主要指对原始数据进行预处理,包括但不限于去除重复值、填充缺失值、纠正错误值以及转换数据类型等工作。在文章的语境中,通过使用Pandas库等工具对从API或其他非结构化源获取的数据进行数据清洗,确保数据满足MongoDB字段类型的要求,从而避免字段类型不匹配的问题。
2023-12-16 08:42:04
184
幽谷听泉-t
转载文章
...的数量自动调整为数组格式,方便开发者获取多个临时文件路径。 针对 uni-app 图片上传过程中出现的TypeError: e.split is not a function等错误,开发者不仅需要理解其背后的原因(即尝试将数组当作字符串处理),还应当关注不同平台API特性和兼容性问题。为了确保在各终端(如微信小程序、H5、App等)上都能稳定运行,建议开发者遵循官方文档指导,并结合社区讨论和实践案例不断优化代码逻辑。 此外,随着移动应用对数据安全及隐私保护要求的提高,uni-app在处理用户上传图片时也需注重合规性。比如,明确告知用户图片用途、存储期限,并在必要时对上传图片进行压缩或加密处理,降低因图片过大导致的性能瓶颈,同时也避免了潜在的数据泄露风险。 深入解读uni-app的图片上传机制,不难发现其与Web开发中的FormData、Blob对象以及移动端特有的临时文件路径管理有诸多相似之处。开发者可借鉴Web前端领域成熟的解决方案,结合uni-app生态内丰富的插件资源,实现更高效、安全且用户体验良好的图片上传功能。 综上所述,uni-app图片上传功能的完善既依赖于开发者对框架本身特性的掌握,也离不开对行业规范、技术趋势的敏锐洞察与灵活运用。通过持续学习与实践,开发者能够更好地应对各种场景下可能出现的问题,并打造出体验优良、适应多端环境的应用产品。
2023-03-05 15:38:13
59
转载
MySQL
将数据传输到MySQL数据库中是数据处理的重要步骤。为方便说明,假设我们要将一个名为“test”的数据表创建到指定MySQL服务器的数据库中。 第一步是连接到MySQL服务器。使用以下PHP代码进行连接: $db_host = "localhost"; // MySQL服务器地址 $db_user = "root"; // MySQL用户名 $db_pass = "password"; // MySQL用户密码 $db_name = "database_name"; // 数据库名 $conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name); if (!$conn) { die("连接错误:" . mysqli_connect_error()); } 连接成功后,我们可以将数据传输到MySQL数据库中。将以下PHP代码放到您的脚本中: $sql = "CREATE TABLE test ( id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30) NOT NULL, email VARCHAR(50) NOT NULL, reg_date TIMESTAMP )"; if (mysqli_query($conn, $sql)) { echo "数据表test创建成功"; } else { echo "创建数据表错误: " . mysqli_error($conn); } 以上代码将在您的MySQL数据库中创建名为test的数据表。该表包含id、name、email和reg_date列。id列将自动递增,并将作为主键。name和email列不能为NULL,而reg_date列将保存创建行的时间戳。 上传数据到MySQL数据库中可能需要一些额外的数据处理。您可以从CSV文件、文本文件、XML文件、JSON数据或通过表格收集的数据中读取数据,然后将其转换为MySQL可以处理的常规数据格式。使用以下PHP代码将数据上传到MySQL数据库中: $myfile = fopen("data.txt", "r") or die("不能打开文件!"); while (!feof($myfile)) { $line = fgets($myfile); $line_arr = explode(",", $line); $name = $line_arr[0]; $email = $line_arr[1]; $sql = "INSERT INTO test (name, email) VALUES ('$name', '$email')"; mysqli_query($conn, $sql); } fclose($myfile); echo "上传数据到MySQL数据库成功"; 以上代码将从文本文件中获取数据,并将其上传到MySQL数据库的test数据表中。请注意,我们将数据数组中的第一和第二个元素映射到MySQL表test中的name和email列。 当您上传或更新数据时,请记得在您的PHP脚本中使用适当的错误处理和安全措施,以确保数据库安全。
2024-01-19 14:50:17
333
数据库专家
Apache Solr
...泛应用于各种场景下的数据检索。不过呢,随着Solr这家伙越来越受欢迎,用得越来越广泛,管理和维护它的工作也变得愈发繁琐复杂了。特别是对于大型系统而言,实时监控和性能日志记录显得尤为重要。这篇文章要手把手教你如何把Solr的实时监控和性能日志功能调校好,让你的系统稳如泰山,靠得住,一点儿都不含糊! 二、实时监控 实时监控可以帮助我们及时发现并解决系统中的问题,保证系统的正常运行。以下是配置Solr实时监控的步骤: 1. 添加JMX支持 Solr自带了JMX的支持,只需要在启动命令行中添加参数-Dcom.sun.management.jmxremote即可启用JMX监控。例如: bash java -Dcom.sun.management.jmxremote -jar start.jar 2. 安装JConsole JConsole是Java提供的一款图形化监控工具,可以通过它来查看Solr的各项指标和状态。 3. 启动JConsole 启动JConsole后,连接到localhost:9999/jconsole即可看到Solr的各种指标和状态。 三、性能日志记录 性能日志记录可以帮助我们了解Solr的工作情况和性能瓶颈,从而进行优化。以下是配置Solr性能日志记录的步骤: 1. 设置日志级别 在Solr的配置文件中设置日志级别,例如: xml ... 这里我们将日志级别设置为info,表示只记录重要信息和错误信息。 2. 设置日志格式 在Solr的配置文件中设置日志格式,例如: xml logs/solr.log %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 这里我们将日志格式设置为"%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n",表示每行日志包含日期、时间、线程ID、日志级别、类名和方法名以及日志内容。 四、结论 配置Solr的实时监控和性能日志记录不仅可以帮助我们及时发现和解决系统中的问题,还可以让我们更好地理解和优化Solr的工作方式和性能。大家伙儿在实际操作时,可得把这些技巧玩转起来,让Solr跑得更溜、更稳当,实实在在提升运行效率和稳定性哈!
2023-03-17 20:56:07
473
半夏微凉-t
Struts2
...遇到一种常见的运行时错误——"No result type defined for action method return value". 这种错误通常出现在我们配置的Action类方法返回值无法匹配到预定义的结果类型时。这次,咱将手牵手、一步步深挖这个错误背后的真相,不仅限于理论讲解,还会结合实际的代码实例,让大家真真切切地看到如何解决这个问题,以及如何提前做好防范,让这类错误无处遁形。 2. 错误的理解与解读 首先,让我们来共同剖析一下这个错误信息。在Struts2这个框架里,当你执行完一个Action方法后,它会像个聪明的小助手一样,根据你这个方法返回的结果字符串,去找到对应的那个结果类型处理器。这就像是拿着一把钥匙去找对应的锁一样,结果字符串就是钥匙,结果类型处理器就是那个特定的锁。若Struts2找不到与之匹配的结果类型,就会抛出此异常。这就像是你给一位厨房大厨一张满载神秘食材的任务卡,可关键的是,菜单上并没有教他具体怎么料理这些稀奇古怪的玩意儿,这样一来,大厨可就懵圈了,完全不知道从何下手。 3. 示例代码与解析 为了更好地理解这个问题,我们先看一段简单的Struts2 Action类代码示例: java public class SampleAction extends ActionSupport { public String execute() { // 执行一些业务逻辑... return "customResult"; // 返回自定义结果字符串 } } 然后,在struts.xml配置文件中,如果我们没有为"customResult"定义相应的结果类型: xml 运行程序并调用该Action时,Struts2就无法找到对应的“customResult”的结果处理器,从而抛出"No result type defined for action method return value: customResult"的错误。 4. 解决方案 要解决这个问题,我们需要在struts.xml配置文件中为"customResult"添加相应结果类型定义: xml /WEB-INF/pages/success.jsp 在这个例子中,我们指定了当execute方法返回"customResult"时,系统应该跳转到"/WEB-INF/pages/success.jsp"页面。这样一来,Struts2就能准确无误地处理Action方法的返回值了。 5. 预防与优化 为了避免这类问题的发生,我们在设计和编写Action类时应遵循以下原则: - 明确每个Action方法可能返回的所有结果类型,并在struts.xml中预先配置好。 - 在团队协作中,统一结果类型命名规则,保持良好的文档记录,方便后续维护和扩展。 - 利用Struts2的通配符结果类型或者默认结果类型等特性,简化配置过程,提高开发效率。 6. 总结 在我们的编程实践中,理解和掌握Struts2框架的工作机制至关重要。当你遇到像"No result type defined for action method return value"这样的怪咖问题时,咱们不光得摸清怎么把它摆平,更关键的是,得学会从这个坑里爬出来的同时,顺手拔点“经验值”,让自己在编程这条路上的修养越来越深厚。这样子做,咱们才能在未来的开发道路上越走越远、越走越稳当,确保每次编程的旅程都充满刺激的挑战和满满的收获。
2023-07-16 19:18:49
80
星河万里
Kubernetes
...看似神秘又让人头疼的错误消息:“MountVolumeSetUp failed for volume pvvolume : mount failed: exit status”。嘿,今天咱们来个深度剖析,就像拆解神秘礼物一样,把那个恼人的错误好好研究研究。咱们一边动手码代码,一边实战演练,看怎么把它这只小妖精搞定! 二、错误解读 首先,让我们理解这个错误的含义。你知道嘛,当你在玩儿Kubernetes的时候,想把那个 Persistent Volume(PV)挂到Pod上去,结果弹出来个"MountVolumeSetUp failed",那家伙八成就是在跟你闹脾气了。可能是你权限不够,路径不合拍,文件系统不认你,或者是哪个设置不小心搞错了,总之就是挂载路上遇到阻碍了。你知道吗,那个"exit status"后面的小数字就像个神秘的密码,它其实是个超级详细的错误信号灯,能帮咱们精准地找出问题出在哪儿。 三、问题分类与排查 1. 权限问题 bash kubectl logs -n | grep "Permission denied" 如果输出中有类似信息,检查PV的owner和group是否与Pod的对应设置一致,或者给予Pod适当的权限。 2. 路径冲突 yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pv-volume-claim spec: accessModes: [ "ReadWriteOnce" ] storageClassName: standard resources: requests: storage: 1Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: template: metadata: name: my-pod spec: containers: - name: my-container volumeMounts: - mountPath: /data name: pv-volume subPath: 检查subPath是否指向了已存在的目录,如果有冲突,可能需要调整路径或清理。 3. 文件系统类型不兼容 yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv-volume spec: storageClassName: nfs capacity: storage: 1Gi nfs: path: /export/mydata 确保PV的存储类型与Pod中期望的挂载类型匹配,如NFS、HostPath等。 四、解决方案与实践 1. 更新权限 bash kubectl exec -it -- chown : /path/to/mount 2. 调整Pod配置 如果是路径冲突,可以修改Pod的subPath,或者在创建PV时指定一个特定的挂载点。 3. 修改PV类型 yaml apiVersion: v1 kind: PersistentVolume spec: ... fsType: ext4 更改为与应用兼容的文件系统类型 五、预防措施 - 定期检查集群资源和配置,确保PV与Pod之间的映射正确。 - 使用Kubernetes的健康检查机制,监控挂载状态,早期发现问题。 - 在应用部署前,先在测试环境中验证PV的挂载。 六、结语 解决“MountVolumeSetUp failed”错误并不是一次性的任务,而是一个持续的过程,需要我们对Kubernetes有深入的理解和实践经验。通过以上步骤和实例,相信你已经在处理这类问题上更加得心应手了。记住,遇到问题不要慌张,一步步分析,代码调试,总能找到答案。Happy Kubernetesing!
2024-05-03 11:29:06
127
红尘漫步
Etcd
...Etcd会周期性地将数据持久化为快照文件以防止数据丢失。然而,当我们重启Etcd服务时,可能会遇到无法加载先前持久化的快照文件的问题,这无疑对系统的稳定性构成了威胁。这篇东西,咱们会好好挖一挖这个问题背后的为啥,然后我还会甩出些实例代码和实战经历,实实在在地给你亮出解决方案。 2. 快照文件加载失败的可能原因 2.1 文件损坏或不完整 在Etcd进行持久化操作时,如果出现如磁盘空间不足、写入过程中服务器宕机等情况,可能导致生成的快照文件损坏或不完整,从而使得Etcd在重启时无法成功加载这些文件。 bash 示例:Etcd启动日志中可能显示的错误信息 etcd: snapshot file /var/lib/etcd/member/snap/db.snap is corrupted or has a wrong version 2.2 版本不兼容 Etcd在升级版本时,旧版本创建的快照文件可能与新版本存在兼容性问题,导致新版本的Etcd服务无法正确加载旧版本的快照文件。 2.3 文件权限问题 如果Etcd进程没有足够的权限访问快照文件,也会导致加载失败。 2.4 配置路径不一致 在Etcd启动配置中,如果指定的数据目录与快照文件的实际存放路径不匹配,自然会导致Etcd找不到并加载快照文件。 3. 解决方案及实战示例 3.1 检查和修复快照文件 首先,我们需要确认快照文件是否损坏或不完整。可以尝试使用etcdctl工具来检查快照文件: bash etcdctl snapshot status /path/to/snapshot.db 如果确实存在问题,可以考虑从备份恢复或者重新启动一个全新的Etcd集群,然后重新导入数据。 3.2 确保版本兼容性 在升级Etcd版本时,应遵循官方发布的升级指南,确保有正确的迁移步骤。如有必要,可先将旧版Etcd的数据进行备份,并在新版Etcd启动后执行恢复操作。 3.3 调整文件权限 确保Etcd进程用户有足够的权限访问快照文件,例如: bash chown -R etcd:etcd /var/lib/etcd/ 3.4 核实启动配置中的数据目录 请确保Etcd启动命令或配置文件中的数据目录参数(--data-dir)指向包含快照文件的实际路径。 bash ./etcd --data-dir=/var/lib/etcd/member --snapshot-count=10000 4. 总结与思考 在处理Etcd无法加载先前持久化快照文件的问题时,我们不仅需要排查具体的技术原因,还要根据实际情况灵活运用各种应对策略。同时呢,这也正好敲响了我们日常运维的小闹钟,告诉我们得把Etcd集群数据的定期备份和检查工作给提上日程,可不能马虎。而且呀,在进行版本升级的时候,也要瞪大眼睛留意一下兼容性问题,别让它成了那只捣蛋的小鬼。说到底,只有真正把它的运作机理摸得门儿清,把那些潜在的风险点都研究透彻了,咱们才能把这个强大的分布式存储工具玩转起来,保证咱的业务系统能够稳稳当当地跑起来。就像医生看病那样,解决技术问题也得我们像老中医似的,耐着性子慢慢来,得“望闻问切”全套做齐了,也就是说,得仔细观察、耐心倾听、多角度询问、深度剖析,一步步把各种可能的问题排除掉,最后才能揪出那个隐藏的“罪魁祸首”。
2023-07-24 14:09:40
778
月下独酌
SpringBoot
...的可能就是前端发送的数据到了后端却莫名其妙地变成了0。这不仅影响用户体验,也对代码调试提出了挑战。接下来,咱们一块儿踏上解谜之旅吧!从头开始,一点点弄懂这个神秘的“0”,就像拆开礼物上的层层包装,最终揭示它的奇妙真相。 二、场景再现 假设我们正在开发一个简单的用户注册系统,前端Vue.js负责收集用户信息,然后通过axios发送给SpringBoot后端进行验证和存储。你知道吗,有时候我们在Vue的那些小元件里边,填好账号名和密码,一激动点发送按钮,结果呢,后头的服务器接收的数据里,邮箱那一栏就莫名其妙地变成了0,就像被人动了手脚似的。 javascript // Vue.js 部分 - 送出数据的部分 methods: { registerUser() { const formData = { username: this.username, password: this.password, email: this.email, // 这里原本应该是用户的邮箱地址 }; axios.post('/api/register', formData) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); } } 三、问题分析 1. 类型转换 首先,检查一下是不是类型转换的问题。SpringBoot在接收数据时,如果类型不匹配,可能会尝试将其转换为可接受的数据类型。比如说,假如你邮箱地址栏不小心输入了个纯数字“0”,当你想把它当成字符串来处理的时候,这家伙可能会调皮地变成一个空荡荡的啥都没有。 java // SpringBoot 部分 - 接收数据的Controller @PostMapping("/register") public ResponseEntity registerUser(@RequestBody Map formData) { String email = formData.get("email").toString(); // 如果email是数字0,这里会变成"" // ... } 2. 默认值 另一个可能的原因是,前端在发送数据前没有正确处理可能的空值或默认值。你知道吗,有时候在发邮件前,email这哥们儿可能还没人填,这时它就暂且是JavaScript里的那个神秘存在“undefined”。一到要变成JSON格式,它就自动变身为“null”,然后后端大哥看见了,贴心地给它换个零蛋。 3. 数据验证 SpringBoot的@RequestBody注解默认会对JSON数据进行有效性校验,如果数据不符合约定的格式,它可能被视作无效,从而转化为默认值。检查Model层是否定义了默认值规则。 java // Model层 public class User { private String email; // ...其他字段 @NotBlank(message = "Email cannot be blank") public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } 四、解决策略 1. 前端校验 确保在发送数据之前对前端数据进行清理和验证,避免空值或非预期值被发送。 2. 明确数据类型 在Vue.js中,可以使用v-model.number或者v-bind:value配合计算属性,确保数据在发送前已转换为正确的类型。 3. 后端配置 SpringBoot可以配置Jackson或Gson等JSON库,设置@JsonInclude(JsonInclude.Include.NON_NULL)来忽略所有空值。 4. 异常处理 添加适当的异常处理,捕获可能的转换异常并提供有用的错误消息。 五、结论 解决这个问题的关键在于理解数据流的每个环节,从前端到后端,每一个可能的类型转换和验证步骤都需要仔细审查。你知道吗,有时候生活就像个惊喜包,比如说JavaScript那些隐藏的小秘密,但别急,咱们一步步找,那问题的源头准能被咱们揪出来!希望这篇文章能帮助你在遇到类似困境时,更好地定位和解决“0”问题,提升开发效率和用户体验。 --- 当然,实际的代码示例可能需要根据你的项目结构和配置进行调整,以上只是一个通用的指导框架。记住,遇到问题时,耐心地查阅文档,结合调试工具,往往能更快地找到答案。祝你在前端与后端的交互之旅中一帆风顺!
2024-04-13 10:41:58
82
柳暗花明又一村_
Kotlin
...方法的要求或者类型不匹配,就会抛出 IllegalArgumentException。这事儿就像你去参加一个超级认真的补习班,老师布置了一道题目让你做,结果你交上去的答案全错了,那肯定得被老师好好点名批评一番了。 第二章:深入剖析 IllegalArgumentException 假设我们有一个简单的函数 calculateAge,它接受一个人的出生年份作为参数,并计算出当前年龄: kotlin fun calculateAge(birthYear: Int): Int { val currentYear = 2023 return currentYear - birthYear } 如果我们不小心传入了一个非整数类型的参数,比如一个字符串,Kotlin会立即察觉到这一点,并优雅地抛出 IllegalArgumentException: kotlin fun test() { val age = calculateAge("2000") println("Your age is $age.") } // 运行结果:编译错误,因为calculateAge接受的是Int类型参数,而"2000"是String类型。 第三章:如何避免 IllegalArgumentException 避免 IllegalArgumentException 的关键在于确保所有传入函数的参数都符合预期的类型和格式。我们可以利用Kotlin的静态类型系统来帮助我们进行这一工作: - 类型检查:确保所有输入的参数都是正确的类型。例如,可以使用 assert 函数在运行时验证类型: kotlin fun safeCalculateAge(birthYear: Any): Int { assert(birthYear is Int) { "Expected an Integer for birthYear" } val currentYear = 2023 return currentYear - birthYear.toInt() } // 使用示例: val age = safeCalculateAge(2000) println("Your age is $age.") - 函数参数验证:在定义函数时就加入类型检查逻辑: kotlin fun calculateAgeWithValidation(birthYear: Int): Int { if (birthYear < 0 || birthYear > 2023) { throw IllegalArgumentException("Birth year must be within the range of 0 to 2023.") } val currentYear = 2023 return currentYear - birthYear } 第四章:实战演练:创建一个更复杂的示例 假设我们要构建一个简单的日历应用,其中包含一个用于计算天数的函数。为了增加复杂性,我们添加了对月份和年份的验证: kotlin data class Date(val day: Int, val month: Int, val year: Int) fun calculateDaysSinceBirthday(dateOfBirth: Date): Int { val currentYear = Calendar.getInstance().get(Calendar.YEAR) val currentMonth = Calendar.getInstance().get(Calendar.MONTH) + 1 // 注意月份是从0开始的 val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) val birthday = dateOfBirth.day to dateOfBirth.month to dateOfBirth.year val birthDate = Date(birthday) val daysSinceBirthday = (currentYear - birthDate.year) 365 + (currentMonth - birthDate.month) 30 + (currentDay - birthDate.day) return daysSinceBirthday } fun main() { val birthDate = Date(day = 1, month = 1, year = 2000) val days = calculateDaysSinceBirthday(birthDate) println("Days since your birthday: $days") } 在上面的代码中,我们通过 Calendar 类获取当前日期,并与生日日期进行比较,计算出天数差值。嘿,兄弟!咱们就拿一年有365天,一个月有30天来打个比方,这可是咱们简化了一下,方便大家理解。实际上啊,生活里头可没这么简单,得分清闰年和普通年是怎么回事,这样日子才过得有模有样呢! 结语:面对挑战,拥抱学习 每一次遇到 IllegalArgumentException 都是一次学习的机会。它们提醒我们,即使在看似完美的代码中,也可能隐藏着一些小错误。通过仔细检查和验证我们的参数,我们可以编写出更加健壮、可维护的代码。哎呀,你瞧这Kotlin,它可真是个能手呢!它那一大堆好用的工具和特性,就像是魔法一样,帮我们解决了好多麻烦事儿。比如说,静态类型这一招,就像是一道坚固的防线,能提前发现那些可能出错的地方。还有函数注解,就像是给代码贴上了标签,让我们一眼就能看出这是干啥的。而模式匹配嘛,简直就是解谜神器,轻轻松松就能解开那些复杂的逻辑难题。这些玩意儿合在一起,就形成了一个强大的武器库,帮我们防患于未然,解决问题更是不在话下。你说是不是,这Kotlin,简直就是程序员的好伙伴!让我们带着好奇心和探索精神,继续在编程的海洋中航行吧! --- 在这篇文章中,我们不仅探讨了 IllegalArgumentException 的由来和解决方法,还通过一系列的代码示例展示了如何在实践中应用这些知识。嘿,兄弟!读完这篇文章后,希望你对Kotlin里的异常处理方式有了一番全新的领悟。别担心,这不像是AI在跟你说话,就像跟老朋友聊天一样轻松。你得尝试将这些小技巧应用到你的实际项目中,让代码不仅好看,而且超级稳定,就像是给你的程序穿上了一件坚固的盔甲。这样,无论遇到什么问题,它都能稳如泰山。所以,拿起你的键盘,动手实践吧!记住,编程是一场持续的学习之旅,每一次遇到困难都是成长的机会。加油!
2024-09-18 16:04:27
112
追梦人
转载文章
...重启 – sync:数据同步写入磁盘命令(一般来说,用户写的内容先保存在一个缓冲区,系统是隔一定时间像磁盘写入缓冲区内写入磁盘),用sync立刻写入 grep ”“ -i :搜索时忽略大小写 grep 默认是匹配字符, -w 选项默认匹配一个单词 例如我想匹配 “like”, 不加 -w 就会匹配到 “liker”, 加 -w 就不会匹配到 du 目录/文件 -sh : 查看某一文件/目录的大小,也可以到一个目录下du -sh,查看这个目录的大小 目录下使用du -sh 查看目录总的大小 du 文件名 -sh 查看指定文件的大小 df:检查linux服务器的文件系统磁盘空间占用情况,默认以kb为单位 gedit 文件:使用gedit软件打开一个文件(类似于windows下面的记事本) ps:查看您当前系统有哪些进程,ubuntu(多用户)下是ps -aux,嵌入式linux(单用户)下面是ps top:进程实时运行状态查询 file 文件名:查看文件类型 ubuntu的fs cd / :根目录,一切都是从根目录发散开来的 /bin:存放二进制可执行文件,比如一些命令 /boot:ubuntu的内核与启动文件 /cdrom:有光盘是存放光盘文件 /dev:存放设备驱动文件 /etc:存放配置文件,如账号和密码文件(加密后的) /home:系统默认的用户主文件夹 /lib:存放库文件 /lib64:存放库文件,. so时linux下面的动态库文件 /media:存放可插拔设备,如sd,u盘就是挂载到这个文件下面 /mnt:用户可使用的挂载点,和media类似,可以手动让可插拔设备挂载到/mnt /opt:可选的文件和程序存放目录,给第三方软件放置的目录 /proc:存放系统的运行信息,实在内存上的不是在flash上,如cat /proc/cpuinfo /root:系统管理员目录,root用户才能访问的文件 /sbin:和bin类似,存放一些二进制可执行文件,sbin下面一般是系统开机过程中所需要的命令 /srv:服务相关的目录,如网络服务 /sys:记录内核信息,是虚拟文件系统 /tmp:临时目录 /usr:不是user的缩写,而是UNIX Software Resource的缩写,存放系统用户有关的文件,占很大空间 /var:存放变化的文件,如日志文件 – 移植就是移植上面这些文件 磁盘管理 linux开发一定要选用FAT32格式的U盘或者SD卡 u盘在/dev中的名字是sd,要确定是哪个,拔了看少了哪个。就是哪个 /dev/sdb表示U盘,/dev/sdb1表示U盘的第一个分区,一般U盘 sd卡只有一个分区 df:显示linux系统的磁盘占用情况 在一个目录里使用du -sh:查看这个目录里面所有内容所占用的资源 du 文件名 -sh:一般用来看单个文件/目录的大小 du -h --max-depth=n:显示n级目录的大小 – 磁盘的挂载与取消挂载: mount 和 umount sudo mount /dev/sdb1 /media/jl/udisk sudo umount /media/jl/u盘名 (-f 强制取消挂载),如果u盘正在使用,如被另一个终端打开,那么该指令无效 mount挂载后中文显示乱码的解决方法 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
79
转载
转载文章
...应内容。 1、时间与日期 ECMAScript 提供了 Date 类型来处理时间和日期。Date 类型内置一系列获取和设置日期时间信息的方法。 Date 类型 ECMAScript 中的 Date 类型是在早期 Java 中 java.util.Date 类基础上构建的。为此,Date 类型使用 UTC(Coordinated Universal Time,国际协调时间[又称世界统一时间])1970 年 1 月 1 日午夜(零时)开始经过的毫秒来保存日期。在使用这种数据存储格式的条件下,Date 类型保存的日期能够精确到 1970 年 1 月 1 日之前或之后的 285616 年。 创建一个日期对象,使用 new 运算符和 Date 构造方法(构造函数)即可。 var box = new Date(); // 创建一个日期对象 在调用 Date 构造方法而不传递参数的情况下,新建的对象自动获取当前的时间和日期。 alert(box); // 不同浏览器显示不同 ECMAScript 提供了两个方法,Date.parse()和 Date.UTC()。Date.parse()方法接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应的毫秒数。ECMA-262 没有定义 Date.parse()应该支持哪种日期格式,因此方法的行为因实现而异,因地区而异。默认通常接收的日期格式如下: ‘月/日/年’,如 6/13/2011; ‘英文月名 日, 年’,如 May 25, 2004; ‘英文星期几 英文月名 日 年 时:分:秒 时区’,如 Tue May 25 2004 00:00:00 GMT-070 alert(Date.parse('6/13/2011')); // 1307894400000 如果 Date.parse()没有传入或者不是标准的日期格式,那么就会返回 NaN。 alert(Date.parse()); // NaN 如果想输出指定的日期,那么把 Date.parse()传入 Date 构造方法里。 var box = new Date(Date.parse('6/13/2011')); // Mon Jun 13 2011 00:00:00 GMT+0800var box = new Date('6/13/2011'); // 直接传入,Date.parse()后台被调用 Date 对象及其在不同浏览器中的实现有许多奇怪的行为。其中有一种倾向是将超出的范围的值替换成当前的值,以便生成输出。例如,在解析“January 32, 2007”时,有的浏览器会将其解释为“February 1, 2007”。而 Opera 则倾向与插入当前月份的当前日期。 Date.UTC()方法同样也返回表示日期的毫秒数,但它与 Date.parse()在构建值时使用不同的信息。(年份,基于 0 的月份[0 表示 1 月,1 表示 2 月],月中的哪一天[1-31],小时数[0-23] ,分钟,秒以及毫秒)。只有前两个参数是必须的。如果没有提供月数,则天数为 1;如果省略其他参数,则统统为 0。 alert(Date.UTC(2011,11)); // 1322697600000 如果 Date.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
529
转载
转载文章
...,探索如何用熊猫准备数据,使用scikit-learn拟合和评估模型,以及更多内容。 让我们开始吧。 2016年10月更新:更新了sklearn v0.18的示例。 2018年2月更新:更新Python和库版本。 2018年3月更新:增加了备用链接以下载一些数据集,因为原始文件似乎已被删除。 2019年5月更新:修复了scikit-learn最新版本的警告消息。 Dave Young的 Python机器学习迷你课程 照片,保留一些权利。 迷你课程面向谁? 在开始之前,请确保您在正确的位置。 下面的列表提供了有关本课程针对谁的一些一般指导。 如果您没有完全匹配这些点,请不要惊慌,您可能只需要在一个或另一个区域刷牙以跟上。 知道如何编写一些代码的开发人员。这意味着,一旦您了解基本语法,就可以选择像Python这样的新编程语言,这对您来说并不重要。这并不意味着您是一名向导编码员,而是可以毫不费力地遵循基本的类似于C的语言。 懂一点机器学习的开发人员。这意味着您了解机器学习的基础知识,例如交叉验证,一些算法和偏差方差折衷。这并不意味着您是机器学习博士,而是您知道地标或知道在哪里查找。 这门迷你课程既不是Python的教科书,也不是机器学习的教科书。 从一个懂一点机器学习的开发人员到一个可以使用Python生态系统获得结果的开发人员,Python生态系统是专业机器学习的新兴平台。 在Python机器学习方面需要帮助吗? 参加我为期2周的免费电子邮件课程,发现数据准备,算法等(包括代码)。 单击立即注册,并获得该课程的免费PDF电子书版本。 立即开始免费的迷你课程! 迷你课程概述 该微型课程分为14节课。 您可以每天完成一堂课(推荐),也可以在一天内完成所有课程(核心!)。这实际上取决于您有空的时间和您的热情水平。 以下是14个课程,可帮助您入门并提高使用Python进行机器学习的效率: 第1课:下载并安装Python和SciPy生态系统。 第2课:深入了解Python,NumPy,Matplotlib和Pandas。 第3课:从CSV加载数据。 第4课:了解具有描述性统计信息的数据。 第5课:通过可视化了解数据。 第6课:通过预处理数据准备建模。 第7课:使用重采样方法进行算法评估。 第8课:算法评估指标。 第9课:现场检查算法。 第10课:模型比较和选择。 第11课:通过算法调整提高准确性。 第12课:利用集合预测提高准确性。 第13课:完成并保存模型。 第14课:Hello World端到端项目。 每节课可能需要您60秒钟或最多30分钟。花点时间按照自己的进度完成课程。提出问题,甚至在以下评论中发布结果。 这些课程希望您能开始学习并做事。我会给您提示,但每节课的重点是迫使您学习从哪里寻求有关Python平台的帮助(提示,我直接在此博客上获得了所有答案,请使用搜索特征)。 在早期课程中,我确实提供了更多帮助,因为我希望您树立一些信心和惯性。 挂在那里,不要放弃! 第1课:下载并安装Python和SciPy 您必须先访问平台才能开始使用Python进行机器学习。 今天的课程很简单,您必须在计算机上下载并安装Python 3.6平台。 访问Python主页并下载适用于您的操作系统(Linux,OS X或Windows)的Python。在计算机上安装Python。您可能需要使用特定于平台的软件包管理器,例如OS X上的macports或RedHat Linux上的yum。 您还需要安装SciPy平台和scikit-learn库。我建议使用与安装Python相同的方法。 您可以使用Anaconda一次安装所有内容(更加容易)。推荐给初学者。 通过在命令行中键入“ python”来首次启动Python。 使用以下代码检查所有您需要的版本: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Python version import sys print('Python: {}'.format(sys.version)) scipy import scipy print('scipy: {}'.format(scipy.__version__)) numpy import numpy print('numpy: {}'.format(numpy.__version__)) matplotlib import matplotlib print('matplotlib: {}'.format(matplotlib.__version__)) pandas import pandas print('pandas: {}'.format(pandas.__version__)) scikit-learn import sklearn print('sklearn: {}'.format(sklearn.__version__)) 如果有任何错误,请停止。现在该修复它们了。 需要帮忙?请参阅本教程: 如何使用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
92
转载
转载文章
...的技术细节,均在协议数据包处理代码中,漏洞类型古典,分别是全局缓冲区溢出、堆溢出和空指针解引用。其中缓冲区溢出类型漏洞可方便构造PoC,实现远程任意代码执行的漏洞利用。 漏洞本身原理简单,也并不是关键。以其中一个为例,Pavel在发现时负责任地向LibVNC作者提交了issue,并跟进漏洞修复过程;在第一次修复之后,复核并指出修复代码无效,给出了有效patch。这个过程是常规操作。 漏洞疑点 有意思的是,在漏洞披露邮件中,Pavel重点谈了自己对这系列漏洞的一些周边发现,也是这里提到的原因。其中,关于存在漏洞的代码,作者表述: 我最初认为,这些问题是libvnc开发者自己代码中的错误,但看起来并非如此。其中有一些(如CoRRE数据处理函数中的堆缓冲区溢出),出现在AT&T实验室1999年的代码中,而后被很多软件开发者原样复制(在Github上搜索一下HandleCoRREBPP函数,你就知道),LibVNC和TightVNC也是如此。 为了证实,翻阅了这部分代码,确实在其中数据处理相关代码文件看到了剑桥和AT&T实验室的文件头GPL声明注释,中国菜刀 这证实这些文件是直接从最初剑桥实验室版本VNC移植过来的,且使用方式是 直接代码包含,而非独立库引用方式。在官方开源发布并停止更新后,LibVNC使用的这部分代码基本没有改动——除了少数变量命名方式的统一,以及本次漏洞修复。通过搜索,我找到了2000年发布的相关代码文件,确认这些文件与LibVNC中引入的原始版本一致。 另外,Pavel同时反馈了TightVNC中相同的问题。TightVNC与LibVNC没有继承和直接引用关系,但上述VNC代码同样被TightVNC使用,问题的模式不约而同。Pavel测试发现在Ubuntu最新版本TightVNC套件(1.3.10版本)中同样存在该问题,上报给当前软件所有者GlavSoft公司,但对方声称目前精力放在不受GPL限制的TightVNC 2.x版本开发中,对开源的1.x版本漏洞代码“可能会进行修复”。看起来,这个问题被踢给了各大Linux发行版社区来焦虑了——如果他们愿意接锅。 问题思考 在披露邮件中,Pavel认为,这些代码bug“如此明显,让人无法相信之前没被人发现过……也许是因为某些特殊理由才始终没得到修复”。 事实上,我们都知道目前存在一些对开源基础软件进行安全扫描的大型项目,例如Google的OSS;同时,仍然存活的开源项目也越来越注重自身代码发布前的安全扫描,Fortify、Coverity的扫描也成为很多项目和平台的标配。在这样一些眼睛注视下,为什么还有这样的问题?我认为就这个具体事例来说,可能有如下两个因素: ·上游已死。仍然在被维护的代码,存在版本更迭,也存在外界的持续关注、漏洞报告和修复、开发的迭代,对于负责人的开发者,持续跟进、评估、同步代码的改动是可能的。但是一旦一份代码走完了生命周期,就像一段史实一样会很少再被改动。 ·对第三方上游代码的无条件信任。我们很多人都有过基础组件、中间件的开发经历,不乏有人使用Coverity开启全部规则进行代码扫描、严格修复所有提示的问题甚至编程规范warning;报告往往很长,其中也包括有源码形式包含的第三方代码中的问题。但是,我们一方面倾向于认为这些被广泛使用的代码不应存在问题(不然早就被人挖过了),一方面考虑这些引用的代码往往是组件或库的形式被使用,应该有其上下文才能认定是否确实有可被利用的漏洞条件,现在单独扫描这部分代码一般出来的都是误报。所以这些代码的问题都容易被忽视。 但是透过这个具体例子,再延伸思考相关的实践,这里最根本的问题可以总结为一个模式: 复制粘贴风险。复制粘贴并不简单意味着剽窃,实际是当前软件领域、互联网行业发展的基础模式,但其中有一些没人能尝试解决的问题: ·在传统代码领域,如C代码中,对第三方代码功能的复用依赖,往往通过直接进行库的引入实现,第三方代码独立而完整,也较容易进行整体更新;这是最简单的情况,只需要所有下游使用者保证仅使用官方版本,跟进官方更新即可;但在实践中很难如此贯彻,这是下节讨论的问题。 ·有些第三方发布的代码,模式就是需要被源码形式包含到其他项目中进行统一编译使用(例如腾讯的开源Json解析库RapidJSON,就是纯C++头文件形式)。在开源领域有如GPL等规约对此进行规范,下游开发者遵循协议,引用代码,强制或可选地显式保留其GPL声明,可以进行使用和更改。这样的源码依赖关系,结合规范化的changelog声明代码改动,侧面也是为开发过程中跟进考虑。但是一个成型的产品,比如企业自有的服务端底层产品、中间件,新版本的发版更新是复杂的过程,开发者在旧版本仍然“功能正常”的情况下往往倾向于不跟进新版本;而上游代码如果进行安全漏洞修复,通常也都只在其最新版本代码中改动,安全修复与功能迭代并存,如果没有类似Linux发行版社区的努力,旧版本代码完全没有干净的安全更新patch可用。 ·在特定场景下,有些开发实践可能不严格遵循开源代码协议限定,引入了GPL等协议保护的代码而不做声明(以规避相关责任),丢失了引入和版本的信息跟踪;在另一些场景下,可能存在对开源代码进行大刀阔斧的修改、剪裁、定制,以符合自身业务的极端需求,但是过多的修改、人员的迭代造成与官方代码严重的失同步,丧失可维护性。 ·更一般的情况是,在开发中,开发者个体往往心照不宣的存在对网上代码文件、代码片段的复制-粘贴操作。被参考的代码,可能有上述的开源代码,也可能有各种Github作者练手项目、技术博客分享的代码片段、正式开源项目仅用来说明用法的不完备示例代码。这些代码的引入完全无迹可寻,即便是作者自己也很难解释用了什么。这种情况下,上面两条认定的那些与官方安全更新失同步的问题同样存在,且引入了独特的风险:被借鉴的代码可能只是原作者随手写的、仅仅是功能成立的片段,甚至可能是恶意作者随意散布的有安全问题的代码。由此,问题进入了最大的发散空间。 在Synopsys下BLACKDUCK软件之前发布的《2018 Open Source Security and Risk Analysis Report》中分析,96%的应用中包含有开源组件和代码,开源代码在应用全部代码中的占比约为57%,78%的应用中在引用的三方开源代码中存在历史漏洞。也就是说,现在互联网上所有厂商开发的软件、应用,其开发人员自己写的代码都是一少部分,多数都是借鉴来的。而这还只是可统计、可追溯的;至于上面提到的非规范的代码引用,如果也纳入进来考虑,三方代码占应用中的比例会上升到多少?曾经有分析认为至少占80%,我们只期望不会更高。 Ⅱ. 从碎片到乱刃:OpenSSH在野后门一览 在进行基础软件梳理时,回忆到反病毒安全软件提供商ESET在2018年十月发布的一份白皮书《THE DARK SIDE OF THE FORSSHE: A landscape of OpenSSH backdoors》。其站在一个具有广泛用户基础的软件提供商角度,给出了一份分析报告,数据和结论超出我们对于当前基础软件使用全景的估量。以下以我的角度对其中一方面进行解读。 一些必要背景 SSH的作用和重要性无需赘言;虽然我们站在传统互联网公司角度,可以认为SSH是通往生产服务器的生命通道,但当前多样化的产业环境已经不止于此(如之前libssh事件中,不幸被我言中的,SSH在网络设备、IoT设备上(如f5)的广泛使用)。 OpenSSH是目前绝大多数SSH服务端的基础软件,有完备的开发团队、发布规范、维护机制,本身是靠谱的。如同绝大多数基础软件开源项目的做法,OpenSSH对漏洞有及时的响应,针对最新版本代码发出安全补丁,但是各大Linux发行版使用的有各种版本的OpenSSH,这些社区自行负责将官方开发者的安全补丁移植到自己系统搭载的低版本代码上。天空彩 白皮书披露的现状 如果你是一个企业的运维管理人员,需要向企业生产服务器安装OpenSSH或者其它基础软件,最简单的方式当然是使用系统的软件管理安装即可。但是有时候,出于迁移成本考虑,可能企业需要在一个旧版本系统上,使用较新版本的OpenSSL、OpenSSH等基础软件,这些系统不提供,需要自行安装;或者需要一个某有种特殊特性的定制版本。这时,可能会选择从某些rpm包集中站下载某些不具名第三方提供的现成的安装包,或者下载非官方的定制化源码本地编译后安装,总之从这里引入了不确定性。 这种不确定性有多大?我们粗估一下,似乎不应成为问题。但这份白皮书给我们看到了鲜活的数据。 ESET研究人员从OpenSSH的一次历史大规模Linux服务端恶意软件Windigo中获得启示,采用某种巧妙的方式,面向在野的服务器进行数据采集,主要是系统与版本、安装的OpenSSH版本信息以及服务端程序文件的一个特殊签名。整理一个签名白名单,包含有所有能搜索到的官方发布二进制版本、各大Linux发行版本各个版本所带的程序文件版本,将这些标定为正常样本进行去除。最终结论是: ·共发现了几百个非白名单版本的OpenSSH服务端程序文件ssh和sshd; ·分析这些样本,将代码部分完全相同,仅仅是数据和配置不同的合并为一类,且分析判定确认有恶意代码的,共归纳为 21个各异的恶意OpenSSH家族; ·在21个恶意家族中,有12个家族在10月份时完全没有被公开发现分析过;而剩余的有一部分使用了历史上披露的恶意代码样本,甚至有源代码; ·所有恶意样本的实现,从实现复杂度、代码混淆和自我保护程度到代码特征有很大跨度的不同,但整体看,目的以偷取用户凭证等敏感信息、回连外传到攻击者为主,其中有的攻击者回连地址已经存在并活跃数年之久; ·这些后门的操控者,既有传统恶意软件黑产人员,也有APT组织; ·所有恶意软件或多或少都在被害主机上有未抹除的痕迹。ESET研究者尝试使用蜜罐引诱出攻击者,但仍有许多未解之谜。这场对抗,仍未取胜。 白皮书用了大篇幅做技术分析报告,此处供细节分析,不展开分析,以下为根据恶意程序复杂度描绘的21个家族图谱: 问题思考 问题引入的可能渠道,我在开头进行了一点推测,主要是由人的原因切入的,除此以外,最可能的是恶意攻击者在利用各种方法入侵目标主机后,主动替换了目标OpenSSH为恶意版本,从而达成攻击持久化操作。但是这些都是止血的安全运维人员该考虑的事情;关键问题是,透过表象,这显露了什么威胁形式? 这个问题很好回答,之前也曾经反复说过:基础软件碎片化。 如上一章节简单提到,在开发过程中有各种可能的渠道引入开发者不完全了解和信任的代码;在运维过程中也是如此。二者互相作用,造成了软件碎片化的庞杂现状。在企业内部,同一份基础软件库,可能不同的业务线各自定制一份,放到企业私有软件仓库源中,有些会有人持续更新供自己产品使用,有些由系统软件基础设施维护人员单独维护,有些则可能是开发人员临时想起来上传的,他们自己都不记得;后续用到的这个基础软件的开发和团队,在这个源上搜索到已有的库,很大概率会倾向于直接使用,不管来源、是否有质量背书等。长此以往问题会持续发酵。而我们开最坏的脑洞,是否可能有黑产人员入职到内部,提交个恶意基础库之后就走人的可能?现行企业安全开发流程中审核机制的普遍缺失给这留下了空位。 将源码来源碎片化与二进制使用碎片化并起来考虑,我们不难看到一个远远超过OpenSSH事件威胁程度的图景。但这个问题不是仅仅靠开发阶段规约、运维阶段规范、企业内部管控、行业自查、政府监管就可以根除的,最大的问题归根结底两句话: 不可能用一场战役对抗持续威胁;不可能用有限分析对抗无限未知。 Ⅲ. 从自信到自省:RHEL、CentOS backport版本BIND漏洞 2018年12月20日凌晨,在备战冬至的软件供应链安全大赛决赛时,我注意到漏洞预警平台捕获的一封邮件。但这不是一个漏洞初始披露邮件,而是对一个稍早已披露的BIND在RedHat、CentOS发行版上特定版本的1day漏洞CVE-2018-5742,由BIND的官方开发者进行额外信息澄(shuǎi)清(guō)的邮件。 一些必要背景 关于BIND 互联网的一个古老而基础的设施是DNS,这个概念在读者不应陌生。而BIND“是现今互联网上最常使用的DNS软件,使用BIND作为服务器软件的DNS服务器约占所有DNS服务器的九成。BIND现在由互联网系统协会负责开发与维护参考。”所以BIND的基础地位即是如此,因此也一向被大量白帽黑帽反复测试、挖掘漏洞,其开发者大概也一直处在紧绷着应对的处境。 关于ISC和RedHat 说到开发者,上面提到BIND的官方开发者是互联网系统协会(ISC)。ISC是一个老牌非营利组织,目前主要就是BIND和DHCP基础设施的维护者。而BIND本身如同大多数历史悠久的互联网基础开源软件,是4个UCB在校生在DARPA资助下于1984年的实验室产物,直到2012年由ISC接管。 那么RedHat在此中是什么角色呢?这又要提到我之前提到的Linux发行版和自带软件维护策略。Red Hat Enterprise Linux(RHEL)及其社区版CentOS秉持着稳健的软件策略,每个大的发行版本的软件仓库,都只选用最必要且质量久经时间考验的软件版本,哪怕那些版本实在是老掉牙。这不是一种过分的保守,事实证明这种策略往往给RedHat用户在最新漏洞面前提供了保障——代码总是跑得越少,潜在漏洞越多。 但是这有两个关键问题。一方面,如果开源基础软件被发现一例有历史沿革的代码漏洞,那么官方开发者基本都只为其最新代码负责,在当前代码上推出修复补丁。另一方面,互联网基础设施虽然不像其上的应用那样爆发性迭代,但依然持续有一些新特性涌现,其中一些是必不可少的,但同样只在最新代码中提供。两个刚需推动下,各Linux发行版对长期支持版本系统的软件都采用一致的策略,即保持其基础软件在一个固定的版本,但对于这些版本软件的最新漏洞、必要的最新软件特性,由发行版维护者将官方开发者最新代码改动“向后移植”到旧版本代码中,即backport。这就是基础软件的“官宣”碎片化的源头。 讲道理,Linux发行版维护者与社区具有比较靠谱的开发能力和监督机制,backport又基本就是一些复制粘贴工作,应当是很稳当的……但真是如此吗? CVE-2018-5742漏洞概况 CVE-2018-5742是一个简单的缓冲区溢出类型漏洞,官方评定其漏洞等级moderate,认为危害不大,漏洞修复不积极,披露信息不多,也没有积极给出代码修复patch和新版本rpm包。因为该漏洞仅在设置DEBUG_LEVEL为10以上才会触发,由远程攻击者构造畸形请求造成BIND服务崩溃,在正常的生产环境几乎不可能具有危害,RedHat官方也只是给出了用户自查建议。 这个漏洞只出现在RHEL和CentOS版本7中搭载的BIND 9.9.4-65及之后版本。RedHat同ISC的声明中都证实,这个漏洞的引入原因,是RedHat在尝试将BIND 9.11版本2016年新增的NTA机制向后移植到RedHat 7系中固定搭载的BIND 9.9版本代码时,偶然的代码错误。NTA是DNS安全扩展(DNSSEC)中,用于在特定域关闭DNSSEC校验以避免不必要的校验失败的机制;但这个漏洞不需要对NTA本身有进一步了解。 漏洞具体分析 官方没有给出具体分析,但根据CentOS社区里先前有用户反馈的bug,我得以很容易还原漏洞链路并定位到根本原因。 若干用户共同反馈,其使用的BIND 9.9.4-RedHat-9.9.4-72.el7发生崩溃(coredump),并给出如下的崩溃时调用栈backtrace: 这个调用过程的逻辑为,在9 dns_message_logfmtpacket函数判断当前软件设置是否DEBUG_LEVEL大于10,若是,对用户请求数据包做日志记录,先后调用8 dns_message_totext、7 dns_message_sectiontotext、6 dns_master_rdatasettotext、5 rdataset_totext将请求进行按协议分解分段后写出。 由以上关键调用环节,联动RedHat在9.9.4版本BIND源码包中关于引入NTA特性的源码patch,进行代码分析,很快定位到问题产生的位置,在上述backtrace中的5,masterdump.c文件rdataset_totext函数。漏洞相关代码片段中,RedHat进行backport后,这里引入的代码为: 这里判断对于请求中的注释类型数据,直接通过isc_buffer_putstr宏对缓存进行操作,在BIND工程中自定义维护的缓冲区结构对象target上,附加一字节字符串(一个分号)。而漏洞就是由此产生:isc_buffer_putstr中不做缓冲区边界检查保证,这里在缓冲区已满情况下将造成off-by-one溢出,并触发了缓冲区实现代码中的assertion。 而ISC上游官方版本的代码在这里是怎么写的呢?找到ISC版本BIND 9.11代码,这里是这样的: 这里可以看到,官方代码在做同样的“附加一个分号”这个操作时,审慎的使用了做缓冲区剩余空间校验的str_totext函数,并额外做返回值成功校验。而上述提到的str_totext函数与RETERR宏,在移植版本的masterdump.c中,RedHat开发者也都做了保留。但是,查看代码上下文发现,在RedHat开发者进行代码移植过程中,对官方代码进行了功能上的若干剪裁,包括一些细分数据类型记录的支持;而这里对缓冲区写入一字节,也许开发者完全没想到溢出的可能,所以自作主张地简化了代码调用过程。 问题思考 这个漏洞本身几乎没什么危害,但是背后足以引起思考。 没有人在“借”别人代码时能不出错 不同于之前章节提到的那种场景——将代码文件或片段复制到自己类似的代码上下文借用——backport作为一种官方且成熟的做法,借用的代码来源、粘贴到的代码上下文,是具有同源属性的,而且开发者一般是追求稳定性优先的社区开发人员,似乎质量应该有足够保障。但是这里的关键问题是:代码总要有一手、充分的语义理解,才能有可信的使用保障;因此,只要是处理他人的代码,因为不够理解而错误使用的风险,只可能减小,没办法消除。 如上分析,本次漏洞的产生看似只是做代码移植的开发者“自作主张”之下“改错了”。但是更广泛且可能的情况是,原始开发者在版本迭代中引入或更新大量基础数据结构、API的定义,并用在新的特性实现代码中;而后向移植开发人员仅需要最小规模的功能代码,所以会对增量代码进行一定规模的修改、剪裁、还原,以此适应旧版本基本代码。这些过程同样伴随着第三方开发人员不可避免的“望文生义”,以及随之而来的风险。后向移植操作也同样助长了软件碎片化过程,其中每一个碎片都存在这样的问题;每一个碎片在自身生命周期也将有持续性影响。 多级复制粘贴无异于雪上加霜 这里简单探讨的是企业通行的系统和基础软件建设实践。一些国内外厂商和社区发布的定制化Linux发行版,本身是有其它发行版,如CentOS特定版本渊源的,在基础软件上即便同其上游发行版最新版本间也存在断层滞后。RedHat相对于基础软件开发者之间已经隔了一层backport,而我们则人为制造了二级风险。 在很多基础而关键的软件上,企业系统基础设施的维护者出于与RedHat类似的初衷,往往会决定自行backport一份拷贝;通过早年心脏滴血事件的洗礼,即暴露出来OpenSSL一个例子。无论是需要RHEL还没来得及移植的新版本功能特性,还是出于对特殊使用上下文场景中更高执行效率的追求,企业都可能自行对RHEL上基础软件源码包进行修改定制重打包。这个过程除了将风险幂次放大外,也进一步加深了代码的不可解释性(包括基础软件开发人员流动性带来的不可解释)。 Ⅳ. 从武功到死穴:从systemd-journald信息泄露一窥API误用 1月10日凌晨两点,漏洞预警平台爬收取一封漏洞披露邮件。披露者是Qualys,那就铁定是重型发布了。最后看披露漏洞的目标,systemd?这就非常有意思了。 一些必要背景 systemd是什么,不好简单回答。Linux上面软件命名,习惯以某软件名后带个‘d’表示后台守护管理程序;所以systemd就可以说是整个系统的看守吧。而即便现在描述了systemd是什么,可能也很快会落伍,因为其初始及核心开发者Lennart Poettering(供职于Red Hat)描述它是“永无开发完结完整、始终跟进技术进展的、统一所有发行版无止境的差异”的一种底层软件。笼统讲有三个作用:中央化系统及设置管理;其它软件开发的基础框架;应用程序和系统内核之间的胶水。如今几乎所有Linux发行版已经默认提供systemd,包括RHEL/CentOS 7及后续版本。总之很基础、很底层、很重要就对了。systemd本体是个主要实现init系统的框架,但还有若干关键组件完成其它工作;这次被爆漏洞的是其journald组件,是负责系统事件日志记录的看守程序。 额外地还想简单提一句Qualys这个公司。该公司创立于1999年,官方介绍为信息安全与云安全解决方案企业,to B的安全业务非常全面,有些也是国内企业很少有布局的方面;例如上面提到的涉及碎片化和代码移植过程的历史漏洞移动,也在其漏洞管理解决方案中有所体现。但是我们对这家公司粗浅的了解来源于其安全研究团队近几年的发声,这两年间发布过的,包括有『stack clash』、『sudo get_tty_name提权』、『OpenSSH信息泄露与堆溢出』、『GHOST:glibc gethostbyname缓冲区溢出』等大新闻(仅截至2017年年中)。从中可见,这个研究团队专门啃硬骨头,而且还总能开拓出来新的啃食方式,往往爆出来一些别人没想到的新漏洞类型。从这个角度,再联想之前刷爆朋友圈的《安全研究者的自我修养》所倡导的“通过看历史漏洞、看别人的最新成果去举一反三”的理念,可见差距。 CVE-2018-16866漏洞详情 这次漏洞披露,打包了三个漏洞: ·16864和16865是内存破坏类型 ·16866是信息泄露 ·而16865和16866两个漏洞组和利用可以拿到root shell。 漏洞分析已经在披露中写的很详细了,这里不复述;而针对16866的漏洞成因来龙去脉,Qualys跟踪的结果留下了一点想象和反思空间,我们来看一下。 漏洞相关代码片段是这样的(漏洞修复前): 读者可以先肉眼过一遍这段代码有什么问题。实际上我一开始也没看出来,向下读才恍然大悟。 这段代码中,外部信息输入通过buf传入做记录处理。输入数据一般包含有空白字符间隔,需要分隔开逐个记录,有效的分隔符包括空格、制表符、回车、换行,代码中将其写入常量字符串;在逐字符扫描输入数据字符串时,将当前字符使用strchr在上述间隔符字符串中检索是否匹配,以此判断是否为间隔符;在240行,通过这样的判断,跳过记录单元字符串的头部连续空白字符。 但是问题在于,strchr这个极其基础的字符串处理函数,对于C字符串终止字符'\0'的处理上有个坑:'\0'也被认为是被检索字符串当中的一个有效字符。所以在240行,当当前扫描到的字符为字符串末尾的NULL时,strchr返回的是WHITESPACE常量字符串的终止位置而非NULL,这导致了越界。 看起来,这是一个典型的问题:API误用(API mis-use),只不过这个被误用的库函数有点太基础,让我忍不住想是不是还会有大量的类似漏洞……当然也反思我自己写的代码是不是也有同样情况,然而略一思考就释然了——我那么笨的代码都用for循环加if判断了:) 漏洞引入和消除历史 有意思的是,Qualys研究人员很贴心地替我做了一步漏洞成因溯源,这才是单独提这个漏洞的原因。漏洞的引入是在2015年的一个commit中: 在GitHub中,定位到上述2015年的commit信息,这里commit的备注信息为: journald: do not strip leading whitespace from messages. Keep leading whitespace for compatibility with older syslog implementations. Also useful when piping formatted output to the logger command. Keep removing trailing whitespace. OK,看起来是一个兼容性调整,对记录信息不再跳过开头所有连续空白字符,只不过用strchr的简洁写法比较突出开发者精炼的开发风格(并不),说得过去。 之后在2018年八月的一个当时尚未推正式版的另一次commit中被修复了,先是还原成了ec5ff4那次commit之前的写法,然后改成了加校验的方式: 虽然Qualys研究者认为上述的修改是“无心插柳”的改动,但是在GitHub可以看到,a6aadf这次commit是因为有外部用户反馈了输入数据为单个冒号情况下journald堆溢出崩溃的issue,才由开发者有目的性地修复的;而之后在859510这个commit再次改动回来,理由是待记录的消息都是使用单个空格作为间隔符的,而上一个commit粗暴地去掉了这种协议兼容性特性。 如果没有以上纠结的修改和改回历史,也许我会倾向于怀疑,在最开始漏洞引入的那个commit,既然改动代码没有新增功能特性、没有解决什么问题(毕竟其后三年,这个改动的代码也没有被反映issue),也并非出于代码规范等考虑,那么这么轻描淡写的一次提交,难免有人为蓄意引入漏洞的嫌疑。当然,看到几次修复的原因,这种可能性就不大了,虽然大家仍可以保留意见。但是抛开是否人为这个因素,单纯从代码的漏洞成因看,一个传统但躲不开的问题仍值得探讨:API误用。 API误用:程序员何苦为难程序员 如果之前的章节给读者留下了我反对代码模块化和复用的印象,那么这里需要正名一下,我们认可这是当下开发实践不可避免的趋势,也增进了社会开发速度。而API的设计决定了写代码和用代码的双方“舒适度”的问题,由此而来的API误用问题,也是一直被当做单纯的软件工程课题讨论。在此方面个人并没有什么研究,自然也没办法系统地给出分类和学术方案,只是谈一下自己的经验和想法。 一篇比较新的学术文章总结了API误用的研究,其中一个独立章节专门分析Java密码学组件API误用的实际,当中引述之前论文认为,密码学API是非常容易被误用的,比如对期望输入数据(数据类型,数据来源,编码形式)要求的混淆,API的必需调用次序和依赖缺失(比如缺少或冗余多次调用了初始化函数、主动资源回收函数)等。凑巧在此方面我有一点体会:曾经因为业务方需要,需要使用C++对一个Java的密码基础中间件做移植。Java对密码学组件支持,有原生的JDK模块和权威的BouncyCastle包可用;而C/C++只能使用第三方库,考虑到系统平台最大兼容和最小代码量,使用Linux平台默认自带的OpenSSL的密码套件。但在开发过程中感受到了OpenSSL满满的恶意:其中的API设计不可谓不反人类,很多参数没有明确的说明(比如同样是表示长度的函数参数,可能在不同地方分别以字节/比特/分组数为计数单位);函数的线程安全没有任何解释标注,需要自行试验;不清楚函数执行之后,是其自行做了资源释放还是需要有另外API做gc,不知道资源释放操作时是否规规矩矩地先擦除后释放……此类问题不一而足,导致经过了漫长的测试之后,这份中间件才提供出来供使用。而在业务场景中,还会存在比如其它语言调用的情形,这些又暴露出来OpenSSL API误用的一些完全无从参考的问题。这一切都成为了噩梦;当然这无法为我自己开解是个不称职开发的指责,但仅就OpenSSL而言其API设计之恶劣也是始终被人诟病的问题,也是之后其他替代者宣称改进的地方。 当然,问题是上下游都脱不了干系的。我们自己作为高速迭代中的开发人员,对于二方、三方提供的中间件、API,又有多少人能自信地说自己仔细、认真地阅读过开发指南和API、规范说明呢?做过通用产品技术运营的朋友可能很容易理解,自己产品的直接用户日常抛出不看文档的愚蠢问题带来的困扰。对于密码学套件,这个问题还好办一些,毕竟如果在没有背景知识的情况下对API望文生义地一通调用,绝大多数情况下都会以抛异常形式告终;但还是有很多情况,API误用埋下的是长期隐患。 不是所有API误用情形最终都有机会发展成为可利用的安全漏洞,但作为一个由人的因素引入的风险,这将长期存在并困扰软件供应链(虽然对安全研究者、黑客与白帽子是很欣慰的事情)。可惜,传统的白盒代码扫描能力,基于对代码语义的理解和构建,但是涉及到API则需要预先的抽象,这一点目前似乎仍然是需要人工干预的事情;或者轻量级一点的方案,可以case by case地分析,为所有可能被误用的API建模并单独扫描,这自然也有很强局限性。在一个很底层可信的开发者还对C标准库API存在误用的现实内,我们需要更多的思考才能说接下来的解法。 Ⅴ. 从规则到陷阱:NASA JIRA误配置致信息泄露血案 软件的定义包括了代码组成的程序,以及相关的配置、文档等。当我们说软件的漏洞、风险时,往往只聚焦在其中的代码中;关于软件供应链安全风险,我们的比赛、前面分析的例子也都聚焦在了代码的问题;但是真正的威胁都来源于不可思议之处,那么代码之外有没有可能存在来源于上游的威胁呢?这里就借助实例来探讨一下,在“配置”当中可能栽倒的坑。 引子:发不到500英里以外的邮件? 让我们先从一个轻松愉快的小例子引入。这个例子初见于Linux中国的一篇译文。 简单说,作者描述了这么一个让人啼笑皆非的问题:单位的邮件服务器发送邮件,发送目标距离本地500英里范围之外的一律失败,邮件就像悠悠球一样只能飞出一定距离。这个问题本身让描述者感到尴尬,就像一个技术人员被老板问到“为什么从家里笔记本上Ctrl-C后不能在公司台式机上Ctrl-V”一样。 经过令人窒息的分析操作后,笔者定位到了问题原因:笔者作为负责的系统管理员,把SunOS默认安装的Senmail从老旧的版本5升级到了成熟的版本8,且对应于新版本诸多的新特性进行了对应配置,写入配置文件sendmail.cf;但第三方服务顾问在对单位系统进行打补丁升级维护时,将系统软件“升级”到了系统提供的最新版本,因此将Sendmail实际回退到了版本5,却为了软件行为一致性,原样保留了高版本使用的配置文件。但Sendmail并没有在大版本间保证配置文件兼容性,这导致很多版本5所需的配置项不存在于保留下来的sendmail.cf文件中,程序按默认值0处理;最终引起问题的就是,邮件服务器与接收端通信的超时时间配置项,当取默认配置值0时,邮件服务器在1个单位时间(约3毫秒)内没有收到网络回包即认为超时,而这3毫秒仅够电信号打来回飞出500英里。 这个“故事”可能会给技术人员一点警醒,错误的配置会导致预期之外的软件行为,但是配置如何会引入软件供应链方向的安全风险呢?这就引出了下一个重磅实例。 JIRA配置错误致NASA敏感信息泄露案例 我们都听过一个事情,马云在带队考察美国公司期间问Google CEO Larry Page自视谁为竞争对手,Larry的回答是NASA,因为最优秀的工程师都被NASA的梦想吸引过去了。由此我们显然能窥见NASA的技术水位之高,这样的人才团队大概至少是不会犯什么低级错误的。 但也许需要重新定义“低级错误”……1月11日一篇技术文章披露,NASA某官网部署使用的缺陷跟踪管理系统JIRA存在错误的配置,可分别泄漏内部员工(JIRA系统用户)的全部用户名和邮件地址,以及内部项目和团队名称到公众,如下: 问题的原因解释起来也非常简单:JIRA系统的过滤器和配置面板中,对于数据可见性的配置选项分别选定为All users和Everyone时,系统管理人员想当然地认为这意味着将数据对所有“系统用户”开放查看,但是JIRA的这两个选项的真实效果逆天,是面向“任意人”开放,即不限于系统登录用户,而是任何查看页面的人员。看到这里,我不厚道地笑了……“All users”并不意味着“All ‘users’”,意不意外,惊不惊喜? 但是这种字面上把戏,为什么没有引起NASA工程师的注意呢,难道这样逆天的配置项没有在产品手册文档中加粗标红提示吗?本着为JIRA产品设计找回尊严的态度,我深入挖掘了一下官方说明,果然在Atlassian官方的一份confluence文档(看起来更像是一份增补的FAQ)中找到了相关说明: 所有未登录访客访问时,系统默认认定他们是匿名anonymous用户,所以各种权限配置中的all users或anyone显然应该将匿名用户包括在内。在7.2及之后版本中,则提供了“所有登录用户”的选项。 可以说是非常严谨且贴心了。比较讽刺的是,在我们的软件供应链安全大赛·C源代码赛季期间,我们设计圈定的恶意代码攻击目标还包括JIRA相关的敏感信息的窃取,但是却想不到有这么简单方便的方式,不动一行代码就可以从JIRA中偷走数据。 软件的使用,你“配”吗? 无论是开放的代码还是成型的产品,我们在使用外部软件的时候,都是处于软件供应链下游的消费者角色,为了要充分理解上游开发和产品的真实细节意图,需要我们付出多大的努力才够“资格”? 上一章节我们讨论过源码使用中必要细节信息缺失造成的“API误用”问题,而软件配置上的“误用”问题则复杂多样得多。从可控程度上讨论,至少有这几种因素定义了这个问题: ·软件用户对必要配置的现有文档缺少了解。这是最简单的场景,但又是完全不可避免的,这一点上我们所有有开发、产品或运营角色经验的应该都曾经体会过向不管不顾用户答疑的痛苦,而所有软件使用者也可以反省一下对所有软件的使用是否都以完整细致的文档阅读作为上手的准备工作,所以不必多说。 ·软件拥有者对配置条目缺少必要明确说明文档。就JIRA的例子而言,将NASA工程师归为上一条错误有些冤枉,而将JIRA归为这条更加合适。在边角但重要问题上的说明通过社区而非官方文档形式发布是一种不负责任的做法,但未引发安全事件的情况下还有多少这样的问题被默默隐藏呢?我们没办法要求在使用软件之前所有用户将软件相关所有文档、社区问答实现全部覆盖。这个问题范围内一个代表性例子是对配置项的默认值以及对应效果的说明缺失。 ·配置文件版本兼容性带来的误配置和安全问题。实际上,上面的SunOS Sendmail案例足以点出这个问题的存在性,但是在真实场景下,很可能不会以这么戏剧性形式出现。在企业的系统运维中,系统的版本迭代常见,但为软件行为一致性,配置的跨版本迁移是不可避免的操作;而且软件的更新迭代也不只会由系统更新推动,还有大量出于业务性能要求而主动进行的定制化升级,对于中小企业基础设施建设似乎是一个没怎么被提及过的问题。 ·配置项组合冲突问题。尽管对于单个配置项可能明确行为与影响,但是特定的配置项搭配可能造成不可预知的效果。这完全有可能是由于开发者与用户在信息不对等的情况下产生:开发者认为用户应该具有必需的背景知识,做了用户应当具备规避配置冲突能力的假设。一个例子是,对称密码算法在使用ECB、CBC分组工作模式时,从密码算法上要求输入数据长度必须是分组大小的整倍数,但如果用户搭配配置了秘钥对数据不做补齐(nopadding),则引入了非确定性行为:如果密码算法库对这种组合配置按某种默认补齐方式操作数据则会引起歧义,但如果在算法库代码层面对这种组合抛出错误则直接影响业务。 ·程序对配置项处理过程的潜在暗箱操作。这区别于简单的未文档化配置项行为,仅特指可能存在的蓄意、恶意行为。从某种意义上,上述“All users”也可以认为是这样的一种陷阱,通过浅层次暗示,引导用户做出错误且可能引起问题的配置。另一种情况是特定配置组合情况下触发恶意代码的行为,这种触发条件将使恶意代码具有规避检测的能力,且在用户基数上具有一定概率的用户命中率。当然这种情况由官方开发者直接引入的可能性很低,但是在众包开发的情况下如果存在,那么扫描方案是很难检测的。 Ⅵ. 从逆流到暗流:恶意代码溯源后的挑战 如果说前面所说的种种威胁都是面向关键目标和核心系统应该思考的问题,那么最后要抛出一个会把所有人拉进赛场的理由。除了前面所有那些在软件供应链下游被动污染受害的情况,还有一种情形:你有迹可循的代码,也许在不经意间会“反哺”到黑色产业链甚至特殊武器中;而现在研究用于对程序进行分析和溯源的技术,则会让你陷入百口莫辩的境地。 案例:黑产代码模块溯源疑云 1月29日,猎豹安全团队发布技术分析通报文章《电信、百度客户端源码疑遭泄漏,驱魔家族窃取隐私再起波澜》,矛头直指黑产上游的恶意信息窃取代码模块,认定其代码与两方产品存在微妙的关联:中国电信旗下“桌面3D动态天气”等多款软件,以及百度旗下“百度杀毒”等软件(已不可访问)。 文章中举证有三个关键点。 首先最直观的,是三者使用了相同的特征字符串、私有文件路径、自定义内部数据字段格式; 其次,在关键代码位置,三者在二进制程序汇编代码层面具有高度相似性; 最终,在一定范围的非通用程序逻辑上,三者在经过反汇编后的代码语义上显示出明显的雷同,并提供了如下两图佐证(图片来源): 文章指出的涉事相关软件已经下线,对于上述样本文件的相似度试验暂不做复现,且无法求证存在相似、疑似同源的代码在三者中占比数据。对于上述指出的代码雷同现象,猎豹安全团队认为: 我们怀疑该病毒模块的作者通过某种渠道(比如“曾经就职”),掌握有中国电信旗下部分客户端/服务端源码,并加以改造用于制作窃取用户隐私的病毒,另外在该病毒模块的代码中,我们还发现“百度”旗下部分客户端的基础调试日志函数库代码痕迹,整个“驱魔”病毒家族疑点重重,其制作传播背景愈发扑朔迷离。 这样的推断,固然有过于直接的依据(例如三款代码中均使用含有“baidu”字样的特征注册表项);但更进一步地,需要注意到,三个样本在所指出的代码位置,具有直观可见的二进制汇编代码结构的相同,考虑到如果仅仅是恶意代码开发者先逆向另外两份代码后借鉴了代码逻辑,那么在面临反编译、代码上下文适配重构、跨编译器和选项的编译结果差异等诸多不确定环节,仍能保持二进制代码的雷同,似乎确实是只有从根本上的源代码泄漏(抄袭)且保持相同的开发编译环境才能成立。 但是我们却又无法做出更明确的推断。这一方面当然是出于严谨避免过度解读;而从另一方面考虑,黑产代码的一个关键出发点就是“隐藏自己”,而这里居然如此堂而皇之地照搬了代码,不但没有进行任何代码混淆、变形,甚至没有抹除疑似来源的关键字符串,如果将黑产视为智商在线的对手,那这里背后是否有其它考量,就值得琢磨了。 代码的比对、分析、溯源技术水准 上文中的安全团队基于大量样本和粗粒度比对方法,给出了一个初步的判断和疑点。那么是否有可能获得更确凿的分析结果,来证实或证伪同源猜想呢? 无论是源代码还是二进制,代码比对技术作为一种基础手段,在软件供应链安全分析上都注定仍然有效。在我们的软件供应链安全大赛期间,针对PE二进制程序类型的题目,参赛队伍就纷纷采用了相关技术手段用于目标分析,包括:同源性分析,用于判定与目标软件相似度最高的同软件官方版本;细粒度的差异分析,用于尝试在忽略编译差异和特意引入的混淆之外,定位特意引入的恶意代码位置。当然,作为比赛中针对性的应对方案,受目标和环境引导约束,这些方法证明了可行性,却难以保证集成有最新技术方案。那么做一下预言,在不计入情报辅助条件下,下一代的代码比对将能够到达什么水准? 这里结合近一年和今年内,已发表和未发表的学术领域顶级会议的相关文章来简单展望: ·针对海量甚至全量已知源码,将可以实现准确精细化的“作者归属”判定。在ACM CCS‘18会议上曾发表的一篇文章《Large-Scale and Language-Oblivious Code Authorship Identification》,描述了使用RNN进行大规模代码识别的方案,在圈定目标开发者,并预先提供每个开发者的5-7份已知的代码文件后,该技术方案可以很有效地识别大规模匿名代码仓库中隶属于每个开发者的代码:针对1600个Google Code Jam开发者8年间的所有代码可以实现96%的成功识别率,而针对745个C代码开发者于1987年之后在GitHub上面的全部公开代码仓库,识别率也高达94.38%。这样的结果在当下的场景中,已经足以实现对特定人的代码识别和跟踪(例如,考虑到特定开发人员可能由于编码习惯和规范意识,在时间和项目跨度上犯同样的错误);可以预见,在该技术方向上,完全可以期望摆脱特定已知目标人的现有数据集学习的过程,并实现更细粒度的归属分析,例如代码段、代码行、提交历史。 ·针对二进制代码,更准确、更大规模、更快速的代码主程序分析和同源性匹配。近年来作为一项程序分析基础技术研究,二进制代码相似性分析又重新获得了学术界和工业界的关注。在2018年和2019(已录用)的安全领域四大顶级会议上,每次都会有该方向最新成果的展示,如S&P‘2019上录用的《Asm2Vec: Boosting Static Representation Robustness for Binary Clone Search against Code Obfuscation and Compiler Optimization》,实现无先验知识的条件下的最优汇编代码级别克隆检测,针对漏洞库的漏洞代码检测可实现0误报、100%召回。而2018年北京HITB会议上,Google Project Zero成员、二进制比对工具BinDiff原始作者Thomas Dullien,探讨了他借用改造Google自家SimHash算法思想,用于针对二进制代码控制流图做相似性检测的尝试和阶段结果;这种引入规模数据处理的思路,也可期望能够在目前其他技术方案大多精细化而低效的情况下,为高效、快速、大规模甚至全量代码克隆检测勾出未来方案。 ·代码比对方案对编辑、优化、变形、混淆的对抗。近年所有技术方案都以对代码“变种”的检测有效性作为关键衡量标准,并一定程度上予以保证。上文CCS‘18论文工作,针对典型源代码混淆(如Tigress)处理后的代码,大规模数据集上可有93.42%的准确识别率;S&P‘19论文针对跨编译器和编译选项、业界常用的OLLVM编译时混淆方案进行试验,在全部可用的混淆方案保护之下的代码仍然可以完成81%以上的克隆检测。值得注意的是以上方案都并非针对特定混淆方案单独优化的,方法具有通用价值;而除此以外还有很多针对性的的反混淆研究成果可用;因此,可以认为在采用常规商用代码混淆方案下,即便存在隐藏内部业务逻辑不被逆向的能力,但仍然可以被有效定位代码复用和开发者自然人。 代码溯源技术面前的“挑战” 作为软件供应链安全的独立分析方,健壮的代码比对技术是决定性的基石;而当脑洞大开,考虑到行业的发展,也许以下两种假设的情景,将把每一个“正当”的产品、开发者置于尴尬的境地。 代码仿制 在本章节引述的“驱魔家族”代码疑云案例中,黑产方面通过某种方式获得了正常代码中,功能逻辑可以被自身复用的片段,并以某种方法将其在保持原样的情况下拼接形成了恶意程序。即便在此例中并非如此,但这却暴露了隐忧:将来是不是有这种可能,我的正常代码被泄漏或逆向后出现在恶意软件中,被溯源后扣上黑锅? 这种担忧可能以多种渠道和形式成为现实。 从上游看,内部源码被人为泄漏是最简单的形式(实际上,考虑到代码的完整生命周期似乎并没有作为企业核心数据资产得到保护,目前实质上有没有这样的代码在野泄漏还是个未知数),而通过程序逆向还原代码逻辑也在一定程度上可获取原始代码关键特征。 从下游看,则可能有多种方式将恶意代码伪造得像正常代码并实现“碰瓷”。最简单地,可以大量复用关键代码特征(如字符串,自定义数据结构,关键分支条件,数据记录和交换私有格式等)。考虑到在进行溯源时,分析者实际上不需要100%的匹配度才会怀疑,因此仅仅是仿造原始程序对于第三方公开库代码的特殊定制改动,也足以将公众的疑点转移。而近年来类似自动补丁代码搜索生成的方案也可能被用来在一份最终代码中包含有二方甚至多方原始代码的特征和片段。 基于开发者溯源的定点渗透 既然在未来可能存在准确将代码与自然人对应的技术,那么这种技术也完全可能被黑色产业利用。可能的忧患包括强针对性的社会工程,结合特定开发者历史代码缺陷的漏洞挖掘利用,联动第三方泄漏人员信息的深层渗透,等等。这方面暂不做联想展开。 〇. 没有总结 作为一场旨在定义“软件供应链安全”威胁的宣言,阿里安全“功守道”大赛将在后续给出详细的分解和总结,其意义价值也许会在一段时间之后才能被挖掘。 但是威胁的现状不容乐观,威胁的发展不会静待;这一篇随笔仅仅挑选六个侧面做摘录分析,可即将到来的趋势一定只会进入更加发散的境地,因此这里,没有总结。 本篇文章为转载内容。原文链接:https://blog.csdn.net/systemino/article/details/90114743。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-05 13:33:43
300
转载
Python
...程实践中,对用户输入数据的验证是保证程序稳定性和安全性的关键环节。近期,随着Python 3.9版本的发布和更新,开发者在处理数字验证时拥有了更多高效、安全的选择。例如,除了文中提到的isdigit()、isnumeric()及isdecimal()函数外,Python引入了zoneinfo模块以支持国际化时间处理,这使得在处理包含日期、时间戳等混合型数字格式的用户输入时更为精准。 同时,在大型项目中,为确保代码健壮性,推荐使用类型注解(Type Annotations)配合库如typeguard进行更严格的输入校验。Python 3.8及以上版本强化了类型提示功能,可以有效预防因类型错误导致的运行时异常,尤其对于涉及复杂数字结构如numpy数组或pandas DataFrame的数据验证具有重要意义。 此外,针对Web开发场景下的用户输入验证,框架Django和Flask提供了内置的表单验证机制,能够自动检查并转换用户提交的数字信息,极大地提升了开发效率和用户体验。 总的来说,Python在数字处理与验证方面持续优化和发展,开发者应紧跟社区动态,充分利用新特性与最佳实践,以提升应用程序的安全性和稳定性。
2023-01-16 10:24:29
404
软件工程师
转载文章
...深入理解Oracle数据库中CEIL和FLOOR函数的运用及它们与其他函数结合时可能出现的问题后,进一步的延伸阅读可关注近期数据库优化实践以及如何确保数据处理的准确性和性能。 近日,一篇关于Oracle 19c版本中数值函数性能提升的文章引起了广泛关注。文中详细介绍了新版本对CEIL、FLOOR等内建函数进行了底层优化,显著降低了处理大数据量时的CPU消耗,并通过实际测试案例展示了其在金融风控业务场景中的高效应用。例如,在处理涉及货币转换与金额四舍五入问题时,借助增强后的CEIL和FLOOR函数,能够更精确地执行批量数据处理任务,同时有效避免了因数据类型不匹配导致的错误。 此外,对于数据库开发者而言,深入理解SQL查询中的类型转换规则是至关重要的。Oracle官方社区近期发布的一篇技术解读文章,以丰富的实例阐述了NVL、TO_NUMBER、REPLACE等函数与CEIL、FLOOR函数联合使用时的最佳实践。作者强调,在进行复杂数据预处理时,务必注意隐式类型转换可能导致的潜在风险,如ORA-01722(无效数字)错误,提倡通过明确的数据类型转换操作确保函数调用的正确性。 综上所述,随着Oracle数据库技术的不断演进,用户在实际业务场景中灵活运用CEIL、FLOOR等数值函数的同时,也需紧跟官方更新动态和技术指南,以便更好地规避数据处理过程中可能遇到的问题,提升系统的稳定性和效率。
2023-11-18 18:54:51
343
转载
JSON
...易语言中处理JSON数据是现代Web开发和API交互的重要技能。随着易语言功能的不断丰富和完善,越来越多的开发者选择易语言进行项目开发,尤其是对于需要与服务器端频繁交换JSON格式数据的应用场景。近期,易语言官方发布了全新的JSON支持库,进一步优化了JSON数据的解析效率与内存占用,使得开发者能够更加便捷高效地操作JSON对象。 实际案例方面,某电商团队利用易语言开发了一款移动端管理工具,通过内建的HTTP客户端发送请求获取服务器返回的大量JSON数据,并利用易语言的JSON模块成功实现了复杂嵌套结构的数据提取与展示,极大地提高了业务处理速度与用户体验。 深入解读上,易语言对JSON的支持不仅体现在基础的读取、解析能力,更在于它如何将JSON数据映射为易语言中的数据结构,以及错误处理机制的设计。例如,当遇到无效或缺失的JSON键值时,易语言可以通过异常捕获机制确保程序稳定运行,同时提供详细的错误信息供开发者定位问题。 此外,为了帮助开发者更好地掌握JSON处理技术,易语言社区定期举办线上教程和实战训练营,邀请行业专家分享JSON在实际项目中的最佳实践,以及易语言与其他主流Web框架整合的最新方案。无论是初学者还是资深开发者,都能从中获得提升JSON数据处理能力的关键知识与技巧,紧跟时代步伐,适应日益增长的数据交换需求。
2023-10-08 20:20:12
490
逻辑鬼才
JSON
...ion)是一种简洁的数据传输格式。它的句法规则简单,容易查看和编写代码,而且很容易与其他编程语言进行交流。但是,在一些情境中,我们需要将JSON数据转化成表格形式,以便于方便地检索、处理和管控数据。 将JSON数据转化成表格形式的过程,通常包含以下几个步骤: 了解JSON数据的构造:在进行转化之前,我们需要先了解JSON数据的属性名、字段类型以及嵌套关系。 创建数据库表:根据JSON数据的构造,我们需要在数据库中创建匹配的表格。 解读JSON数据:我们可以使用各种编程语言提供的JSON解读器来解读JSON数据,将其转化成数据结构。 将数据结构添加数据库表:最后,我们可以使用SQL语句将数据结构添加数据库表中。 -- 创建数据库表 CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50), email VARCHAR(50), address VARCHAR(100) ); -- 解读JSON数据 var data = JSON.parse('[ { "id": 1, "name": "Alice", "email": "alice@example.com", "address": { "street": "123 Main St", "city": "Anytown", "state": "USA", "zipcode": "12345" } }, { "id": 2, "name": "Bob", "email": "bob@example.com", "address": { "street": "456 High St", "city": "Anytown", "state": "USA", "zipcode": "67890" } } ]'); -- 将数据结构添加数据库表 for(var i = 0; i< data.length; i++) { var user = data[i]; var query = "INSERT INTO users (id, name, email, address) VALUES (?, ?, ?, ?)"; db.query(query, [user.id, user.name, user.email, JSON.stringify(user.address)]); } 在上述代码中,我们使用了JavaScript语言进行示例展示,但是相应的处理在其他编程语言,例如Python、Java、PHP等,也有相应的实现方法。总的来说,将JSON数据转化成表格形式,可以方便地对数据进行增删改查等处理,提高数据的处理速度和数据管控的便捷性。
2023-11-04 08:47:08
443
算法侠
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
wc -l file.txt
- 计算文件的行数。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
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
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"