偷偷告诉你5个好用的Pandas技巧

thbcm阅读(268)

在工作中,效率是一个很关键的因素,一个人做事的效率高低,决定了花费时间的多少。所以当我们项目涉及到一些基础编码时,使用pandas库就能大大的节省你的时间,提高你的工作效率。

Pandas是一个开源包。它有助于用Python语言执行数据分析和数据操作。此外,它还为我们提供了灵活的数据结构。

接下来带你们了解一下pandas的几个实用技巧

1.行的条件选择

首先,数据探索是必要步骤。Pandas为进行各种分析提供了一种快速简便的方法。其中一个非常重要的技巧是根据条件选择行或过滤数据。

行的条件选择可以基于由逻辑运算符分隔的单个语句中的单个条件或多个条件。

例如,我使用一个关于贷款预测的数据集。

我们将挑选一排还没有毕业、收入低于5400英镑的客户。让我们看看我们该怎么做。

import pandas as pd
data = pd.read_csv('../Data/loan_train.csv')
data.head()
data2 = data.loc[(data['Education'] == 'Not Graduate') & (data['ApplicantIncome'] <= 5400)]
data2

注意:记住把每个条件放在括号内。

2.数据的存储

数据可以有两种类型-连续的和离散的,这取决于我们的分析要求。有时我们不需要连续变量中的精确值,但需要它所属的群体。

例如,你的数据中有一个连续变量,年龄。但你需要一个年龄组来进行分析,比如儿童、青少年、成人、老年人。实际上,Binning非常适合解决我们这里的问题。

为了执行Binning,我们使用cut()函数。这对于从连续变量到离散变量非常有用。

import pandas as pd


df = pd.read_csv('titanic.csv')
from sklearn.utils import shuffle


# 随机化
df = shuffle(df, random_state = 42)


df.head()


bins = [0,4,17,65,99]
labels =['Toddler','Child','Adult','Elderly']


category = pd.cut(df['Age'], bins = bins, labels = labels)


df.insert(2, 'Age Group', category)


df.head()


df['Age Group'].value_counts()


df.isnull().sum()

3.分组数据

这种操作在数据科学家和分析师的日常生活中经常执行。Pandas提供了一个基本的函数来执行数据分组,即Groupby

Groupby操作包括根据特定条件拆分对象,应用函数,然后组合结果。

让我们再看一次贷款预测数据集,假设我想看看给来自不同财产领域的人的平均贷款额,比如农村、半城市和城市。花点时间来理解这个问题陈述并思考如何解决它。

嗯,Pandasgroupby可以非常有效地解决这个问题。首先根据属性区域划分数据。其次,我们将mean()函数应用于每个类别。最后,我们将它们组合在一起,并将其打印为新的数据帧。

#导入数据集
import pandas as pd


df = pd.read_csv('../Data/loan_train.csv')
df.head()


# 男女平均收入
df.groupby(['Gender'])[['ApplicantIncome']].mean()


# 平均贷款金额不同的财产地区,如城市,农村
df.groupby(['Property_Area'])[['LoanAmount']].mean()


# 比较不同教育背景的贷款状况
df.groupby(['Education'])[['Loan_Status']].count()

4.Pandas map

map是另一个提供高度灵活性和实际应用的重要操作。

Pandas map()用于根据输入对应关系将序列中的每个值映射到其他值。实际上,这个输入可以是一个序列、字典,甚至是一个函数。

让我们举一个有趣的例子。我们有一个虚拟的雇员数据集。此数据集由以下列组成–姓名、年龄、职业、城市。

现在需要添加另一列,说明相应的状态。你会怎么做?如果数据集的范围是10行,你可以手动执行,但是如果有数千行呢?使用Pandas map会更有利。

#样本数据
data = {'name': ['A', 'B', 'C', 'D', 'E'], 
        'age': [22, 26, 33, 44, 50],
        'profession' : ['data engineer', 'data scientist', 'entrepreneur', 'business analyst', 'self-employed'], 
        'city': ['Gurgaon', 'Bangalore', 'Gurgaon', 'Pune', 'New Delhi']}


df = pd.DataFrame(data)
df


# 城市与州
map_city_to_states = { 'Gurgaon' : 'Haryana', 
                  'Bangalore' : 'Karnataka', 
                  'Pune' : 'Maharashtra', 
                  'New Delhi' : 'Delhi'}


# 将城市列映射为州
df['state'] = df['city'].map(map_city_to_states)
df

5.Pandas DataFrame的条件格式化

这是我最喜欢的Pandas技巧之一。这个技巧让我有能力直观地定位特定条件下的数据。

可以使用Pandasstyle属性将条件格式应用于数据框。事实上,条件格式是根据某种条件对数据帧应用视觉样式的操作。

虽然Pandas提供了大量的操作,但我将在这里向你展示一个简单的操作。例如,我们有对应于每个销售人员的销售数据。我想查看的是销售价值高于80的。

import pandas as pd


data = pd.read_excel("../Data/salesman_performance.xlsx")
data


data.style


def highlight_green(sales):
    color = 'green' if sales > 80 else 'black'
    return 'color: %s' % color


formatting = data.iloc[:,1:6].style.applymap(highlight_green)
formatting

(推荐教程:Pandas中文教程

结尾

以上就是关于Pandas的5个实用技巧的介绍了,希望这些技巧能帮大家更好更快的完成工作。

使用canal+Kafka进行数据库同步操作

thbcm阅读(258)

平时工作中数据库是我们经常使用的,在微服务拆分的架构中,各服务拥有自己的数据库,所以常常会遇到服务之间数据通信的问题。比如,B 服务数据库的数据来源于A服务的数据库;A 服务的数据有变更操作时,需要同步到 B 服务中。

第一种解决方案:

在代码逻辑中,有相关 A 服务数据写操作时,以调用接口的方式,调用 B 服务接口,B 服务再将数据写到新的数据库中。这种方式看似简单,但其实“坑”很多。在 A 服务代码逻辑中会增加大量这种调用接口同步的代码,增加了项目代码的复杂度,以后会越来越难维护。并且,接口调用的方式并不是一个稳定的方式,没有重试机制,没有同步位置记录,接口调用失败了怎么处理,突然的大量接口调用会产生的问题等,这些都要考虑并且在业务中处理。这里会有不少工作量。想到这里,就将这个方案排除了。

(推荐课程:SQL教程)

第二种解决方案:

通过数据库的binlog进行同步。这种解决方案,与 A 服务是独立的,不会和 A 服务有代码上的耦合。可以直接 TCP连接进行传输数据,优于接口调用的方式。 这是一套成熟的生产解决方案,也有不少binlog同步的中间件工具,所以我们关注的就是哪个工具能够更好的构建稳定、性能满足且易于高可用部署的方案。

经过调研,我们选择了canalcanal是阿里巴巴 MySQL binlog 增量订阅&消费组件,已经有在生产上实践的例子,并且方便的支持和其他常用的中间件组件组合,比如kafkaelasticsearch等,也有了canal-go go语言的client库,满足我们在go上的需求,其他具体内容参阅canalgithub主页。

原理简图

![原理简图](https://atts.w3cschool.cn/attachments/image/20200817/1597643005519402.jpg “原理简图”)

![原理简图](https://atts.w3cschool.cn/attachments/image/20200817/1597643026794359.jpg “原理简图”)

OK,开始干!现在要将 A 数据库的数据变更同步到 B 数据库。根据wiki很快就用docker跑起了一台canal-server服务,直接用canal-gocanal-client代码逻辑。用canal-go直接连canal-servercanal-servercanal-client之间是Socket来进行通信的,传输协议是TCP,交互协议采用的是 Google Protocol Buffer 3.0

工作流程

  1. Canal连接到 A 数据库,模拟slave
  2. canal-clientCanal建立连接,并订阅对应的数据库表
  3. A 数据库发生变更写入到binlogCanal向数据库发送dump请求,获取binlog并解析,发送解析后的数据给canal-client
  4. canal-client收到数据,将数据同步到新的数据库

Protocol Buffer的序列化速度还是很快的。反序列化后得到的数据,是每一行的数据,按照字段名和字段的值的结构,放到一个数组中 代码简单示例:

func Handler(entry protocol.Entry) { var keys []string rowChange := &protocol.RowChange{} proto.Unmarshal(entry.GetStoreValue(), rowChange) if rowChange != nil { eventType := rowChange.GetEventType() for _, rowData := range rowChange.GetRowDatas() { // 遍历每一行数据 if eventType == protocol.EventType_DELETE || eventType == protocol.EventType_UPDATE { columns := rowData.GetBeforeColumns() // 得到更改前的所有字段属性 } else if eventType == protocol.EventType_INSERT { columns := rowData.GetAfterColumns() // 得到更后前的所有字段属性 } …… } } }

遇到的问题

为了高可用和更高的性能,我们会创建多个canal-client构成一个集群,来进行解析并同步到新的数据库。这里就出现了一个比较重要的问题,如何保证canal-client集群解析消费binlog的顺序性呢?

我们使用的binlogrow模式。每一个写操作都会产生一条binlog日志。 举个简单的例子:插入了一条 a 记录,并且立马修改 a 记录。这样会有两个消息发送给canal-client,如果由于网络等原因,更新的消息早于插入的消息被处理了,还没有插入记录,更新操作的最后效果是失败的。

怎么办呢? canal可以和消息队列组合呀!而且支持kafkarabbitmqrocketmq多种选择,如此优秀。我们在消息队列这层来实现消息的顺序性。

选择canal+kafka方案

我们选择了消息队列的业界标杆: kafka UCloud提供了kafkarocketMQ消息队列产品服务,使用它们能够快速便捷的搭建起一套消息队列系统。加速开发,方便运维。

下面就让我们来一探究竟:

1.选择kafka消息队列产品,并申请开通

![kafka消息队列](https://atts.w3cschool.cn/attachments/image/20200817/1597643207499951.jpg “kafka消息队列”)

2.开通完成后,在管理界面,创建kafka集群,根据自身需求,选择相应的硬件配置

![硬件配置](https://atts.w3cschool.cn/attachments/image/20200817/1597643247605810.jpg “硬件配置”)

3.一个kafka + ZooKeeper集群就搭建出来了,给力!

![kafka+ZooKeeper集群](https://atts.w3cschool.cn/attachments/image/20200817/1597643275823817.jpg “kafka+ZooKeeper集群”)

并且包含了节点管理、Topic管理、Consumer Group管理,能够非常方便的直接在控制台对配置进行修改

监控视图方面,监控的数据包括kafka生成和消费QPS,集群监控,ZooKeeper的监控。能够比较完善的提供监控指标。

![监控指标](https://atts.w3cschool.cn/attachments/image/20200817/1597643316579478.jpg “监控指标”)

![监控指标](https://atts.w3cschool.cn/attachments/image/20200817/1597643347799100.jpg “监控指标”)

![监控指标](https://atts.w3cschool.cn/attachments/image/20200817/1597643363690805.jpg “监控指标”)

canal的kafka配置

canal配上kafka也非常的简单。 vi /usr/local/canal/conf/canal.properties

# 可选项: tcp(默认), kafka, RocketMQ
canal.serverMode = kafka
# ...
# kafka/rocketmq 集群配置: 192.168.1.117:9092,192.168.1.118:9092,192.168.1.119:9092
canal.mq.servers = 127.0.0.1:9002
canal.mq.retries = 0
# flagMessage模式下可以调大该值, 但不要超过MQ消息体大小上限
canal.mq.batchSize = 16384
canal.mq.maxRequestSize = 1048576
# flatMessage模式下请将该值改大, 建议50-200
canal.mq.lingerMs = 1
canal.mq.bufferMemory = 33554432
# Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下)
canal.mq.canalBatchSize = 50
# Canal get数据的超时时间, 单位: 毫秒, 空为不限超时
canal.mq.canalGetTimeout = 100
# 是否为flat json格式对象
canal.mq.flatMessage = false
canal.mq.compressionType = none
canal.mq.acks = all
# kafka消息投递是否使用事务
canal.mq.transaction = false


# mq config
canal.mq.topic=default
# dynamic topic route by schema or table regex
#canal.mq.dynamicTopic=mytest1.user,mytest2\\\\..*,.*\\\\..*
canal.mq.dynamicTopic=mydatabase.mytable
canal.mq.partition=0
# hash partition config
canal.mq.partitionsNum=3
canal.mq.partitionHash=mydatabase.mytable

解决顺序消费问题

看到下面这一行配置

canal.mq.partitionHash=mydatabase.mytable

我们配置了kafkapartitionHash,并且我们一个Topic就是一个表。这样的效果就是,一个表的数据只会推到一个固定的partition中,然后再推给consumer进行消费处理,同步到新的数据库。通过这种方式,解决了之前碰到的binlog日志顺序处理的问题。这样即使我们部署了多个kafka consumer端,构成一个集群,这样consumer从一个partition消费消息,就是消费处理同一个表的数据。这样对于一个表来说,牺牲掉了并行处理,不过个人觉得,凭借kafka的性能强大的处理架构,我们的业务在kafka这个节点产生瓶颈并不容易。并且我们的业务目的不是实时一致性,在一定延迟下,两个数据库保证最终一致性。

(推荐微课:SQL微课)

下图是最终的同步架构,我们在每一个服务节点都实现了集群化。全都跑在UCloudUK8s服务上,保证了服务节点的高可用性。

canal也是集群换,但是某一时刻只会有一台canal在处理binlog,其他都是冗余服务。当这台canal服务挂了,其中一台冗余服务就会切换到工作状态。同样的,也是因为要保证binlog的顺序读取,所以只能有一台canal在工作。

![最终同步架构](https://atts.w3cschool.cn/attachments/image/20200817/1597643437868571.jpg “最终同步架构”)

并且,我们还用这套架构进行缓存失效的同步。我们使用的缓存模式是:Cache-Aside。同样的,如果在代码中数据更改的地方进行缓存失效操作,会将代码变得复杂。所以,在上述架构的基础上,将复杂的触发缓存失效的逻辑放到kafka-client端统一处理,达到一定解耦的目的。

以上就是关于使用canal + Kafka进行数据库同步操作的相关介绍了,希望对大家有所帮助。

List 集合去重的 3 种方法

thbcm阅读(299)

问题由来

在实际开发的时候,我们经常会碰到这么一个困难:一个集合容器里面有很多重复的对象,里面的对象没有主键,但是根据业务的需求,实际上我们需要根据条件筛选出没有重复的对象。

比较暴力的方法,就是根据业务需求,通过两层循环来进行判断,没有重复的元素就加入到新集合中,新集合中已经有的元素就跳过。

操作例子如下,创建一个实体对象PenBean,代码如下:

/**
 * 笔实体
 */
public class PenBean {


    /**类型*/
    private String type;


    /**颜色*/
    private String color;


    //... 省略 setter 和 getter


    public PenBean(String type, String color) {
        this.type = type;
        this.color = color;
    }


    @Override
    public String toString() {
        return "PenBean{" +
                "type='" + type + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}

测试 demo,如下:

public static void main(String[] args) {
    //添加信息,PenBean中没有主键
    List<PenBean> penBeanList = new ArrayList<PenBean>();
    penBeanList.add(new PenBean("铅笔","black"));
    penBeanList.add(new PenBean("铅笔","white"));
    penBeanList.add(new PenBean("铅笔","black"));
    penBeanList.add(new PenBean("中性笔","white"));
    penBeanList.add(new PenBean("中性笔","white"));


    //新数据
    List<PenBean> newPenBeanList = new ArrayList<PenBean>();
    //传统重复判断
    for (PenBean penBean : penBeanList) {
        if(newPenBeanList.isEmpty()){
            newPenBeanList.add(penBean);
        }else{
            boolean isSame = false;
            for (PenBean newPenBean : newPenBeanList) {
                //依靠type、color来判断,是否有重复元素
                //如果新集合包含元素,直接跳过
                if(penBean.getType().equals(newPenBean.getType()) && penBean.getColor().equals(newPenBean.getColor())){
                    isSame = true;
                    break;
                }
            }
            if(!isSame){
                newPenBeanList.add(penBean);
            }
        }
    }

一般处理数组类型的对象时,可以通过这种方法来对数组元素进行去重操作,以筛选出没有包含重复元素的数组。

那有没有更加简洁的写法呢?

答案肯定是有的,List中的contains()方法就是!

(推荐教程:Java教程

1、利用list中contains方法去重

在使用contains()之前,必须要对PenBean类重写equals()方法,为什么要这么做?等会会详细解释!

我们先在PenBean类中重写equals()方法,内容如下:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    PenBean penBean = (PenBean) o;
   //当type、color 内容都相等的时候,才返回true
    return Objects.equals(type, penBean.type) &&
            Objects.equals(color, penBean.color);
}

修改测试 demo,如下:

public static void main(String[] args) {
    //添加信息
    List<PenBean> penBeanList = new ArrayList<PenBean>();
    penBeanList.add(new PenBean("铅笔","black"));
    penBeanList.add(new PenBean("铅笔","white"));
    penBeanList.add(new PenBean("铅笔","black"));
    penBeanList.add(new PenBean("中性笔","white"));
    penBeanList.add(new PenBean("中性笔","white"));


    //新数据
    List<PenBean> newPenBeanList = new ArrayList<PenBean>();
    //使用contain判断,是否有相同的元素
    for (PenBean penBean : penBeanList) {
        if(!newPenBeanList.contains(penBean)){
            newPenBeanList.add(penBean);
        }
    }


    //输出结果
    System.out.println("=========新数据======");
    for (PenBean penBean : newPenBeanList) {
        System.out.println(penBean.toString());
    }
}

输出结果如下:

=========新数据======
PenBean{type='铅笔', color='black'}
PenBean{type='铅笔', color='white'}
PenBean{type='中性笔', color='white'}

如果PenBean对象不重写equals()contains()方法的都是false!新数据与源数据是一样的,并不能达到我们想要除去重复元素的目的

那么contains()是怎么做到,判断一个集合里面有相同的元素呢?

我们打开ArrayListcontains()方法,源码如下:

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

找到indexOf(o)方法,继续往下看,源码如下:

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
           //对象通过 equals 方法,判断是否相同
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

此时,非常清晰了,如果传入的对象是null,for 循环判断数组中的元素是否有null,如果有就返回下标;如果传入的对象不是null,通过对象的equals()方法,for 循环判断是否有相同的元素,如果有就返回下标!

如果是数组返回的下标,肯定是大于0,否则返回-1!

这就是为什么在List中使用contains()方法,对象需要重写equals()方法的原因!

2、java 8中去重操作

当然,有些朋友可能会想到JDK1.8中的流式写法,例如 jdk1.8 中的集合元素去重写法如下:

public static void main(String[] args) {
    //添加信息
    List<PenBean> penBeanList = new ArrayList<PenBean>();
    penBeanList.add(new PenBean("铅笔","black"));
    penBeanList.add(new PenBean("铅笔","white"));
    penBeanList.add(new PenBean("铅笔","black"));
    penBeanList.add(new PenBean("中性笔","white"));
    penBeanList.add(new PenBean("中性笔","white"));


    //使用java8新特性stream进行List去重
    List<PenBean> newPenBeanList = penBeanList.stream().distinct().collect(Collectors.toList());


    //输出结果
    System.out.println("=========新数据======");
    for (PenBean penBean : newPenBeanList) {
        System.out.println(penBean.toString());
    }
}

利用 jdk1.8 中提供的Stream.distinct()列表去重,Stream.distinct()使用hashCode()equals()方法来获取不同的元素,因此使用这种写法,对象需要重写hashCode()equals()方法!

PenBean对象重写hashCode()方法,代码如下:

@Override
public int hashCode() {
    return Objects.hash(type, color);
}

在运行测试demo,结果如下:

=========新数据======
PenBean{type='铅笔', color='black'}
PenBean{type='铅笔', color='white'}
PenBean{type='中性笔', color='white'}

即可实现集合元素的去重操作!

那为什么当我们使用String类型的对象作为集合元素时,没有重写呢?

因为 java 中String原生类,已经重写好了,源码如下:

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {

 
 @Override
 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

 
 @Override
 public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;


        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
}

(推荐微课:Java微课

3、HashSet去重操作

在上面的分享中,我们介绍了 List 的集合去重操作!其中网友还提到了HashSet可以实现元素的去重!

的确,HashSet集合天然支持元素不重复!

实践代码如下!

还是先创建一个对象PenBean,同时重写Object中的equals()hashCode()方法,如下:

/**
 * 笔实体
 */
public class PenBean {
    /**类型*/
    private String type;
    /**颜色*/
    private String color;
    //... 省略 setter 和 getter
    public PenBean(String type, String color) {
        this.type = type;
        this.color = color;
    }
    @Override
    public String toString() {
        return "PenBean{" +
                "type='" + type + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

 
 @Override
 public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      PenBean penBean = (PenBean) o;
      //当type、color 内容都相等的时候,才返回true
      return Objects.equals(type, penBean.type) &&
          Objects.equals(color, penBean.color);
 }

 
 @Override
 public int hashCode() {
    return Objects.hash(type, color);
 }

  
}

创建测试 demo,如下:

public static void main(String[] args) {
    //添加信息
    List<PenBean> penBeanList = new ArrayList<PenBean>();
    penBeanList.add(new PenBean("铅笔","black"));
    penBeanList.add(new PenBean("铅笔","white"));
    penBeanList.add(new PenBean("铅笔","black"));
    penBeanList.add(new PenBean("中性笔","white"));
    penBeanList.add(new PenBean("中性笔","white"));


    //新数据
    List<PenBean> newPenBeanList = new ArrayList<PenBean>();
    //set去重
    HashSet<PenBean> set = new HashSet<>(penBeanList);
    newPenBeanList.addAll(set);


    //输出结果
    System.out.println("=========新数据======");
    for (PenBean penBean : newPenBeanList) {
        System.out.println(penBean.toString());
    }
}

输出结果如下:

=========新数据======
PenBean{type='铅笔', color='white'}
PenBean{type='铅笔', color='black'}
PenBean{type='中性笔', color='white'}

很明细,返回的新集合没有重复元素!

HashSet是怎么做的的呢?

打开HashSet的源码,查看我们传入的构造方法如下:

public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

很显然,首先创建了一个HashMap对象,然后调用addAll()方法,继续往下看这个方法!

public boolean addAll(Collection<? extends E> c) {
    boolean modified = false;
    for (E e : c)
        if (add(e))
            modified = true;
    return modified;
}

首先遍历List中的元素,然后调用add()方法,这个方法,源码如下:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

其实,就是向HashMap对象中插入元素,其中PRESENT是一个new Object()常量!

private static final Object PRESENT = new Object();

到这里就基本很清楚了,向HashSet中添加元素,其实等同于

Map<Object,Object> map = new HashMap<Object,Object>();
map.put(e,new Object);//e表示要插入的元素

其中插入的元素 e,就是HashMap中的key

我们知道HashMap,是通过equals()hashCode()来判断插入的key是否为同一个key,因此,当我们对PenBean对象进行重写equals()hashCode()时,保证判断是同一个key时,就可以达到元素去重的目的!

(推荐内容:Java面试基础题

最后,对已经去重的集合HashSet,再通过ArrayList中的addAll()方法进行包装,即可得到我们想要的不包含重复元素的数据!

文章来源于:mp.weixin.qq.com/s/LBjfarlK5Qv-A85Hey0btA
作者:鸭血粉丝

以上就是关于List集合去重的 3 种方法的相关介绍,希望对大家有所帮助。

Python实用小技巧,30个Python极简代码

thbcm阅读(287)

怎么学习编程最快,当然是各种小项目实战,只有自己去动脑想,动手做,印象才是最深刻的。本文是 30 个极简任务,初学者可以尝试着自己实现;本文同样也是 30 段代码,Python 开发者也可以看看是不是有没想到的用法。

Python 是机器学习最广泛采用的编程语言,它最重要的优势在于编程的易用性。如果读者对基本的 Python 语法已经有一些了解,那么这篇文章可能会给你一些启发。作者简单概览了 30 段代码,它们都是平常非常实用的技巧,我们只要花几分钟就能从头到尾浏览一遍。

1.重复元素判定

以下方法可以检查给定列表是不是存在重复元素,它会使用 set() 函数来移除所有重复元素。

def all_unique(lst):  
return len(lst)== len(set(lst))  
x = [1,1,2,2,3,2,3,4,5,6]  
y = [1,2,3,4,5]  
all_unique(x) # False  
all_unique(y) # True 

2.字符元素组成判定

检查两个字符串的组成元素是不是一样的。

from collections import Counter  
def anagram(first, second):  
return Counter(first) == Counter(second)  
anagram("abcd3", "3acdb") # True 

3.内存占用

import sys  
variable = 30  
print(sys.getsizeof(variable)) # 24 

4.字节占用

下面的代码块可以检查字符串占用的字节数。

def byte_size(string):  
return(len(string.encode('utf-8')))  
byte_size('') # 4  
byte_size('Hello World') # 11 

5.打印 N 次字符串

该代码块不需要循环语句就能打印 N 次字符串。

n = 2  
s ="Programming"  
print(s * n)  
# ProgrammingProgramming 

6.大写第一个字母

以下代码块会使用 title() 方法,从而大写字符串中每一个单词的首字母。

s = "programming is awesome"  
print(s.title())  
# Programming Is Awesome 

7.分块

给定具体的大小,定义一个函数以按照这个大小切割列表。

from math import ceil  
def chunk(lst, size):  
return list(  
map(lambda x: lst[x * size:x * size + size],  
list(range(0, ceil(len(lst) / size)))))  
chunk([1,2,3,4,5],2)  
# [[1,2],[3,4],5] 

8.压缩

这个方法可以将布尔型的值去掉,例如(False,None,0,“”),它使用 filter() 函数。

def compact(lst):  
return list(filter(bool, lst))  
compact([0, 1, False, 2, '', 3, 'a', 's', 34])  
# [ 1, 2, 3, 'a', 's', 34 ] 

9.解包

如下代码段可以将打包好的成对列表解开成两组不同的元组。

array = [['a', 'b'], ['c', 'd'], ['e', 'f']]  
transposed = zip(*array)  
print(transposed)  
# [('a', 'c', 'e'), ('b', 'd', 'f')] 

10.链式对比

我们可以在一行代码中使用不同的运算符对比多个不同的元素。

a = 3  
print( 2 < a < 8) # True  
print(1 == a < 2) # False 

(推荐教程:python教程

11.逗号连接

下面的代码可以将列表连接成单个字符串,且每一个元素间的分隔方式设置为了逗号。

hobbies = ["basketball", "football", "swimming"]  
print("My hobbies are: " + ", ".join(hobbies))  
# My hobbies are: basketball, football, swimming 

12.元音统计

以下方法将统计字符串中的元音 (‘a’, ‘e’, ‘i’, ‘o’, ‘u’) 的个数,它是通过正则表达式做的。

import re  
def count_vowels(str):  
return len(len(re.findall(r'[aeiou]', str, re.IGNORECASE)))  
count_vowels('foobar') # 3  
count_vowels('gym') # 0 

13.首字母小写

如下方法将令给定字符串的第一个字符统一为小写。

def decapitalize(string):  
return str[:1].lower() + str[1:]  
decapitalize('FooBar') # 'fooBar'  
decapitalize('FooBar') # 'fooBar' 

14.展开列表

该方法将通过递归的方式将列表的嵌套展开为单个列表。

def spread(arg):  
ret = []  
for i in arg: 
if isinstance(i, list):  
ret.extend(i)  
else:  
ret.append(i)  
return ret  
def deep_flatten(lst):  
result = []  
result.extend(  
spread(list(map(lambda x: deep_flatten(x) if type(x) == list else x, lst))))  
return result  
deep_flatten([1, [2], [[3], 4], 5]) # [1,2,3,4,5] 

15.列表的差

该方法将返回第一个列表的元素,其不在第二个列表内。如果同时要反馈第二个列表独有的元素,还需要加一句 set_b.difference(set_a)

def difference(a, b):  
setset_a = set(a)  
setset_b = set(b) 
comparison = set_a.difference(set_b)  
return list(comparison)  
difference([1,2,3], [1,2,4]) # [3] 

16.通过函数取差

如下方法首先会应用一个给定的函数,然后再返回应用函数后结果有差别的列表元素。

def difference_by(a, b, fn):  
b = set(map(fn, b))  
return [item for item in a if fn(item) not in b]  
from math import floor  
difference_by([2.1, 1.2], [2.3, 3.4],floor) # [1.2]  
difference_by([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], lambda v : v['x'])  
# [ { x: 2 } ] 

17.链式函数调用

你可以在一行代码内调用多个函数。

def add(a, b):  
return a + b  
def subtract(a, b):  
return a - b  
a, b = 4, 5  
print((subtract if a > b else add)(a, b)) # 9 

18.检查重复项

如下代码将检查两个列表是不是有重复项。

def has_duplicates(lst):  
return len(lst) != len(set(lst))  
x = [1,2,3,4,5,5]  
y = [1,2,3,4,5]  
has_duplicates(x) # True  
has_duplicates(y) # False

19.合并两个字典

下面的方法将用于合并两个字典。

def merge_two_dicts(a, b):  
c = a.copy() # make a copy of a   
c.update(b) # modify keys and values of a with the once from b  
return c  
a={'x':1,'y':2}  
b={'y':3,'z':4}  
print(merge_two_dicts(a,b))  
#{'y':3,'x':1,'z':4} 

Python 3.5 或更高版本中,我们也可以用以下方式合并字典:

def merge_dictionaries(a, b)  
return {**a, **b}  
a = { 'x': 1, 'y': 2}  
b = { 'y': 3, 'z': 4}  
print(merge_dictionaries(a, b))  
# {'y': 3, 'x': 1, 'z': 4} 

20.将两个列表转化为字典

如下方法将会把两个列表转化为单个字典。

def to_dictionary(keys, values):  
return dict(zip(keys, values))  
keys = ["a", "b", "c"]  
values = [2, 3, 4]  
print(to_dictionary(keys, values))  
#{'a': 2, 'c': 4, 'b': 3} 

21.使用枚举

我们常用 For 循环来遍历某个列表,同样我们也能枚举列表的索引与值。

list = ["a", "b", "c", "d"]  
for index, element in enumerate(list):   
print("Value", element, "Index ", index, )  
# ('Value', 'a', 'Index ', 0)  
# ('Value', 'b', 'Index ', 1)  
#('Value', 'c', 'Index ', 2)  
# ('Value', 'd', 'Index ', 3) 

22.执行时间

如下代码块可以用来计算执行特定代码所花费的时间。

import time  
start_time = time.time()  
a = 1 
b = 2  
c = a + b  
print(c) #3  
end_time = time.time()  
total_time = end_time - start_time  
print("Time: ", total_time)  
# ('Time: ', 1.1205673217773438e-05)  

23.Try else

我们在使用 try/except 语句的时候也可以加一个 else 子句,如果没有触发错误的话,这个子句就会被运行。

try:  
2*3  
except TypeError:  
print("An exception was raised")  
else:  
print("Thank God, no exceptions were raised.")  
#Thank God, no exceptions were raised. 

24.元素频率

下面的方法会根据元素频率取列表中最常见的元素。

def most_frequent(list):  
return max(set(list), key = list.count)  
list = [1,2,1,2,3,2,1,4,2]  
most_frequent(list) 

25.回文序列

以下方法会检查给定的字符串是不是回文序列,它首先会把所有字母转化为小写,并移除非英文字母符号。最后,它会对比字符串与反向字符串是否相等,相等则表示为回文序列。

def palindrome(string):  
from re import sub  
s = sub('[\W_]', '', string.lower())  
return s == s[::-1]  
palindrome('taco cat') # True 

26.不使用 if-else 的计算子

这一段代码可以不使用条件语句就实现加减乘除、求幂操作,它通过字典这一数据结构实现:

import operator  
action = {  
"+": operator.add,  
"-": operator.sub, 
"/": operator.truediv,  
"*": operator.mul,  
"**": pow  
}  
print(action['-'](50, 25)) # 25 

27.Shuffle

该算法会打乱列表元素的顺序,它主要会通过 Fisher-Yates算法对新列表进行排序:

from copy import deepcopy  
from random import randint  
def shuffle(lst):  
temp_lst = deepcopy(lst)  
m = len(temp_lst)  
while (m):  
m -= 1  
i = randint(0, m)  
temp_lst[m], temp_lst[i] = temp_lst[i], temp_lst[m]  
return temp_lst  
foo = [1,2,3]  
shuffle(foo) # [2,3,1] , foo = [1,2,3] 

28.展开列表

将列表内的所有元素,包括子列表,都展开成一个列表。

def spread(arg):  
ret = []  
for i in arg:if isinstance(i, list):  
ret.extend(i) 
else:  
ret.append(i)  
return ret  
spread([1,2,3,[4,5,6],[7],8,9]) # [1,2,3,4,5,6,7,8,9] 

29.交换值

不需要额外的操作就能交换两个变量的值。

def swap(a, b):  
return b, a  
a, b = -1, 14  
swap(a, b) # (14, -1)  
spread([1,2,3,[4,5,6],[7],8,9]) # [1,2,3,4,5,6,7,8,9] 

30.字典默认值

通过Key 取对应的 Value 值,可以通过以下方式设置默认值。如果 get() 方法没有设置默认值,那么如果遇到不存在的 Key,则会返回 None

d = {'a': 1, 'b': 2}  
print(d.get('c', 3)) # 3  

(推荐微课:python3基础微课

以上就是关于Python实用小技巧,30个Python极简代码的相关介绍了,希望对大家有所帮助。

了解RXJS中四种Subject的区别

thbcm阅读(329)

「RxJS」全称 「Reactive Extensions for JavaScript」, RxJS 是一个库,它通过使用 observable 序列来编写异步和基于事件的程序。本文带你了解RXJS中四种Subject的使用。

介绍

Subject 是用于多播的Observable,这意味着Subject确保每个订阅都获得与订阅者之间共享可观察执行完全相同的值。

在介绍它们之前,我们先来看一下四种Subject与普通Observable的区别:

Subject

Subject其实是观察者模式的实现,所以当观察者订阅Subject对象时,它会把订阅者添加到观察者列表中,每当有接收到新值时,它就会遍历观察者列表,依次调用观察者内部的next方法,把值一一送出。我们先来看一下Subject的使用方法:

上面例子就比较容易理解:

  1. 我们创建了一个Subject
  2. 发出了一个值1,但由于此时并没有订阅者,所以这个值不会被订阅到
  3. 创建了订阅者 A
  4. 又发出一个值 2,这时候订阅者 A 会接收到这个值
  5. 又创建一个订阅者 B
  6. 最后发出一个值 3,这时候已经订阅的都会接收到这个值

BehaviorSubject

很多时候我们会希望Subject能代表当下的状态,而不是单纯的事件发送,也就是说如果当前有一个新的订阅,我们希望Subject能立即给出最新的值,而不是没有回应。这个时候我们就可以使用到BehaviorSubject

BehaviorSubject继承自Subject,它具有存储当前值的特征。这表示你可以始终直接从BehaviorSubject获取到最后发出的值。请参阅下面代码示例:

这段代码做了那些工作呢?

  1. 我们首先创建了一个BehaviorSubject的实例behavior$,并在实例化时传入初始值 0。
  2. 然后我们订阅了这个这个实例behavior$,由于BehaviorSubject的特点是把最新的值发布给订阅者,订阅者 A 会得到初始值 0,所以就会打引出订阅者 A,订阅值为:0
  3. behavior$使用内置的next方法发出一个新的值 1,这时候订阅者 A 将会收到新的值,打印出订阅者 A,订阅值为 1
  4. 我们增加一个订阅者 B,这时候它会得到最新的值 1,所以打印结果为订阅者B,订阅值为 1
  5. 最后我们再一次调用behavior$next方法,由于我们之前已经订阅了两次,所以订阅者 A 和订阅者 B 都会接收到新的value

(推荐教程:JavaScript教程

ReplaySubject

有时候我们创建一个Subject,但又想在每次新的订阅时,它都会重新发送最后几个值,这个时候我们就可以用到ReplaySubject

ReplaySubject可以将旧数据发送给新的订阅者,这点很像是BehaviorSubject,但是它还有一个额外的特性,它可以记录一部分的observable执行,所以它可以存储多个旧值并发送给它的新订阅者。

创建ReplaySubject时,可以指定要存储多少值以及要存储多长时间。它的第一个参数 bufferSize指定了缓存的大小,默认为 Infinity,即缓存所有发出的值,是一个「空间限制」。我们还可以向其传递第二个参数 windowTime,指定缓存的「时间限制」,默认为 Infinity,即不限制值的失效时间。请参阅下面代码示例:

这里发生了一些事情:

  1. 我们创建了一个ReplaySubject的实例replay$,并指定我们只想存储最后两个值
  2. 我们创建了一个订阅者 A
  3. 调用三次replay$next方法,把值发布给订阅者。这时订阅者 A 将会打印三次
  4. 现在就来体验ReplaySubject的魔力。我们使用replay$创建了一个新的订阅者 B,由于我们告诉ReplaySubject,存储两个值,因此它将直接向订阅者 B 发出这些最后的值,订阅者 B 将打印出这些值。
  5. replay$发出另外一个值,这时候,订阅者 A 和订阅者 B 都接收到值的改变,打印出另外一个值

如前面所说的一样,你还可以指定值在ReplaySubject存储的时间,我们来看一个例子

上面代码中发生了那些事情呢:

  1. 我们创建了ReplaySubject,并指定它只存储最后两个值,但是不超过 100ms
  2. 创建一个订阅者 A
  3. 我们开始每 200ms 发出一个新的值。订阅者 A 会接收到发出的所有值
  4. 我们创建一个订阅者 B,由于是在 1000ms 后进行订阅。这意味着在开始订阅之前,replay$已经发出了 5 个值。在创建ReplaySubject时,我们指定最多存储 2 个值,并且不能超过 100ms。这意味着在 1000ms 后,订阅者 B 开始订阅时,由于replay$是 200ms 发出一个值,因此订阅者 B 只会接收到 1 个值。

有的同学看到这里,会感觉到ReplaySubject(1)是不是就等同于BehaviorSubject。但是,二者无论在概念上还是行为上,都是有所区别的。

首先概念上的区别是本质的,ReplaySubject只是缓存了最近的值,它仍然反映的是不断有值产生的流(「多值」),而BehaviorSubject反映的则是随时间变化的值(「单值」)。因此,BehaviorSubject需要传入一个初始值,然后这个值将不断变化,我们只能看见当前的值。

在行为上,由于ReplaySubject侧重于缓存,那么当它完成时,并不会影响我们继续观测它缓存的值。我们来看下面这个例子:

ReplaySubject在执行完complate时,我们订阅它仍然可以拿到缓存的值,而BehaviorSubject在执行完complate时,我们继续订阅它已经没有任何作用了。

AsyncSubject

虽然BehaviorSubjectReplaySubject都存储值,但AsyncSubject的工作方式却有所不同。AsyncSubject是一个Subject变体,其中仅Observable执行的最后一个值发送到其订阅者,并且仅在执行完成时发送(类似于rxjs/operators里面的last方法)。请参考下面的示例代码:

这次没有太多的事情发生。但是,让我们看一下执行步骤:

  1. 我们创建AsyncSubject的实例async$
  2. 我们通过订阅者 A 进行订阅
  3. async$发出 2 个值,仍然没有发生变化
  4. 我们创建一个订阅者 B
  5. 发出新的值,但是两个订阅者都没有任何反应
  6. async$执行complate完成,这时候将最后一个值发送给所有订阅者

从上面的代码示例可以看出来AsyncSubject会在执行complate后才送出最后一个值,其实这个行为跟 Promise 很像,都是等到事情结束后送出一个值。在 Promise 中,我们可以通过 resolve(value)声明任务完成,并将获得的值发送出去,然后再通过Promise.then()方法中处理得到的值。

(推荐微课:JavaScript微课

以上就是关于RXJS中四种Subject的区别的相关介绍了,希望对大家有所帮助。

Java跟Linux内核距离有多远

thbcm阅读(264)

本文中我们将站在非内核开发者的角度,给大家介绍应用和系统工程师如何梳理 Linux 内核代码。希望大家读完之后能有所收获,也希望更多的开发者能够关注到内核开发领域,毕竟连祖师爷 Linus 都表示内核维护者要后继无人了呀!

Java 离内核有多远?

测试环境版本信息:

Ubuntu(lsb_release -a) Distributor ID: UbuntuDescription: Ubuntu 19.10Release: 19.10
Linux(uname -a) Linux yahua 5.5.5 #1 SMP … x86_64 x86_64 x86_64 GNU/Linux
Java Openjdk jdk14

玩内核的人怎么也懂 Java?这主要得益于我学校的 Java 课程和毕业那会在华为做 Android 手机的经历,几个模块从 APP/Framework/Service/HAL/Driver 扫过一遍,自然对 Java 有所了解。

每次提起 Java,我都会想到一段有趣的经历。刚毕业到部门报到第一个星期,部门领导(在华为算是 Manager)安排我们熟悉 Android。我花了几天写了个 Android 游戏,有些类似连连看那种。开周会的时候,领导看到我的演示后,一脸不悦,质疑我的直接领导(在华为叫 PL,Project Leader)没有给我们讲明白部门的方向。

emm,我当时确实没明白所谓的熟悉 Android 是该干啥,后来 PL 说,是要熟悉 xxx 模块,APP 只是其中一部分。话说如果当时得到的是肯定,也许我现在就是一枚 Java 工程师了(哈哈手动狗头)。

(推荐教程:Java教程

从 launcher 说起

世界上最远的距离,是咱俩坐隔壁,我在看底层协议,而你在研究 spring……如果想拉近咱俩的距离,先下载 openjdk 源码(openjdk),然后下载 glibcglibc),再下载内核源码kernel)。

Java 程序到 JVM,这个大家肯定比我熟悉,就不班门弄斧了。

我们就从 JVM 的入口为例,分析 JVM 到内核的流程,入口就是 main 函数了(java.base/share/native/launcher/main.c):

JNIEXPORT int
main(int argc, char **argv)
{
    //中间省略一万行参数处理代码
    return JLI_Launch(margc, margv,
                   jargc, (const char**) jargv,
                   0, NULL,
                   VERSION_STRING,
                   DOT_VERSION,
                   (const_progname != NULL) ? const_progname : *margv,
                   (const_launcher != NULL) ? const_launcher : *margv,
                   jargc > 0,
                   const_cpwildcard, const_javaw, 0);
}

JLI_Launch 做了三件我们关心的事。

首先,调用 CreateExecutionEnvironment 查找设置环境变量,比如 JVM 的路径(下面的变量 jvmpath),以我的平台为例,就是 /usr/lib/jvm/java-14-openjdk-amd64/lib/server/libjvm.sowindow 平台可能就是 libjvm.dll

其次,调用 LoadJavaVM 加载 JVM,就是 libjvm.so 文件,然后找到创建 JVM 的函数赋值给 InvocationFunctions 的对应字段:

jboolean LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
void *libjvm;
//省略出错处理
    libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
    ifn->CreateJavaVM = (CreateJavaVM_t)
        dlsym(libjvm, "JNI_CreateJavaVM");
    ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
        dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
    ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
        dlsym(libjvm, "JNI_GetCreatedJavaVMs");
    return JNI_TRUE;
}

dlopendlsym 涉及动态链接,简单理解就是 libjvm.so 包含 JNI_CreateJavaVMJNI_GetDefaultJavaVMInitArgsJNI_GetCreatedJavaVMs 的定义,动态链接完成后,ifn->CreateJavaVMifn->GetDefaultJavaVMInitArgsifn->GetCreatedJavaVMs 就是这些函数的地址。

不妨确认下 libjvm.so 有这三个函数。

objdump -D /usr/lib/jvm/java-14-openjdk-amd64/lib/server/libjvm.so | grep -E 
"CreateJavaVM|GetDefaultJavaVMInitArgs|GetCreatedJavaVMs" | grep ":$"
00000000008fa9d0 <JNI_GetDefaultJavaVMInitArgs@@SUNWprivate_1.1>:
00000000008faa20 <JNI_GetCreatedJavaVMs@@SUNWprivate_1.1>:
00000000009098e0 <JNI_CreateJavaVM@@SUNWprivate_1.1>:

openjdk 源码里有这些实现的(hotspot/share/prims/下),有兴趣的同学可以继续钻研。

最后,调用 JVMInit 初始化 JVMload Java 程序。

JVMInit 调用 ContinueInNewThread,后者调用 CallJavaMainInNewThread。插一句,我是真的不喜欢按照函数调用的方式讲述问题,a 调用 b,b 又调用 c,简直是在浪费篇幅,但是有些地方跨度太大又怕引起误会(尤其对初学者而言)。相信我,注水,是真没有,我不需要经验+3 哈哈。

CallJavaMainInNewThread 的主要逻辑如下:

int CallJavaMainInNewThread(jlong stack_size, void* args) {
    int rslt;
    pthread_t tid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    if (stack_size > 0) {
        pthread_attr_setstacksize(&attr, stack_size);
    }
    pthread_attr_setguardsize(&attr, 0); // no pthread guard page on java threads
    if (pthread_create(&tid, &attr, ThreadJavaMain, args) == 0) {
        void* tmp;
        pthread_join(tid, &tmp);
        rslt = (int)(intptr_t)tmp;
    } 
   else {
        rslt = JavaMain(args);
    }
    pthread_attr_destroy(&attr);
    return rslt;
}

看到 pthread_create 了吧,破案了,Java 的线程就是通过 pthread 实现的。此处就可以进入内核了,但是我们还是先继续看看 JVMThreadJavaMain 直接调用了 JavaMain,所以这里的逻辑就是,如果创建线程成功,就由新线程执行 JavaMain,否则就知道在当前进程执行JavaMain

JavaMain 是我们关注的重点,核心逻辑如下:

int JavaMain(void* _args)
{
    JavaMainArgs *args = (JavaMainArgs *)_args;
    int argc = args->argc;
    char **argv = args->argv;
    int mode = args->mode;
    char *what = args->what;
    InvocationFunctions ifn = args->ifn;
    JavaVM *vm = 0;
    JNIEnv *env = 0;
    jclass mainClass = NULL;
    jclass appClass = NULL; // actual application class being launched
    jmethodID mainID;
    jobjectArray mainArgs;
    int ret = 0;
    jlong start, end;
    /* Initialize the virtual machine */
    if (!InitializeJVM(&vm, &env, &ifn)) {    //1
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }
    mainClass = LoadMainClass(env, mode, what);    //2
    CHECK_EXCEPTION_NULL_LEAVE(mainClass);
    mainArgs = CreateApplicationArgs(env, argv, argc);
    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");    //3
    CHECK_EXCEPTION_NULL_LEAVE(mainID);
    /* Invoke main method. */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);    //4
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
    LEAVE();
}

第 1 步,调用 InitializeJVM 初始化 JVMInitializeJVM 会调用 ifn->CreateJavaVM,也就是libjvm.so 中的 JNI_CreateJavaVM

第 2 步,LoadMainClass,最终调用的是 JVM_FindClassFromBootLoader,也是通过动态链接找到函数(定义在 hotspot/share/prims/ 下),然后调用它。

第 3 和第 4 步,Java 的同学应该知道,这就是调用 main 函数。

有点跑题了……我们继续以 pthread_create 为例看看内核吧。

其实,pthread_create 离内核还有一小段距离,就是 glibcnptl/pthread_create.c)。创建线程最终是通过 clone 系统调用实现的,我们不关心 glibc 的细节(否则又跑偏了),就看看它跟直接 clone 的不同。

(推荐微课:Java微课

以下关于线程的讨论从书里摘抄过来。

const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM
   | CLONE_SIGHAND | CLONE_THREAD
   | CLONE_SETTLS | CLONE_PARENT_SETTID
   | CLONE_CHILD_CLEARTID
   | 0);
__clone (&start_thread, stackaddr, clone_flags, pd, &pd->tid, tp, &pd->tid);

各个标志的说明如下表(这句话不是摘抄的。。。)。

标志 描述
CLONE_VM 与当前进程共享VM
CLONE_FS 共享文件系统信息
CLONE_FILES 共享打开的文件
CLONE_PARENT 与当前进程共有同样的父进程
CLONE_THREAD 与当前进程同属一个线程组,也意味着创建的是线程
CLONE_SYSVSEM 共享sem_undo_list
…… ……

与当前进程共享 VM、共享文件系统信息、共享打开的文件……看到这些我们就懂了,所谓的线程是这么回事。

Linux实际上并没有从本质上将进程和线程分开,线程又被称为轻量级进程(Low Weight Process, LWP),区别就在于线程与创建它的进程(线程)共享内存、文件等资源。

完整的段落如下(双引号扩起来的几个段落),有兴趣的同学可以详细阅读:

fork 传递至 _do_forkclone_flags 参数是固定的,所以它只能用来创建进程,内核提供了另一个系统调用 cloneclone 最终也调用 _do_fork 实现,与 fork 不同的是用户可以根据需要确定 clone_flags,我们可以使用它创建线程,如下(不同平台下 clone 的参数可能不同):

SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
 int __user *, parent_tidptr, int, tls_val, int __user *, child_tidptr)
{
return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}

Linux 将线程当作轻量级进程,但线程的特性并不是由 Linux 随意决定的,应该尽量与其他操作系统兼容,为此它遵循 POSIX 标准对线程的要求。所以,要创建线程,传递给 clone 系统调用的参数也应该是基本固定的。

创建线程的参数比较复杂,庆幸的是 pthread(POSIX thread)为我们提供了函数,调用pthread_create 即可,函数原型(用户空间)如下。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

第一个参数 thread 是一个输出参数,线程创建成功后,线程的 id 存入其中,第二个参数用来定制新线程的属性。新线程创建成功会执行 start_routine 指向的函数,传递至该函数的参数就是arg

pthread_create 究竟如何调用 clone 的呢,大致如下:

//来源: glibc
const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM
   | CLONE_SIGHAND | CLONE_THREAD
   | CLONE_SETTLS | CLONE_PARENT_SETTID
   | CLONE_CHILD_CLEARTID
   | 0);
__clone (&start_thread, stackaddr, clone_flags, pd, &pd->tid, tp, &pd->tid);

clone_flags 置位的标志较多,前几个标志表示线程与当前进程(有可能也是线程)共享资源,CLONE_THREAD 意味着新线程和当前进程并不是父子关系。

clone 系统调用最终也通过 _do_fork 实现,所以它与创建进程的 fork 的区别仅限于因参数不同而导致的差异,有以下两个疑问需要解释。

首先,vfork 置位了 CLONE_VM 标志,导致新进程对局部变量的修改会影响当前进程。那么同样置位了 CLONE_VMclone,也存在这个隐患吗?答案是没有,因为新线程指定了自己的用户栈,由 stackaddr 指定。copy_thread 函数的 sp参数就是 stackaddrchildregs->sp = sp 修改了新线程的 pt_regs,所以新线程在用户空间执行的时候,使用的栈与当前进程的不同,不会造成干扰。那为什么 vfork 不这么做,请参考 vfork 的设计意图。

其次,fork 返回了两次,clone 也是一样,但它们都是返回到系统调用后开始执行,pthread_create 如何让新线程执行 start_routine 的?start_routine 是由 start_thread 函数间接执行的,所以我们只需要清楚 start_thread 是如何被调用的。start_thread 并没有传递给 clone 系统调用,所以它的调用与内核无关,答案就在 __clone 函数中。

(推荐教程:Linux教程

为了彻底明白新进程是如何使用它的用户栈和 start_thread 的调用过程,有必要分析 __clone 函数了,即使它是平台相关的,而且还是由汇编语言写的。

/*i386*/
ENTRY (__clone)
movl    $-EINVAL,%eax
movl    FUNC(%esp),%ecx /* no NULL function pointers */
testl   %ecx,%ecx
jz  SYSCALL_ERROR_LABEL
movl    STACK(%esp),%ecx    /* no NULL stack pointers */    //1
testl   %ecx,%ecx
jz  SYSCALL_ERROR_LABEL
andl    $0xfffffff0, %ecx  /*对齐*/    //2
subl    $28,%ecx
movl    ARG(%esp),%eax  /* no negative argument counts */
movl    %eax,12(%ecx)
movl    FUNC(%esp),%eax
movl    %eax,8(%ecx)
movl    $0,4(%ecx)
pushl   %ebx    //3
pushl   %esi
pushl   %edi
movl    TLS+12(%esp),%esi    //4
movl    PTID+12(%esp),%edx
movl    FLAGS+12(%esp),%ebx
movl    CTID+12(%esp),%edi
movl    $SYS_ify(clone),%eax
movl    %ebx, (%ecx)    //5
int $0x80    //6
popl    %edi    //7
popl    %esi
popl    %ebx
test    %eax,%eax    //8
jl  SYSCALL_ERROR_LABEL
jz  L(thread_start)
ret    //9
L(thread_start):    //10
movl    %esi,%ebp   /* terminate the stack frame */
testl   $CLONE_VM, %edi
je  L(newpid)
L(haspid):
call    *%ebx
/*…*/

__clone (&start_thread, stackaddr, clone_flags, pd, &pd->tid, tp, &pd->tid) 为例,

FUNC(%esp) 对应 &start_thread

STACK(%esp) 对应 stackaddr

ARG(%esp) 对应 pd(新进程传递给 start_thread 的参数)。

  • 第 1 步,将新进程的栈 stackaddr 赋值给 ecx,确保它的值不为 0。
  • 第 2 步,将 pd&start_thread 和 0 存入新线程的栈,对当前进程的栈无影响。
  • 第 3 步,将当前进程的三个寄存器的值入栈,esp寄存器的值相应减12。
  • 第 4 步,准备系统调用,其中将 FLAGS+12(%esp) 存入 ebx,对应 clone_flags,将clone 的系统调用号存入 eax。
  • 第 5 步,将 clone_flags 存入新进程的栈中。
  • 第 6 步,使用 int 指令发起系统调用,交给内核创建新线程。截止到此处,所有的代码都是当前进程执行的,新线程并没有执行。
  • 从第 7 步开始的代码,当前进程和新线程都会执行。对当前进程而言,程序将它第 3 步入栈的寄存器出栈。但对新线程而言,它是从内核的 ret_from_fork 执行的,切换到用户态后,它的栈已经成为 stackaddr 了,所以它的 edi 等于 clone_flagsesi 等于 0,ebx 等于&start_thread
  • 系统调用的结果由 eax 返回,第 8 步判断 clone 系统调用的结果,对当前进程而言,clone 系统调用如果成功返回的是新线程在它的 pid namespace 中的 id,大于 0,所以它执行 ret 退出__clone 函数。对新线程而言,clone 系统调用的返回值等于 0,所以它执行L(thread_start) 处的代码。clone_flagsCLONE_VM 标志被置位的情况下,会执行 call *%ebxebx 等于&start_thread,至此 start_thread 得到了执行,它又调用了提供给pthread_createstart_routine,结束。

如此看来,Java JVM glibc 内核,好像也没有多远。

(推荐微课:Linux微课

以上就是关于JavaLinux内核距离有多远的相关介绍了,希望对大家有所帮助。

学习Vue3.0,你需要先了解一下Proxy

thbcm阅读(259)

产品经理身旁过,需求变更逃不过。 测试姐姐眯眼笑,今晚bug必然多。

据悉Vue3.0的正式版将要在本月(8月)发布,从发布到正式投入到正式项目中,还需要一定的过渡期,但我们不能一直等到Vue3正式投入到项目中的时候才去学习,提前学习,让你更快一步掌握Vue3.0,升职加薪迎娶白富美就靠它了。不过在学习Vue3之前,还需要先了解一下Proxy,它是Vue3.0实现数据双向绑定的基础。

了解代理模式

一个例子

作为一个单身钢铁直男程序员,小王最近逐渐喜欢上了前端小妹,不过呢,他又和前台小妹不熟,所以决定委托与前端小妹比较熟的UI小姐姐帮忙给自己搭桥引线。小王于是请UI小姐姐吃了一顿大餐,然后拿出一封情书委托它转交给前台小妹,情书上写的 我喜欢你,我想和你睡觉,不愧钢铁直男。不过这样写肯定是没戏的,UI小姐姐吃人嘴短,于是帮忙改了情书,改成了我喜欢你,我想和你一起在晨辉的沐浴下起床,然后交给了前台小妹。虽然有没有撮合成功不清楚啊,不过这个故事告诉我们,小王活该单身狗。

其实上面就是一个比较典型的代理模式的例子,小王想给前台小妹送情书,因为不熟所以委托UI小姐姐UI小姐姐相当于代理人,代替小王完成了送情书的事情。

引申

通过上面的例子,我们想想Vue的数据响应原理,比如下面这段代码

const xiaowang = {
  love: '我喜欢你,我想和你睡觉'
}
// 送给小姐姐情书
function sendToMyLove(obj) {
    console.log(obj.love)
    return '流氓,滚'
}
console.log(sendToMyLove(xiaowang))

如果没有UI小姐姐代替送情书,显示结局是悲惨的,想想Vue2.0的双向绑定,通过Object.defineProperty来监听的属性 get,set方法来实现双向绑定,这个Object.defineProperty就相当于UI小姐姐

const xiaowang = {
  loveLetter: '我喜欢你,我想和你睡觉'
}
// UI小姐姐代理
Object.defineProperty(xiaowang,'love', {
  get() {
    return xiaowang.loveLetter.replace('睡觉','一起在晨辉的沐浴下起床')
  }
})


// 送给小姐姐情书
function sendToMyLove(obj) {
    console.log(obj.love)
    return '小伙子还挺有诗情画意的么,不过老娘不喜欢,滚'
}
console.log(sendToMyLove(xiaowang))

虽然依然是一个悲惨的故事,因为送奔驰的成功率可能会更高一些。但是我们可以看到,通过Object.defineproperty可以对对象的已有属性进行拦截,然后做一些额外的操作。

存在的问题

Vue2.0中,数据双向绑定就是通过Object.defineProperty去监听对象的每一个属性,然后在get,set方法中通过发布订阅者模式来实现的数据响应,但是存在一定的缺陷,比如只能监听已存在的属性,对于新增删除属性就无能为力了,同时无法监听数组的变化,所以在Vue3.0中将其换成了功能更强大的Proxy

(推荐教程:Vue 2教程

了解Proxy

ProxyES6新推出的一个特性,可以用它去拦截js操作的方法,从而对这些方法进行代理操作。

用Proxy重写上面的例子

比如我们可以通过Proxy对上面的送情书情节进行重写:

const xiaowang = {
  loveLetter: '我喜欢你,我想和你睡觉'
}
const proxy = new Proxy(xiaowang, {
  get(target,key) {
    if(key === 'loveLetter') {
      return target[key].replace('睡觉','一起在晨辉的沐浴下起床')
    }
  }
})
// 送给小姐姐情书
function sendToMyLove(obj) {
    console.log(obj.loveLetter)
    return '小伙子还挺有诗情画意的么,不过老娘不喜欢,滚'
}
console.log(sendToMyLove(proxy))

再看这样一个场景

请分别使用Object.definePropertyProxy完善下面的代码逻辑.

function observe(obj, callback) {}


const obj = observe(
  {
    name: '子君',
    sex: '男'
  },
  (key, value) => {
    console.log(`属性[${key}]的值被修改为[${value}]`)
  }
)


// 这段代码执行后,输出 属性[name]的值被修改为[妹纸]
obj.name = '妹纸'


// 这段代码执行后,输出 属性[sex]的值被修改为[女]
obj.name = '女'

看了上面的代码,希望大家可以先自行实现以下,下面我们分别用Object.definePropertyProxy去实现上面的逻辑.

  1. 使用Object.defineProperty
/**
 * 请实现这个函数,使下面的代码逻辑正常运行
 * @param {*} obj 对象
 * @param {*} callback 回调函数
 */
function observe(obj, callback) {
  const newObj = {}
  Object.keys(obj).forEach(key => {
    Object.defineProperty(newObj, key, {
      configurable: true,
      enumerable: true,
      get() {
        return obj[key]
      },
      // 当属性的值被修改时,会调用set,这时候就可以在set里面调用回调函数
      set(newVal) {
        obj[key] = newVal
        callback(key, newVal)
      }
    })
  })
  return newObj
}


const obj = observe(
  {
    name: '子君',
    sex: '男'
  },
  (key, value) => {
    console.log(`属性[${key}]的值被修改为[${value}]`)
  }
)


// 这段代码执行后,输出 属性[name]的值被修改为[妹纸]
obj.name = '妹纸'


// 这段代码执行后,输出 属性[sex]的值被修改为[女]
obj.name = '女'

  1. 使用Proxy
function observe(obj, callback) {
  return new Proxy(obj, {
    get(target, key) {
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      callback(key, value)
    }
  })
}


const obj = observe(
  {
    name: '子君',
    sex: '男'
  },
  (key, value) => {
    console.log(`属性[${key}]的值被修改为[${value}]`)
  }
)


// 这段代码执行后,输出 属性[name]的值被修改为[妹纸]
obj.name = '妹纸'


// 这段代码执行后,输出 属性[sex]的值被修改为[女]
obj.name = '女'

通过上面两种不同实现方式,我们可以大概的了解到Object.definePropertyProxy的用法,但是当给对象添加新的属性的时候,区别就出来了,比如

// 添加编程网站
obj.gzh = 'W3Cschool编程狮'

使用Object.defineProperty无法监听到新增属性,但是使用Proxy是可以监听到的。对比上面两段代码可以发现有以下几点不同

  • Object.defineProperty监听的是对象的每一个属性,而Proxy监听的是对象自身
  • 使用Object.defineProperty需要遍历对象的每一个属性,对于性能会有一定的影响
  • Proxy对新增的属性也能监听到,但Object.defineProperty无法监听到。

初识Proxy

概念与语法

MDN中,关于Proxy是这样介绍的: Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。什么意思呢?Proxy就像一个拦截器一样,它可以在读取对象的属性,修改对象的属性,获取对象属性列表,通过for in循环等等操作的时候,去拦截对象上面的默认行为,然后自己去自定义这些行为,比如上面例子中的set,我们通过拦截默认的set,然后在自定义的set里面添加了回调函数的调用

Proxy的语法格式如下

/**
* target: 要兼容的对象,可以是一个对象,数组,函数等等
* handler: 是一个对象,里面包含了可以监听这个对象的行为函数,比如上面例子里面的`get`与`set`
* 同时会返回一个新的对象proxy, 为了能够触发handler里面的函数,必须要使用返回值去进行其他操作,比如修改值
*/
const proxy = new Proxy(target, handler)

在上面的例子里面,我们已经使用到了handler里面提供的getset方法了,接下来我们一一看一下handler里面的方法。

handler 里面的方法列表

handler里面的方法可以有以下这十三个,每一个都对应的一种或多种针对proxy代理对象的操作行为

  1. handler.get

当通过proxy去读取对象里面的属性的时候,会进入到get钩子函数里面

  1. handler.set

当通过proxy去为对象设置修改属性的时候,会进入到set钩子函数里面

  1. handler.has

当使用in判断属性是否在proxy代理对象里面时,会触发has,比如

   const obj = {
     name: '子君'
   }
   console.log('name' in obj)

  1. handler.deleteProperty

当使用delete去删除对象里面的属性的时候,会进入deleteProperty`钩子函数

  1. handler.apply

proxy监听的是一个函数的时候,当调用这个函数时,会进入apply钩子函数

  1. handle.ownKeys

当通过Object.getOwnPropertyNames,Object.getownPropertySymbols,Object.keys,Reflect.ownKeys去获取对象的信息的时候,就会进入ownKeys这个钩子函数

  1. handler.construct

当使用new操作符的时候,会进入construct这个钩子函数

  1. handler.defineProperty

当使用Object.defineProperty去修改属性修饰符的时候,会进入这个钩子函数

  1. handler.getPrototypeOf

当读取对象的原型的时候,会进入这个钩子函数

  1. handler.setPrototypeOf

当设置对象的原型的时候,会进入这个钩子函数

  1. handler.isExtensible

当通过Object.isExtensible去判断对象是否可以添加新的属性的时候,进入这个钩子函数

  1. handler.preventExtensions

当通过Object.preventExtensions去设置对象不可以修改新属性时候,进入这个钩子函数

  1. handler.getOwnPropertyDescriptor

在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时会进入这个钩子函数

Proxy提供了十三种拦截对象操作的方法,本文主要挑选其中一部分在Vue3中比较重要的进行说明,其余的建议可以直接阅读MDN关于Proxy的介绍。

详细介绍

get

当通过proxy去读取对象里面的属性的时候,会进入到get钩子函数里面

当我们从一个proxy代理上面读取属性的时候,就会触发get钩子函数,get函数的结构如下

/**
 * target: 目标对象,即通过proxy代理的对象
 * key: 要访问的属性名称
 * receiver: receiver相当于是我们要读取的属性的this,一般情况
 *           下他就是proxy对象本身,关于receiver的作用,后文将具体讲解
 */
handle.get(target,key, receiver)

示例

我们在工作中经常会有封装axios的需求,在封装过程中,也需要对请求异常进行封装,比如不同的状态码返回的异常信息是不同的,如下是一部分状态码及其提示信息:

// 状态码提示信息
const errorMessage = {
  400: '错误请求',
  401: '系统未授权,请重新登录',
  403: '拒绝访问',
  404: '请求失败,未找到该资源'
}


// 使用方式
const code = 404
const message = errorMessage[code]
console.log(message)

但这存在一个问题,状态码很多,我们不可能每一个状态码都去枚举出来,所以对于一些异常状态码,我们希望可以进行统一提示,如提示为系统异常,请联系管理员,这时候就可以使用Proxy对错误信息进行代理处理

// 状态码提示信息
const errorMessage = {
  400: '错误请求',
  401: '系统未授权,请重新登录',
  403: '拒绝访问',
  404: '请求失败,未找到该资源'
}


const proxy = new Proxy(errorMessage, {
  get(target,key) {
    const value = target[key]
    return value || '系统异常,请联系管理员'
  }
})


// 输出 错误请求
console.log(proxy[400])
// 输出 系统异常,请联系管理员
console.log(proxy[500])

set

当为对象里面的属性赋值的时候,会触发set

当给对象里面的属性赋值的时候,会触发set,set函数的结构如下

/**
 * target: 目标对象,即通过proxy代理的对象
 * key: 要赋值的属性名称
 * value: 目标属性要赋的新值
 * receiver: 与 get的receiver 基本一致
 */
handle.set(target,key,value, receiver)

示例

某系统需要录入一系列数值用于数据统计,但是在录入数值的时候,可能录入的存在一部分异常值,对于这些异常值需要在录入的时候进行处理, 比如大于100的值,转换为100, 小于0的值,转换为0, 这时候就可以使用proxyset,在赋值的时候,对数据进行处理

const numbers = []
const proxy = new Proxy(numbers, {
  set(target,key,value) {
    if(value < 0) {
      value = 0
    }else if(value > 100) {
      value = 100
    }
    target[key] = value
    // 对于set 来说,如果操作成功必须返回true, 否则会被视为失败
    return true
  }
})


proxy.push(1)
proxy.push(101)
proxy.push(-10)
// 输出 [1, 100, 0]
console.log(numbers)

对比Vue2.0

在使用Vue2.0的时候,如果给对象添加新属性的时候,往往需要调用$set, 这是因为Object.defineProperty只能监听已存在的属性,而新增的属性无法监听,而通过$set相当于手动给对象新增了属性,然后再触发数据响应。但是对于Vue3.0来说,因为使用了Proxy, 在他的set钩子函数中是可以监听到新增属性的,所以就不再需要使用$set

const obj = {
  name: '子君'
}
const proxy = new Proxy(obj, {
  set(target,key,value) {
    if(!target.hasOwnProperty(key)) {
      console.log(`新增了属性${key},值为${value}`)
    }
    target[key] = value
    return true
  }
})
// 新增 公众号 属性
// 输出 新增了属性gzh,值为前端有的玩
proxy.gzh = '前端有的玩'

has

当使用in判断属性是否在proxy代理对象里面时,会触发has

/**
 * target: 目标对象,即通过proxy代理的对象
 * key: 要判断的key是否在target中
 */
 handle.has(target,key)

示例

一般情况下我们在js中声明私有属性的时候,会将属性的名字以_开头,对于这些私有属性,是不需要外部调用,所以如果可以隐藏掉是最好的,这时候就可以通过has在判断某个属性是否在对象时,如果以_开头,则返回false

const obj =  {
  publicMethod() {},
  _privateMethod(){}
}
const proxy = new Proxy(obj, {
  has(target, key) {
    if(key.startsWith('_')) {
      return false
    }
    return Reflect.get(target,key)
  }
})


// 输出 false
console.log('_privateMethod' in proxy)


// 输出 true
console.log('publicMethod' in proxy)

deleteProperty

当使用delete去删除对象里面的属性的时候,会进入deleteProperty`拦截器

/**
 * target: 目标对象,即通过proxy代理的对象
 * key: 要删除的属性
 */
 handle.deleteProperty(target,key)

示例

现在有一个用户信息的对象,对于某些用户信息,只允许查看,但不能删除或者修改,对此使用Proxy可以对不能删除或者修改的属性进行拦截并抛出异常,如下

const userInfo = {
  name: '子君',
  gzh: '前端有的玩',
  sex: '男',
  age: 22
}
// 只能删除用户名和公众号
const readonlyKeys = ['name', 'gzh']
const proxy = new Proxy(userInfo, {
  set(target,key,value) {
    if(readonlyKeys.includes(key)) {
      throw new Error(`属性${key}不能被修改`)
    }
    target[key] = value
    return true
  },
   deleteProperty(target,key) {
    if(readonlyKeys.includes(key)) {
      throw new Error(`属性${key}不能被删除`)
      return
    }
    delete target[key]
    return true
  }
})
// 报错 
delete proxy.name

对比Vue2.0

其实与$set解决的问题类似,Vue2.0是无法监听到属性被删除的,所以提供了$delete用于删除属性,但是对于Proxy,是可以监听删除操作的,所以就不需要再使用$delete

其他操作

在上文中,我们提到了Proxyhandler提供了十三个函数,在上面我们列举了最常用的三个,其实每一个的用法都是基本一致的,比如ownKeys,当通过Object.getOwnPropertyNames,Object.getownPropertySymbols,Object.keys,Reflect.ownKeys去获取对象的信息的时候,就会进入ownKeys这个钩子函数,使用这个我们就可以对一些我们不像暴露的属性进行保护,比如一般会约定_开头的为私有属性,所以在使用Object.keys去获取对象的所有key的时候,就可以把所有_开头的属性屏蔽掉。关于剩余的那些属性,建议大家多去看看MDN中的介绍。

Reflect

在上面,我们获取属性的值或者修改属性的值都是通过直接操作target来实现的,但实际上ES6已经为我们提供了在Proxy内部调用对象的默认行为的API,即Reflect。比如下面的代码

const obj = {}
const proxy = new Proxy(obj, {
  get(target,key,receiver) {
    return Reflect.get(target,key,receiver)
  }
})

大家可能看到上面的代码与直接使用target[key]的方式没什么区别,但实际上Reflect的出现是为了让Object上面的操作更加规范,比如我们要判断某一个prop是否在一个对象中,通常会使用到in,即

const obj = {name: '子君'}
console.log('name' in obj)

但上面的操作是一种命令式的语法,通过Reflect可以将其转变为函数式的语法,显得更加规范

Reflect.has(obj,'name')

除了has,get之外,其实Reflect上面总共提供了十三个静态方法,这十三个静态方法与Proxyhandler上面的十三个方法是一一对应的,通过将ProxyReflect相结合,就可以对对象上面的默认操作进行拦截处理,当然这也就属于函数元编程的范畴了。

(推荐微课:Vue 2.x 微课

总结

有的同学可能会有疑惑,我不会ProxyReflect就学不了Vue3.0了吗?其实懂不懂这个是不影响学习Vue3.0的,但是如果想深入 去理解Vue3.0,还是很有必要了解这些的。比如经常会有人在使用Vue2的时候问,为什么我数组通过索引修改值之后,界面没有变呢?当你了解到Object.defineProperty的使用方式与限制之后,就会恍然大悟,原来如此。

来源公众号:前端有点玩

文章来源链接:mp.weixin.qq.com/s/JQDA6bP805xuN-tzuimtAA

作者:前端进击者

以上就是关于学习Vue3.0,你需要先了解一下Proxy的相关介绍了,希望对大家有所帮助。

跨平台C++开发工具Qt,开发GUI之前你可以了解一下Qt类

thbcm阅读(250)

Qt(发音为“ cute”,而不是“ cu-tee”)是一个跨平台框架,通常用作图形工具包,它不仅创建CLI应用程序中也非常有用。而且它也可以在三种主要的台式机操作系统以及移动操作系统(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式设备,Android(Necessitas)和 iOS 的端口上运行。

(推荐教程:C++教程

Qt广泛使用子类,尤其是在Widgets模块中。下图显示了其中一些继承关系:

QObjectQt中最基本的类。Qt中的大多数类都由衍生。QObject提供了一些非常强大的功能,例如:

  • 对象名称 :您可以为对象设置名称(字符串),然后按名称搜索对象。
  • 子系统(在以下部分中介绍)
  • 信号和插槽(在下一章中介绍)
  • 事件管理

小部件能够响应事件并使用子系统以及信号和插槽机制。所有小部件都继承自QObject。最基本的小部件是QWidgetQWidget包含用于描述窗口或窗口小部件的大多数属性,例如位置和大小,鼠标光标,工具提示等。

备注 :在Qt中,小部件也可以是窗口。在上一节中,我们显示了一个按钮,它是一个小部件,但它直接显示为窗口。不需要 “QWindow” 类。

几乎所有图形元素都从QWidget继承。我们可以列举例如:

QAbstractButton,所有按钮类型的基类 
QPushButton  
QCheckBox 
QRadioButton 。
QFrame,显示框架  
QLabel,显示文本或图片  

进行此子类是为了促进属性管理。大小和光标等共享属性可以在其他图形组件上使用,并且QAbstractButton提供了所有按钮共享的基本属性。

(推荐微课:C++微课

文章来源:www.toutiao.com/a6861850838445326852/

以上就是关于跨平台C++开发工具Qt的相关介绍了,希望对大家有所帮助。

10款强大的数据挖掘软件,你值得拥有

thbcm阅读(254)

数据是一种相当重要的资源,我们很多时候需要收集分析数据。然而,大多数数据是非结构化的,因此需要一个过程和方法从数据中提取有用的信息,并将其转换为可理解的和可用的形式。本文就给大家介绍10款强大的数据挖掘工具。

1、KNIME

KNIME可以完成常规的数据分析,进行数据挖掘,常见的数据挖掘算法,如回归、分类、聚类等等都有。而且它引入很多大数据组件,如HiveSpark等等。它还通过模块化的数据流水线概念,集成了机器学习和数据挖掘的各种组件,能够帮助商业智能和财务数据分析。

2、Rapid Miner

Rapid Miner,也叫YALE,以Java编程语言编写,通过基于模板的框架提供高级分析,是用于机器学习和数据挖掘实验的环境,用于研究和实践数据挖掘。使用它,实验可以由大量的可任意嵌套的操作符组成,而且用户无需编写代码,它已经有许多模板和其他工具,帮助轻松地分析数据。

3、SAS Data Mining

SAS Data Mining是一个商业软件,它为描述性和预测性建模提供了更好的理解数据的方法。SAS Data Mining有易于使用的GUI,有自动化的数据处理工具。此外,它还包括可升级处理、自动化、强化算法、建模、数据可视化和勘探等先进工具。

4、IBM SPSS Modeler

IBM SPSS Modeler适合处理文本分析等大型项目,它的可视化界面做得很好。它允许在不编程的情况下生成各种数据挖掘算法,而且可以用于异常检测、CARMACox回归以及使用多层感知器进行反向传播学习的基本神经网络。

5、Orange

Orange是一个基于组件的数据挖掘和机器学习软件套件,它以Python编写。它的数据挖掘可以通过可视化编程或Python脚本进行,它还包含了数据分析、不同的可视化、从散点图、条形图、树、到树图、网络和热图的特征。

6、Rattle

Rattle是一个在统计语言R编写的开源数据挖掘工具包,是免费的。它提供数据的统计和可视化汇总,将数据转换为便于建模的表单,从数据中构建无监督模型和监督模型,以图形方式呈现模型性能,并对新数据集进行评分。它支持的操作系统有GNU / LinuxMacintosh OS XMS / Windows

7、Python

Python是一个免费且开放源代码的语言,它的学习曲线很短,便于开发者学习和使用,往往很快就能开始构建数据集,并在几分钟内完成极其复杂的亲和力分析。只要熟悉变量、数据类型、函数、条件和循环等基本编程概念,就能轻松使用Python做业务用例数据可视化。

(推荐教程:python教程

8、Oracle Data Mining

Oracle数据挖掘功能让用户能构建模型来发现客户行为目标客户和开发概要文件,它让数据分析师、业务分析师和数据科学家能够使用便捷的拖放解决方案处理数据库内的数据, 它还可以为整个企业的自动化、调度和部署创建SQLPL / SQL脚本。

9、Kaggle

Kaggle是全球最大的数据科学社区,里面有来自世界各地的统计人员和数据挖掘者竞相制作最好的模型,相当于是数据科学竞赛的平台,基本上很多问题在其中都可以找到,感兴趣的朋友可以去看看。

10、Framed Data

最后介绍的Framed Data是一个完全管理的解决方案,它在云中训练、优化和存储产品的电离模型,并通过API提供预测,消除基础架构开销。也就是说,框架数据从企业获取数据,并将其转化为可行的见解和决策,这样使得用户很省心。

以上就是关于10款强大的数据挖掘软件的相关介绍了,希望对大家有所帮助。

高效使用GitHub的4个小技巧

thbcm阅读(271)

作为一名程序员,日常会经常使用到GitHub,在使用中我发现了4个小技巧,能提高我的效率。在这里给大家分享一下。

技巧1:用文件查找器快速、轻松地搜索仓库中的文件

GitHub提供使用Git进行软件开发和版本控制的托管,有数千个存储库、项目和文件。因此,如何高效地在GitHub上搜索文件是非常重要的。第一个技巧是使用GitHub在仓库中提供的快捷方式搜索仓库中的文件。

![更有效使用GitHub的4个技巧](https://atts.w3cschool.cn/attachments/image/20200818/1597734667167201.gif “更有效使用GitHub的4个技巧”)

如上图所示,在运行时的仓库页面,按键盘上的 t 键,那么GitHub就会激活文件查找器。然后你只需要输入目标文件名,比如ServiceProvider.cs 文件,文件查找器就会显示你想要的文件。

技巧2:使用搜索限定词搜索你想要的目标

现在,假设你不知道目标文件位于哪个仓库中,或者你想在组织中查找某个用户。然后,你可以使用搜索限定词在GitHub的任何页面上搜索所需的目标。

![更有效使用GitHub的4个技巧](https://atts.w3cschool.cn/attachments/image/20200818/1597734688699051.gif “更有效使用GitHub的4个技巧”) 如你在上面看到的,我们在Marketplace页面上,并希望在dotnet组织中搜索ConfigurationBuilder.cs文件。然后,你只需要输入搜索限定词即可表明此目的。

org:dotnet filename:ConfigurationBuilder.cs

GitHub就会显示你想要的文件。

技巧3:在Github个人资料页面上启用自述文件

是的,看来Github不仅在6月份重新设计了GitHub的UI,还增加了一些小秘密。你可以在Github个人资料页面上获得个人资料README。它是你自己的自述文件,而不是项目的自述文件。有趣!

![更有效使用GitHub的4个技巧](https://atts.w3cschool.cn/attachments/image/20200818/1597734709396913.png “更有效使用GitHub的4个技巧”)

启用它非常简单,你只需要创建一个与你的Github账户用户名相同的新仓库,这是一个特殊的仓库,你可以用它在你的Github配置文件中添加README.md

![更有效使用GitHub的4个技巧](https://atts.w3cschool.cn/attachments/image/20200818/1597734721435634.png “更有效使用GitHub的4个技巧”)

技巧4:将徽章添加到GitHub仓库中

从技术上讲,这不是GitHub提供的功能。但是,使用此小技巧可以使你的GitHub仓库页面更专业,并反映你项目的当前状态。

一些社区为开发者提供了自己的徽章,例如,你可以从Azure DevOps中获得如下所示的构建/部署状态徽章。

![更有效使用GitHub的4个技巧](https://atts.w3cschool.cn/attachments/image/20200818/1597734768786544.png “更有效使用GitHub的4个技巧”)

另一方面,你可以从一些供应商那里获得更通用的徽章,比如shields.ioshield.io 可以读取你项目的状态并生成相应的徽章。此外,你还可以在 shield.io 上创建自己的徽章。

![更有效使用GitHub的4个技巧](https://atts.w3cschool.cn/attachments/image/20200818/1597734791759739.gif “更有效使用GitHub的4个技巧”)

然后,你只需要将markdown链接复制并粘贴到GitHub仓库中的README.md文件中。

![更有效使用GitHub的4个技巧](https://atts.w3cschool.cn/attachments/image/20200818/1597734802918577.png “更有效使用GitHub的4个技巧”)

(推荐教程:Github教程

以上就是关于高效使用 GitHub 的 4 个小技巧的相关介绍了,希望对大家有所帮助。

联系我们