如何通过Python创建Twitter应用程序和API接口

来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:9min本文说明了如何运用Python API使用Twitter库连接到Twitter帐户。具体来说,此API允许用户提取与特定Twitter帐户相关的大量数据,以及通过 Python 管理 Twitter 的帖子(例如一次发布多个推文)。 即使你是 Python 的初学者, 使用 Twitter Python 依赖包在分析方面也非常有用。例如,虽然Web开发人员可能更倾向于使用PHP等语言来连接API,但Python可以更灵活地分析数据的趋势和统计数据。因此,数据科学家和其他分析师会发现Python更适合这个目的。我们将从Python连接到Twitter API的一些基本步骤开始,然后查看如何流式传输所需的数据。需要注意的是,虽然Twitter库(以及其他Python库,如Tweepy和Twython)可以使用数据执行大量不同的任务,但我们将专注于本文中的一些更基本(和有用)的查询,解决以下问题:使用适当的凭据将Python连接到Twitter API下载与特定帐户关联的推文下载帐户的所有关注和关注用户的列表一次发布多条推文在Twitter上自定义搜索特定术语的实例。1.将Python连接到Twitter API本教程使用iPython作为Python接口连接到Twitter。为了连接到API,我们需要获取Consumer Key,Consumer Secret和Access Token Secret。要获得这些,您需要在apps.twitter.com上登录您的帐户。到那里后,系统会提示您创建一个应用程序:创建应用程序后,您将在Keys and Access Tokens部分下找到相关的密钥和令牌。首先,我们在终端中安装python-twitter库,如下所示:pip install python twitter完成后,我们导入Twitter库并输入凭据,如下所示:import twitterapi = twitter.Api(consumer_key=‘your_consumer_key’, consumer_secret=‘your_consumer_secret’, access_token_key=‘your_access_token_key’, access_token_secret=‘your_access_token_secret’)print(api.VerifyCredentials())输入正确的凭证后,与API的连接即告完成,我们现在可以通过Python平台控制我们的Twitter帐户!2.下载用户时间线现在我们已经将Python连接到Twitter API,我们可以继续开始远程使用不同的Twitter功能。例如,如果我们希望下载推文的用户时间线,我们使用如下方法(并指定相应帐户的屏幕名称),然后使用该功能显示结果:statuses = api.GetUserTimeline(screen_name=‘Michael Grogan’)print([s.text for s in statuses])一旦我们输入了上述内容,我们就会在Python界面中看到相应的时间轴:3.下载以下和以下联系人Twitter库还使我们能够下载特定用户正在关注的帐户列表,以及作为该特定用户的关注者的帐户。为此,我们使用前者,后者使用:users = api.GetFriends()print([u.name for u in users])followers = api.GetFollowers()print([f.name for f in followers])请注意,我们还可以设置我们希望获取的用户数的上限。例如,如果我们希望为任何特定帐户获取100个关注者,我们可以通过向total_count函数添加变量来实现,如下所示:followers = api.GetFollowers(total_count=100)print([f.name for f in followers])4.发布多个推文使用Twitter API的一个巧妙之处是能够一次发布多条推文。例如,我们可以使用该命令同时发布以下两条推文(同样,使用该功能进行确认)。一旦我们转到相关的Twitter帐户,我们就会看到这两条推文都已发布:status = api.PostUpdate(‘How to calculate the Variance Inflation Factor in R: http://www.michaeljgrogan.com/ordinary-least-squares-an-analysis-of-stock-returns/ #rstats #datascience #programming’)print(status.text)status = api.PostUpdate(’#BigData Scientists Earn 10X to 15X More Money Compared to Engineers, CAs http://bit.ly/1NoAgto #datascience’)print(status.text)5.搜索推文Twitter库中包含的getsearch()函数是一个特别强大的工具。此功能允许我们在Twitter上搜索特定术语。请注意,这适用于已输入特定术语的所有用户,而不仅仅是我们在Python中提供凭据的帐户。例如,让我们在Python中搜索术语“bigdata”。我们设置的参数是自2016年11月21日起包含该术语的推文,我们选择限制流式传输的推文数量为10:api.GetSearch(term=‘bigdata’, since=2016-11-21, count=10)请注意,我们可以通过各种方式自定义GetSearch()函数,具体取决于我们希望如何提取数据。例如,如果没有指定日期,这将花费更长的时间来流式传输,我们也可以选择在2016年11月21日之前收集包含术语“bigdata”的推文,如下所示:api.GetSearch(term=‘bigdata’, until=2016-11-21, count=10)值得注意的是,此函数在我们在until变量下指定的日期之前下载最多7天的数据。此外,我们不仅限于仅通过术语搜索GetSearch。例如,假设我们希望通过地理位置搜索推文 - 特别是自11月18日以来在纽约时代广场1英里范围内发送的推文(请注意,距离可以使用mi或km分别以英里或公里格式化):api.GetSearch(geocode=“40.758896,-73.985130,1mi”, since=2016-11-18)运行该函数后,我们看到Python返回以下推文(当然,还有什么更好的地方可以找到Donald Trump!):GetSearch()如何使用这些数据?如前所述,Python对流式社交网络数据极具吸引力的一个特殊原因是能够对我们收集的信息进行深入的数据分析。例如,我们已经看到了如何使用位置搜索推文GetSearch。随着机器学习在分析社交媒体趋势的数据科学家中风靡一时,在这一领域变得非常流行的一种特殊技术是网络分析。这种技术实际上可以显示分散的数据(或节点)以形成紧密的网络,通常某些节点被证明是一个焦点。例如,假设我们要分析全球十个不同地点的1000条最受欢迎的推文。在随机的某一天,尽管我们看到网络中不同推文之间存在一些相关性,但我们可能仍会发现伦敦推文上的主题标签与纽约推文的主题标签差别很大。然而,在美国大选之夜或英国退欧这样的重大世界事件中,当Twitter对这一特定主题发展趋势时,发现网络往往更加紧密,因此,在这种情况下,情感分析的机会更多。一个场景,例如,很明显谁将赢得总统职位,或英国投票退出欧盟。人们通常会看到网络以不同的方式聚集,这取决于趋势推文,因为可以获得更多的实时信息。这只是Python的优势之一。虽然使用API连接到Twitter(可以在许多编程语言中完成)是一回事,但是能够使用分析以有意义的方式对数据进行排序是另一回事。可以通过Python使用机器学习技术来分析来自社交网络的流数据并从该数据进行有意义的预测。结论模块文档提供了可用于Python下载,过滤和操作数据的不同功能的非常详细的描述。最后,虽然我们还研究了使用API直接发布到Twitter的方法,但上述技术在分析趋势时尤其有用,例如标签流行度,按位置搜索术语的频率等等。在这方面,通过Python与Twitter交互对于那些希望对收集的信息实施数据分析技术的人特别有用。当然,与Twitter的API交互可以使用多种语言完成,具体取决于您的最终目标。如果目标是Web开发或设计,那么PHP或Ruby可能是您最好的选择。但是,如果您的目标是使用从Twitter获得的数据进行有意义的分析,那么Python就是不二之选。 ...

April 15, 2019 · 1 min · jiezi

学习如何在PostgreSQL中管理安全性

学习如何在PostgreSQL中管理安全性来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:17min在处理安全性时,记住这些级别以便以有组织的方式处理与安全相关的问题。· 绑定地址:listen_addresses在文件中 postgresql.conf· 基于主机的访问控制:pg_hba.conf文件· 实例级权限:用户,角色,数据库创建,登录和复制· 数据库级权限:连接,创建模式等· 架构级权限:使用架构并在架构内创建对象· 表级权限:选择,插入,更新等· 列级权限:允许或限制对列的访问· 行级安全性:限制对行的访问为了读取值, PostgreSQL必须确保我们在每个级别都有足够的权限。整个权限链必须是正确的。在本文中,您将学习处理SSL,列级安全性和配置默认权限等的过程。了解绑定地址和连接配置PostgreSQL服务器时,需要做的第一件事就是定义远程访问。默认情况下,PostgreSQL不接受远程连接。重要的是由于PostgreSQL不能监听端口,所以导致它接收不到连接。如果我们尝试连接,错误消息实际上将来自操作系统。假设有一个使用默认配置的数据库服务器192.168.0.123,将发生以下情况:iMac:~ hs$ telnet 192.168.0.123 5432 Trying 192.168.0.123… telnet: connect to address 192.168.0.123: Connection refused telnet: Unable to connect to remote hostTelnet尝试在端口上创建连接,5432并立即被远程框拒绝。从外面看,看起来PostgreSQL根本就没有运行。成功连接的关键可以在postgresql.conf文件中找到:# - Connection Settings - # shop_addresses =‘localhost’ # 要监听的IP地址; # 逗号分隔的地址列表; # defaults为’localhost’; 使用’*‘表示所有# (更改需要重启)该listen_addresses设置将告诉PostgreSQL要监听的地址。从技术上讲,这些地址是绑定地址。这究竟意味着什么?假设我们的机器中有四个IP地址。我们这些IP地址只监听其中的三个。PostgreSQL只考虑这三个IP的请求,而不考虑第四个。如果我们放入 *,PostgreSQL会监听分配给您机器的每个IP。但是,有更多与连接管理相关的设置对于理解非常重要。它们如下:#port = 5432 # (change requires restart) max_connections = 100 # (change requires restart) # Note: Increasing max_connections costs ~400 bytes of # shared memory per # connection slot, plus lock space # (see max_locks_per_transaction). #superuser_reserved_connections = 3 # (change requires restart) #unix_socket_directories = ‘/tmp’ # comma-separated list of directories # (change requires restart) #unix_socket_group = ’’ # (change requires restart) #unix_socket_permissions = 0777 # begin with 0 to use octal notation # (change requires restart)首先,PostgreSQL侦听单个TCP端口,默认值为5432。请记住,PostgreSQL只会侦听单个端口。每当有请求进入时,postmaster将创建一个新进程来处理连接。默认情况下,最多允许100个普通连接。最重要的是,为超级用户保留了三个额外的连接。这意味着我们可以拥有97个连接加上三个超级用户或100个超级用户连接。处理SSLPostgreSQL允许我们加密服务器和客户端之间的传输。加密非常有用,特别是如果我们进行远距离通信。SSL提供了一种简单而安全的方式来确保没有人能够收听您的通信。在本节中,我们将学习如何设置SSL。首先要做的是在服务器启动时将ssl参数设置on为postgresql.conf文件。在下一步中,我们可以将SSL证书放入$PGDATA目录中。如果我们不希望证书位于其他目录中,请更改以下参数:#ssl_cert_file = ‘server.crt’ # (change requires restart) #ssl_key_file = ‘server.key’ # (change requires restart) #ssl_ca_file = ’’ # (change requires restart) #ssl_crl_file = ’’ # (change requires restart)如果我们要使用自签名证书,请执行以下步骤:openssl req -new -text -out server.req回答OpenSSL提出的问题。确保我们输入本地主机名作为通用名称。我们可以将密码留空。此调用将生成一个受密码保护的密钥; 它不会接受长度少于四个字符的密码短语。要删除密码(如果要自动启动服务器,则必须如此),请运行以下命令:openssl rsa -in privkey.pem -out server.keyrm privkey.pem输入旧密码以解锁现有密钥。现在,执行此操作将证书转换为自签名证书,并将密钥和证书复制到服务器将查找的位置:openssl req -x509 -in server.req -text -key server.key -out server.crt执行此操作后,请确保文件具有正确的权限集:chmod og-rwx server.key一旦将适当的规则放入pg_hba.conf文件中,我们就可以使用SSL连接到您的服务器。要验证我们确实使用SSL,请考虑签出该pg_stat_ssl功能。它将告诉我们每个连接以及它是否使用SSL,它将提供有关加密的一些重要信息:test=# \d pg_stat_sslView “pg_catalog.pg_stat_ssl"Column | Type | Modifiers ————-+———-+———– pid | integer | ssl | boolean | version | text | cipher | text | bits | integer | compression | boolean |clientdn | text |如果ssl进程的字段包含true; PostgreSQL做了我们期望它做的事情:postgres=# select * from pg_stat_ssl; -[ RECORD 1 ] —————————- pid | 20075ssl | t version | TLSv1.2cipher | ECDHE-RSA-AES256-GCM-SHA384 bits | 256compression | f clientdn |处理实例级安全性到目前为止,我们已经配置了绑定地址,我们告诉PostgreSQL使用哪种IP范围进行身份验证。到目前为止,配置纯粹与网络相关。在下一步中,我们可以将注意力转移到实例级别的权限。最重要的是要知道PostgreSQL中的用户存在于实例级别。如果我们创建一个用户,它不仅在一个数据库中可见; 它可以被所有数据库看到。用户可能只具有访问单个数据库的权限,但基本上用户是在实例级别创建的。对于那些刚接触PostgreSQL的人来说,还有一件事要记住:用户和角色是一回事。CREATE ROLE和CREATE USER子句有不同的默认值(字面上,唯一的区别是LOGIN默认情况下角色没有得到属性),但在一天结束时,用户和角色是相同的。因此,CREATE ROLE和CREATE USER子句支持完全相同的语法:test=# \h CREATE USERCommand: CREATE USERDescription: define a new database roleSyntax:CREATE USER name [ [ WITH ] option [ … ] ]where option can be:SUPERUSER | NOSUPERUSER| CREATEDB | NOCREATEDB| CREATEROLE | NOCREATEROLE| INHERIT | NOINHERIT| LOGIN | NOLOGIN| REPLICATION | NOREPLICATION| BYPASSRLS | NOBYPASSRLS| CONNECTION LIMIT connlimit| [ ENCRYPTED ] PASSWORD ‘password’| VALID UNTIL ’timestamp’| IN ROLE role_name [, …]| IN GROUP role_name [, …]| ROLE role_name [, …]| ADMIN role_name [, …]| USER role_name [, …]| SYSID uid让我们逐个讨论这些语法元素。我们首先看到的是用户可以是超级用户或普通用户。如果某人被标记为a SUPERUSER ,则不再有普通用户必须面对的任何限制。A SUPERUSER 可以根据需要删除对象(数据库等)。下一个重要的事情是它在实例级别上获取创建新数据库的权限。规则是这样的:创建者总是自动成为对象的所有者(除非另有说明,否则可以使用该CREATE DATABASE子句)。美丽的是,对象所有者也可以再次丢弃一个对象。下一个重要的是INHERIT/ NOINHERITclause。如果INHERIT设置了子句(这是默认值),则用户可以继承其他用户的权限。使用继承权限允许我们使用角色,这是抽象权限的好方法。例如,我们可以创建角色bookkeeper并使许多其他角色继承bookkeeper。我们的想法是bookkeeper,即使我们有很多人从事会计工作,我们也只需要告诉PostgreSQL一次允许做什么。该LOGIN/ NOLOGIN子句定义一个角色是否可以登录到该实例。在理论介绍之后,是时候实际创建用户并看看在实际示例中如何使用事物:test=# CREATE ROLE bookkeeper NOLOGIN; CREATE ROLE test=# CREATE ROLE joe LOGIN; CREATE ROLE test=# GRANT bookkeeper TO joe; GRANT ROLE这里做的第一件事bookkeeper是创建一个名为的角色。请注意,我们不希望人们以身份登录bookkeeper,因此角色标记为NOLOGIN。另请注意,NOLOGIN如果使用该CREATE ROLE子句,则为默认值。如果您更喜欢该CREATE USER子句,则默认设置为LOGIN。然后,joe创建角色并标记为LOGIN。最后,将bookkeeper角色分配给joe角色,以便他可以执行bookkeeper实际允许的所有操作。一旦用户到位,我们可以测试到目前为止我们拥有的内容:[hs@zenbook ~]$ psql test -U bookkeeper psql: FATAL: role “bookkeeper” is not permitted to log in正如所料,该bookkeeper角色不允许登录系统。如果joe角色尝试登录会发生什么? [hs@zenbook ~]$ psql test -U joe … test=>这实际上将按预期工作。但请注意,命令提示符已更改。这只是PostgreSQL向您显示您未以超级用户身份登录的一种方式。创建用户后,可能需要对其进行修改。我们可能想要改变的一件事是密码。在PostgreSQL中,允许用户更改自己的密码。下面是它的工作原理:test=> ALTER ROLE joe PASSWORD ‘abc’; ALTER ROLE test=> SELECT current_user; current_user ————– joe (1 row)该ALTER ROLE条款(或ALTER USER)将允许我们改变它可以创建用户时设置大多数设置。但是,管理用户还有很多。在许多情况下,我们希望为用户分配特殊参数。该ALTER USER条款为我们提供了这样做的方法:ALTER ROLE { role_specification | ALL } [ IN DATABASE database_name ] SET configuration_parameter { TO | = } { value | DEFAULT } ALTER ROLE { role_specification | ALL } [ IN DATABASE database_name ] SET configuration_parameter FROM CURRENT ALTER ROLE { role_specification | ALL } [ IN DATABASE database_name ] RESET configuration_parameter ALTER ROLE { role_specification | ALL } [ IN DATABASE database_name ] RESET ALL语法非常简单,非常简单。为了描述为什么这非常有用,我添加了一个真实的例子。让我们假设Joe碰巧住在毛里求斯岛上。当他登录时,即使他的数据库服务器位于欧洲,他也希望自己在他自己的时区:test=> ALTER ROLE joe SET TimeZone = ‘UTC-4’; ALTER ROLE test=> SELECT now(); now ——————————- 2017-01-09 20:36:48.571584+01 (1 row) test=> q [hs@zenbook ~]$ psql test -U joe … test=> SELECT now(); now ——————————- 2017-01-09 23:36:53.357845+04 (1 row)该ALTER ROLE子句将修改用户。一旦joe重新连接,就会为他设置时区。时区不会立即更改。您应该重新连接或使用SET … TO DEFAULT子句。这里重要的是这对于某些内存参数也是可能的,例如work_mem等等。数据库级别的安全性在实例级别配置用户之后,可以深入挖掘并查看在数据库级别可以执行的操作。出现的第一个主要问题是:我们明确允许Joe登录数据库实例,但是谁或什么允许Joe实际连接到其中一个数据库?也许我们不希望Joe访问系统中的所有数据库。限制对某些数据库的访问正是我们在此级别上可以实现的目标。对于数据库,可以使用GRANT子句设置以下权限:GRANT { { CREATE | CONNECT | TEMPORARY | TEMP } [, …] | ALL [ PRIVILEGES ] } ON DATABASE database_name [, …] TO role_specification [, …] [ WITH GRANT OPTION ] 数据库级别有两个主要权限值得密切关注:· CREATE:这允许某人在数据库中创建模式。请注意,CREATE子句不允许创建表; 它是关于模式的。在PostgreSQL中,表位于模式中,因此您必须首先进入模式级别才能创建表。· CONNECT:这允许有人连接到数据库。现在的问题是:没有人明确CONNECT为joe角色分配权限,那么这些权限实际上来自何处?答案是:有一个叫做的东西public,类似于Unix世界。如果世界被允许做某事,那么joe,谁是一般公众的一部分。最重要的是,public它不是一个角色,它可以被删除和重命名。我们可以简单地将其视为系统中每个人的等价物。因此,为了确保不是每个人都可以随时连接到任何数据库,CONNECT可能必须从公众中撤销。为此,我们可以以超级用户身份进行连接并解决问题:[hs@zenbook ~]$ psql test -U postgres … test=# REVOKE ALL ON DATABASE test FROM public; REVOKE test=# \q [hs@zenbook ~]$ psql test -U joe psql: FATAL: permission denied for database “test” DETAIL: User does not have CONNECT privilege.我们可以看到,该joe角色不再允许连接。此时,只有超级用户才能进行测试。通常,postgres 即使在创建其他数据库之前,最好还是从数据库中撤消权限。这个概念背后的想法是,这些权限将不再存在于所有新创建的数据库中。如果某人需要访问某个数据库,则必须明确授予权限。权利不再自动存在。如果我们想允许joe角色连接到测试数据库,请以超级用户身份尝试以下行:[hs@zenbook ~]$ psql test -U postgres … test=# GRANT CONNECT ON DATABASE test TO bookkeeper; GRANT test=# \q [hs@zenbook ~]$ psql test -U joe … test=>基本上,这里有两种选择:· 我们可以joe直接允许角色,以便只有joe角色才能连接。· 或者,我们可以为该bookkeeper角色授予权限。请记住,joe角色将继承角色的所有权限bookkeeper,因此,如果我们希望所有会计师都能够连接到数据库,则为角色分配权限bookkeeper似乎是一个有吸引力的想法。如果我们为该bookkeeper角色授予权限,则它没有风险,因为该角色不允许首先登录到该实例,因此它纯粹作为权限来源。处理列级安全性在某些情况下,并不是每个人都可以看到所有数据。想象一下银行,有些人可能会看到有关银行帐户的全部信息,而其他人可能仅限于数据的一部分。在现实世界的情况下,可能不允许某人阅读余额栏,或者有人可能看不到人民贷款的利率。另一个例子是,人们可以看到人们的个人资料,但不能看到他们的照片或其他私人信息。现在的问题是:如何使用列级安全性?为了证明这一点,我们将向属于该joe角色的现有表添加一列:test=> ALTER TABLE t_useful ADD COLUMN name text; ALTER TABLE该表现在由两列组成。该示例的目标是确保用户只能看到其中一列:test=> \d t_useful Table “public.t_useful” Column | Type | Modifiers ——–+———+———– id | integer | name | text |作为超级用户,让我们创建一个用户并让它访问包含我们表的模式:test=# CREATE ROLE paul LOGIN; CREATE ROLE test=# GRANT CONNECT ON DATABASE test TO paul; GRANT test=# GRANT USAGE ON SCHEMA public TO paul; GRANTCONNECT被撤销了public。因此,明确授予绝对是必要的,以确保我们甚至可以到达桌面。该SELECT权限可以赋予paul角色:test=# GRANT SELECT (id) ON t_useful TO paul; GRANT 基本上,这已经足够了。已经可以以用户身份连接到数据库paul并阅读该列:[hs@zenbook ~]$ psql test -U paul … test=> SELECT id FROM t_useful; id —- (0 rows)如果我们使用列级权限,请记住一件重要的事情,我们应该停止使用SELECT *,因为它不再起作用了:test=> SELECT * FROM t_useful; ERROR: permission denied for relation t_useful仍然意味着所有列,但由于无法访问所有列,事情将立即出错。配置默认权限到目前为止,已经配置了很多东西。如果将新表添加到系统会发生什么?逐个处理这些表并设置适当的权限可能会非常痛苦和风险。如果这些事情会自动发生,那不是很好吗?这正是该ALTER DEFAULT PRIVILEGES条款的作用。这个想法是为用户提供一个选项,让PostgreSQL在对象出现后立即自动设置所需的权限。有人不能再忘记设置这些权利了。以下清单显示了语法规范的第一部分:postgres=# \h ALTER DEFAULT PRIVILEGESCommand: ALTER DEFAULT PRIVILEGESDescription: define default access privilegesSyntax:ALTER DEFAULT PRIVILEGES [ FOR { ROLE | USER } target_role [, …] ] [ IN SCHEMA schema_name [, …] ] abbreviated_grant_or_revokewhere abbreviated_grant_or_revoke is one of:GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }[, …] | ALL [ PRIVILEGES ] }ON TABLESTO { [ GROUP ] role_name | PUBLIC } [, …] [ WITH GRANT OPTION ]…基本上,语法与GRANT子句类似,因此使用起来简单直观。为了向我们展示它的工作原理,我编写了一个简单的例子。我们的想法是,如果joe角色创建了一个表,该paul角色将自动使用它:test=# ALTER DEFAULT PRIVILEGES FOR ROLE joe IN SCHEMA public GRANT ALL ON TABLES TO paul; ALTER DEFAULT PRIVILEGES让我们joe现在作为角色连接并创建一个表:[hs@zenbook ~]$ psql test -U joe … test=> CREATE TABLE t_user (id serial, name text, passwd text); CREATE TABLE作为paul角色连接将证明该表已分配给适当的权限集:[hs@zenbook ~]$ psql test -U paul … test=> SELECT * FROM t_user; id | name | passwd —-+——+——– (0 row ...

April 11, 2019 · 4 min · jiezi

如何通过Telnet和SSH远程监控主机

来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:13min在本文中,你将学习如何在配置了Telnet和SSH的服务器上执行基本配置。我们将首先使用Telnet模块,之后我们将使用首选方法实现相同的配置:使用Python中的不同模块进行SSH,了解如何telnetlib,subprocess,fabric,Netmiko,和paramiko模块的工作。telnetlib()模块在本节中,我们将了解Telnet协议,然后我们将通过远程服务器上的telnetlib模块执行Telnet操作。Telnet是一种允许用户与远程服务器通信的网络协议。它主要由网络管理员用于远程访问和管理设备。要访问设备,请使用终端中远程服务器的IP地址或主机名运行Telnet命令。Telnet在默认端口号上使用TCP 23。要使用Telnet,请确保它已安装在你的系统上。如果没有,请运行以下命令进行安装:$ sudo apt-get install telnetd要使用简单的终端运行Telnet,您只需输入以下命令:$ telnet ip_address_of_your_remote_serverPython具有telnetlib通过Python脚本执行Telnet功能的模块。在telnet远程设备或路由器之前,请确保它们已正确配置,如果没有,则可以使用路由器终端中的以下命令进行基本配置:configure terminalenable password ‘set_Your_password_to_access_router’username ‘set_username’ password ‘set_password_for_remote_access’line vty 0 4 login local transport input all interface f0/0 ip add ‘set_ip_address_to_the_router’ ‘put_subnet_mask’no shut end show ip interface brief现在,让我们看一下Telnet远程设备的示例。为此,创建一个telnet_example.py脚本并在其中编写以下内容:import telnetlibimport getpassimport sysHOST_IP = “your host ip address"host_user = input(“Enter your telnet username: “)password = getpass.getpass()t = telnetlib.Telnet(HOST_IP)t.read_until(b"Username:")t.write(host_user.encode(“ascii”) + b”\n”)if password:t.read_until(b"Password:")t.write(password.encode(“ascii”) + b”\n")t.write(b"enable\n")t.write(b"enter_remote_device_password\n") #password of your remote devicet.write(b"conf t\n")t.write(b"int loop 1\n")t.write(b"ip add 10.1.1.1 255.255.255.255\n")t.write(b"int loop 2\n")t.write(b"ip add 20.2.2.2 255.255.255.255\n")t.write(b"end\n")t.write(b"exit\n")print(t.read_all().decode(“ascii”) )运行脚本,获得如下输出:student@ubuntu:$ python3 telnet_example.pyOutput:Enter your telnet username: studentPassword:server>enablePassword:server#conf tEnter configuration commands, one per line. End with CNTL/Z.server(config)#int loop 1server(config-if)#ip add 10.1.1.1 255.255.255.255server(config-if)#int loop 23server(config-if)#ip add 20.2.2.2 255.255.255.255server(config-if)#endserver#exit在前面的示例中,我们使用该telnetlib模块访问和配置了Cisco路由器。在此脚本中,首先,我们从用户那里获取用户名和密码,以初始化与远程设备的Telnet连接。建立连接后,我们在远程设备上进行了进一步配置。远程登录后,我们将能够访问远程服务器或设备。但是这个Telnet协议有一个非常重要的缺点,即所有数据,包括用户名和密码,都是以文本方式通过网络发送的,这可能会带来安全风险。因此,如今Telnet很少被使用,并且被称为Secure Shell的非常安全的协议所取代,称为SSH。通过在终端中运行以下命令来安装SSH:$ sudo apt install ssh此外,在用户想要通信的远程服务器上,必须安装并运行SSH服务器。SSH使用TCP协议,22默认使用端口号。您可以ssh通过终端运行 命令,如下所示:$ ssh host_name@host_ip_address现在来学习使用Python中的不同模块来执行SSH,例如subprocess,fabric,Netmiko和Paramiko。现在,我们将逐一看到这些模块。subprocess.Popen()模块此模块的底层的进程创建与管理由 Popen 类处理。它提供了很大的灵活性,因此开发者能够处理未被便利函数覆盖的不常见用例。子程序执行将在新进程中完成。要在Unix / Linux上执行子程序,该类将使用该 os.execvp()函数。要在Windows中执行子程序,该类将使用CreateProcess()函数。现在,让我们看一些有用的参数subprocess.Popen():class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)让我们看看每个论点:· args: 它可以是一系列程序参数或单个字符串。如果args是序列,则执行args中的第一项。如果args是一个字符串,它建议将args作为序列传递。· shell:shell参数默认设置为False,它指定是否使用shell执行程序。如果是shell True,则建议将args作为字符串传递。在 Linux中,如果shell=True,shell默认为/bin/sh。如果args是字符串,则字符串指定要通过shell执行的命令。· bufsize:如果bufsize是0(默认情况下是0),则表示无缓冲,如果bufsize是1,则表示行缓冲。如果bufsize是任何其他正值,请使用给定大小的缓冲区。如果bufsize是任何其他负值,则表示完全缓冲。· executable:它指定要执行的替换程序。· stdin,, stdout和stderr:这些参数分别定义标准输入,标准输出和标准错误。· preexec_fn: 这被设置为可调用对象,将在子进程中执行子进程之前调用。· close_fds: 在Linux中,如果close_fds是真的,所有的文件描述符,除了0,1和2执行子进程之前,将被关闭。在Windows中,如果close_fds是,true那么子进程将继承没有句柄。· env: 如果值不是None,则映射将为新进程定义环境变量。· universal_newlines: 如果该值True则stdout和stderr将被打开,在新行模式下的文本文件。现在,我们将看到一个例子subprocess.Popen()。为此,创建一个 ssh_using_sub.py 脚本并在其中写入以下内容:import subprocessimport sysHOST=“your host username@host ip"COMMAND= “ls"ssh_obj = subprocess.Popen([“ssh”, “%s” % HOST, COMMAND],shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE)result = ssh_obj.stdout.readlines()if result == []:err = ssh_obj.stderr.readlines()print(sys.stderr, “ERROR: %s” % err)else:print(result)运行脚本,您将获得如下输出:student@ubuntu:$ python3 ssh_using_sub.pyOutput :student@192.168.0.106’s password:[b’Desktop\n’, b’Documents\n’, b’Downloads\n’, b’examples.desktop\n’, b’Music\n’, b’Pictures\n’, b’Public\n’, b’sample.py\n’, b’spark\n’, b’spark-2.3.1-bin-hadoop2.7\n’, b’spark-2.3.1-bin-hadoop2.7.tgz\n’, b’ssh\n’, b’Templates\n’, b’test_folder\n’, b’test.txt\n’, b’Untitled1.ipynb\n’, b’Untitled.ipynb\n’, b’Videos\n’, b’work\n’]在前面的示例中,首先,我们导入了子进程模块,然后我们定义了要建立SSH连接的主机地址。之后,我们给出了一个通过远程设备执行的简单命令。完成所有这些后,我们将此信息放在 subprocess.Popen()函数中。此函数执行该函数内定义的参数以创建与远程设备的连接。建立SSH连接后,执行我们定义的命令并提供结果。然后我们在终端上打印SSH的结果,如输出中所示。SSH使用Fabric模块Fabric是一个Python库,也是一个使用SSH的命令行工具。它用于通过网络进行系统管理和应用程序部署。我们也可以通过SSH执行shell命令。要使用结构模块,首先必须使用以下命令安装它:$ pip3 install fabric3现在,我们将看到一个例子。创建一个 fabfile.py脚本并在其中写入以下内容:from fabric.api import *env.hosts=[“host_name@host_ip”]env.password=‘your password’def dir(): run(‘mkdir fabric’) print(‘Directory named fabric has been created on your host network’)def diskspace(): run(‘df’)运行脚本,您将获得如下输出:student@ubuntu:$ fab dirOutput:[student@192.168.0.106] Executing task ‘dir’[student@192.168.0.106] run: mkdir fabric Done.Disconnecting from 192.168.0.106… done.在前面的示例中,首先,我们导入了fabric.api模块,然后设置主机名和密码以与主机网络连接。之后,我们设置了一个不同的任务来执行SSH。因此,为了执行我们的程序而不是Python3 fabfile.py,我们使用了fabutility(fab dir),之后我们声明所需的任务应该从我们的执行fabfile.py。在我们的例子中,我们执行了dir任务,该任务’fabric’在远程网络上创建了一个名称目录。您可以在Python文件中添加特定任务。它可以使用fab结构模块的实用程序执行。SSH使用Paramiko库Paramiko是一个实现SSHv2协议的库,用于与远程设备的安全连接。Paramiko是一个围绕SSH的纯Python界面。在使用Paramiko之前,请确保已在系统上正确安装。如果未安装,可以通过在终端中运行以下命令来安装它:$ sudo pip3 install paramiko现在,我们将看到一个使用示例paramiko。对于此paramiko连接,我们使用的是Cisco设备。Paramiko支持基于密码和基于密钥对的身份验证,以实现与服务器的安全连接。在我们的脚本中,我们使用基于密码的身份验证,这意味着我们检查密码,如果可用,则使用普通用户名/密码身份验证尝试进行身份验证。在我们要对你的远程设备或多层路由器进行SSH连接之前,请确保它们已正确配置,如果没有,可以在多层路由器终端中使用以下命令进行基本配置:configure tip domain-name cciepython.comcrypto key generate rsaHow many bits in the modulus [512]: 1024interface range f0/0 - 1switchport mode accessswitchport access vlan 1no shutint vlan 1ip add ‘set_ip_address_to_the_router’ ‘put_subnet_mask’no shutexitenable password ‘set_Your_password_to_access_router’username ‘set_username’ password ‘set_password_for_remote_access’username ‘username’ privilege 15line vty 0 4login localtransport input allend现在,创建一个pmiko.py脚本并在其中编写以下内容:import paramikoimport timeip_address = “host_ip_address"usr = “host_username"pwd = “host_password"c = paramiko.SSHClient()c.set_missing_host_key_policy(paramiko.AutoAddPolicy())c.connect(hostname=ip_address,username=usr,password=pwd)print(“SSH connection is successfully established with “, ip_address)rc = c.invoke_shell()for n in range (2,6):print(“Creating VLAN " + str(n))rc.send(“vlan database\n”)rc.send(“vlan " + str(n) + “\n”)rc.send(“exit\n”)time.sleep(0.5)time.sleep(1)output = rc.recv(65535)print(output)c.close运行脚本,您将获得如下输出:student@ubuntu:$ python3 pmiko.pyOutput:SSH connection is successfuly established with 192.168.0.70Creating VLAN 2Creating VLAN 3Creating VLAN 4Creating VLAN 5在前面的示例中,首先,我们导入了paramiko模块,然后我们定义了连接远程设备所需的SSH凭据。提供凭证后,我们创建一个实例’c’的paramiko.SSHclient(),它是用于与远程设备建立连接和执行命令或操作主客户端。创建SSHClient对象允许我们使用该.connect()函数建立远程连接。然后,我们设置策略paramiko连接,因为默认情况下, paramiko.SSHclient将SSH策略设置为拒绝策略状态。这会导致策略在没有任何验证的情况下拒绝任何SSH连接。在我们的脚本中,我们忽略了SSH连接丢失的可能性 AutoAddPolicy()在不提示的情况下自动添加服务器主机密钥的功能。我们可以将此策略用于测试目的,但出于安全目的,这在生产环境中不是一个好的选择。建立SSH连接后,你可以在设备上执行所需的任何配置或操作。在这里,我们在远程设备上创建了一些虚拟LAN。创建VLAN后,我们只关闭了连接。SSH使用Netmiko库在本节中,我们将了解Netmiko。Netmiko库是Paramiko的高级版本。这是一个multi_vendor基于Paramiko 的图书馆。Netmiko简化了与网络设备的SSH连接,并对设备进行了特殊操作。在对远程设备或多层路由器进行SSH连接之前,请确保它们已正确配置,如果没有,则可以通过Paramiko部分中提到的命令进行基本配置。现在,让我们看一个例子。创建一个 nmiko.py脚本并在其中编写以下代码:from netmiko import ConnectHandlerremote_device={‘device_type’: ‘cisco_ios’,‘ip’: ‘your remote_device ip address’,‘username’: ‘username’,‘password’: ‘password’,}remote_connection = ConnectHandler(**remote_device)#net_connect.find_prompt()for n in range (2,6):print(“Creating VLAN " + str(n))commands = [’exit’,‘vlan database’,‘vlan ’ + str(n), ’exit’]output = remote_connection.send_config_set(commands)print(output)command = remote_connection.send_command(‘show vlan-switch brief’)print(command)运行脚本,您将获得如下输出:student@ubuntu:~$ python3 nmiko.pyOutput:Creating VLAN 2config termEnter configuration commands, one per line. End with CNTL/Z.server(config)#exitserver #vlan databaseserver (vlan)#vlan 2VLAN 2 modified:server (vlan)#exitAPPLY completed.Exiting….server #……..switch#Creating VLAN 5config termEnter configuration commands, one per line. End with CNTL/Z.server (config)#exitserver #vlan databaseserver (vlan)#vlan 5VLAN 5 modified:server (vlan)#exitAPPLY completed.Exiting….VLAN Name Status Ports—- ——————————– ——— ——————————-1 default active Fa0/0, Fa0/1, Fa0/2, Fa0/3, Fa0/4, Fa0/5, Fa0/6, Fa0/7, Fa0/8, Fa0/9, Fa0/10, Fa0/11, Fa0/12, Fa0/13, Fa0/14, Fa0/152 VLAN0002 active 3 VLAN0003 active 4 VLAN0004 active 5 VLAN0005 active 1002 fddi-default active 1003 token-ring-default active 1004 fddinet-default active 1005 trnet-default active在前面的示例中,我们使用Netmiko库来执行SSH,而不是Paramiko。在这个脚本中,首先,我们ConnectHandler从Netmiko库导入,我们通过传入设备字典来建立与远程网络设备的SSH连接。在我们的例子中,那个词典是remote_device。建立连接后,我们执行配置命令以使用该send_config_set()功能创建多个虚拟LAN 。当我们使用这种类型.send_config_set()的函数来传递远程设备上的命令时,它会自动将我们的设备设置为配置模式。发送配置命令后,我们还传递了一个简单的命令来获取有关已配置设备的信息。 ...

April 11, 2019 · 3 min · jiezi

应用现代CSS创建React App项目

来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:13min以前使用Create React App,你实际上没有很多选项可以直观地清理。经常处于随机级联样式表 (CSS)项目维护者的心血来潮之中,并且试图让项目编译过程中涉及的其他库,框架或预处理器经常成为一场噩梦。Create React App上下文中的预处理器基本上是构建过程中的一个步骤。在这种情况下,我们讨论的是采用某些样式代码(CSS或其他格式)的东西,将其编译为基本CSS,并将其添加到构建过程的输出中。在本文的篇幅中,我们将介绍涵盖与样式相关的功能的各种材料,并突出显示在我看来,Create React App中最好的新功能之一:支持CSS模块和SASS。介绍CSS模块CSS模块能够以防止引入全局重叠命名空间的方式模块化你所导入的任何CSS代码,尽管最终结果仍然只是一个巨大的CSS文件。更好的项目组织让我们首先清理一下我们项目中的目录结构。我们要做的就是将每个具有CSS和JavaScript代码的组件分离到自己的文件夹中。先来创建NewTodo,Todo,App,TodoList,和Divider文件夹,并将它们所有相关的代码放在其中的每一个文件夹中。我们还需要在每个被调用的目录中创建一个新文件,该文件index.js只负责导入和导出相应的组件。例如,App索引文件(src/App/index.js)将如下所示:import App from “./App”;export default App;Todo (src/Todo/index.js)的新索引文件 如下所示:import Todo from “./Todo”;export default Todo;你可以根据此模式猜测索引文件的内容NewTodo,TodoList以及它们的Divider外观。接下来,我们需要更改引用这些文件的每个位置,以便更轻松地导入所有这些文件。不幸的是,这将是一些繁琐的工作,但我们需要做同样的事情,以确保我们不会破坏过程中的任何事情。首先,在中src/App/App.js,将TodoList import 组件更改为以下内容:import TodoList from “../TodoList”;我们不需要做任何事情,Divider因为它是一个没有导入的组件。NewTodo 并且Todo 是类似的类型,所以我们也可以跳过它们。src/TodoList/TodoList.js另一方面,我们需要处理很多事情,因为它是我们最高级别的组件之一并且进口很多:import Todo from “../Todo”;import NewTodo from “../NewTodo”;import Divider from “../Divider”;但那还不是全部。我们的测试文件src/TodoList/TodoList.test.js也需要修改为包含文件的这些新路径,否则我们的测试将失败!我们需要几乎与之前相同的导入列表:import TodoList from “./TodoList”;import NewTodo from “../NewTodo”;import Todo from “../Todo”;当你重新加载你的应用程序时,你的代码应该仍然正常工作,测试应该全部通过,一切都应该干净利落!我们的完整项目结构现在应如下所示:src/ App/ App.css App.js App.test.js index.js Divider/ Divider.css Divider.js index.js NewTodo/ NewTodo.css NewTodo.js NewTodo.test.js index.js Todo/ Todo.css Todo.js Todo.test.js index.js TodoList/ TodoList.css TodoList.js TodoList.test.js index.js index.css index.js setupTests.js … etc …将CSS模块引入我们的应用程序如果我们想使用CSS模块,我们需要遵循一些简单的指南。首先,我们需要命名我们的文件[whatever].module.css,而不是[whatever].css。接下来我们需要做的是确保我们的样式简单命名并且易于引用。让我们首先遵循这些约定并将我们的CSS文件重命名为Todoas src/Todo/Todo.module.css,然后我们将稍微改变一下内容:.todo { border: 2px solid black; text-align: center; background: #f5f5f5; color: #333; margin: 20px; padding: 20px;}.done {background: #f5a5a5;}接下来,我们将开放src/Todo/Todo.js以利用CSS模块。我们在我们的Todo组件中创建了一个辅助函数cssClasses(),它返回我们应该在组件中使用的样式,并且我们不需要进行更改以使所有工作与之前完全相同。我们还需要import在顶部更改我们的语句,因为我们重命名了文件并且正在改变我们的CSS加载到代码中的方式!看看以下内容:import styles from “./Todo.module.css”;这使我们的代码可以Todo.module.css通过引用它们来利用定义的任何类名styles.[className]。例如,在前一个文件中,我们定义了两个CSS类名:todo和done,所以我们现在可以通过styles.Todo和在组件中引用它们styles.done。我们需要更改cssClasses()函数才能使用它,所以让我们现在进行那些确切的更改。在src/Todo/Todo.js,我们的cssClasses()功能现在应该如下所示: cssClasses() { let classes = [styles.todo]; if (this.state.done) { classes = […classes, styles.done]; } return classes.join(’ ‘); }保存并重新加载,我们的应用程序应该恢复正常!接下来,让我们更改组件hr内部的标签todo以拥有自己的样式和效果。返回src/Todo/Todo.module.css并为我们的hr标签添加以下块,我们将给出一个新类redDivider:.redDivider { border: 2px solid red;}最后,返回我们的render()函数src/Todo/Todo.js,并将render()函数的hr标记包含更改保存并重新加载,现在我们应该完全划分CSS代码而不必担心冲突和全局命名空间!这是输出的样子:与CSS模块的可组合性这并不是CSS模块给我们的全部,尽管它肯定是CSS模块的重要组成部分之一,我们立即得到并毫不费力。我们还获得了CSS可组合性,它能够从其他类继承CSS类,无论它们是否在主文件中。当您设置更复杂的嵌套组件时,这可能非常有用,这些组件都需要处理略有不同的样式表,但彼此之间并没有太大的不同。假设我们希望能够将某些组件标记为关键组件而不仅仅是常规Todos。我们不想对组件做太多改变; 我们希望它继承与所有其他Todos相同的基本规则。我们需要设置一些代码来实现这一目标。回到后面src/Todo/Todo.js,我们将进行一些修改以允许一个名为的新状态属性critical。我们将从constructor 组件开始,我们将添加新state属性和bind 函数标记: constructor(props) { super(props); this.state = { done: false, critical: false };this.markAsDone = this.markAsDone.bind(this);this.removeTodo = this.removeTodo.bind(this);this.markCritical = this.markCritical.bind(this);}我们在critical属性中添加一个新属性,state 并将其设置为默认值false。然后我们还引用了一个函数(我们还没有编写)markCritical,并且我们绑定了this,因为我们稍后将在事件处理程序中使用它。接下来,我们将解决这个问题markCritical(): markCritical() { this.setState({ critical: true }); }我们还需要修改我们的cssClasses()函数,以便它可以对这个新state属性做出反应。为了演示CSS模块的可组合性功能,我们将其设置classes为原来是一个空数组,然后第一个项目变为critical或todo,取决于项目是否标记为critical: cssClasses() { let classes = []; if (this.state.critical) { classes = [styles.critical]; } else { classes = [styles.todo]; } if (this.state.done) { classes = […classes, styles.done]; } return classes.join(’ ‘); }最后,在我们的render函数中,我们将创建button 标记以将项目标记为critical: render() { return ( {this.props.description} Mark as Done Remove Me Mark as Critical ); }我们还没有完成,尽管我们至少有90%的方式。我们还想回到src/Todo/Todo.module.css并为critical类名添加一个新块,我们也将使用我们的可组合属性:.critical { composes: todo; border: 4px dashed red;}要使用合成,您需要做的就是添加一个新的CSS属性,composes并为其指定一个您希望它组成的类名(或多个类名)。在这种情况下,撰写是一种奇特的方式,它表示它继承了其他类名的行为,并允许您覆盖其他类名。在前面的例子中,我们说的critical是一个CSS模块类,它由一个todo 模型作为基础,并添加border 一个大的红色虚线的组件,因为,我们只是说这意味着它是关键的。像往常一样保存并重新加载,您应该能够将项目标记为标记为完成,标记为严重或两者,或通过单击删除我删除它们,如以下屏幕截图所示:这就是我们对CSS模块的简要介绍!在继续之前,您还需要通过在屏幕中点击U来快速更新测试快照yarn test。将SASS引入我们的项目SASS本质上是具有扩展功能支持的CSS。当我在这里说扩展功能支持时,我的意思是它!SASS支持以下功能集,CSS中缺少这些功能集:· 变量· 嵌套· 部分CSS文件· 导入支持· 混入· 扩展和继承· 运算符和计算安装和配置SASS好消息是,在Create React App项目中获得SASS支持非常简单。我们首先需要通过yarn或安装它npm。$ yarn add node-sass我们将看到它的大量输出,但假设没有错误并且一切顺利,我们应该能够重新启动我们的开发服务器并开始使用一些SASS。让我们创建一个更通用的实用程序SASS文件,它将负责存储我们想要在整个应用程序中使用的标准化颜色,以及存储整齐渐变hr模式的东西,以防我们想在其他地方使用它。我们还将更改我们正在使用的一些颜色,以便有一些红色,绿色和蓝色,这取决于项目是分别是关键,完成还是两者都不是。此外,我们需要稍微改变我们的项目,并添加一个新文件,以获得一些共享样式和颜色的概念。那么,让我们开始吧:src/shared.scss在我们的项目中创建一个新文件,并为其提供以下主体:$todo-critical: #f5a5a5;$todo-normal: #a5a5f5;$todo-complete: #a5f5a5;$fancy-gradient: linear-gradient( to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0));接下来,跳转到src/Divider/Divider.css并将文件重命名为src/Divider/Divider.scss。接下来,我们将更改对Divider.cssin 的引用src/Divider/Divider.js,如下所示:import “./Divider.scss”;现在我们需要更改代码Divider.scss以在我们的共享变量文件中导入并使用变量作为其中的一部分:@import “../shared”;hr {border: 0;height: 1px;background-image: $fancy-gradient;}因此,我们在新的共享SASS文件中导入src/,然后该background-image值只引用$fancy-gradient我们创建的变量,这意味着我们现在可以在需要时重新创建那个花哨的渐变而无需反复重写它。保存并重新加载,您应该看到没有任何重大变化。混合SASS和CSS模块好消息是,在Create React App中将SASS引入CSS模块基本上并不复杂。事实上,这些步骤是相同的!所以,如果我们想要开始混合这两者,我们需要做的就是重命名一些文件并改变我们的导入处理方式。让我们看看这个行动:首先,返回我们的src/Todo/Todo.module.css文件并进行一个非常小的修改。具体来说,让我们重命名它 src/Todo/Todo.module.scss。接下来,我们需要改变我们的import陈述src/Todo/Todo.js,否则整个事情将崩溃:import styles from “./Todo.module.scss”;现在,我们应该让我们的SASS使用Todo组件的CSS模块,所以让我们开始利用它。再次,我们需要import我们的shared文件放到此文件SASS也。请注意以下内容src/Todo/Todo.module.scss:@import ‘../shared’;接下来,我们需要开始更改对各种背景颜色的引用。我们将常规Todos的背景更改为 $todo-normal。然后,我们将完成的Todo背景更改为 $todo-complete。最后,我们要将critical项目更改为 $todo-critical:.todo { border: 2px solid black; text-align: center; background: $todo-normal; color: #333; margin: 20px; padding: 20px;}.done {background: $todo-complete;}.hr {border: 2px solid red;}.critical {composes: todo;background: $todo-critical;}保存并重新加载我们的项目,让我们确保新的配色方案得到尊重:现在,我们在Create React App项目中很好地集成了CSS模块和SASS,而无需安装单个新依赖项。我们让他们一起玩得很好 ,这是一个更大的成就! ...

April 10, 2019 · 2 min · jiezi

创建聊天机器人以协助网络操作

创建聊天机器人以协助网络操作来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:11min在本文中,我们将了解如何利用聊天机器人来协助网络操作。随着我们向智能化运营迈进,另一个需要关注的领域是移动性。有一个脚本可以执行配置,修复甚至故障排除,但它仍然需要存在来监视,启动甚至执行这些程序或脚本。诺基亚的MIKA是操作人员可以用来进行网络故障排除和修复的聊天机器人的一个很好的例子。根据诺基亚的博客,MIKA根据此单个网络的实际情况响应警报优先级信息,并将当前情况与此网络及其他网络过去事件的整个服务历史进行比较,以确定当前网络的最佳解决方案。让我们创建一个聊天机器人来协助网络运营。对于这个用例,我们将使用广泛使用的聊天应用程序Slack。参考Splunk的智能数据分析功能,我们会看到一些用户聊天与聊天机器人的交互,以获得对环境的一些了解。当我们部署了我们的Web框架时,我们将利用相同的框架与Slack聊天机器人进行交互,而后者又将与Splunk进行交互。它还可以直接与网络设备交互,因此我们可以启动一些复杂的聊天,例如在需要时从Slack重启路由器。这最终为工程师提供了移动性,他可以从任何地方(甚至是手机)处理任务,而不必绑定到某个位置或办公室。要创建聊天机器人,以下是基本步骤:在Slack上创建一个工作区(或帐户):在工作区中创建一个应用程序(在我们的例子中,我们创建了一个名为的应用程序mybot):以下是有关应用程序的基本信息(应用程序ID和客户端ID可以与唯一标识此应用程序的其他信息一起使用):为此应用程序添加bot功能:添加事件订阅并映射到将要发布消息的外部API。事件订阅是指某人在聊天中键入对聊天机器人的引用,然后将使用此聊天机器人与聊天中键入的数据调用哪个API:在这里,关键的一步是,一旦我们输入接受聊天消息的URL,就需要从Slack验证特定的URL。验证涉及API端点将相同的响应作为从Slack发送到该端点的字符串或JSON发回。如果我们收到相同的响应,Slack确认端点是可信的并将其标记为已验证。这是一次性过程,API URL中的任何更改都将导致重复此步骤。以下是Ops API框架中的 Python代码,它响应此特定查询:import falconimport jsondef on_get(self,req,resp): # Handles GET request resp.status=falcon.HTTP_200 # Default status resp.body=json.dumps({“Server is Up!”})def on_post(self,req,resp): # Handles POST Request print(“In post”) data=req.bounded_stream.read() try: # Authenticating end point to Slack data=json.loads(data)[“challenge”] # Default status resp.status=falcon.HTTP_200 # Send challenge string back as response resp.body=data except: # URL already verified resp.status=falcon.HTTP_200 resp.body=““这将验证,如果从Slack发送质询,它将回复相同的质询值,确认它是Slack通道发送聊天数据的正确端点。将此应用程序(或聊天机器人)安装到任何渠道(这类似于在群聊中添加用户):响应特定聊天消息的核心API框架代码执行以下操作:· 确认发送给Slack的任何帖子都会200在三秒内响应。如果没有这样做,Slack报告说: endpoint not reachable。· 确保从聊天机器人(不是来自任何真实用户)发送的任何消息再次不作为回复发回。这可以创建一个循环,因为从聊天机器人发送的消息将被视为Slack聊天中的新消息,并且它将再次发送到URL。这最终会使聊天无法使用,从而导致聊天中出现重复的消息。· 使用将被发送回Slack的令牌对响应进行身份验证,以确保来自Slack的响应来自经过身份验证的源。代码如下:import falconimport jsonimport requestsimport base64from splunkquery import runfrom splunk_alexa import alexafrom channel import channel_connect,set_dataclass Bot_BECJ82A3V(): def on_get(self,req,resp): # Handles GET request resp.status=falcon.HTTP_200 # Default status resp.body=json.dumps({“Server is Up!”}) def on_post(self,req,resp): # Handles POST Request print(“In post”) data=req.bounded_stream.read() try: bot_id=json.loads(data)[“event”][“bot_id”] if bot_id==“BECJ82A3V”: print(“Ignore message from same bot”) resp.status=falcon.HTTP_200 resp.body=”” return except: print(“Life goes on. . .”) try: # Authenticating end point to Slack data=json.loads(data)[“challenge”] # Default status resp.status=falcon.HTTP_200 # Send challenge string back as response resp.body=data except: # URL already verified resp.status=falcon.HTTP_200 resp.body="" print(data) data=json.loads(data) #Get the channel and data information channel=data[“event”][“channel”] text=data[“event”][“text”] # Authenticate Agent to access Slack endpoint token=“xoxp-xxxxxx” # Set parameters print(type(data)) print(text) set_data(channel,token,resp) # Process request and connect to slack channel channel_connect(text) return# falcon.API instance , callable from gunicornapp= falcon.API()# instantiate helloWorld classBot3V=Bot_BECJ82A3V()# map URL to helloWorld classapp.add_route("/slack",Bot3V)执行频道交互响应:此代码负责在聊天频道中解释使用chat-bot执行的特定聊天。此外,这将通过回复,特定用户或通道ID以及对Slack API的身份验证令牌进行响应,这确保了消息或回复Slack聊天的消息显示在特定频道上,从它发起的位置。作为示例,我们将使用聊天来加密或解密特定值。例如,如果我们写encrypt username[:]password,它将返回带有base64值的加密字符串。类似地,如果我们写,聊天机器人将在解密编码的字符串后返回。decrypt代码如下:import jsonimport requestsimport base64from splunk_alexa import alexachannl=““token=““resp=““def set_data(Channel,Token,Response): global channl,token,resp channl=Channel token=Token resp=Responsedef send_data(text):global channl,token,resprint(channl)resp = requests.post(“https://slack.com/api/chat.postMessage",data='{"channel":"'+channl+'","text":"'+text+'"}',headers={"Content-type": “application/json”,“Authorization”: “Bearer “+token},verify=False)def channel_connect(text):global channl,token,resptry: print(text)arg=text.split(’ ‘)print(str(arg))path=arg[0].lower()print(path in [“decode”,“encode”])if path in [“decode”,“encode”]:print(“deecode api”)else:result=alexa(arg,resp)text=““try:for i in result:print(i)print(str(i.values()))for j in i.values():print(j)text=text+’ ‘+j#print(j)if text==”” or text==None:text=“None"send_data(text)returnexcept:text=“None"send_data(text)returndecode=arg[1]except:print(“Please enter a string to decode”)text=” argument cannot be empty"send_data(text)returndeencode(arg,text)def deencode(arg,text):global channl,token,respdecode=arg[1]if arg[1]==’–help’:#print(“Sinput”)text=“encode/decode “send_data(text)returnif arg[0].lower()==“encode”:encoded=base64.b64encode(str.encode(decode))if ‘[:]’ in decode:text=“Encoded string: “+encoded.decode(‘utf-8’)send_data(text)returnelse:text=“sample string format username[:]password"send_data(text)returntry:creds=base64.b64decode(decode)creds=creds.decode(“utf-8”)except:print(“problem while decoding String”)text=“Error decoding the string. Check your encoded string.“send_data(text)returnif ‘[:]’ in str(creds):print(”[:] substring exists in the decoded base64 credentials”)# split based on the first match of “[:]“credentials = str(creds).split(’[:]’,1)username = str(credentials[0])password = str(credentials[1])status = ‘success’else:text=“encoded string is not in standard format, use username[:]password"send_data(text)print(“the encoded base64 is not in standard format username[:]password”)username = “Invalid"password = “Invalid"status = ‘failed’temp_dict = {}temp_dict[‘output’] = {‘username’:username,‘password’:password}temp_dict[‘status’] = statustemp_dict[‘identifier’] = ““temp_dict[’type’] = “"#result.append(temp_dict)print(temp_dict)text=” “+username+” “+passwordsend_data(text)print(resp.text)print(resp.status_code)return此代码查询Splunk实例以查找与聊天机器人的特定聊天。聊天会要求任何Loopback45当前关闭的管理界面()。另外,在聊天中,用户可以询问管理接口所在的所有路由器up。此英语响应将转换为Splunk查询,并根据Splunk的响应将状态返回到Slack聊天。让我们看看执行动作来响应结果的代码,对Slack聊天:from splunkquery import rundef alexa(data,resp): try: string=data.split(’ ‘) except: string=data search=’ ‘.join(string[0:-1]) param=string[-1] print(“param”+param) match_dict={0:“routers management interface”,1:“routers management loopback”} for no in range(2): print(match_dict[no].split(’ ‘)) print(search.split(’ ‘)) test=list(map(lambda x:x in search.split(’ ‘),match_dict[no].split(’ ‘))) print(test) print(no) if False in test: pass else: if no in [0,1]: if param.lower()==“up”: query=“search%20index%3D%22main%22%20earliest%3D0%20%7C%20dedup%20interface_name%2Crouter_name%20%7C%20where%20interface_name%3D%22Loopback45%22%20%20and%20interface_status%3D%22up%22%20%7C%20table%20router_name” elif param.lower()==“down”: query=“search%20index%3D%22main%22%20earliest%3D0%20%7C%20dedup%20interface_name%2Crouter_name%20%7C%20where%20interface_name%3D%22Loopback45%22%20%20and%20interface_status%21%3D%22up%22%20%7C%20table%20router_name” else: return “None” result=run(query,resp) return result以下Splunk查询获取状态:· 对于UP接口:查询如下:index=“main” earliest=0 | dedup interface_name,router_name | where interface_name=“Loopback45” and interface_status=“up” | table router_name· 对于DOWN接口(除了以外的任何状态):查询如下:index=“main” earliest=0 | dedup interface_name,router_name | where interface_name=“Loopback45” and interface_status!=“up” | table router_name让我们看看聊天机器人聊天的最终结果以及根据聊天记录发回的响应。编码/解码示例如下:正如我们在这里看到的,我们发送了一条encode abhishek[:]password123 消息聊天。此聊天作为POST请求发送到API,后者又将其加密到base64并使用添加的单词作为回复。在下一个聊天中,我们使用decode选项传递相同的字符串。这会通过解码来自API函数的信息进行响应,并使用用户名和密码回复Slack聊天。Encoded string: abhishekpassword123让我们看一下Splunk查询聊天的示例:在此查询中,我们已关闭 Loopback45 接口rtr1。在我们通过Python脚本计划发现这些接口的过程中 ,数据现在位于Splunk中。当查询哪个管理接口(Loopback45)关闭时,它将回复rtr1。松弛的聊天,On which routers the management interface is down会将此传递给API,在收到此有效负载后,它将运行Splunk查询以获取统计信息。返回值(在本例中为rtr1)将作为聊天中的响应返回。类似地,中,反向查询On which routers the management interface is up,将查询的Splunk和最终共享回响应rtr2,rtr3和rtr4(因为所有这些路由器接口是UP)。可以扩展此聊天用例,以确保使用简单聊天可以进行完整的端到端故障排除。可以使用各种后端功能构建大量案例,从问题的基本识别到复杂任务,例如基于已识别情况的补救。 ...

April 10, 2019 · 3 min · jiezi

如何在React Native应用程序中保持动画以60 FPS运行

来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:4min任何高质量移动应用程序的一个重要方面是用户界面的流动性。动画用于提供丰富的用户体验,任何jank或抖动都会对此产生负面影响。动画可能会用于各种交互,从视图之间的转换,到对用户在组件上的触摸交互作出反应。高质量动画的第二个最重要的因素是确保它们不会阻止 JavaScript线程。为了使动画保持流畅而不中断UI交互,渲染循环必须在16.67 ms内渲染每个帧,以便可以实现60 FPS。在本文中,我们将介绍几种在React Native移动应用程序中提高动画性能的技术 。这些技术将防止 JavaScript执行中断主线程。对于这篇文章,我们假设你有一个React Native应用程序,它定义了一些动画。怎么做首先,在React Native中调试动画性能时,我们需要启用性能监视器。为此,请显示开发菜单(从模拟器中摇动设备或cmd + D),然后点击显示Perf监视器。iOS中的输出类似于以下屏幕截图:Android中的输出类似于以下屏幕截图:如果您要为组件的过渡(opacity)或尺寸(width,height)设置动画,请确保使用LayoutAnimation。如果要LayoutAnimation在 Android上使用,则需要在应用程序启动时添加以下代码: UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true)。如果您需要对动画进行有限控制,建议您使用AnimatedReact Native附带的库。此库允许您将所有动画工作卸载到本机UI线程上。为此,我们必须将useNativeDriver属性添加到我们的Animated调用中。我们来看一个示例Animated示例并将其卸载到本机线程:componentWillMount() { this.setState({ fadeAnimimation: new Animated.Value(0) }); }componentDidMount() { Animated.timing(this.state.fadeAnimimation, { toValue: 1, useNativeDriver: true }).start(); }如果您无法将动画工作卸载到本机线程上,则仍然可以提供顺畅体验的解决方案。我们可以InteractionManager在动画完成后使用它来执行任务:componentWillMount() { this.setState({ isAnimationDone: false }); } componentWillUpdate() { LayoutAnimation.easeInAndOut(); }componentDidMount() { InteractionManager.runAfterInteractions(() => { this.setState({ isAnimationDone: true }); }) }render() { if (!this.state.isAnimationDone) { return this.renderPlaceholder(); } return this.renderMainScene(); }最后,如果您仍然遇到性能不佳的问题,则必须重新考虑动画策略或将效果不佳的视图实现为目标平台上的自定义UI视图组件。您必须使用iOS和/或Android SDK 本地实现视图和动画 。这个怎么运作本文中的提示着重于防止JavaScript线程锁定的简单目标。我们的JavaScript线程开始丢帧(锁定)的那一刻,我们失去了与我们的应用程序交互的能力,即使它只是一小段时间。这可能看起来无关紧要,但精明的用户会立即感受到这种效果。本文中提示的重点是将动画卸载到GPU上。当动画在主线程(由GPU渲染的本机层)上运行时,用户可以自由地与应用交互,而不会出现口吃,悬挂,抖动或抖动。其他这里有一个useNativeDriver可用的快速参考:功能iOS版Android的style,value,propertys√√decay √timing√√spring √add√√multiply√√modulo√ diffClamp√√interpoloate√√event √division√√transform√√ ...

April 9, 2019 · 1 min · jiezi

深度揭秘:机器学习对软件开发带来哪些影响?

摘要: 当软件开发碰见机器学习,到底能碰撞出什么样的火花呢?机器学习有望从根本上改变软件开发的本质,这也许是自FORTRAN和LISP被发明以来软件开发领域改变最大的一次。这些变化对数百万正在从事软件开发的人而言,意味着什么呢?失业?裁员?现有的软件开发将变得面目全非?自20世纪70年代以来,我们尽可能的构建了足够多的软件。我们有高级语言,低级语言,脚本语言以及用于构建和测试软件的工具,但我们利用这些工具做的事情却没有发生太大变化。我们现在拥有的语言和工具比50年前要好得多,但它们本质上是一样的。我们仍然使用代码编辑器,但这些编辑器变得更花哨了:他们有彩色的高亮,变量名补全,它们有时可以帮助我们完成重构等任务,但他们仍然是emacs和vi的后代。面向对象编程代表了一种不同的编程风格,但从某种本质上而言并不是“全新”的事物,对于函数式编程我们可以一直追溯到50年代。未来我们将专注于机器学习而不是人工智能。机器学习曾经被称为“AI最管用的那一部分”,更重要的是,“机器学习”这种提法可以避开类似“通用智能”这种叫法。因为这样的系统目前不存在,并且可能永远不存在,目前来看只有人类才能做到这一点。而机器学习可能只比模式识别多一点点,但我们已经看到模式识别可以完成很多工作。实际上,手工编码的模式识别是我们当前工具集的核心:这真的是现代优化编译器所正在做的。麦肯锡估计“使用现有技术,只有不到5%的职业可以完全自动化。然而,大约有60%的职业工作活动中,具有30%或更多的组成部分能够被自动化。”软件开发和数据科学不会成为完全被自动化的职业之一。但优秀的软件开发人员一直在寻求对于繁琐,重复的任务的自动化,毫无疑问,软件开发本身将日益变得可以被自动化。这并不是一个激进的愿景,因为我们在过去的半个世纪里持续的为了自动化工具而努力。编译器对编写机器代码的过程进行了自动化。脚本语言通过将更大,更复杂的程序粘合在一起来自动执行许多枯燥无味的任务。软件测试工具、自动部署工具、容器和容器编排系统等等,这些都是为了对开发、部署、管理软件系统的过程进行自动化的工具。而且这些都没有利用机器学习,但这肯定是下一步它们要做的。机器学习会不会吞并软件?毕竟,“软件吞噬世界”是一个日益抽象和普遍化的过程。笔记本电脑,手机或智能手表已经逐渐取代收音机,电视机,锁和钥匙,电灯开关,这是因为我们并没有将计算机仅仅看成数字计算器而是通用机器。从这个角度来看,很容易将机器学习想象成下一个抽象层次,这是我们迄今为止发现的最通用的问题解决工具。当然,神经网络已经证明了它们可以执行许多特定任务。由资深人士乐观地表示,对于许多任务而言,收集数据比编写程序更容易。对于一些非常有趣且困难的程序,这无疑是正确的,比如说收集围棋或国际象棋的训练数据很容易,但很难写一个程序成功地玩这些游戏。另一方面,数据收集并不总是那么容易。我们无法设想自动标记图片的程序,特别是在Facebook和阿里巴巴这样收集了数十亿张图片的网站,而且其中许多图片已被人类标记过。对于像人脸识别这样的任务,我们不知道如何编写软件,而且很难收集数据。对于其他任务,例如计费,可以很容易地根据一些简单的业务规则编写程序。如果你能够收集数据,你编写的程序将更好地适应不同的情况,还能够检测异常,这一点当“将人类纳入软件迭代的循环”时,尤为如此。正在代替代码的机器学习机器学习正在使代码变得高效:Google的Jeff Dean说,500行TensorFlow代码已经取代了谷歌翻译中的500000行代码。虽然代码行数是一个值得质疑的指标,但无论是从编程工作量角度来看还是从需要维护的代码量来看,这个突破都是可称赞的。更重要的是这段代码是如何工作的:相比于五十万行的代码,这是一个经过训练以用于翻译的神经网络。神经网络可以随着语言的变化和使用场景的变化,在新数据上被重新训练,而且整个代码都不需要重写。虽然我们不应低估训练任何复杂度的神经网络的难度,但我们同样也不应低估管理和调试一个巨大代码库带来的问题。研究表明,神经网络可以通过组合现有模块来创建新程序。虽然以这种方式构建的程序很简单,但是让单个神经网络能够学习执行几个不同的任务是很重要的,每个任务通常都需要一个单独的程序。Pete Warden认为:“开发人员必须成为一名教师(教机器),一名训练数据的策划人。”我们发现,这种说法非常具有启发性。软件开发不会消失,但开发人员必须以不同的方式来思考自己。你如何构建一个解决一般问题的系统,然后教该系统解决一个特定的任务?这貌似听起来像是一个风险很高又麻烦的场景。但这意味着我们的系统将变得更加灵活,具有很强的适应性。Warden设想的未来,更多是关于产出的,而不是关于撰写代码行数。Peter经过更加系统的思考,认为机器学习可以从训练数据中产生短程序,而不是很大的程序。数据管理和基础设施早期的迹象表明,机器学习有着可以胜过传统的数据库索引的性能:它可以学习预测数据的存储位置或者预测数据是否存在。机器学习明显更快,并且需要更少的内存,但也有着相当大的限制性:当前基于机器学习的工具不包括多维索引,并假设数据库不经常更新。重新训练比重建传统数据库索引需要更长的时间。尽管如此,研究人员正在研究如何学习到多维索引,查询的优化,重新训练的性能。机器学习已经进入了数据基础设施的其他领域。数据工程师正在使用机器学习来管理Hadoop,从而可以更快地响应Hadoop集群中的内存不足等问题。 Kafka工程师使用机器学习来诊断问题,从而简化了管理许多配置的问题,这些配置会影响数据库的性能。数据工程师和数据库管理员不会过时,但他们可能需要发展一下他们的机器学习技能。机器学习将帮助他们使困难的问题变得更简单,管理数据基础架构这个工作,将不会像正确设置数百个不同的配置参数那样,它会更像是在训练一个系统,让整个管理工作运行的更有条理。使困难问题变得可管理是数据科学最重要的问题之一。数据工程师负责维护数据管道:提取数据、清理数据、特征工程和模型构建。同时他们还需要负责在非常复杂的环境中部署软件,一旦部署了这些基础架构,还需要不断监视它,以检测(或防止)资源用尽,确保模型正确运行。这些都是非常适合用机器学习处理的任务,我们越来越多地看到像MLFlow这样的软件能够被用于管理数据管道。数据科学在自动化编程的早期表现形式中,工具旨在使数据分析师能够执行更高级的分析任务。Automatic Statistician是一种更新的工具,可自动进行探索性数据分析,并为时间序列类型的数据提供统计模型,且附有详细说明。随着深度学习的兴起,数据科学家发现自己需要寻找合适的神经网络架构和参数。让神经网络学着找到合适自己架构的过程,也可能被自动化。毕竟,神经网络就只是单纯的自动化学习工具:虽然构建神经网络结构需要大量的人力工作,但是不可能手动调整模型的所有参数。未来的场景应该是使用机器学习来探索所有可能的神经网络架构:正如一篇文章指出的,10层网络可能就有10的10次方种可能性。已经有其他研究人员使用强化学习来让神经网络架构开发变得更加容易。模型创建不是一劳永逸的事情:数据模型需要不断进行测试和调整。我们开始看到的用于持续监控和模型调整的这些工具并不是特别新颖,比如用于A/B测试的老虎机算法已经存在了一段时间,对于许多公司来说,老虎机算法算是强化学习的第一步。机器学习同样也可以用来查找软件中的漏洞,有些系统会浏览代码,并寻找已知的缺陷。这些系统不一定需要能够修复代码,也不承诺找到所有潜在的问题。但是他们可以很容易地对危险的代码进行高亮显示,并且他们可以允许在大型代码库上进行开发的程序员提出诸如“还有类似这样的问题存在于其他地方吗?”之类的问题。游戏开发者也正在探索利用机器学习来降低游戏开发成本以及创造更多有趣的游戏。机器学习可以用来制作看起来更逼真的背景和场景吗?游戏开发者都知道对逼真的场景和图像进行绘制和建模又耗钱,又费时。目前,非玩家角色(NPC)所做的一切都必须明确编程。机器学习可以用来模拟NPC的行为吗?如果NPC可以学习到行为,我们可以期待更有创意的游戏玩法出现。展望未来软件开发人员的未来是什么样的?软件开发是否会同样走上麦肯锡为其他行业预测的演化路径呢?在软件开发和数据科学中所涉及的30%的工作是否会被自动化?也许,刚刚我们所谈的只是对未来某种情况的简单解读。但毫无疑问,机器学习将改变软件开发。如果未来我们现在所认为的“编程”中很大一部分被自动化了,那也没什么好惊讶的。编译器不进行机器学习,但他们通过自动生成机器代码来改变软件行业,这在未来可能并不是什么新鲜事。重要的问题是软件开发和数据科学将如何变化。一种可能性,实际上是一种事实:软件开发人员会在数据收集和准备方面投入更多精力。没有数据训练,机器学习就什么都不是。开发人员必须做的,不仅仅是收集数据; 他们必须构建数据管道,以及构建管理这些管道的基础设施,我们称之为“数据工程”。在许多情况下,这些管线本身将使用机器学习来监控和优化自己。我们可以看到训练机器学习算法成为一个独特的子专业;我们可能很快会有一个新职业-“训练工程师”,就像我们目前谈论的“数据工程师”一样。Andrew Ng在他自己的《机器学习渴望》一书时中说:“这本书的重点不是教你ML算法,而是教你如何让ML算法有效。没有编码,也没有复杂的数学。本书几乎完全侧重于模型训练过程,而不仅仅是编码,训练才是让机器学习有效工作的本质。”我们提出的想法都涉及一种能力:它使人类能够生产出更快、更可靠、更好的能够生效的产品。开发人员将能够将更多时间花在有趣且更重要的问题上,而不是把基本工作做好。那些问题可能是什么问题呢?在一篇关于智能增强的讨论里,Nicky论证了,计算机在针对一个问题寻找最佳答案上表现出色。因为计算机的本质是计算工具。但是他们不是很擅长“找到一个值得回答的有趣问题”,这件事是人类做的。那么,我们需要提出哪些重要的问题呢?这些重要的问题已经在不断的被发现了,比如我们刚刚开始认识到道德在计算中的重要性,才开始考虑更好的用户界面,包括会话界面:它们将如何运作?即使在人工智能的帮助下,我们的安全问题也不会消失。先不管安全问题怎样,我们所有的设备都在变得“聪明”。这意味着什么?我们希望它们做什么?人类不会编写尽可能多的低级代码。但是正因为他们将不会去编写那些代码,所以他们可以自由地思考代码应该做什么,以及它应该如何与人交互。需要解决的问题永远不会少。很难想象“人类不再创建软件”的未来,但很容易想象“将人纳入软件研发的循环”中在未来将占越来越多的比重。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。

April 8, 2019 · 1 min · jiezi

构建API的最佳编程语言是什么?

构建API的最佳编程语言是什么?来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:4min你是否正在设计第一个Web应用程序?也许你过去已经建立了一些,但是目前也正在寻找语言的变化以提高你的技能,或尝试新的东西。有了所有信息,就很难决定为下一个产品或项目选择哪种编程语言。因为任何编程语言最终都可以用于编写API,所以有些编程语言比其他编程语言更好,更有效。今天我们将讨论在选择编程语言以构建适用于你的Web应用程序的 API时应该考虑的因素。在编程语言方面,舒适性至关重要这适用于任何具有某种语言经验的开发人员。如果你已经掌握了某种语言的经验,那么你最终将能够更轻松地开发,理解所涉及的概念,并且能够立即取得更多进展。这也转化为改进的代码和性能,因为你可以花更多的时间在上面而不是学习一种全新的编程语言。例如,如果我已经在Python中开发了几年,但我可以选择使用PHP或 Python作为项目的编程语言,我只选择 Python,因为已经花了很多时间学习Python。这非常重要,因为在尝试执行新操作时,你希望限制项目中未知的数量。这将有助于学习并帮助你取得更好的成果。如果你是一位零编程经验的全新开发人员,则以下部分可帮助你缩小搜索范围。支持开发API的库和框架在消除潜在的编程语言以构建API的过程中要问的下一个问题是:该语言是否为有助于开发API的库或框架提供了大量不同的选项?继续上一节中的Python示例, Django REST框架是专门构建在Django之上的。 Django是一个用于Python 的 Web开发框架,可以更快,更轻松地在编程语言中创建API。这些库和框架允许通过包含处理构建API中的大量重复工作的函数和对象来加速开发过程。一旦你花了一些时间研究可用于语言的库和框架的可用内容,那么现在是时候检查社区的活跃程度了。支持和社区在这个过程中要问自己的下一个问题是:这个编程语言的框架和库是否仍然受支持?如果支持,开发者社区的活跃程度如何?他们是否对其软件和功能进行了持续或定期更新?更新是否有助于提高安全性和可用性?鉴于没有多少人使用该语言,将来也没有更新错误修复,你可能不想继续使用它。另一件需要注意的是用户社区。有足够的资源供你学习吗?文档的清晰度和可用性如何?是否有经验丰富的开发人员有关于必要主题的博客文章需要学习?Stack Overflow是否有问题和答案?是否有任何硬资源如杂志或教科书向你展示如何使用这些语言和框架?构建API的潜在语言根据我的经验,有许多更好的编程语言。这是一些这些语言的示例框架,你可以使用它来开始开发下一个API:LanguageFrameworkJavaSpringJavaScript(Node)ExpressPythonDjangoPHPLaravelRubyRuby on Rails综上,你选择的编程语言取决于几个因素:你使用该语言时的体验;可用于API构建的框架以及支持和社区的活跃程度。不要害怕尝试新事物!你可以随时学习,但如果担心速度和开发的简易性,请使用这些标准来帮助选择使用语言。愿码·全思维工程师社群全球招募愿码全思维工程师社群面向全球招募,如果你不甘平凡、如果你敢于突破自我、如果你希望有一份可观的睡后收入,Come on,关注公众号回复愿码两个字申请加入社群。

April 4, 2019 · 1 min · jiezi

ApacheCN 编程/大数据/数据科学/人工智能学习资源 2019.4

我们是一个大型开源社区,旗下 QQ 群共 9000 余人,Github Star 数量超过 20k 个,网站日 uip 超过 4k,拥有 CSDN 博客专家和简书程序员优秀作者认证。我们组织公益性的翻译活动、学习活动和比赛组队活动,并和 DataWhale、LinuxStory 等国内著名开源组织保持良好的合作关系。 与商业组织不同,我们并不会追逐热点,或者唯利是图。作为公益组织,我们将完成项目放在首要位置,并有足够时间把项目打磨到极致。我们希望做出广大 AI 爱好者真正需要的东西,打造真正有价值的长尾作品。【主页】apachecn.org【归档】home.apachecn.org【社区】bbs.apachecn.org【Github】@ApacheCN自媒体平台微博:@ApacheCN知乎:@ApacheCN公众号:@ApacheCNCSDN | OSChina | 博客园简书 | 搜狐号 | bilibili 专栏We are ApacheCN Open Source Organization, not ASF! We are fans of AI, and have no relationship with ASF!合作 or 侵权,请联系 <apachecn@163.com> | 请抄送一份到 <wizard.z@foxmail.com>特色项目AILearning - 机器学习实战文字教程教学版视频讨论版视频PyTorch 0.2/0.3/0.4/1.0 中文文档和教程Sklearn 与 TensorFlow 机器学习实用指南人工智能/机器学习/数据科学比赛系列Kaggle 项目实战教程:文档 + 代码 + 视频数据科学比赛收集平台LeetCode,HackRank,剑指 offer,经典算法实现Girls In AI:面向编程零基础女孩子的AI算法工程师养成计划司镜的数据结构课程(持续更新)UCB CS/DS 系列课本UCB CS61a 课本:SICP Python 描述UCB CS61b 课本:Java 中的数据结构UCB Data8 课本:计算与推断思维UCB Prob140 课本:面向数据科学的概率论UCB DS100 课本:数据科学的原理与技巧Numpy 技术栈中文文档NumPy 中文文档Pandas 中文文档Matplotlib 中文文档Sklearn 0.19 中文文档statsmodels 中文文档seaborn 0.9 中文文档编程语言JavaJava 编程思想Java 从0~1个人笔记后端/大数据Spark 2.2.0 中文文档Storm 1.1.0 中文文档Kafka 1.0.0 中文文档Beam 中文文档Zeppelin 0.7.2 中文文档Elasticsearch 5.4 中文文档Kibana 5.2 中文文档Kudu 1.4.0 中文文档Spring Boot 1.5.2 中文文档Airflow 中文文档HBase 3.0 中文参考指南Flink 1.7 中文文档工具Git 中文参考区块链Solidity 中文文档数学笔记MIT 18.06 线性代数笔记Python 数据科学NumPy 中文文档Pandas 中文文档Matplotlib 中文文档 |UCB Data8 课本:计算与推断思维UCB Prob140 课本:面向数据科学的概率论UCB DS100 课本:数据科学的原理与技巧利用 Python 进行数据分析 · 第 2 版fast.ai 数值线性代数讲义 v2Pandas Cookbook 带注释源码statsmodels 中文文档数据科学 IPython 笔记本seaborn 0.9 中文文档CS 教程LeetCode,HackRank,剑指 offer,经典算法实现GeeksForGeeks 翻译计划UCB CS61a 课本:SICP Python 描述UCB CS61b 课本:Java 中的数据结构数据结构思维中国大学 MOOC 计算机操作系统笔记简单数据结构实现司镜的数据结构课程(持续更新)AI 教程AILearning - 机器学习实战文字教程教学版视频讨论版视频Sklearn 与 TensorFlow 机器学习实用指南面向机器学习的特征工程Python 数据分析与挖掘实战(带注释源码)SciPyCon 2018 Sklearn 教程TensorFlow 学习指南fast.ai 机器学习和深度学习中文笔记HackCV 网站文章翻译台湾大学林轩田机器学习笔记Scikit-learn 秘籍写给人类的机器学习数据科学和人工智能技术笔记Girls In AI:面向编程零基础女孩子的AI算法工程师养成计划Machine Learning Mastery 博客文章翻译AI 文档Sklearn 0.19 中文文档PyTorch 0.2/0.3/0.4/1.0 中文文档和教程XGBoost 中文文档LightGBM 中文文档FastText 中文文档Gensim 中文文档OpenCV 4.0 中文文档AI 比赛Kaggle 项目实战教程:文档 + 代码 + 视频数据科学比赛收集平台其它独立开发/自由职业/远程工作资源列表通往财富自由之路精细笔记5 分钟商学院精细笔记翻译征集要求:机器学习/数据科学相关或者编程相关原文必须在互联网上开放不能只提供 PDF 格式(我们实在不想把精力都花在排版上)请先搜索有没有人翻译过请回复这个帖子。赞助我们 ...

April 3, 2019 · 1 min · jiezi

天马行空脚踏实地,阿里巴巴有群百里挑一的天才应届生

阿里巴巴有一群天马行空脚踏实地的阿里星。阿里巴巴的春季校招已经启动。在阿里的技术大咖储备团队中,有三分之一是来自高校招聘,这当中,有一项名为阿里星的神秘计划。这是校招中专门针对高校顶尖大学生的一个培养计划,每年平均录取人数不到20人,录取比例不足1%。百里挑一的阿里星作为阿里面向应届生的顶级人才计划,只有精英中的精英才有机会获得阿里星称号。自2011年起,每一届阿里星都是百里挑一的人才,他们大多是博士,经过十几轮面试,入职前就自带各种主角光环,比如“高考状元”“清华博士”“SCI期刊审稿人”等等。在以阿里星身份入职后,这些精英应届生将获得上不封顶的年薪,由副总裁级别担任主管进行重点培养,定期和阿里合伙人面对面交流,更重要的是,他们有机会直面最具挑战性的项目。8年来,历届阿里星中有人成为了阿里云高级安全工程师,有人成为了菜鸟高级算法工程师,也有人直接踏进达摩院科学家的战队。在最近一届的阿里星中,也有不少新星崭露头角——比如中科大毕业的陈谦,年仅27岁就独立研发人机对话系统,在国际大赛中接连击败IBM、麻省理工等机构,拿下两个世界第一,将人机对话准确率的世界纪录提升至94.1%。“前几年传得很神,说阿里星都是3岁会编程,10岁夺国际奖项的天才。”菜鸟高级算法工程师丁见亚第一次听说阿里星时,还在清华大学攻读博士,旁人偶尔说起,自动化专业的师兄盖坤是第一届的阿里星,目前已是阿里的算法研究员。从小学编程的天才2018年,直到丁见亚自己也成了阿里星,他才明白“3岁会编程”的标准或许有点夸大其词,然而不可否认的是,阿里星们大都是从小接触编程。丁见亚的第一行代码,就是在小学的机房里写下,用来控制屏幕上一只小乌龟的运动轨迹。而2018届阿里星中年纪最小的刘煜堃,小学六年级就会熬夜翻看论坛,通过视频偷偷学习黑客知识。相比其他一路顺风顺水的同学,刘煜堃可算是一个经历过失败的天才。上高中后,他又迷上了网络游戏,尤其到寒暑假,一天能玩上七八个小时,影响了学习,最终高考发挥失常进了福州大学,就读信息安全专业。大二暑假,导师推荐刘煜堃与同学组队,参加了CTF(网络安全技术)竞赛,作为“萌新”却意外打进决赛,刘煜堃由此入坑,回校后组建了一支战队。每周六早上9点,战队成员围坐在电脑前,在规定的48小时内轮流上线解题,通过挖掘漏洞攻击对手得分,修补漏洞避免丢分。从两周一赛,到一周一赛,刘煜堃一路“打”进了清华大学网络与信息安全实验室,成为清华蓝莲花战队的一员。2016年,蓝莲花战队代表中国参加世界黑客大赛DEF CON CTF决赛并获得亚军。实践经历成敲门砖在2018届阿里星中,只有刘煜堃是硕士,但多年CTF的战绩成为他入选阿里星的敲门砖,最终成为阿里云智能安全部的高级安全工程师。这也是阿里在历年校招中始终秉承的理念——文凭只是衡量人才的一个方面,实践能力也很重要,而最能反映实践能力的就是实践经历。丁见亚曾在滴滴实习,需要分析供给侧与需求侧的诉求,建立合理的定价模型,以作为平台定价的参考。2017年暑假,丁见亚还跟着导师做过一篇关于“分享经济”的论文。这两次经历成为丁见亚在阿里星面试时与主管的谈资。“主管一听我的论文,就问我对滴滴的定价怎么看,这刚好是我下一趴要讲的内容。”一般情况下,阿里星的面试起码进行4轮,有些人的面试高达13轮。刘煜堃的主管曾告诉他,在面试时,自己最看重的是阿里星的技术功底、技术视野和求知欲,光靠理论知识,很难达到这个标准。![清华求学的胡庆达](https://upload-images.jianshu…丁见亚同样有着丰富的实践经历,他还在EJOR等高水平期刊发表了多篇论文,并数次担任SCI期刊审稿人。同样来自清华大学的2018届阿里星胡庆达,在成为阿里云数据库技术专家之前,除了有“多篇顶级会议论文”的标签,也在工业圈的实践经验上,积累了近20万行的代码。到阿里就为了战斗都说结业是一项双向选择,那么作为应届生中百里挑一的精英,阿里星们为什么选择了阿里?2018届阿里星杨涛,是安徽省芜湖市高考状元。一次,他在清华大学的报告厅里,听到阿里巴巴达摩院副院长华先胜关于城市大脑的演讲,被“用数据智能助力城市管理和服务”这一目标感染。2018年6月,博士毕业离校前夕,杨涛在清华的老图书馆里,接到了阿里HR的面试电话,希望他以阿里星的身份加入达摩院。“懂技术的人,对技术是有一种敬仰和向往。”杨涛接下了这份邀约。刘煜堃则是在面试时,被主管一句话击中,“阿里云上的场景是天然的战场,你打过仗,能力才会得到提升。”成就感,也是未来星选择阿里的重要因素。丁见亚放弃了在美国名校读博后的机会,加入了阿里旗下的菜鸟网络。阿里一直鼓励工程师把技术和业务场景结合起来,在加入菜鸟大快递智能化部门后,畅游在“数据场景”中的丁见亚更是坚定了最初的选择。偶然听到周围朋友刷着手机感叹“现在快递越送越快了”,丁见亚会暗自窃喜。在物流车调度场景中,丁见亚和同事通过机器学习的算法挖掘物流车的历史线路规律,甚至连司机爱超的小道,以及中途停靠抽烟片区的偏好,也被纳入计算因子,这套算法替代了人工调度,输出一套最优化的方案。“数据配合当下场景加强用户的体感,这满足了我在数学建模中的梦。”马云曾经对来阿里求职的人说过,“我不能保证你有钱,也不能保证你成名,但能保证一定有更多历练。”阿里星计划正是凭借着更多更丰富的实战场景和历炼的机会,吸引着高校中最年轻、最顶级的竞赛控、学习控、技术控、逻辑控、实战控。天马行空脚踏实地奋斗在阿里巴巴生态圈里,阿里星们“高考状元”“清华博士”“论文达人”的光环早已褪去,但是不断学习,不断接受挑战,仍然是这些学霸的本色。“清华流行一句话,不要让考上清华成为你的人生巅峰。”丁见亚每周至少阅读两篇论文“补充能量”,他也喜欢通过“撸铁”释放压力,午餐前的一个小时,他会先在菜鸟园区内的健身房完成一系列的力量训练。丁见亚还有个坚持十年的习惯——随时随地思考,随时随地记录,哪怕在洗澡时想出的点子,他也会立马擦干双手,把想法记录到手机上。刘煜堃的主管,经常对他提出高出职级范围的要求。“目前安全攻防人才多数关注于单点突破,而安全架构的设计必须考虑周全。”在推进的可信云项目中,主管要求刘煜堃在保持现有攻防技能的情况下,承担更多的安全架构设计,在项目结束后能完成从一个顶级的攻防人才到安全架构人才的转变。如何缩短理论研究与实际应用的距离,则是胡庆达要攻破的难题。他还记得刚入职那天,主管说过“造火箭得先学会拧螺丝”。杨涛到了达摩院后,与优酷合作,用算法来选择个性化的视频封面分发,实现视频领域的“千人千面”,他明白了“阿里的科研并不是天马行空的科研,是始终面向商业需求”。“天马行空,脚踏实地。”阿里巴巴集团CEO张勇的这句话很契合阿里星们的真实状态,2019年春季校招初始,更多的“最强大脑”将会加入阿里巴巴的生态圈,赶上这场技术创造新商业的变革时期。本文作者:王安忆阅读原文本文来自云栖社区合作伙伴“ 天下网商”,如需转载请联系原作者。

April 1, 2019 · 1 min · jiezi

前端学习之路之资源篇

不知不觉毕业已经快一年了,做前端也快一年了,收集的各种资料充满了我的收藏,作为一个强迫症患者,我不得不把他们好好整理,那看得叫一个清爽啊,既然整理出来了,就献给那些有需要的盆友吧,大家共勉!!详情请移步我的笔记仓库, 这个我会一直更新来着。ReactReact - AntdUxCoreZanUI: PC、移动、小程序React.part: 查找React的组件VueVue - AntdIView: 一套基于 Vue.js 的高质量Element: Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库Mint UI: 基于 Vue.js 的移动端组件库VUX: 一个凑合的 Vue.js 移动端 UI 组件库Vue-Map: 基于 Vue 2.x 和高德地图的地图组件文档NodejsExpress: 高度包容、快速而极简的 Node.js Web 框架koaegg: 为企业级框架和应用而生Nodejs学习笔记Javascript现代 Javascript 教程: 从基础知识到高阶主题,只需既简单又详细的解释。Philip Roberts: Visualizing the javascript runtime at runtimeGithub -> DemoLodashRamdaUnderscoreGithub30 seconds of codeGithubAST ExplorerMockEasy-Mock: 高效伪造数据Mock.js:生成随机数据,拦截 Ajax 请求Rapid-Api动画库Animate.cssAnimejsTweenMax.jsGreenSock测试框架MochaMocha GitHubChaiChai GithubJestJest GithubjsPerf — JavaScript performance playgroundGitHub优秀项目 & 插件WebpackWebpack config tool: webpack 配置工具BootCDN: 稳定、快速、免费的前端开源项目 CDN 加速服务BootStrapBootsWatch: Free themes for BootstrapRxJS: 使用 Observables 的响应式编程的库,它使编写异步或基于回调的代码更容易。Github中文文档LayuiGithub开发资源Awesomes前端开发资源算法学习 & 机器学习Rappid算法学习机器深度学习VisuAlgo - 数据结构和算法动态可视化 (Chinese)Algorithm Visualizer GithubPapers With Code : the latest in machine learning[Data Structure Visualizations]https://www.cs.usfca.edu/gal…: 旧金山大学CS Data StructureBestofML: 收集汇总了机器学习相关的资源,包括书籍、课程、博客、论文等Github数学知识学习微积分线性代数概率论最优化方法Math ∩ ProgrammingImmersive Linear Algebra: 一本会动的线代书,O(∩_∩)O哈哈机器学习的数学基础知识GithubDownloadLinuxLinux命令大全Iodide: Mozilla 支持的在 Web 中实现各种数据科学的效果GithubIcon & 设计 & 网页IconfontFontAwesomeIoniconsIcomoonMobiriseiconszwiconunDraw优设Can I Use: 查询浏览器的特性支持情况Package Different查询 NodeJS 的 ES2018 特性支持情况开发社区 & 学习社区Vue.js 社区React.js社区掘金InfoQ: InfoQ 是一个实践驱动的社区资讯站点,致力于促进软件开发领域知识与创新的传播。w3cplusV2EX大前端开源中国segmentfaultbest-chinese-front-end-blogs: 收集优质的中文前端博客softnshare路径及文章众成翻译Fly63前端博客 & 团队阮一峰ES6入门廖雪峰官网AlloyTeam - 腾讯Web前端团队凹凸实验室淘宝前端团队FED奇舞团腾讯互娱路径程序员不能错过的28份技术知识图谱,你的进阶路上必备学好机器学习需要哪些数学知识?浏览器的工作原理工具CodesanboxCodepenRepl.itGlitch知乎个人博客Github ...

March 27, 2019 · 1 min · jiezi

如何用cmake编译

本文由云+社区发表作者:工程师小熊CMake编译原理CMake是一种跨平台编译工具,比make更为高级,使用起来要方便得多。CMake主要是编写CMakeLists.txt文件,然后用cmake命令将CMakeLists.txt文件转化为make所需要的makefile文件,最后用make命令编译源码生成可执行程序或共享库(so(shared object))。因此CMake的编译基本就两个步骤:cmakemakecompile.shg++ -rdynamic ../include/incl/tfc_base_config_file.cpp ../include/mq/*.cpp local_util.cpp AgentMemRpt.cpp AgentDiskRpt.cpp AgentLoadRpt.cpp AgentIoRpt.cpp AgentNetRpt.cpp AgentCpuRpt.cpp AgentProcessRpt.cpp AgentParentRpt.cpp AgentSysTop_5.cpp BaseFeatureRptMain.cpp -o rpt_main -I../include/incl -I../include/mq -I../include/rapidjson -lpthread -ldlCMake说明一般把CMakeLists.txt文件放在工程目录下,使用时,先创建一个叫build的文件夹(这个并非必须,因为cmake命令指向CMakeLists.txt所在的目录,例如cmake .. 表示CMakeLists.txt在当前目录的上一级目录。cmake后会生成很多编译的中间文件以及makefile文件,所以一般建议新建一个新的目录,专门用来编译),然后执行下列操作:cd build cmake .. make 其中cmake .. 在build里生成Makefile,make根据生成makefile文件,编译程序,make应当在有Makefile的目录下,根据Makefile生成可执行文件。编写 CMakeList.txt# 1. 声明要求的cmake最低版本cmake_minimum_required( VERSION 2.8 )# 2. 添加c++11标准支持#set( CMAKE_CXX_FLAGS “-std=c++11” )# 3. 声明一个cmake工程PROJECT(rpt_main)MESSAGE(STATUS “Project: SERVER”) #打印相关消息消息 # 4. 头文件include_directories(${PROJECT_SOURCE_DIR}/../include/mq ${PROJECT_SOURCE_DIR}/../include/incl ${PROJECT_SOURCE_DIR}/../include/rapidjson)# 5. 通过设定SRC变量,将源代码路径都给SRC,如果有多个,可以直接在后面继续添加set(SRC ${PROJECT_SOURCE_DIR}/../include/incl/tfc_base_config_file.cpp ${PROJECT_SOURCE_DIR}/../include/mq/tfc_ipc_sv.cpp ${PROJECT_SOURCE_DIR}/../include/mq/tfc_net_ipc_mq.cpp${PROJECT_SOURCE_DIR}/../include/mq/tfc_net_open_mq.cpp ${PROJECT_SOURCE_DIR}/local_util.cpp${PROJECT_SOURCE_DIR}/AgentMemRpt.cpp ${PROJECT_SOURCE_DIR}/AgentDiskRpt.cpp ${PROJECT_SOURCE_DIR}/AgentLoadRpt.cpp ${PROJECT_SOURCE_DIR}/AgentIoRpt.cpp${PROJECT_SOURCE_DIR}/AgentNetRpt.cpp ${PROJECT_SOURCE_DIR}/AgentCpuRpt.cpp ${PROJECT_SOURCE_DIR}/AgentProcessRpt.cpp ${PROJECT_SOURCE_DIR}/AgentParentRpt.cpp${PROJECT_SOURCE_DIR}/AgentSysTop_5.cpp ${PROJECT_SOURCE_DIR}/BaseFeatureRptMain.cpp )# 6. 创建共享库/静态库# 设置路径(下面生成共享库的路径)set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)# 即生成的共享库在工程文件夹下的lib文件夹中 set(LIB_NAME rpt_main_lib)# 创建共享库(把工程内的cpp文件都创建成共享库文件,方便通过头文件来调用)# 这时候只需要cpp,不需要有主函数 # ${PROJECT_NAME}是生成的库名 表示生成的共享库文件就叫做 lib工程名.so# 也可以专门写cmakelists来编译一个没有主函数的程序来生成共享库,供其它程序使用# SHARED为生成动态库,STATIC为生成静态库add_library(${LIB_NAME} STATIC ${SRC}) # 7. 链接库文件# 把刚刚生成的${LIB_NAME}库和所需的其它库链接起来# 如果需要链接其他的动态库,-l后接去除lib前缀和.so后缀的名称,以链接# libpthread.so 为例,-lpthreadtarget_link_libraries(${LIB_NAME} pthread dl) # 8. 编译主函数,生成可执行文件# 先设置路径set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) # 可执行文件生成add_executable(${PROJECT_NAME} ${SRC}) # 这个可执行文件所需的库(一般就是刚刚生成的工程的库咯)target_link_libraries(${PROJECT_NAME} pthread dl ${LIB_NAME})使用 cmake进入/home/pzqu/agent/libvirt_base_feature/build目录执行命令 cmake ..cmake ..查看生成的目录结构,此目录结构是中间代码,不用提交到git[root@TJSJZVM000456 /home/pzqu/agent/libvirt_base_feature/build]# tree.|– CMakeCache.txt|– CMakeFiles| |– 2.8.12.2| | |– CMakeCCompiler.cmake| | |– CMakeCXXCompiler.cmake| | |– CMakeDetermineCompilerABI_C.bin| | |– CMakeDetermineCompilerABI_CXX.bin| | |– CMakeSystem.cmake| | |– CompilerIdC| | | |– CMakeCCompilerId.c| | | -- a.out| | – CompilerIdCXX| | |– CMakeCXXCompilerId.cpp| | -- a.out| |-- CMakeDirectoryInformation.cmake| |-- CMakeOutput.log| |-- CMakeTmp| |-- Makefile.cmake| |-- Makefile2| |-- TargetDirectories.txt| |-- cmake.check_cache| |-- progress.marks| |-- rpt_main.dir| | |-- DependInfo.cmake| | |-- build.make| | |-- cmake_clean.cmake| | |-- depend.make| | |-- flags.make| | |-- home| | | – pzqu| | | -- agent| | | – include| | | |– incl| | | -- mq| | |-- link.txt| | – progress.make| -- rpt_main_lib.dir| |-- DependInfo.cmake| |-- build.make| |-- cmake_clean.cmake| |-- cmake_clean_target.cmake| |-- depend.make| |-- flags.make| |-- home| | – pzqu| | -- agent| | – include| | |– incl| | -- mq| |-- link.txt| – progress.make|– Makefile`– cmake_install.cmake使用make命令编译得到二进制文件make二进制文件所在目录(CMakeLists.txt文件配置)成功生成二进制文件下次教大家如何用Clion自动同步代码到服务器上,并进行debug此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

March 13, 2019 · 2 min · jiezi

刚刚,阿里开源 iOS 协程开发框架 coobjc!

阿里妹导读:刚刚,阿里巴巴正式对外开源了基于 Apache 2.0 协议的协程开发框架 coobjc,开发者们可以在 Github 上自主下载。coobjc是为iOS平台打造的开源协程开发框架,支持Objective-C和Swift,同时提供了cokit库为Foundation和UIKit中的部分API提供了协程化支持,本文将为大家详细介绍coobjc的设计理念及核心优势。开源地址https://github.com/alibaba/coobjciOS异步编程问题从2008年第一个iOS版本发布至今的11年时间里,iOS的异步编程方式发展缓慢。基于 Block 的异步编程回调是目前 iOS 使用最广泛的异步编程方式,iOS 系统提供的 GCD 库让异步开发变得很简单方便,但是基于这种编程方式的缺点也有很多,主要有以下几点:容易进入"嵌套地狱"错误处理复杂和冗长容易忘记调用 completion handler条件执行变得很困难从互相独立的调用中组合返回结果变得极其困难在错误的线程中继续执行(如子线程操作UI)难以定位原因的多线程崩溃(手淘中多线程crash已占比60%以上)锁和信号量滥用带来的卡顿、卡死针对多线程以及尤其引发的各种崩溃和性能问题,我们制定了很多编程规范、进行了各种新人培训,尝试降低问题发生的概率,但是问题依然很严峻,多线程引发的问题占比并没有明显的下降,异步编程本来就是很复杂的事情,单靠规范和培训是难以从根本上解决问题的,需要有更加好的编程方式来解决。解决方案上述问题在很多系统和语言开发中都可能会碰到,解决问题的标准方式就是使用协程,C#、Kotlin、Python、Javascript 等热门语言均支持协程极其相关语法,使用这些语言的开发者可以很方便的使用协程及相关功能进行异步编程。2017 年的 C++ 标准开始支持协程,Swift5 中也包含了协程相关的标准,从现在的发展趋势看基于协程的全新的异步编程方式,是我们解决现有异步编程问题的有效的方式,但是苹果基本已经不会升级 Objective-C 了,因此使用Objective-C的开发者是无法使用官方的协程能力的,而最新 Swift 的发布和推广也还需要时日,为了让广大iOS开发者能快速享受到协程带来的编程方式上的改变,手机淘宝架构团队基于长期对系统底层库和汇编的研究,通过汇编和C语言实现了支持 Objective-C 和 Swift 协程的完美解决方案 —— coobjc。核心能力提供了类似C#和Javascript语言中的Async/Await编程方式支持,在协程中通过调用await方法即可同步得到异步方法的执行结果,非常适合IO、网络等异步耗时调用的同步顺序执行改造。提供了类似Kotlin中的Generator功能,用于懒计算生成序列化数据,非常适合多线程可中断的序列化数据生成和访问。提供了Actor Model的实现,基于Actor Model,开发者可以开发出更加线程安全的模块,避免由于直接函数调用引发的各种多线程崩溃问题。提供了元组的支持,通过元组Objective-C开发者可以享受到类似Python语言中多值返回的好处。内置系统扩展库提供了对NSArray、NSDictionary等容器库的协程化扩展,用于解决序列化和反序列化过程中的异步调用问题。提供了对NSData、NSString、UIImage等数据对象的协程化扩展,用于解决读写IO过程中的异步调用问题。提供了对NSURLConnection和NSURLSession的协程化扩展,用于解决网络异步请求过程中的异步调用问题。提供了对NSKeyedArchieve、NSJSONSerialization等解析库的扩展,用于解决解析过程中的异步调用问题。coobjc设计最底层是协程内核,包含了栈切换的管理、协程调度器的实现、协程间通信channel的实现等。中间层是基于协程的操作符的包装,目前支持async/await、Generator、Actor等编程模型。最上层是对系统库的协程化扩展,目前基本上覆盖了Foundation和UIKit的所有IO和耗时方法。核心实现原理协程的核心思想是控制调用栈的主动让出和恢复。一般的协程实现都会提供两个重要的操作:Yield:是让出cpu的意思,它会中断当前的执行,回到上一次Resume的地方。Resume:继续协程的运行。执行Resume后,回到上一次协程Yield的地方。我们基于线程的代码执行时候,是没法做出暂停操作的,我们现在要做的事情就是要代码执行能够暂停,还能够再恢复。 基本上代码执行都是一种基于调用栈的模型,所以如果我们能把当前调用栈上的状态都保存下来,然后再能从缓存中恢复,那我们就能够实现yield和 resume。实现这样操作有几种方法呢?第一种:利用glibc 的 ucontext组件(云风的库)。第二种:使用汇编代码来切换上下文(实现c协程),原理同ucontext。第三种:利用C语言语法switch-case的奇淫技巧来实现(Protothreads)。第四种:利用了 C 语言的 setjmp 和 longjmp。第五种:利用编译器支持语法糖。上述第三种和第四种只是能过做到跳转,但是没法保存调用栈上的状态,看起来基本上不能算是实现了协程,只能算做做demo,第五种除非官方支持,否则自行改写编译器通用性很差。而第一种方案的 ucontext 在iOS上是废弃了的,不能使用。那么我们使用的是第二种方案,自己用汇编模拟一下 ucontext。模拟ucontext的核心是通过getContext和setContext实现保存和恢复调用栈。需要熟悉不同CPU架构下的调用约定(Calling Convention). 汇编实现就是要针对不同cpu实现一套,我们目前实现了 armv7、arm64、i386、x86_64,支持iPhone真机和模拟器。Show me the code说了这么多,还是看看代码吧,我们从一个简单的网络请求加载图片功能来看看coobjc到底是如何使用的。下面是最普通的网络请求的写法:下面是使用coobjc库协程化改造后的代码:原本需要20行的代码,通过coobjc协程化改造后,减少了一半,整个代码逻辑和可读性都更加好,这就是coobjc强大的能力,能把原本很复杂的异步代码,通过协程化改造,转变成逻辑简洁的顺序调用。coobjc还有很多其他强大的能力,本文对于coobjc的实际使用就不过多介绍了,感兴趣的朋友可以去官方github仓库自行下载查看。性能提升我们在iPhone7 iOS11.4.1的设备上使用协程和传统多线程方式分别模拟高并发读取数据的场景,下面是两种方式得到的压测数据。测试机器:iPhone7 iOS11.4.1数据文件大小:20M协程最多使用线程数:4数据测试结果(统计的是所有并发访问结束的总耗时):从上面的表格我们可以看到使用在并发量很小的场景,由于多线程可以完全使用设备的计算核心,因此coobjc总耗时要比传统多线程略高,但是由于整体耗时都很小,因此差异并不明显,但是随着并发量的增大,coobjc的优势开始逐渐体现出来,当并发量超过1000以后,传统多线程开始出现线程分配异常,而导致很多并发任务并没有执行,因此在上表中显示的是大于20秒,实际是任务已经无法正常执行了,但是coobjc仍然可以正常运行。我们在手机淘宝这种超级App中尝试了协程化改造,针对部分性能差的页面,我们发现在滑动过程中存在很多主线程IO调用、数据解析,导致帧率下降严重,通过引入coobjc,在不改变原有业务代码的基础上,通过全局hook部分IO、数据解析方法,即可让原来在主线程中同步执行的IO方法异步执行,并且不影响原有的业务逻辑,通过测试验证,这样的改造在低端机(iPhone6及以下的机器)上的帧率有20%左右的提升。优势简明概念少:只有很少的几个操作符,相比响应式几十个操作符,简直不能再简单了。原理简单:协程的实现原理很简单,整个协程库只有几千行代码。易用使用简单:它的使用方式比GCD还要简单,接口很少。改造方便:现有代码只需要进行很少的改动就可以协程化,同时我们针对系统库提供了大量协程化接口。清晰同步写异步逻辑:同步顺序方式写代码是人类最容易接受的方式,这可以极大的减少出错的概率。可读性高:使用协程方式编写的代码比block嵌套写出来的代码可读性要高很多。性能调度性能更快:协程本身不需要进行内核级线程的切换,调度性能快,即使创建上万个协程也毫无压力。减少卡顿卡死: 协程的使用以帮助开发减少锁、信号量的滥用,通过封装会引起阻塞的IO等协程接口,可以从根源上减少卡顿、卡死,提升应用整体的性能。总结程序是写来给人读的,只会偶尔让机器执行一下。——Abelson and Sussman基于协程实现的编程范式能够帮助开发者编写出更加优美、健壮、可读性更强的代码。协程可以帮助我们在编写并发代码的过程中减少线程和锁的使用,提升应用的性能和稳定性。本文作者:淘宝技术阅读原文本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。

March 1, 2019 · 1 min · jiezi

基于泛型编程的序列化实现方法

写在前面序列化是一个转储-恢复的操作过程,即支持将一个对象转储到临时缓冲或者永久文件中和恢复临时缓冲或者永久文件中的内容到一个对象中等操作,其目的是可以在不同的应用程序之间共享和传输数据,以达到跨应用程序、跨语言和跨平台的解耦,以及当应用程序在客户现场发生异常或者崩溃时可以即时保存数据结构各内容的值到文件中,并在发回给开发者时再恢复数据结构各内容的值以协助分析和定位原因。泛型编程是一个对具有相同功能的不同类型的抽象实现过程,比如STL的源码实现,其支持在编译期由编译器自动推导具体类型并生成实现代码,同时依据具体类型的特定性质或者优化需要支持使用特化或者偏特化及模板元编程等特性进行具体实现。Hello World#include <iostream>int main(int argc, char* argv[]){ std::cout << “Hello World!” << std::endl; return 0;}泛型编程其实就在我们身边,我们经常使用的std和stl命名空间中的函数和类很多都是泛型编程实现的,如上述代码中的std::cout即是模板类std::basic_ostream的一种特化namespace std{ typedef basic_ostream<char> ostream;}从C++的标准输入输出开始除了上述提到的std::cout和std::basic_ostream外,C++还提供了各种形式的输入输出模板类,如std::basic_istream, std::basic_ifstream,std::basic_ofstream, std::basic_istringstream,std::basic_ostringstream等等,其主要实现了内建类型(built-in)的输入输出接口,比如对于Hello World可直接使用于字符串,然而对于自定义类型的输入输出,则需要重载实现操作符>>和<<,如对于下面的自定义类class MyClip{ bool mValid; int mIn; int mOut; std::string mFilePath;};如使用下面的方式则会出现一连串的编译错误MyClip clip;std::cout << clip;错误内容大致都是一些clip不支持<<操作符并在尝试将clip转为cout支持的一系列的内建类型如void*和int等等类型时转换操作不支持等信息。为了解决编译错误,我们则需要将类MyClip支持输入输出操作符>>和<<,类似实现代码如下inline std::istream& operator>>(std::istream& st, MyClip& clip){ st >> clip.mValid; st >> clip.mIn >> clip.mOut; st >> clip.mFilePath; return st;}inline std::ostream& operator<<(std::ostream& st, MyClip const& clip){ st << clip.mValid << ’ ‘; st << clip.mIn << ’ ’ << clip.mOut << ’ ‘; st << clip.mFilePath << ’ ‘; return st;}为了能正常访问类对象的私有成员变量,我们还需要在自定义类型里面增加序列化和反序列化的友元函数(回忆一下这里为何必须使用友元函数而不能直接重载操作符>>和<<?),如friend std::istream& operator>>(std::istream& st, MyClip& clip);friend std::ostream& operator<<(std::ostream& st, MyClip const& clip);这种序列化的实现方法是非常直观而且容易理解的,但缺陷是对于大型的项目开发中,由于自定义类型的数量较多,可能达到成千上万个甚至更多时,对于每个类型我们则需要实现2个函数,一个是序列化转储数据,另一个则是反序列化恢复数据,不仅仅增加了开发实现的代码数量,如果后期一旦对部分类的成员变量有所修改,则需要同时修改这2个函数。同时考虑到更复杂的自定义类型,比如含有继承关系和自定义类型的成员变量class MyVideo : public MyClip{ std::list<MyFilter> mFilters;};上述代码需要转储-恢复类MyVideo的对象内容时,事情会变得更复杂些,因为还需要转储-恢复基类,同时成员变量使用了STL模板容器list与自定义类’MyFilter`的结合,这种情况也需要自己去定义转储-恢复的实现方式。针对以上疑问,有没有一种方法能减少我们代码修改的工作量,同时又易于理解和维护呢?Boost序列化库对于使用C++标准输入输出的方法遇到的问题,好在Boost提供了一种良好的解决方式,则是将所有类型的转储-恢复操作抽象到一个函数中,易于理解,如对于上述类型,只需要将上述的2个友元函数替换为下面的一个友元函数template<typename Archive> friend void serialize(Archive&, MyClip&, unsigned int const);友元函数的实现类似下面的样子template<typename A>void serialize(A &ar, MyClip &clip, unsigned int const ver){ ar & BOOST_SERIALIZATION_NVP(clip.mValid); ar & BOOST_SERIALIZATION_NVP(clip.mIn); ar & BOOST_SERIALIZATION_NVP(clip.mOut); ar & BOOST_SERIALIZATION_NVP(clip.mFilePath);}其中BOOST_SERIALIZATION_NVP是Boost内部定义的一个宏,其主要作用是对各个变量进行打包。转储-恢复的使用则直接作用于操作符>>和<<,比如// storeMyClip clip;······std::ostringstream ostr;boost::archive::text_oarchive oa(ostr);oa << clip;// loadstd::istringstream istr(ostr.str());boost::archive::text_iarchive ia(istr);ia >> clip;这里使用的std::istringstream和std::ostringstream即是分别从字符串流中恢复数据以及将类对象的数据转储到字符串流中。对于类MyFilter和MyVideo则使用相同的方式,即分别增加一个友元模板函数serialize的实现即可,至于std::list模板类,boost已经帮我们实现了。这时我们发现,对于每一个定义的类,我们需要做的仅仅是在类内部声明一个友元模板函数,同时类外部实现这个模板函数即可,对于后期类的成员变量的修改,如增加、删除或者重命名成员变量,也仅仅是修改一个函数即可。Boost序列化库已经足够完美了,但故事并未结束!在用于端上开发时,我们发现引用Boost序列化库遇到了几个挑战端上的编译资料很少,官方对端上编译的资料基本没有,在切换不同的版本进行编译时经常会遇到各种奇怪的编译错误问题Boost在不同的C++开发标准之间兼容性不够好,尤其是使用libc++标准进行编译链接时遇到的问题较多Boost增加了端上发行包的体积Boost每次序列化都会增加序列化库及版本号等私有头信息,反序列化时再重新解析,降低了部分场景下的使用性能基于泛型编程的序列化实现方法为了解决使用Boost遇到的这些问题,我们觉得有必要重新实现序列化库,以剥离对Boost的依赖,同时能满足如下要求由于现有工程大量使用了Boost序列化库,因此兼容现有的代码以及开发者的习惯是首要目标尽量使得代码修改和重构的工作量最小兼容不同的C++开发标准提供比Boost序列化库更高的性能降低端上发行包的体积为了兼容现有使用Boost的代码以及保持当前开发者的习惯,同时使用代码修改的重构的工作量最小,我们应该保留模板函数serialize,同时对于模板函数内部的实现,为了提高效率也不需要对各成员变量重新打包,即直接使用如下定义#define BOOST_SERIALIZATION_NVP(value) value对于转储-恢复的接口调用,仍然延续目前的调用方式,只是将输入输出类修改为alivc::text_oarchive oa(ostr);alivc::text_iarchive ia(istr);好了,到此为止,序列化库对外的接口工作已经做好,剩下的就是内部的事情,应该如何重新设计和实现序列化库的内部框架才能满足要求呢?先来看一下当前的设计架构的处理流程图比如对于转储类text_oarchive,其支持的接口必须包括explicit text_oarchive(std::ostream& ost, unsigned int version = 0);template <typename T> text_oarchive& operator<<(T& v);template <typename T> text_oarchive& operator&(T& v);开发者调用操作符函数<<时,需要首先回调到相应类型的模板函数serialize中template <typename T>text_oarchive& operator<<(T& v){ serialize(*this, v, mversion); return *this;}当开始对具体类型的各个成员进行操作时,这时需要进行判断,如果此成员变量的类型已经是内建类型,则直接进行序列化,如果是自定义类型,则需要重新回调到对应类型的模板函数serialize中template <typename T>text_oarchive& operator&(T& v){ basic_save<T>::invoke(*this, v, mversion); return *this;}上述代码中的basic_save::invoke则会在编译期完成模板类型推导并选择直接对内建类型进行转储还是重新回调到成员变量对应类型的serialize函数继续重复上述过程。由于内建类型数量有限,因此这里我们选择使模板类basic_save的默认行为为回调到相应类型的serialize函数中template <typename T, bool E = false>struct basic_load_save{ template <typename A> static void invoke(A& ar, T& v, unsigned int version) { serialize(ar, v, version); }};template <typename T>struct basic_save : public basic_load_save<T, std::is_enum<T>::value>{};这时会发现上述代码的模板参数多了一个参数E,这里主要是需要对枚举类型进行特殊处理,使用偏特化的实现如下template <typename T>struct basic_load_save<T, true>{ template <typename A> static void invoke(A& ar, T& v, unsigned int version) { int tmp = v; ar & tmp; v = (T)tmp; }};到这里我们已经完成了重载操作符&的默认行为,即是不断进行回溯到相应的成员变量的类型中的模板函数serialize中,但对于碰到内建模型时,我们则需要让这个回溯过程停止,比如对于int类型template <typename T>struct basic_pod_save{ template <typename A> static void invoke(A& ar, T const& v, unsigned int) { ar.template save(v); }};template <>struct basic_save<int> : public basic_pod_save<int>{};这里对于int类型,则直接转储整数值到输出流中,此时text_oarchive则还需要增加一个终极转储函数template <typename T>void save(T const& v){ most << v << ’ ‘;}这里我们发现,在save成员函数中,我们已经将具体的成员变量的值输出到流中了。对于其它的内建类型,则使用相同的方式处理,要以参考C++ std::basic_ostream的源码实现。相应的,对于恢复操作的text_iarchive的操作流程如下图测试结果我们对使用Boost以及重新实现的序列化库进行了对比测试,其结果如下代码修改的重构的工作非常小,只需要删除Boost的相关头文件,以及将boost相关命名空间替换为alivc,BOOST_SERIALIZATION_FUNCTION以及BOOST_SERIALIZATION_NVP的宏替换Android端下的发行包体积大概减少了500KB目前的消息处理框架中,处理一次消息的平均时间由100us降低到了25us代码实现约300行,更轻量级未来还能做什么由于当前项目的原因,重新实现的序列化还没有支持转储-恢复指针所指向的内存数据,但当前的设计框架已经考虑了这种拓展性,未来会考虑支持。总结泛型编程能够大幅提高开发效率,尤其是在代码重用方面能发挥其优势,同时由于其类型推导及生成代码均在编译期完成,并不会降低性能序列化对于需要进行转储-恢复的解耦处理以及协助定位异常和崩溃的原因分析具有重要作用利用C++及模板自身的语言特性优势,结合合理的架构设计,即易于拓展又能尽量避免过度设计参考资料https://www.ibm.com/developerworks/cn/aix/library/au-boostserialization/本文作者:lifesider阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 25, 2019 · 2 min · jiezi

每天一本电子书 - JavaScript for Kids

JavaScript for Kids: A Playful Introduction to Programming作者: Nick Morgan 出版社: No Starch Press副标题: A Playful Introduction to Programming出版年: 2014-12-12页数: 336定价: USD 34.95装帧: PaperbackISBN: 9781593274085内容简介 · · · · · ·JavaScript for Kids是对JavaScript语言和编程的轻松介绍。 在儿童友好的例子的帮助下,作者Nick Morgan教授JavaScript的基本知识。 Morgan从字符串,数组和循环的基础开始,然后继续向读者展示如何使用jQuery修改元素并使用canvas绘制图形。 在本书的最后,您将准备好创建自己的有趣动画和游戏,并且您将对JavaScript的基础知识有充分的了解。目录IntroductionPart I: FundamentalsChapter 1: What Is JavaScript?Chapter 2: Data Types and VariablesChapter 3: ArraysChapter 4: ObjectsChapter 5: The Basics of HTMLChapter 6: Conditionals and LoopsChapter 7: Creating a Hangman GameChapter 8: FunctionsPart II: Advanced JavaScriptChapter 9: The DOM and jQueryChapter 10: Interactive ProgrammingChapter 11: Find the Buried Treasure!Chapter 12: Object-Oriented ProgrammingPart III: CanvasChapter 13: The canvas ElementChapter 14: Making Things Move on the CanvasChapter 15: Controlling Animations with the KeyboardChapter 16: Making a Snake Game: Part 1Chapter 17: Making a Snake Game: Part 2Afterword: Where to Go from HereGlossaryIndex作者简介 · · · · · ·Nick Morgan是Twitter的高级前端工程师。 他和他的毛茸茸的狗Pancake一起住在旧金山。 他在skilldrick.co.uk上发表关于JavaScript的博客。更多信息文档资源搜索Maning参考https://medium.com/javascript-scene/12-books-every-javascript-developer-should-read-9da76157fb3 ...

January 25, 2019 · 1 min · jiezi

C语言权威指南和书单 - 专家级别

注: 点击标题即可下载1. Advanced Programming in the UNIX Environment, 3rd Edition2. Essential C3. Computer Systems(Second Edition) A Programmer’s Perspective更多免费电子书资源搜索 | Maning参考The Definitive C Book Guide and List

January 25, 2019 · 1 min · jiezi

阿里毕玄:程序员如何提升自己的硬实力

从业余程序员到职业程序员程序员刚入行时,我觉得最重要的是把自己培养成职业的程序员。我的程序员起步比同龄人都晚了很多,更不用说现在的年轻人了。我大学读的是生物专业,在上大学前基本算是完全没接触过计算机。军训的时候因为很无聊,我和室友每天跑去学校的机房玩,我现在还印象很深刻,我第一次走进机房的时候,别人问,你是要玩windows,还是dos,我那是完全的一抹黑。后来就只记得在机房一堆人都是在练习盲打,军训完,盲打倒是练的差不多了,对计算机就这么产生了浓厚的兴趣,大一的时候都是玩组装机,捣鼓了一些,对计算机的硬件有了那么一些了解。到大二后,买了一些书开始学习当时最火的网页三剑客,学会了手写HTML、PS的基本玩法之类的,课余、暑假也能开始给人做做网站什么的(那个时候做网站真的好赚钱),可能那样过了个一年左右,做静态的网页就不好赚钱了,也不好找实习工作,于是就开始学asp,写些简单的CRUD,做做留言板、论坛这些动态程序,应该算是在这个阶段接触编程了。毕业后加入了深圳的一家做政府行业软件的公司,一个非常靠谱和给我空间的Leader,使得自己在那几年有了不错的成长,终于成了一个职业的程序员。通常来说,业余或半职业的程序员,多数是1个人,或者很小的一个团队一起开发,使得在开发流程、协作工具(例如jira、cvs/svn/git等)、测试上通常会有很大的欠缺,而职业的程序员在这方面则会专业很多。另外,通常职业的程序员做的系统都要运行较长的时间,所以在可维护性上会特别注意,这点我是在加入阿里后理解更深的。一个运行10年的系统,和一个写来玩玩的系统显然是有非常大差别的。这块自己感觉也很难讲清楚,只能说模模糊糊有个这样的概念。通常在有兴趣的基础上,从业余程序员跨越到成为职业程序员我觉得不会太难。编程能力的成长作为程序员,最重要的能力始终是编程能力,就我自己的感受而言,我觉得编程能力的成长主要有这么几个部分:1、编程能力初级:会用编程,首先都是从学习编程语言的基本知识学起的,不论是什么编程语言,有很多共同的基本知识,例如怎么写第一个Hello World、if/while/for、变量等,因此我比较建议在刚刚开始学一门编程语言的时候,看看编程语言自己的一些文档就好,不要上来就去看一些高阶的书。我当年学Java的时候上来就看Think in Java、Effective Java之类的,真心好难懂。除了看文档以外,编程是个超级实践的活,所以一定要多写代码,只有这样才能真正熟练起来。这也是为什么我还是觉得在面试的时候让面试者手写代码是很重要的,这个过程是非常容易判断写代码的熟悉程度的。很多人会说由于写代码都是高度依赖IDE的,导致手写很难,但我绝对相信写代码写了很多的人,手写一段不太复杂的、可运行的代码是不难的。即使像我这种三年多没写过代码的人,让我现在手写一段不太复杂的可运行的Java程序,还是没问题的,前面N年的写代码生涯使得很多东西已经深入骨髓了。我觉得编程能力初级这个阶段对于大部分程序员来说都不会是问题,勤学苦练,是这个阶段的核心。2、编程能力中级:会查和避免问题除了初级要掌握的会熟练的使用编程语言去解决问题外,中级我觉得首先是提升查问题的能力。在写代码的过程中,出问题是非常正常的,怎么去有效且高效的排查问题,是程序员群体中通常能感受到的大家在编程能力上最大的差距。解决问题能力强的基本很容易在程序员群体里得到很高的认可。在查问题的能力上,首先要掌握的是一些基本的调试技巧,好用的调试工具,在Java里有JDK自带的jstat、jmap、jinfo,不在JDK里的有mat、gperf、btrace等。工欲善其事必先利其器,在查问题上是非常典型的,有些时候大家在查问题时的能力差距,有可能仅仅是因为别人比你多知道一个工具而已。除了调试技巧和工具外,查问题的更高境界就是懂原理。一个懂原理的程序员在查问题的水平上和其他程序员是有明显差距的。我想很多的同学应该能感受到,有些时候查出问题的原因仅仅是因为有效的工具,知其然不知其所以然。我给很多阿里的同学培训过Java排查问题的方法,在这个培训里,我经常也会讲到查问题的能力的培养最主要的也是熟练,多尝试给自己写一些会出问题的程序,多积极的看别人是怎么查问题的,多积极的去参与排查问题,很多最后查问题能力强的人多数仅仅是因为“无他,但手熟尔”。我自己排查问题能力的提升主要是在2009年和2010年。那两年作为淘宝消防队(处理各种问题和故障的虚拟团队)的成员,处理了很多的故障和问题。当时消防队还有阿里最公认的技术大神——多隆,我向他学习到了很多排查问题的技巧。和他比,我排查问题的能力就是初级的那种。印象最深刻的是一次我们一起查一个应用cpu us高的问题,我们两定位到是一段代码在某种输入参数的时候会造成cpu us高的原因后,我能想到的继续查的方法是去生产环境抓输入参数,然后再用参数来本地debug看是什么原因。但多隆在看了一会那段代码后,给了我一个输入参数,我拿这个参数一运行,果然cpu us很高!这种case不是一次两次。所以我经常和别人说,我是需要有问题场景才能排查出问题的,但多隆是完全有可能直接看代码就能看出问题的,这是本质的差距。除了查问题外,更厉害的程序员是在写代码的过程就会很好的去避免问题。大家最容易理解的就是在写代码时处理各种异常情况,这里通常也是造成程序员们之间很大的差距的地方。写一段正向逻辑的代码,大部分情况下即使有差距,也不会太大,但在怎么很好的处理这个过程中有可能出现的异常上,这个时候的功力差距会非常明显。很多时候一段代码里处理异常逻辑的部分都会超过正常逻辑的代码量。我经常说,一个优秀程序员和普通程序员的差距,很多时候压根就不需要看什么满天飞的架构图,而只用show一小段的代码就可以。举一个小case大家感受下。当年有一个严重故障,最后查出的原因是输入的参数里有一个是数组,把这个数组里的值作为参数去查数据库,结果前面输入了一个很大的数组,导致从数据库查了大量的数据,内存溢出了,很多程序员现在看都会明白对入参、出参的保护check,但类似这样的case我真的碰到了很多。在中级这个阶段,我会推荐大家尽可能的多刻意的去培养下自己这两个方面的能力,成为一个能写出高质量代码、有效排查问题的优秀程序员。3、编程能力高级:懂高级API和原理就我自己的经历而言,我是在写了多年的Java代码后,才开始真正更细致的学习和掌握Java的一些更高级的API,我相信多数Java程序员也是如此。我算是从2003年开始用Java写商业系统的代码,但直到在2007年加入淘宝后,才开始非常认真地学习Java的IO通信、并发这些部分的API。尽管以前也学过也写过一些这样的代码,但完全就是皮毛。当然,这些通常来说有很大部分的原因会是工作的相关性,多数的写业务系统的程序员可能基本就不需要用到这些,所以导致会很难懂这些相对高级一些的API,但这些API对真正的理解一门编程语言,我觉得至关重要。在之前的程序员成长路线的文章里我也讲到了这个部分,在没有场景的情况下,只能靠自己去创造场景来学习好。我觉得只要有足够的兴趣,这个问题还是不大的,毕竟现在有各种开源,这些是可以非常好的帮助自己创造机会学习的,例如学Java NIO,可以自己基于NIO包一个框架,然后对比Netty,看看哪些写的是不如Netty的,这样会非常有助于真正的理解。在学习高级API的过程中,以及排查问题的过程中,我自己越来越明白懂编程语言的运行原理是非常重要的,因此我到了后面的阶段开始学习Java的编译机制、内存管理、线程机制等。对于我这种非科班出身的而言,学这些会因为缺乏基础更难很多,但这些更原理性的东西学会了后,对自己的编程能力会有质的提升,包括以后学习其他编程语言的能力,学这些原理最好的方法我觉得是先看看一些讲相关知识的书,然后去翻看源码,这样才能真正的更好的掌握,最后是在以后写代码的过程中、查问题的过程中多结合掌握的原理,才能做到即使在N年后也不会忘。在编程能力的成长上,我觉得没什么捷径。我非常赞同1万小时理论,在中级、高级阶段,如果有人指点或和优秀的程序员们共事,会好非常多。不过我觉得这个和读书也有点像,到了一定阶段后(例如高中),天分会成为最重要的分水岭,不过就和大部分行业一样,大部分的情况下都还没到拼天分的时候,只需要拼勤奋就好。系统设计能力的成长除了少数程序员会进入专深的领域,例如Linux Kernel、JVM,其他多数的程序员除了编程能力的成长外,也会越来越需要在系统设计能力上成长。通常一个编程能力不错的程序员,在一定阶段后就会开始承担一个模块的工作,进而承担一个子系统、系统、跨多领域的更大系统等。我自己在工作的第三年开始承担一个流程引擎的设计和实现工作,一个不算小的系统,并且也是当时那个项目里的核心部分。那个阶段我学会了一些系统设计的基本知识,例如需要想清楚整个系统的目标、模块的划分和职责、关键的对象设计等,而不是上来就开始写代码。但那个时候由于我是一个人写整个系统,所以其实对设计的感觉并还没有那么强力的感觉。在那之后的几年也负责过一些系统,但总体感觉好像在系统设计上的成长没那么多,直到在阿里的经历,在系统设计上才有了越来越多的体会。(点击文末阅读原文,查看:我在系统设计上犯过的14个错,可以看到我走的一堆的弯路)。在阿里有一次做分享,讲到我在系统设计能力方面的成长,主要是因为三段经历,负责专业领域系统的设计 -> 负责跨专业领域的专业系统的设计 -> 负责阿里电商系统架构级改造的设计。第一段经历,是我负责HSF。HSF是一个从0开始打造的系统,它主要是作为支撑服务化的框架,是个非常专业领域的系统,放在整个淘宝电商的大系统来看,其实它就是一个很小的子系统,这段经历里让我最深刻的有三点:1).要设计好这种非常专业领域的系统,专业的知识深度是非常重要的。我在最早设计HSF的几个框的时候,是没有设计好服务消费者/提供者要怎么和现有框架结合的,在设计负载均衡这个部分也反复了几次,这个主要是因为自己当时对这个领域掌握不深的原因造成的;2). 太技术化。在HSF的阶段,出于情怀,在有一个版本里投入了非常大的精力去引进OSGi以及去做动态化,这个后来事实证明是个非常非常错误的决定,从这个点我才真正明白在设计系统时一定要想清楚目标,而目标很重要的是和公司发展阶段结合;3). 可持续性。作为一个要在生产环境持续运行很多年的系统而言,怎么样让其在未来更可持续的发展,这个对设计阶段来说至关重要。这里最low的例子是最早设计HSF协议的时候,协议头里竟然没有版本号,导致后来升级都特别复杂;最典型的例子是HSF在早期缺乏了缺乏了服务Tracing这方面的设计,导致后面发现了这个地方非常重要后,全部落地花了长达几年的时间;又例如HSF早期缺乏Filter Chain的设计,导致很多扩展、定制化做起来非常不方便。第二段经历,是做T4。T4是基于LXC的阿里的容器,它和HSF的不同是,它其实是一个跨多领域的系统,包括了单机上的容器引擎,容器管理系统,容器管理系统对外提供API,其他系统或用户通过这个来管理容器。这个系统发展过程也是各种犯错,犯错的主要原因也是因为领域掌握不深。在做T4的日子里,学会到的最重要的是怎么去设计这种跨多个专业领域的系统,怎么更好的划分模块的职责,设计交互逻辑,这段经历对我自己更为重要的意义是我有了做更大一些系统的架构的信心。第三段经历,是做阿里电商的异地多活。这对我来说是真正的去做一个巨大系统的架构师,尽管我以前做HSF的时候参与了淘宝电商2.0-3.0的重大技术改造,但参与和自己主导是有很大区别的,这个架构改造涉及到了阿里电商众多不同专业领域的技术团队。在这个阶段,我学会的最主要的:1). 子系统职责划分。在这种超大的技术方案中,很容易出现某些部分的职责重叠和冲突,这个时候怎么去划分子系统,就非常重要了。作为大架构师,这个时候要从团队的职责、团队的可持续性上去选择团队;2). 大架构师最主要的职责是控制系统风险。对于这种超大系统,一定是多个专业领域的架构师和大架构师共同设计,怎么确保在执行的过程中对于系统而言最重要的风险能够被控制住,这是我真正的理解什么叫系统设计文档里设计原则的部分。设计原则我自己觉得就是用来确保各个子系统在设计时都会遵循和考虑的,一定不能是虚的东西,例如在异地多活架构里,最重要的是如何控制数据风险,这个需要在原则里写上,最基本的原则是可接受系统不可用,但也要保障数据一致,而我看过更多的系统设计里设计原则只是写写的,或者千篇一律的,设计原则切实的体现了架构师对目标的理解(例如当时异地多活这个其实开始只是个概念,但做到什么程度才叫做到异地多活,这是需要解读的,也要确保在技术层面的设计上是达到了目标的),技术方案层面上的选择原则,并确保在细节的设计方案里有对于设计原则的承接以及执行;3). 考虑问题的全面性。像异地多活这种大架构改造,涉及业务层面、各种基础技术层面、基础设施层面,对于执行节奏的决定要综合考虑人力投入、机器成本、基础设施布局诉求、稳定性控制等,这会比只是做一个小的系统的设计复杂非常多。系统设计能力的成长,我自己觉得最重要的一是先在一两个技术领域做到专业,然后尽量扩大自己的知识广度。例如除了自己的代码部分外,还应该知道具体是怎么部署的,部署到哪去了,部署的环境具体是怎么样的,和整个系统的关系是什么样的。像我自己,是在加入基础设施团队后才更加明白有些时候软件上做的一个决策,会导致基础设施上巨大的硬件、网络或机房的投入,但其实有可能只需要在软件上做些调整就可以避免,做做研发、做做运维可能是比较好的把知识广度扩大的方法。第二点是练习自己做tradeoff的能力,这个比较难,做tradeoff这事需要综合各种因素做选择,但这也是所有的架构师最关键的,可以回头反思下自己在做各种系统设计时做出的tradeoff是什么。这个最好是亲身经历,听一些有经验的架构师分享他们选择背后的逻辑也会很有帮助,尤其是如果恰好你也在同样的挑战阶段,光听最终的架构结果其实大多数时候帮助有限。技术Leader我觉得最好是能在架构师的基础上,后续注重成长的方面还是有挺大差别,就不在这篇里写了,后面再专门来写一篇。程序员金字塔我认为程序员的价值关键体现在作品上,被打上作品标签是一种很大的荣幸,作品影响程度的大小我觉得决定了金字塔的层次,所以我会这么去理解程序员的金字塔。当然,要打造一款作品,仅有上面的两点能力是不够的,作品里很重要的一点是对业务、技术趋势的判断。希望作为程序员的大伙,都能有机会打造一款世界级的作品,去为技术圈的发展做出贡献。由于目前IT技术更新速度还是很快的,程序员这个行当是特别需要学习能力的。我一直认为,只有对程序员这个职业真正的充满兴趣,保持自驱,才有可能在这个职业上做好,否则的话是很容易淘汰的。作者简介:毕玄,2007年加入阿里,十多年来主要从事在软件基础设施领域,先后负责阿里的服务框架、Hbase、Sigma、异地多活等重大的基础技术产品和整体架构改造。本文作者:云效鼓励师阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 24, 2019 · 1 min · jiezi

Git合并不同url的项目

本文由云+社区发表作者:工程师小熊摘要:为了让项目能实现Git+Gerrit+Jenkin的持续集成,我们把项目从Git上迁移到了Gerrit上,发现有的同事在老Git提交代码,因为Gerrit做了同步,在Gerrit上有新提交的时候就会刷新老git,这样就会把他提交的代码冲掉。这个时候我就必须要在两个相似项目之间合并提交了。步骤将老Git url加到我们新Git的本地使用命令git remote add [shortname] [url]将老Git url加到我们新Git的本地这里我把他取名为gitoa_web(随便取)查看使用命令git remot -v查看远程仓库的情况可以看到此处我们有三个远程仓库分别名为gerrit、 gitoa_web、origin同步代码使用命令git fetch gitoa_web刷新远程仓库到本地字符串 gitoa_web 指代对应的仓库地址了.比如说,要抓取所有 gitoa_web 有的,但本地仓库没有的信息,可以用合并项目使用命令git merge gitoa_web/master合并项目gitoa_web是指代仓库,master指代分支,当然如果有需要也可以合并别的分支过来 报错发现不同email地址错误不能成功提交因为这个commit不是我的修正错误把email地址更新成我的再提交就成功了小结知识点:git merge还可以合并其他项目的到本项目git fetch 仓库名可以指定同步哪个仓库git remot -v查看本地有哪些远程仓库的情况,包含各个仓库url本次我们对以下命令加深了理解git remote #不带参数,列出已经存在的远程分支git remote -v #(-v是–verbose 的简写,取首字母)列出详细信息,在每一个名字后面列出其远程urlgit remote add [shortname] [url] #添加远程仓库git fetch origin #字符串 origin 指代对应的仓库地址了.比如说,要抓取所有 origin 有的,但本地仓库没有的信息,可以用ps: 这里git remote add以后,我认为还能用cherry-pick来加不同仓库的commit过来,有兴趣的朋友可以自己尝试。附Git常用命令此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

January 23, 2019 · 1 min · jiezi

人人都可以做深度学习应用:入门篇

本文由云+社区发表作者:徐汉彬一、人工智能和新科技革命2017年围棋界发生了一件比较重要事,Master(Alphago)以60连胜横扫天下,击败各路世界冠军,人工智能以气势如虹的姿态出现在我们人类的面前。围棋曾经一度被称为“人类智慧的堡垒”,如今,这座堡垒也随之成为过去。从2016年三月份AlphaGo击败李世石开始,AI全面进入我们大众的视野,对于它的讨论变得更为火热起来,整个业界普遍认为,它很可能带来下一次科技革命,并且,在未来可预见的10多年里,深刻得改变我们的生活。其实,AI除了可以做我们熟知的人脸、语音等识别之外,它可以做蛮多有趣的事情。例如,让AI学习大量古诗之后写古诗,并且可以写出质量非常不错的古诗。又或者,将两部设计造型不同的汽车进行融合,形成全新一种设计风格的新汽车造型。还有,之前大家在朋友圈里可能看过的,将相片转换成对应的艺术风格的画作。当前,人工智能已经在图像、语音等多个领域的技术上,取得了全面的突破。与此同时,另外一个问题随之而来,如果这一轮的AI浪潮真的将会掀起新的科技革命,那么在可预见的未来,我们整个互联网都将发生翻天覆地的变化,深刻影响我们的生活。那么作为普通业务开发工程师的我,又应该以何种态度和方式应对这场时代洪流的冲击呢?在回答这个问题之前,我们先一起看看上一轮由计算机信息技术引领的科技革命中,过去30多年中国程序员的角色变化:通过上图可以简总结:编程技术在不断地发展并且走向普及,从最开始掌握在科学家和专家学者手中的技能,逐渐发展为一门大众技能。换而言之,我们公司内很多资深的工程师,如果带着今天对编程和计算机的理解和理念回到1980年,那么他无疑就是那个时代的计算机专家。如果这一轮AI浪潮真的会带来新的一轮科技革命,那么我们相信,它也会遵循类似的发展轨迹,逐步发展和走向普及。如果基于这个理解,或许,我们可以通过积极学习,争取成为第一代AI工程师。二、深度学习技术这一轮AI的技术突破,主要源于深度学习技术,而关于AI和深度学习的发展历史我们这里不重复讲述,可自行查阅。我用了一个多月的业务时间,去了解和学习了深度学习技术,在这里,我尝试以一名业务开发工程师的视角,以尽量容易让大家理解的方式一起探讨下深度学习的原理,尽管,受限于我个人的技术水平和掌握程度,未必完全准确。1. 人的智能和神经元人类智能最重要的部分是大脑,大脑虽然复杂,它的组成单元却是相对简单的,大脑皮层以及整个神经系统,是由神经元细胞组成的。而一个神经元细胞,由树突和轴突组成,它们分别代表输入和输出。连在细胞膜上的分叉结构叫树突,是输入,那根长长的“尾巴”叫轴突,是输出。神经元输出的有电信号和化学信号,最主要的是沿着轴突细胞膜表面传播的一个电脉冲。忽略掉各种细节,神经元,就是一个积累了足够的输入,就产生一次输出(兴奋)的相对简单的装置。树突和轴突都有大量的分支,轴突的末端通常连接到其他细胞的树突上,连接点上是一个叫“突触”的结构。一个神经元的输出通过突触传递给成千上万个下游的神经元,神经元可以调整突触的结合强度,并且,有的突触是促进下游细胞的兴奋,有的是则是抑制。一个神经元有成千上万个上游神经元,积累它们的输入,产生输出。人脑有1000亿个神经元,1000万亿个突触,它们组成人脑中庞大的神经网络,最终产生的结果即是人的智能。2. 人工神经元和神经网络一个神经元的结构相对来说是比较简单的,于是,科学家们就思考,我们的AI是否可以从中获得借鉴?神经元接受激励,输出一个响应的方式,同计算机中的输入输出非常类似,看起来简直就是量身定做的,刚好可以用一个函数来模拟。通过借鉴和参考神经元的机制,科学家们模拟出了人工神经元和人工神经网络。当然,通过上述这个抽象的描述和图,比较难让大家理解它的机制和原理。我们以“房屋价格测算”作为例子,一起来看看:一套房子的价格,会受到很多因素的影响,例如地段、朝向、房龄、面积、银行利率等等,这些因素如果细分,可能会有几十个。一般在深度学习模型里,这些影响结果的因素我们称之为特征。我们先假设一种极端的场景,例如影响价格的特征只有一种,就是房子面积。于是我们收集一批相关的数据,例如,50平米50万、93平米95万等一系列样本数据,如果将这些样本数据放到而为坐标里看,则如下图:然后,正如我们前面所说的,我们尝试用一个“函数”去拟合这个输入(面积x)和输出(价格y),简而言之,我们就是要通过一条直线或者曲线将这些点“拟合”起来。假设情况也比较极端,这些点刚好可以用一条“直线”拟合(真实情况通常不会是直线),如下图:那么我们的函数是一个一次元方程f(x) = ax +b,当然,如果是曲线的话,我们得到的将是多次元方程。我们获得这个f(x) = ax +b的函数之后,接下来就可以做房价“预测”,例如,我们可以计算一个我们从未看见的面积案例81.5平方米,它究竟是多少钱?这个新的样本案例,可以通过直线找到对应的点(黄色的点),如图下:粗略的理解,上面就是AI的概括性的运作方式。这一切似乎显得过于简单了?当然不会,因为,我们前面提到,影响房价其实远不止一个特征,而是有几十个,这样问题就比较复杂了,接下来,这里则要继续介绍深度学习模型的训练方式。这部分内容相对复杂一点,我尽量以业务工程师的视角来做一个粗略而简单的阐述。3. 深度学习模型的训练方式当有好几十个特征共同影响价格的时候,自然就会涉及权重分配的问题,例如有一些对房价是主要正权重的,例如地段、面积等,也有一些是负权重的,例如房龄等。(1)初始化权重计算那么,第一个步其实是给这些特征加一个权重值,但是,最开始我们根本不知道这些权重值是多少?怎么办呢?不管那么多了,先给它们随机赋值吧。随机赋值,最终计算出来的估算房价肯定是不准确的,例如,它可能将价值100万的房子,计算成了10万。(2)损失函数因为现在模型的估值和实际估值差距比较大,于是,我们需要引入一个评估“不准确”程度的衡量角色,也就是损失(loss)函数,它是衡量模型估算值和真实值差距的标准,损失函数越小,则模型的估算值和真实值的察觉越小,而我们的根本目的,就是降低这个损失函数。让刚刚的房子特征的模型估算值,逼近100万的估算结果。(3)模型调整通过梯度下降和反向传播,计算出朝着降低损失函数的方向调整权重参数。举一个不恰当的比喻,我们给面积增加一些权重,然后给房子朝向减少一些权重(实际计算方式,并非针对单个个例特征的调整),然后损失函数就变小了。(4)循环迭代调整了模型的权重之后,就可以又重新取一批新的样本数据,重复前面的步骤,经过几十万次甚至更多的训练次数,最终估算模型的估算值逼近了真实值结果,这个模型的则是我们要的“函数”。为了让大家更容易理解和直观,采用的例子比较粗略,并且讲述深度学习模型的训练过程,中间省略了比较多的细节。讲完了原理,那么我们就开始讲讲如何学习和搭建demo。三、深度学习环境搭建在2个月前,人工智能对我来说,只是一个高大上的概念。但是,经过一个多月的业余时间的认真学习,我发现还是能够学到一些东西,并且跑一些demo和应用出来的。1. 学习的提前准备(1)部分数学内容的复习,高中数学、概率、线性代数等部分内容。(累计花费了10个小时,挑了关键的点看了下,其实还是不太够,只能让自己看公式的时候,相对没有那么懵)(2)Python基础语法学习。(花费了3个小时左右,我以前从未写过Python,因为后面Google的TensorFlow框架的使用是基于Python的)(3)Google的TensorFlow深度学习开源框架。(花费了10多个小时去看)数学基础好或者前期先不关注原理的同学,数学部分不看也可以开始做,全凭个人选择。2. Google的TensorFlow开源深度学习框架深度学习框架,我们可以粗略的理解为是一个“数学函数”集合和AI训练学习的执行框架。通过它,我们能够更好的将AI的模型运行和维护起来。深度学习的框架有各种各样的版本(Caffe、Torch、Theano等等),我只接触了Google的TensorFlow,因此,后面的内容都是基于TensorFlow展开的,它的详细介绍这里不展开讲述,建议直接进入官网查看。非常令人庆幸的是TensorFlow比较早就有中文社区了,尽管里面的内容有一点老,搭建环境方面有一些坑,但是已经属于为数不多的中文文档了,大家且看且珍惜。TensorFlow 的中文社区TensorFlow 的英文社区3. TensorFlow环境搭建环境搭建本身并不复杂,主要解决相关的依赖。但是,基础库的依赖可以带来很多问题,因此,建议尽量一步到位,会简单很多。(1)操作系统我搭建环境使用的机器是腾讯云上的机器,软件环境如下:操作系统:CentOS 7.2 64位(GCC 4.8.5)因为这个框架依赖于python2.7和glibc 2.17。比较旧的版本的CentOS一般都是python2.6以及版本比较低的glibc,会产生比较的多基础库依赖问题。而且,glibc作为Linux的底层库,牵一发动全身,直接对它升级是比较复杂,很可能会带来更多的环境异常问题。(2)软件环境我目前安装的Python版本是python-2.7.5,建议可以采用yum install python的方式安装相关的原来软件。然后,再安装 python内的组件包管理器pip,安装好pip之后,接下来的其他软件的安装就相对比较简单了。例如安装TensorFlow,可通过如下一句命令完成(它会自动帮忙解决一些库依赖问题):pip install -U tensorflow这里需要特别注意的是,不要按照TensorFlow的中文社区的指引去安装,因为它会安装一个非常老的版本(0.5.0),用这个版本跑很多demo都会遇到问题的。而实际上,目前通过上述提供的命令安装,是tensorflow (1.0.0)的版本了。Python(2.7.5)下的其他需要安装的关键组件:tensorflow (0.12.1),深度学习的核心框架image (1.5.5),图像处理相关,部分例子会用到PIL (1.1.7),图像处理相关,部分例子会用到除此之后,当然还有另外的一些依赖组件,通过pip list命令可以查看我们安装的python组件:appdirs (1.4.0)backports.ssl-match-hostname (3.4.0.2)chardet (2.2.1)configobj (4.7.2)decorator (3.4.0)Django (1.10.4)funcsigs (1.0.2)image (1.5.5)iniparse (0.4)kitchen (1.1.1)langtable (0.0.31)mock (2.0.0)numpy (1.12.0)packaging (16.8)pbr (1.10.0)perf (0.1)PIL (1.1.7)Pillow (3.4.2)pip (9.0.1)protobuf (3.2.0)pycurl (7.19.0)pygobject (3.14.0)pygpgme (0.3)pyliblzma (0.5.3)pyparsing (2.1.10)python-augeas (0.5.0)python-dmidecode (3.10.13)pyudev (0.15)pyxattr (0.5.1)setuptools (34.2.0)six (1.10.0)slip (0.4.0)slip.dbus (0.4.0)tensorflow (1.0.0)urlgrabber (3.10)wheel (0.29.0)yum-langpacks (0.4.2)yum-metadata-parser (1.1.4)按照上述提供的来搭建系统,可以规避不少的环境问题。搭建环境的过程中,我遇到不少问题。例如:在跑官方的例子时的某个报错,AttributeError: ‘module’ object has no attribute ‘gfile’,就是因为安装的TensorFlow的版本比较老,缺少gfile模块导致的。而且,还有各种各样的。(不要问我是怎么知道的,说多了都是泪啊~)更详细的安装说明:Installing TensorFlow on Ubuntu(3)TensorFlow环境测试运行测试是否安装成功,可以采用官方的提供的一个短小的例子,demo生成了一些三维数据, 然后用一个平面拟合它们(官网的例子采用的初始化变量的函数是initialize_all_variables,该函数在新版本里已经被废弃了):#!/usr/bin/python#coding=utf-8import tensorflow as tfimport numpy as np# 使用 NumPy 生成假数据(phony data), 总共 100 个点.x_data = np.float32(np.random.rand(2, 100)) # 随机输入y_data = np.dot([0.100, 0.200], x_data) + 0.300# 构造一个线性模型# b = tf.Variable(tf.zeros([1]))W = tf.Variable(tf.random_uniform([1, 2], -1.0, 1.0))y = tf.matmul(W, x_data) + b# 最小化方差loss = tf.reduce_mean(tf.square(y - y_data))optimizer = tf.train.GradientDescentOptimizer(0.5)train = optimizer.minimize(loss)# 初始化变量,旧函数(initialize_all_variables)已经被废弃,替换为新函数init = tf.global_variables_initializer()# 启动图 (graph)sess = tf.Session()sess.run(init)# 拟合平面for step in xrange(0, 201): sess.run(train) if step % 20 == 0: print step, sess.run(W), sess.run(b)# 得到最佳拟合结果 W: [[0.100 0.200]], b: [0.300]运行的结果类似如下:经过200次的训练,模型的参数逐渐逼近最佳拟合的结果(W: [[0.100 0.200]], b: [0.300]),另外,我们也可以从代码的“风格”中,了解到框架样本训练的基本运行方式。虽然,官方的教程后续会涉及越来越多更复杂的例子,但从整体上看,也是类似的模式。步骤划分准备数据:获得有标签的样本数据(带标签的训练数据称为有监督学习);设置模型:先构建好需要使用的训练模型,可供选择的机器学习方法其实也挺多的,换而言之就是一堆数学函数的集合; 损失函数和优化方式:衡量模型计算结果和真实标签值的差距;真实训练运算:训练之前构造好的模型,让程序通过循环训练和学习,获得最终我们需要的结果“参数”;验证结果:采用之前模型没有训练过的测试集数据,去验证模型的准确率。其中,TensorFlow为了基于python实现高效的数学计算,通常会使用到一些基础的函数库,例如Numpy(采用外部底层语言实现),但是,从外部计算切回到python也是存在开销的,尤其是在几万几十万次的训练过程。因此,Tensorflow不单独地运行单一的函数计算,而是先用图描述一系列可交互的计算操作流程,然后全部一次性提交到外部运行(在其他机器学习的库里,也是类似的实现)。所以,上述流程图中,蓝色部分都只是设置了“计算操作流程”,而绿色部分开始才是真正的提交数据给到底层库进行实际运算,而且,每次训练一般是批量执行一批数据的。此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 21, 2019 · 1 min · jiezi

谈一谈自动编程

这里所说的自动编程,是运用人工智能技术来自动生成程序,尽量免除人类劳动。在自动产生程序之前,先要知道需求是什么吧?要能把需求准确地描述给机器,这就需要一种需求描述语言。假如我们制造了这种需求描述语言,它进一步发展,越发规范,成为了一种DSL(领域特定语言)。嗯,这就变成了DSL编程。假如要生成一个应用程序,要选择一些开发框架、中间件和技术方案吧?人类开发时要考虑开发成本和效益的权衡,但是机器既然已懂得多种技术方案,对它来说开发成本为0,那么选择最好的那一个技术方案就行了。这就相当于存在一套最好(也可能最复杂)的框架,用DSL在框架之上编程。闹了半天就是DSL和框架啊?智能到哪去了?要想真正发挥人工智能的作用,我有两个思路:自动提问就算有了需求描述语言,人类就能准确描述需求吗?如果描述得不好,机器能否通过一些分析,针对不足之处,向人类提问,用答案来完善需求描述呢?自主学习人工智能的精髓是自主学习。如果机器能学习世界上现有的代码,不需要人类特意为它提供需求描述语言和技术方案,就方便多了。最好是人类直接说需求,机器自己思考,有问题就提问。有一个有趣的事——最初的自动编程是什么呢?当编译器技术刚发明时,它就是自动编程:自动把高级语言代码转化成汇编码或机器码。我个人认为,凡是能给人类省事的技术,哪怕是if-else,也可以算是某种人工智能。现在要求高了,编译器是硬编码的智能,而我们还要可扩展、甚至自动扩展的智能。现在的自动编程主要有两个流派:基于规则推理的、基于机器学习的(包括统计学习、深度学习等)。学术界对自动编程有一个更特别的说法——程序合成(Program Synthesis)。由于机器学习的火爆,比较流行的似乎是基于机器学习(+深度学习)的自动编程,其中一种是通过学习输入输出数据的样本,自动“猜”出一个能处理这些数据的程序。对此,摘录马毅教授的一条微博:数学告诉我们,无论overfit了多大的样本数据,经验事实如何震撼,也取代不了逻辑严格的推理证明——这是唯一能将结论从有限样本扩展到无限的方法。例如一个递归程序,只有数学归纳法能准确生成它,任何有限样本都无法准确生成它(只能近似猜测)。对于有一点小bug就能出大事的程序,不能松懈啊。因此我不是很赞成基于机器学习的自动编程,即使要用,也只是辅助手段吧。啊哈哈~

January 15, 2019 · 1 min · jiezi

TiDB 源码阅读系列文章(二十三)Prepare/Execute 请求处理

作者:苏立在之前的一篇文章《TiDB 源码阅读系列文章(三)SQL 的一生》中,我们介绍了 TiDB 在收到客户端请求包时,最常见的 Command — COM_QUERY 的请求处理流程。本文我们将介绍另外一种大家经常使用的 Command — Prepare/Execute 请求在 TiDB 中的处理过程。Prepare/Execute Statement 简介首先我们先简单回顾下客户端使用 Prepare 请求过程:客户端发起 Prepare 命令将带 “?” 参数占位符的 SQL 语句发送到数据库,成功后返回 stmtID。具体执行 SQL 时,客户端使用之前返回的 stmtID,并带上请求参数发起 Execute 命令来执行 SQL。不再需要 Prepare 的语句时,关闭 stmtID 对应的 Prepare 语句。相比普通请求,Prepare 带来的好处是:减少每次执行经过 Parser 带来的负担,因为很多场景,线上运行的 SQL 多是相同的内容,仅是参数部分不同,通过 Prepare 可以通过首次准备好带占位符的 SQL,后续只需要填充参数执行就好,可以做到“一次 Parse,多次使用”。在开启 PreparePlanCache 后可以达到“一次优化,多次使用”,不用进行重复的逻辑和物理优化过程。更少的网络传输,因为多次执行只用传输参数部分,并且返回结果 Binary 协议。因为是在执行的同时填充参数,可以防止 SQL 注入风险。某些特性比如 serverSideCursor 需要是通过 Prepare statement 才能使用。TiDB 和 MySQL 协议 一样,对于发起 Prepare/Execute 这种使用访问模式提供两种方式:Binary 协议:即上述的使用 COM_STMT_PREPARE,COM_STMT_EXECUTE,COM_STMT_CLOSE 命令并且通过 Binary 协议获取返回结果,这是目前各种应用开发常使用的方式。文本协议:使用 COM_QUERY,并且用 PREPARE,EXECUTE,DEALLOCATE PREPARE 使用文本协议获取结果,这个效率不如上一种,多用于非程序调用场景,比如在 MySQL 客户端中手工执行。下面我们主要以 Binary 协议来看下 TiDB 的处理过程。文本协议的处理与 Binary 协议处理过程比较类似,我们会在后面简要介绍一下它们的差异点。COM_STMT_PREPARE首先,客户端发起 COM_STMT_PREPARE,在 TiDB 收到后会进入 clientConn#handleStmtPrepare,这个函数会通过调用 TiDBContext#Prepare 来进行实际 Prepare 操作并返回 结果 给客户端,实际的 Prepare 处理主要在 session#PrepareStmt 和 PrepareExec 中完成:调用 Parser 完成文本到 AST 的转换,这部分可以参考《TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现》。使用名为 paramMarkerExtractor 的 visitor 从 AST 中提取 “?” 表达式,并根据出现位置(offset)构建排序 Slice,后面我们会看到在 Execute 时会通过这个 Slice 值来快速定位并替换 “?” 占位符。检查参数个数是否超过 Uint16 最大值(这个是 协议限制,对于参数只提供 2 个 Byte)。进行 Preprocess, 并且创建 LogicPlan, 这部分实现可以参考之前关于 逻辑优化的介绍,这里生成 LogicPlan 主要为了获取并检查组成 Prepare 响应中需要的列信息。生成 stmtID,生成的方式是当前会话中的递增 int。保存 stmtID 到 ast.Prepared (由 AST,参数类型信息,schema 版本,是否使用 PreparedPlanCache 标记组成) 的映射信息到 SessionVars#PreparedStmts 中供 Execute 部分使用。保存 stmtID 到 TiDBStatement (由 stmtID,参数个数,SQL 返回列类型信息,sendLongData 预 BoundParams 组成)的映射信息保存到 TiDBContext#stmts。在处理完成之后客户端会收到并持有 stmtID 和参数类型信息,返回列类型信息,后续即可通过 stmtID 进行执行时,server 可以通过 6、7 步保存映射找到已经 Prepare 的信息。COM_STMT_EXECUTEPrepare 成功之后,客户端会通过 COM_STMT_EXECUTE 命令请求执行,TiDB 会进入 clientConn#handleStmtExecute,首先会通过 stmtID 在上节介绍中保存的 TiDBContext#stmts 中获取前面保存的 TiDBStatement,并解析出是否使用 userCursor 和请求参数信息,并且调用对应 TiDBStatement 的 Execute 进行实际的 Execute 逻辑:生成 ast.ExecuteStmt 并调用 planer.Optimize 生成 plancore.Execute,和普通优化过程不同的是会执行 Exeucte#OptimizePreparedPlan。使用 stmtID 通过 SessionVars#PreparedStmts 获取到到 Prepare 阶段的 ast.Prepared 信息。使用上一节第 2 步中准备的 prepared.Params 来快速查找并填充参数值;同时会保存一份参数到 sessionVars.PreparedParams 中,这个主要用于支持 PreparePlanCache 延迟获取参数。判断对比判断 Prepare 和 Execute 之间 schema 是否有变化,如果有变化则重新 Preprocess。之后调用 Execute#getPhysicalPlan 获取物理计划,实现中首先会根据是否启用 PreparedPlanCache 来查找已缓存的 Plan,本文后面我们也会专门介绍这个。在没有开启 PreparedPlanCache 或者开启了但没命中 cache 时,会对 AST 进行一次正常的 Optimize。在获取到 PhysicalPlan 后就是正常的 Executing 执行。COM_STMT_CLOSE在客户不再需要执行之前的 Prepared 的语句时,可以通过 COM_STMT_CLOSE 来释放服务器资源,TiDB 收到后会进入 clientConn#handleStmtClose,会通过 stmtID 在 TiDBContext#stmts 中找到对应的 TiDBStatement,并且执行 Close 清理之前的保存的 TiDBContext#stmts 和 SessionVars#PrepareStmts,不过通过代码我们看到,对于前者的确直接进行了清理,对于后者不会删除而是加入到 RetryInfo#DroppedPreparedStmtIDs 中,等待当前事务提交或回滚才会从 SessionVars#PrepareStmts 中清理,之所以延迟删除是由于 TiDB 在事务提交阶段遇到冲突会根据配置决定是否重试事务,参与重试的语句可能只有 Execute 和 Deallocate,为了保证重试还能通过 stmtID 找到 prepared 的语句 TiDB 目前使用延迟到事务执行完成后才做清理。其他 COM_STMT除了上面介绍的 3 个 COM_STMT,还有另外几个 COM_STMT_SEND_LONG_DATA,COM_STMT_FETCH,COM_STMT_RESET 也会在 Prepare 中使用到。COM_STMT_SEND_LONG_DATA某些场景我们 SQL 中的参数是 TEXT,TINYTEXT,MEDIUMTEXT,LONGTEXT and BLOB,TINYBLOB,MEDIUMBLOB,LONGBLOB 列时,客户端通常不会在一次 Execute 中带大量的参数,而是单独通过 COM_SEND_LONG_DATA 预先发到 TiDB,最后再进行 Execute。TiDB 的处理在 client#handleStmtSendLongData,通过 stmtID 在 TiDBContext#stmts 中找到 TiDBStatement 并提前放置 paramID 对应的参数信息,进行追加参数到 boundParams(所以客户端其实可以多次 send 数据并追加到一个参数上),Execute 时会通过 stmt.BoundParams() 获取到提前传过来的参数并和 Execute 命令带的参数 一起执行,在每次执行完成后会重置 boundParams。COM_STMT_FETCH通常的 Execute 执行后,TiDB 会向客户端持续返回结果,返回速率受 max_chunk_size 控制(见《TiDB 源码阅读系列文章(十)Chunk 和执行框架简介》), 但实际中返回的结果集可能非常大。客户端受限于资源(一般是内存)无法一次处理那么多数据,就希望服务端一批批返回,COM_STMT_FETCH 正好解决这个问题。它的使用首先要和 COM_STMT_EXECUTE 配合(也就是必须使用 Prepared 语句执行), handleStmtExeucte 请求协议 flag 中有标记要使用 cursor,execute 在完成 plan 拿到结果集后并不立即执行而是把它缓存到 TiDBStatement 中,并立刻向客户端回包中带上列信息并标记 ServerStatusCursorExists,这部分逻辑可以参看 handleStmtExecute。客户端看到 ServerStatusCursorExists 后,会用 COM_STMT_FETCH 向 TiDB 拉去指定 fetchSize 大小的结果集,在 connClient#handleStmtFetch 中,会通过 session 找到 TiDBStatement 进而找到之前缓存的结果集,开始实际调用执行器的 Next 获取满足 fetchSize 的数据并返回客户端,如果执行器一次 Next 超过了 fetchSize 会只返回 fetchSize 大小的数据并把剩下的数据留着下次再给客户端,最后对于结果集最后一次返回会标记 ServerStatusLastRowSend 的 flag 通知客户端没有后续数据。COM_STMT_RESET主要用于客户端主动重置 COM_SEND_LONG_DATA 发来的数据,正常 COM_STMT_EXECUTE 后会自动重置,主要针对客户端希望主动废弃之前数据的情况,因为 COM_STMT_SEND_LONG_DATA 是一直追加的操作,客户端某些场景需要主动放弃之前预存的参数,这部分逻辑主要位于 connClient#handleStmtReset 中。Prepared Plan Cache通过前面的解析过程我们看到在 Prepare 时完成了 AST 转换,在之后的 Execute 会通过 stmtID 找之前的 AST 来进行 Plan 跳过每次都进行 Parse SQL 的开销。如果开启了 Prepare Plan Cache,可进一步在 Execute 处理中重用上次的 PhysicalPlan 结果,省掉查询优化过程的开销。TiDB 可以通过 修改配置文件 开启 Prepare Plan Cache, 开启后每个新 Session 创建时会初始化一个 SimpleLRUCache 类型的 preparedPlanCache 用于保存用于缓存 Plan 结果,缓存的 key 是 pstmtPlanCacheKey(由当前 DB,连接 ID,statementID,schemaVersion, snapshotTs,sqlMode,timezone 组成,所以要命中 plan cache 这以上元素必须都和上次缓存的一致),并根据配置的缓存大小和内存大小做 LRU。在 Execute 的处理逻辑 PrepareExec 中除了检查 PreparePlanCache 是否开启外,还会判断当前的语句是否能使用 PreparePlanCache。只有 SELECT,INSERT,UPDATE,DELETE 有可能可以使用 PreparedPlanCache 。并进一步通过 cacheableChecker visitor 检查 AST 中是否有变量表达式,子查询,“order by ?",“limit ?,?” 和 UnCacheableFunctions 的函数调用等不可以使用 PlanCache 的情况。如果检查都通过则在 Execute#getPhysicalPlan 中会用当前环境构建 cache key 查找 preparePlanCache。未命中 Cache我们首先来看下没有命中 Cache 的情况。发现没有命中后会用 stmtID 找到的 AST 执行 Optimize,但和正常执行 Optimize 不同对于 Cache 的 Plan, 我需要对 “?” 做延迟求值处理, 即将占位符转换为一个 function 做 Plan 并 Cache, 后续从 Cache 获取后 function 在执行时再从具体执行上下文中实际获取执行参数。回顾下构建 LogicPlan 的过程中会通过 expressionRewriter 将 AST 转换为各类 expression.Expression,通常对于 ParamMarkerExpr 会重写为 Constant 类型的 expression,但如果该条 stmt 支持 Cache 的话会重写为 Constant 并带上一个特殊的 DeferredExpr 指向一个 GetParam 的函数表达式,而这个函数会在执行时实际从前面 Execute 保存到 sessionVars.PreparedParams 中获取,这样就做到了 Plan 并 Cache 一个参数无关的 Plan,然后实际执行的时填充参数。新获取 Plan 后会保存到 preparedPlanCache 供后续使用。命中 Cache让我们回到 getPhysicalPlan,如果 Cache 命中在获取 Plan 后我们需要重新 build plan 的 range,因为前面我们保存的 Plan 是一个带 GetParam 的函数表达式,而再次获取后,当前参数值已经变化,我们需要根据当前 Execute 的参数来重新修正 range,这部分逻辑代码位于 Execute#rebuildRange 中,之后就是正常的执行过程了。文本协议的 Prepared前面主要介绍了二进制协议的 Prepared 执行流程,还有一种执行方式是通过二进制协议来执行。客户端可以通过 COM_QUREY 发送:PREPARE stmt_name FROM prepareable_stmt;EXECUTE stmt_name USING @var_name1, @var_name2,…DEALLOCTE PREPARE stmt_name来进行 Prepared,TiDB 会走正常 文本 Query 处理流程,将 SQL 转换 Prepare,Execute,Deallocate 的 Plan, 并最终转换为和二进制协议一样的 PrepareExec,ExecuteExec,DealocateExec 的执行器进行执行。写在最后Prepared 是提高程序 SQL 执行效率的有效手段之一。熟悉 TiDB 的 Prepared 实现,可以帮助各位读者在将来使用 Prepared 时更加得心应手。另外,如果有兴趣向 TiDB 贡献代码的读者,也可以通过本文更快的理解这部分的实现。 ...

January 4, 2019 · 3 min · jiezi

如何设计一个 RPC 系统

本文由云+社区发表RPC是一种方便的网络通信编程模型,由于和编程语言的高度结合,大大减少了处理网络数据的复杂度,让代码可读性也有可观的提高。但是RPC本身的构成却比较复杂,由于受到编程语言、网络模型、使用习惯的约束,有大量的妥协和取舍之处。本文就是通过分析几种流行的RPC实现案例,提供大家在设计RPC系统时的参考。由于RPC底层的网络开发一般和具体使用环境有关,而编程实现手段也非常多样化,但不影响使用者,因此本文基本涉及如何实现一个RPC系统。认识 RPC (远程调用)我们在各种操作系统、编程语言生态圈中,多少都会接触过“远程调用”的概念。一般来说,他们指的是用简单的一行代码,通过网络调用另外一个计算机上的某段程序。比如:RMI——Remote Method Invoke:调用远程的方法。“方法”一般是附属于某个对象上的,所以通常RMI指对在远程的计算机上的某个对象,进行其方法函数的调用。RPC——Remote Procedure Call:远程过程调用。指的是对网络上另外一个计算机上的,某段特定的函数代码的调用。远程调用本身是网络通信的一种概念,他的特点是把网络通信封装成一个类似函数的调用。网络通信在远程调用外,一般还有其他的几种概念:数据包处理、消息队列、流过滤、资源拉取等待。下面比较一下他们差异:方案编程方式信息封装传输模型典型应用远程调用调用函数,输入参数,获得返回值。使用编程语言的变量、类型、函数发出请求,获得响应Java RMI数据包处理调用Send()/Recv(),使用字节码数据,编解码,处理内容把通信内容构造成二进制的协议包发送/接收UDP编程消息队列调用Put()/Get(),使用“包”对象,处理其包含的内容消息被封装成语言可用的对象或结构对某队列,存入一个消息;取出一个消息ActiveMQ流过滤读取一个流,或写出一个流,对流中的单元包即刻处理单元长度很小的统一数据结构连接;发送/接收;处理网络视频资源拉取输入一个资源ID,获得资源内容请求或响应都包含:头部+正文请求后等待响应WWW针对远程调用的特点——调用函数。业界在各种语言下都开发过类似的方案,同时也有些方案是试图做到跨语言的。尽管远程调用在编程方式上,看起来似乎是最简单易用的,但是也有明显的缺点。所以了解清楚远程调用的优势和缺点,是决定是否要开发、或者使用远程调用这种模型的关键问题。远程调用的优势有:屏蔽了网络层。因此在传输协议和编码协议上,我们可以选择不同的方案。比如WebService方案就是用的HTTP传输协议+SOAP编码协议;而REST的方案往往使用HTTP+JSON协议。Facebook的Thrift甚至可以定制任何不同的传输协议和编码协议,你可以用TCP+Google Protocol Buffer,也可以用UDP+JSON……。由于屏蔽了网络层,你可以根据实际需要来独立的优化网络部分,而无需涉及业务逻辑的处理代码,这对于需要在各种网络环境下运行的程序来说,非常有价值。函数映射协议。你可以直接用编程语言来书写数据结构和函数定义,取代编写大量的编码协议格式和分包处理逻辑。对于那些业务逻辑非常复杂的系统,比如网络游戏,可以节省大量定义消息格式的时间。而且函数调用模型非常容易学习,不需要学习通信协议和流程,让经验较浅的程序员也能很容易的开始使用网络编程。远程调用的缺点:增加了性能消耗。由于把网络通信包装成“函数”,需要大量额外的处理。比如需要预生产代码,或者使用反射机制。这些都是额外消耗CPU和内存的操作。而且为了表达复杂的数据类型,比如变长的类型string/map/list,这些都要数据包中增加更多的描述性信息,则会占用更多的网络包长度。不必要的复杂化。如果你仅仅是为了某些特定的业务需求,比如传送一个固定的文件,那么你应该用HTTP/FTP协议模型。如果为了做监控或者IM软件,用简单的消息编码收发会更快速高效。如果是为了做代理服务器,用流式的处理会很简单。另外,如果你要做数据广播,那么消息队列会很容易做到,而远程调用这几乎无法完成。因此,远程调用最适合的场景是:业务需求多变,网络环境多变。RPC方案的核心问题由于远程调用的使用接口是“函数”,所以要如何构建这个“函数”,就产生了三个方面需要决策的问题:1 . 如何表示“远程”的信息所谓远程,就是指网络上另外一个位置,那么网络地址就是必须要输入的部分。在TCP/IP网络下,IP地址和端口号代表了运行中程序的一个入口。所以指定IP地址和端口是发起远程调用所必需的。然而,一个程序可能会运行很多个功能,可以接收多个不同含义的远程调用。这样如何去让用户指定这些不同含义的远程调用入口,就成为了另外一个问题。当然最简单的是每个端口一种调用,但是一个IP最多支持65535个端口,而且别的网络功能也可能需要端口,所以这种方案可能会不够用,同时一个数字代表一个功能也不太好理解,必须要查表才能明白。所以我们必须想别的方法。在面向对象的思想下,有些方案提出了:以不同的对象来归纳不同的功能组合,先指定对象,再指定方法。这个想法非常符合程序员的理解方式,EJB就是这种方案的。一旦你确定了用对象这种模型来定义远程调用的地址,那么你就需要有一种指定远程对象的方法,为了指定对象,你必须要能把对象的一些信息,从被调用方(服务器端)传输给调用方(客户端)。最简单的方案就是客户端输入一串字符串作为对象的“名字”,发给服务器端,查找注册了这个“名字”的对象,如果找到了,服务器端就会用某种技术“传输”这个对象给客户端,然后客户端就可以调用他的方法了。当然这种传输不可能是把整个服务器上的对象数据拷贝给客户端,而是用一些符号或者标志的方法,来代表这个服务器上的对象,然后发给客户端。如果你不是使用面向对象的模型,那么远程的一个函数,也是必须要定位和传输的,因为你调用的函数必须先能找到,然后成为客户端侧的一个接口,才能调用。针对“远程对象”(这里说的对象包括面向对象的对象或者仅仅是 函数)如何表达才能在网络上定位;以及定位成功之后以什么形式供客户端调用,都是“远程调用”设计方案中第一个重要的问题。2 . 函数的接口形式应该如何表示远程调用由于受到网络通信的约束,所以往往不能完全的支持编程语言的所有特性。比如C语言函数中的指针类型参数,就无法通过网络传递出去。因此远程调用的函数定义,能用语言中的什么特性,不能用什么特性,是需要在设计方案是规定下来的。这种规定如果太严格,会影响使用者的易用性;如果太宽泛,则可能导致远程调用的性能低下。如何去设计一种方式,把编程语言中的函数,描述成一个远程调用的函数,也是需要考虑的问题。很多方案采用了配置文件这种通用的方式,而另外一些方案可以直接在源代码中里面加特殊的注释。一般来说,编译型语言如C/C++只能采用源代码根据配置文件生成的方案,虚拟机型语言如C#/JAVA可以采用反射机制结合配置文件(设置是在源代码中用特殊注释来代替配置文件)的方案,如果是脚本语言就更简单,有时候连配置文件都不需要,因为脚本自己就可以充当。总之远程调用的接口要满足怎样的约束,也是一个需要仔细考虑的问题。3. 用什么方法来实现网络通信远程调用最重要的实现细节,就是关于网络通信。用何种通信方式来承载远程调用的问题,细化下来就是两个子问题:用什么样的服务程序提供网络功能?用什么样的通信协议?远程调用系统可以自己直接对TCP/IP编程来实现通信,也可以委托一些其他软件,比如Web服务器、消息队列服务器等等……也可以使用不同的网络通信框架,如Netty/Mina这些开源框架。通信协议则一般有两层:一个是传输协议,比如TCP/UDP或者高层一点的HTTP,或者自己定义的传输协议;另外一个是编码协议,就是如何把一个编程语言中的对象,序列化和反序列化成为二进制字节流的方案,流行的方案有JSON、Google Protocol Buffer等等,很多开发语言也有自己的序列化方案,如JAVA/C#都自带。以上这些技术细节,应该选择使用哪些,直接关系到远程调用系统的性能和环境兼容性。以上三个问题,就是远程调用系统必须考虑的核心选型。根据每个方案所面对的约束不同,他们都会在这三个问题上做出取舍,从而适应其约束。但是现在并不存在一个“万能”或者“通用”的方案,其原因就是:在如此复杂的一个系统中,如果要照顾的特性越多,需要付出的成本(易用性代价、性能开销)也会越多。下面,我们可以研究下业界现存的各种远程调用方案,看他们是如何在这三个方面做平衡和选择的。业界方案举例1. CORBACORBA是一个“古老”的,雄心勃勃的方案,他试图在完成远程调用的同时,还完成跨语言的通信的任务,因此其复杂程度是最高的,但是它的设计思想,也被后来更多的其他方案所学习。在通信对象的定位上,它使用URL来定义一个远程对象,这是在互联网时代非常容易接受的。其对象的内容则限定在C语言类型上,并且只能传递值,这也是非常容易理解的。为了能让不同语言的程序通信,所以就必须要在各种编程语言之外独立设计一种仅仅用于描述远程接口的语言,这就是所谓的IDL:Interface Description Language 接口描述语言。用这个方法,你就可以先用一种超然于所有语言之外的语言来定义接口,然后使用工具自动生成各种编程语言的代码。这种方案对于编译型语言几乎是唯一选择。CORBA并没有对通信问题有任何约定,而是留给具体语言的实现者去处理,这也许是他没有广泛流行的原因之一。实际上CORBA有一个非常著名的继承者,他就是Facebook公司的Thrift框架。Thrift也是使用一种IDL编译生成多种语言的远程调用方案,并且用C++/JAVA等多种语言完整的实现了通信承载,所以在开源框架中是特别有号召力的一个。Thrfit的通信承载还有个特点,就是能组合使用各种不同的传输协议和编码协议,比如TCP/UDP/HTTP配合JSON/BIN/PB……这让它几乎可以选择任何的网络环境。Thrift的模型类似下图,这里有的stub表示“桩代码”,就是客户端直接使用的函数形式程序;skeleton表示“骨架代码”,是需要程序员编写具体提供远程服务功能的模板代码,一般对模版做填空或者继承(扩展)即可。这个stub-skeleton模型几乎是所有远程调用方案的标配。2. JAVA RMIJAVA RMI是JAVA虚拟机自带的一个远程调用方案。它也是可以使用URL来定位远程对象,使用JAVA自带的序列化编码协议传递参数值。在接口描述上,由于这是一个仅限于JAVA环境下的方案,所以直接用JAVA语言的Interface类型作为定义语言。用户通过实现这个接口类型来提供远程服务,同时JAVA会根据这个接口文件自动生成客户端的调用代码供调用者使用。他的底层通信实现,还是用TCP协议实现的。在这里,Interface文件就是JAVA语言的IDL,同时也是skeleton模板,供开发者来填写远程服务内容。而stub代码则由于JAVA的反射功能,由虚拟机直接包办了。这个方案由于JAVA虚拟机的支持,使用起来非常简单,完全按照标志的JAVA编程方法就可以轻松解决问题,但是这也仅仅能在JAVA环境下运行,限制了其适用的范围。鱼与熊掌不可兼得,易用性和适用性往往是互相冲突的。这和CORBA/Thrift追求最大范围的适用性有很大的差别,也导致了两者在易用性上的不同。3. Windows RPCWindows中对RPC支持是比较早和比较完善的。首先它通过GUID来查询对象,然后使用C语言类型作为参数值的传递。由于Windows的API主要是C语言的,所以对于RPC功能来说,还是要用一种IDL来描述接口,最后生成.h和.c文件来生产RPC的stub和skeleton代码。而通信机制,由于是操作系统自带的,所以使用内核LPC机制承载,这一点还是对使用者来说比较方便的。但是也限制了只能用于Windows程序之间做调用。4. WebService & REST在互联网时代,程序需要通过互联网来互相调用。而互联网上最流行的协议是HTTP协议和WWW服务,因此使用HTTP协议的Web Service就顺理成章的成为跨系统调用的最流行方案。由于可以使用大多数互联网的基础设施,所以Web Service的开发和实现几乎是毫无难度的。一般来说,它都会使用URL来定位远程对象,而参数则通过一系列预定义的类型(主要是C语言基础类型),以及对象序列化方式来传递。接口生成方面,你可以自己直接对HTTP做解析,也可以使用诸如WSDL或者SOAP这样的规范。在REST的方案中,则限定了只有PUT/GET/DELETE/POST四种操作函数,其他都是参数。总结一下上面的这些RPC方案,我们发现,针对远程调用的三个核心问题,一般业界有以下几个选择:远程对象定位:使用URL;或者使用名字服务来查找远程调用参数传递:使用C的基本类型定义;或者使用某种预订的序列化(反序列化)方案接口定义:使用某种特定格式的技术,直接按预先约定一种接口定义文件;或者使用某种描述协议IDL来生成这些接口文件通信承载:有使用特定TCP/UDP之类的服务器,也有可以让用户自己开发定制的通信模型;还有使用HTTP或者消息队列这一类更加高级的传输协议方案选型在我们确定了远程调用系统方案几个可行选择后,自然就要明确一下各个方案的优缺点,这样才能选择真正合适需求的设计:1. 对于远程对象的描述:使用URL是互联网通行的标准,比较方便用户理解,也容易添加日后需要扩展到内容,因为URL本身是一个由多个部分组合的字符串;而名字服务则老式一些,但是依然有他的好处,就是名字服务可以附带负载均衡、容灾扩容、自定义路由等一系列特性,对于需求复杂的定位比较容易实现。2. 远程调用的接口描述:如果只限制于某个语言、操作系统、平台上,直接利用“隐喻”方式的接口描述,或者以“注解”类型注释手段来标注源代码,实现远程调用接口的定义,是最方便不过的。但是,如果需要兼容编译型语言,如C/C++,就一定要用某种IDL来生成这些编译语言的源代码了。3.通信承载:给用户自己定制通信模块,能提供最好的适用性,但是也让用户增加了使用的复杂程度。而HTTP/消息队列这种承载方式,在系统的部署、运维、编程上都会比较简单,缺点就是对于性能、传输特性的定制空间就比较小。分析完核心问题,我们还需要考虑一些适用性场景:1. 面向对象还是面向过程:如果我们只是考虑做面向过程的远程调用,只需要定位到“函数”即可。而如果是面向对象的,则需要定位到“对象”。由于函数是无状态的,所以其定位过程可以简单到一个名字即可,而对象则需要动态的查找到其ID或句柄。2.跨语言还是单一语言:单一语言的方案中,头文件或接口定义完全用一种语言处理即可,如果是跨语言的,就少不免要IDL3. 混合式通信承载还是使用HTTP服务器承载:混合式承载可能可以用到TCP/UDP/共享内存等底层技术,可以提供最优的性能,但是使用起来必然非常麻烦。使用HTTP服务器的话,则非常简单,因为WWW服务的开源软件、库众多,而且客户端使用浏览器或者一些JS页面即可调试,缺点是其性能较低。假设我们现在要为某种业务逻辑非常多变的领域,如企业业务应用领域,或游戏服务器端领域,去设计一个远程调用系统,我们可能应该如下选择:1. 使用名字服务定位远程对象:由于企业服务是需要高可用性的,使用名字服务能在查询名字时识别和选择可用性服务对象。J2EE方案中的EJB(企业JavaBean)就是用名字服务的。2. 使用IDL来生成接口定义:由于企业服务或游戏服务,其开发语言可能不是统一的,又或者需要高性能的编程语言如C/C++,所以只能使用IDL。3.使用混合式通信承载:虽然企业服务看起来无需在很复杂的网络下运行,但是不同的企业的网络环境又可能是千差万别的,所以要做一个通用的系统,最好还是不怕麻烦提供混合式的通信承载,这样可以在TCP/UDP等各种协议中选择。此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

January 3, 2019 · 1 min · jiezi

阿里专家杜万:Java响应式编程,一文全面解读

本篇文章来自于2018年12月22日举办的《阿里云栖开发者沙龙—Java技术专场》,杜万专家是该专场第四位演讲的嘉宾,本篇文章是根据杜万专家在《阿里云栖开发者沙龙—Java技术专场》的演讲视频以及PPT整理而成。摘要:响应式宣言如何解读,Java中如何进行响应式编程,Reactor Streams又该如何使用?热衷于整合框架与开发工具的阿里云技术专家杜万,为大家全面解读响应式编程,分享Spring Webflux的实践。从响应式理解,到Reactor项目示例,再到Spring Webflux框架解读,本文带你进入Java响应式编程。演讲嘉宾简介:杜万(倚贤),阿里云技术专家,全栈工程师,从事了12年 Java 语言为主的软件开发工作,热衷于整合框架与开发工具,Linux拥趸,问题终结者。合作翻译《Elixir 程序设计》。目前负责阿里云函数计算的工具链开发,正在实践 WebFlux 和 Reactor 开发新的 Web 应用。本次直播视频精彩回顾,戳这里!https://yq.aliyun.com/live/721PPT下载地址:https://yq.aliyun.com/download/3187以下内容根据演讲嘉宾视频分享以及PPT整理而成。本文围绕以下三部分进行介绍:1.Reactive2.Project Reactor3.Spring Webflux一.Reactive1.Reactive Manifesto下图是Reactive Manifesto官方网站上的介绍,这篇文章非常短但也非常精悍,非常值得大家去认真阅读。响应式宣言是一份构建现代云扩展架构的处方。这个框架主要使用消息驱动的方法来构建系统,在形式上可以达到弹性和韧性,最后可以产生响应性的价值。所谓弹性和韧性,通俗来说就像是橡皮筋,弹性是指橡皮筋可以拉长,而韧性指在拉长后可以缩回原样。这里为大家一一解读其中的关键词:1)响应性:快速/一致的响应时间。假设在有500个并发操作时,响应时间为1s,那么并发操作增长至5万时,响应时间也应控制在1s左右。快速一致的响应时间才能给予用户信心,是系统设计的追求。2)韧性:复制/遏制/隔绝/委托。当某个模块出现问题时,需要将这个问题控制在一定范围内,这便需要使用隔绝的技术,避免连锁性问题的发生。或是将出现故障部分的任务委托给其他模块。韧性主要是系统对错误的容忍。3)弹性:无竞争点或中心瓶颈/分片/扩展。如果没有状态的话,就进行水平扩展,如果存在状态,就使用分片技术,将数据分至不同的机器上。4)消息驱动:异步/松耦合/隔绝/地址透明/错误作为消息/背压/无阻塞。消息驱动是实现上述三项的技术支撑。其中,地址透明有很多方法。例如DNS提供的一串人类能读懂的地址,而不是IP,这是一种不依赖于实现,而依赖于声明的设计。再例如k8s每个service后会有多个Pod,依赖一个虚拟的服务而不是某一个真实的实例,从何实现调用1 个或调用n个服务实例对于对调用方无感知,这是为分片或扩展做了准备。错误作为消息,这在Java中是不太常见的,Java中通常将错误直接作为异常抛出,而在响应式中,错误也是一种消息,和普通消息地位一致,这和JavaScript中的Promise类似。背压是指当上游向下游推送数据时,可能下游承受能力不足导致问题,一个经典的比喻是就像用消防水龙头解渴。因此下游需要向上游声明每次只能接受大约多少量的数据,当接受完毕再次向上游申请数据传输。这便转换成是下游向上游申请数据,而不是上游向下游推送数据。无阻塞是通过no-blocking IO提供更高的多线程切换效率。2.Reactive Programming响应式编程是一种声明式编程范型。下图中左侧显示了一个命令式编程,相信大家都比较熟悉。先声明两个变量,然后进行赋值,让两个变量相加,得到相加的结果。但接着当修改了最早声明的两个变量的值后,sum的值不会因此产生变化。而在Java 9 Flow中,按相同的思路实现上述处理流程,当初始变量的值变化,最后结果的值也同步发生变化,这就是响应式编程。这相当于声明了一个公式,输出值会随着输入值而同步变化。响应式编程也是一种非阻塞的异步编程。下图是用reactor.ipc.netty实现的TCP通信。常见的server中会用循环发数据后,在循环外取出,但在下图的实现中没有,因为这不是使用阻塞模型实现,是基于非阻塞的异步编程实现。响应式编程是一种数据流编程,关注于数据流而不是控制流。下图中,首先当页面出现点击操作时产生一个click stream,然后页面会将250ms内的clickStream缓存,如此实现了一个归组过程。然后再进行map操作,得到每个list的长度,筛选出长度大于2的,这便可以得出多次点击操作的流。这种方法应用非常广泛,例如可以筛选出双击操作。由此可见,这种编程方式是一种数据流编程,而不是if else的控制流编程。之前有提及消息驱动,那么消息驱动(Message-driven)和事件驱动(Event-driven)有什么区别呢。1)消息驱动有确定的目标,一定会有消息的接受者,而事件驱动是一件事情希望被观察到,观察者是谁无关紧要。消息驱动系统关注消息的接受者,事件驱动系统关注事件源。2)在一个使用响应式编程实现的响应式系统中,消息擅长于通讯,事件擅长于反应事实。3.Reactive StreamsReactive Streams提供了一套非阻塞背压的异步流处理标准,主要应用在JVM、JavaScript和网络协议工作中。通俗来说,它定义了一套响应式编程的标准。在Java中,有4个Reactive Streams API,如下图所示:这个API中定义了Publisher,即事件的发生源,它只有一个subscribe方法。其中的Subscriber就是订阅消息的对象。作为订阅者,有四个方法。onSubscribe会在每次接收消息时调用,得到的数据都会经过onNext方法。onError方法会在出现问题时调用,Throwable即是出现的错误消息。在结束时调用onComplete方法。Subscription接口用来描述每个订阅的消息。request方法用来向上游索要指定个数的消息,cancel方法用于取消上游的数据推送,不再接受消息。Processor接口继承了Subscriber和Publisher,它既是消息的发生者也是消息的订阅者。这是发生者和订阅者间的过渡桥梁,负责一些中间转换的处理。Reactor Library从开始到现在已经历经多代。第0代就是java包Observable 接口,也就是观察者模式。具体的发展见下图:第四代虽然仍然是RxJava2,但是相比第三代的RxJava2,其中的小版本有了不一样的改进,出现了新特性。Reactor Library主要有两点特性。一是基于回调(callback-based),在事件源附加回调函数,并在事件通过数据流链时被调用;二是声明式编程(Declarative),很多函数处理业务类似,例如map/filter/fold等,这些操作被类库固化后便可以使用声明式方法,以在程序中快速便捷使用。在生产者、订阅者都定义后,声明式方法便可以用来实现中间处理者。二.Project ReactorProject Reactor,实现了完全非阻塞,并且基于网络HTTP/TCP/UDP等的背压,即数据传输上游为网络层协议时,通过远程调用也可以实现背压。同时,它还实现了Reactive Streams API和Reactive Extensions,以及支持Java 8 functional API/Completable Future/Stream /Duration等各新特性。下图所示为Reactor的一个示例:首先定义了一个words的数组,然后使用flatMap做映射,再将每个词和s做连接,得出的结果和另一个等长的序列进行一个zipWith操作,最后打印结果。这和Java 8 Stream非常类似,但仍存在一些区别:1)Stream是pull-based,下游从上游拉数据的过程,它会有中间操作例如map和reduce,和终止操作例如collect等,只有在终止操作时才会真正的拉取数据。Reactive是push-based,可以先将整个处理数据量构造完成,然后向其中填充数据,在出口处可以取出转换结果。2)Stream只能使用一次,因为它是pull-based操作,拉取一次之后源头不能更改。但Reactive可以使用多次,因为push-based操作像是一个数据加工厂,只要填充数据就可以一直产出。3)Stream#parallel()使用fork-join并发,就是将每一个大任务一直拆分至指定大小颗粒的小任务,每个小任务可以在不同的线程中执行,这种多线程模型符合了它的多核特性。Reactive使用Event loop,用一个单线程不停的做循环,每个循环处理有限的数据直至处理完成。在上例中,大家可以看到很多Reactive的操作符,例如flatMap/concatWith/zipWith等,这样的操作符有300多个,这可能是学习这个框架最大的压力。如何理解如此繁多的操作符,可能一个归类会有所帮助:1)新序列创建,例如创建数组类序列等;2)现有序列转换,将其转换为新的序列,例如常见的map操作;3)从现有的序列取出某些元素;4)序列过滤;5)序列异常处理。6)与时间相关的操作,例如某个序列是由时间触发器定期发起事件;7)序列分割;8)序列拉至同步世界,不是所有的框架都支持异步,再需要和同步操作进行交互时就需要这种处理。上述300+操作符都有如下所示的弹珠图(Marble Diagrams),用表意的方式解释其作用。例如下图的操作符是指,随着时间推移,逐个产生了6个元素的序列,黑色竖线表示新元素产生终止。在这个操作符的作用下,下方只取了前三个元素,到第四个元素就不取了。这些弹珠图大家可以自行了解。三.Spring Webflux1.Spring Webflux框架Spring Boot 2.0相较之前的版本,在基于Spring Framework 5的构建添加了新模块Webflux,将默认的web服务器改为Netty,支持Reactive应用,并且Webflux默认运行在Netty上。而Spring Framework 5也有了一些变化。Java版本最低依赖Java 8,支持Java 9和Java 10,提供许多支持Reactive的基础设施,提供面向Netty等运行时环境的适配器,新增Webflux模块(集成的是Reactor 3.x)。下图所示为Webflux的框架:左侧是通常使用的框架,通过Servlet API的规范和Container进行交互,上一层是Spring-Webmvc,再上一层则是经常使用的一些注解。右侧为对应的Webflux层级,只要是支持NIO的Container,例如Tomcat,Jetty,Netty或Undertow都可以实现。在协议层的是HTTP/Reactive Streams。再上一层是Spring-Webflux,为了保持兼容性,它支持这些常用的注解,同时也有一套新的语法规则Router Functions。下图显示了一个调用的实例:在Client端,首先创建一个WebClient,调用其get方法,写入URL,接收格式为APPLICATION_STREAM_JSON的数据,retrieve获得数据,取得数据后用bodyToFlux将数据转换为Car类型的对象,在doOnNext中打印构造好的Car对象,block方法意思是直到回调函数被执行才可以结束。在Server端,在指定的path中进行get操作,produces和以前不同,这里是application/stream+json,然后返回Flux范型的Car对象。传统意义上,如果数据中有一万条数据,那么便直接返回一万条数据,但在这个示例返回的Flux范型中,是不包含数据的,但在数据库也支持Reactive的情况下,request可以一直往下传递,响应式的批量返回。传统方式这样的查询很有可能是一个全表遍历,这会需要较多资源和时间,甚至影响其他任务的执行。而响应式的方法除了可以避免这种情况,还可以让用户在第一时间看到数据而不是等待数据采集完毕,这在架构体验的完整性上有了很大的提升。application/stream+json也是可以让前端识别出,这些数据是分批响应式传递,而不会等待传完才显示。现在的Java web应用可以使用Servlet栈或Reactive栈。Servlet栈已经有很久的使用历史了,而现在又增加了更有优势的Reactive栈,大家可以尝试实现更好的用户体验。2.Reactive编程模型下图中是Spring实现的一个向后兼容模型,可以使用annotation来标注Container。这是一个非常清晰、支持非常细节化的模型,也非常利于同事间的交流沟通。下图是一个Functional编程模型,通过写函数的方式构造。例如下图中传入一个Request,返回Response,通过函数的方法重点关注输入输出,不需要区分状态。然后将这些函数注册至Route。这个模型和Node.js非常接近,也利于使用。3.Spring Data框架Spring Data框架支持多种数据库,如下图所示,最常用的是JPA和JDBC。在实践中,不同的语言访问不同的数据库时,访问接口是不一样的,这对编程人员来说是个很大的工作量。Spring Data便是做了另一层抽象,使你无论使用哪种数据库,都可以使用同一个接口。具体特性这里不做详谈。下图展示了一个Spring Data的使用示例。只需要写一个方法签名,然后注解为Query,这个方法不需要实现,因为框架后台已经采用一些技术,直接根据findByFirstnameAndLastname就可以查询到。这种一致的调用方式无疑提供了巨大的方便。现在Reactive对Spring Data的支持还是不完整的,只支持了MongoDB/Redis/Cassandra和Couchbase,对JPA/LDAP/Elasticsearch/Neo4j/Solr等还不兼容。但也不是不能使用,例如对JDBC数据库,将其转为同步即可使用,重点在于findAll和async两个函数,这里不再展开详述,具体代码如下图所示:Reactive不支持JDBC最根本的原因是,JDBC不是non-blocking设计。但是现在JavaOne已经在2016年9月宣布了Non-blocking JDBC API的草案,虽然还未得到Java 10的支持,但可见这已经成为一种趋势。四.总结Spring MVC框架是一个命令式逻辑,方便编写和调试。Spring WebFlux也具有众多优势,但调试却不太容易,因为它经常需要切换线程执行,出现错误的栈可能已经销毁。当然这也是现今Java的编译工具对WebFlux不太友好,相信以后会改善。下图中列出了Spring MVC和Spring WebFlux各自的特性及交叉的部分。最后也附上一些参考资料。本文作者:李博bluemind阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 28, 2018 · 1 min · jiezi

区块链编程初学者入门指南

我有很多问题需要了解区块链Blockchain的工作原理。重要的是“我如何在其上构建应用程序dapp?”。花了几个星期的时间挖掘,阅读和试验才最终了解。我找不到简短而全面的指南。现在,我有一些不错的理解,我想写一个可以帮助别人的。这是一个快速指南,我只保留了重要的部分,以减少学习曲线。目录区块链的目的区块链是如何发明的以太坊和智能合约简介在以太坊上编写智能合约区块链的目的Roopa住在Delhi的一个偏远地区。印度政府每个月都会分配她少量的食物资源。因为她属于BPL(贫困线以下)类别。政府通过中介来分配这些食物资源。只有三分之一的食物资源可供人们使用,如Roopa,其余的则被中介出售以获取利润。Sara写小说,她在亚马逊上发表。她很沮丧,因为亚马逊将50%的销售额作为佣金。这是不公平的,因为她一个人投入了写作和营销的努力。问题是中介渴望权力和金钱。他们的座右铭已成为“不惜任何代价获利”,为了支持生产者和赋予穷人权力,我们需要中介采取道德行为。这几乎是不可能实现的,但是如果我们可以用自治系统取代中介呢?由于计算机没有偏见,因此既不需要金钱也不需要权力。这可能是Satoshi Namakato在2008年使用区块链技术发明比特币时的想法。Blockchain是如何发明的?随着时间的推移,货币逐渐发展,每次发展都降低了生产成本,使交易更加便利。金币的生产成本很高。纸币的发明解决了这个问题。但是,在计算机和互联网的发明之后,人们找到了一种更方便,更快捷的交易方式。为了安全地保持我们一生的收入并促进数字交易,我们需要一个中介(银行)。这使银行变得强大,他们可以对我们的提款/交易征收高额费用,出售我们的私人信息等。银行对金钱的渴望导致了2008年的金融危机。银行未能尊重客户的隐私。他们薄弱的安全系统引发了数字欺诈。货币的下一次演变必须解决以下问题。它不应存储在中央实体。它需要高度安全。它应该确保隐私。由于法定货币由政府控制,Satoshi别无选择,只能发明一种新货币(比特币)。他借助点对点网络和密码学解决了这些问题。去中心化分享权力Torrent使用点对点技术来共享文件。torrent应用程序不会从中央服务器或单台计算机下载文件,而是连接到其网络中的人员,找出谁拥有该文件并从其计算机下载。你可以从世界各地的不同计算机上获取文件。如果网络中的某个人离开,你的下载不会受到影响,因为还有其他人可以共享该文件。Satoshi采用这种技术,因为它以去中心化分散的方式存储钱。任何单一实体都无法控制它。加密在密码学中,人们可以对消息进行数字签名。为了做到这一点,我们需要三个东西:公钥,私钥和消息。公钥和私钥是一组数学连接的长字符。公钥就像你的用户名一样公开,私钥就像你的密码一样是秘密。消息是你要授权的信息,例如:“我授权你向John支付100美元”。如果你使用公钥,私钥和消息输入算法。加密算法将产生签名。这是该消息内容唯一的另一组字符。Public Key —–BEGIN EC PUBLIC KEY—– MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE50uE+YSxqDgMkFByhpcgTVqXCqHOh68Ljt1z0jklDff/WV7xo+U6o3REBtK/C0/LM+Ef3FB3wR9aXMGNMLb9EA== —–END EC PUBLIC KEY—–Private Key —–BEGIN EC PRIVATE KEY—– MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgwqIsXl9FqsgrzMdxaxI6flBwWIev0Z7i+WF4j8BGnrKhRANCAATnS4T5hLGoOAyQUHKGlyBNWpcKoc6HrwuO3XPSOSUN9/9ZXvGj5TqjdEQG0r8LT8sz4R/cUHfBH1pcwY0wtv0Q —–END EC PRIVATE KEY—–MessageHello WorldSignatureB0A9A4F641D3A2E3A65576B7311DCD62ABE78BBF4D3F5FE856598508E24FCB2E6F0277C2F8D57E9E2E108B7C493986E783F5316B8046597019951669B4EE6922要验证消息,必须输入公钥,消息和签名。加密算法可以验证消息是否由公钥的所有者签名。破解加密算法需要1000年的时间。由于我们的计算限制,这不能更快地完成。未来的量子计算机可能会挑战这一点。但是,可以升级比特币系统以确保安全性。Satoshi在他的系统中加入了密码术,以帮助人们从他们的钱包中授权比特币交易。隐私你通过生成钱包(公钥/私钥)注册到比特币。系统不会收集电子邮件ID,全名等信息。因此,除非你公布你的公钥,否则你将是匿名的。把它放在一起Satoshi使用加密技术和点对点网络构建了一个共享分类账。当有人向某人发送比特币时,会以加密方式签署一条消息并将其广播给网络中的所有人。他们更新了他们的分类账,因此网络中的每个人都知道谁拥有什么。Blockchain每隔十分钟,交易就会组合成一个块并链接回以前的块。这个过程产生一个连续的区块链。挖掘是确认块的过程,这涉及网络中的计算机来解决数学问题。第一个解决问题的计算机/矿工获得了凭空制造的比特币奖励。区块确认并添加到网络后,将在整个网络中进行复制。区块链是在制作自主比特币系统的过程中发明的,该系统在没有人为干预的情况下确认了交易。如果你正在寻找区块链的简化说明,这里有一个故事。以太坊和智能合约简介早些时候我们谈到用自治系统取代中介。这可以通过编程来完成。比特币的系统很难让人们对自治系统进行编码。因此,Vitalik Buterin建立了一种名为以太坊的新加密货币。它不仅是一个去中心化的加密货币,而且是一个可以以智能合约的形式托管代码的计算机网络。在智能合约中,我们可以编制条件。如果你想建立一个去中心化的书店。你编写说明以帮助作者添加新书,在客户进行交易后将下载链接发送到电子书等。智能合约不仅存储条件,还存储数据。去中心化的书店的智能合约本身存储书籍列表,购买等。但是,我们应该承认智能合约的局限性。有些系统需要人工支持,计算机无法处理。在现实世界中实施智能合约并不容易。一旦发布的智能合约不能改变,愚蠢的错误可能代价高昂。在以太坊上编写智能合约我们将建立一个简单的智能合约,存储和检索学生的成绩。我们将以合理的方式编写合约。这是github repo。pragma solidity ^0.4.18;contract Grades{}第一行告诉编译器我们正在使用哪种版本的solidity。然后我们定义合约等级。我们需要在合约中存储两项内容,学生姓名和成绩。因此,我们将创建一个数组来存储学生姓名和一个关联数组来存储他们的成绩。pragma solidity ^0.4.18;contract Grades{mapping (bytes32 => string) public grades;bytes32[] public studentList;}现在,我们将创建一种方式来发送合约,即学生姓名列表。我们将在构造函数中执行此操作。pragma solidity ^0.4.18;contract Grades{mapping (bytes32 => string) public grades;bytes32[] public studentList;function Grades(bytes32[] studentNames) public {studentList = studentNames;}}在Solidity中,我们只调用一次构造函数。我们将学生名称作为参数传递,该参数将存储在我们之前声明的studentList数组中。现在,我们需要编写一个函数来为学生分配他们的成绩。我们还需要另一个功能来检查学生是否有效。pragma solidity ^0.4.18;contract Grades{mapping (bytes32 => string) public grades;bytes32[] public studentList;function Grades(bytes32[] studentNames) public {studentList = studentNames;}function giveGradeToStudent(bytes32 student, string grade) public {require(validStudent(student));grades[student] = grade;}function validStudent(bytes32 student) view public returns (bool) {for(uint i = 0; i < studentList.length; i++) {if (studentList[i] == student) {return true;}}return false;}}giveGradeToStudent函数有两个参数,学生姓名和成绩。require函数检查validStudent函数是返回true还是false。如果返回false,则取消执行。最后,我们需要编写一个函数来获取学生的成绩。getGradeForStudent函数将学生姓名作为参数,从关联数组中返回相应的成绩。pragma solidity ^0.4.18;contract Grades{mapping (bytes32 => string) public grades;bytes32[] public studentList;function Grades(bytes32[] studentNames) public {studentList = studentNames;}function giveGradeToStudent(bytes32 student, string grade) public {require(validStudent(student));grades[student] = grade;}function validStudent(bytes32 student) view public returns (bool) {for(uint i = 0; i < studentList.length; i++) {if (studentList[i] == student) {return true;}}return false;}function getGradeForStudent(bytes32 student) view public returns (string) {require(validStudent(student));return grades[student];}}为了本教程的目的,你可以将其部署在个人区块链上。你可以用Ganache创建。以下是安装和运行ganache的命令。npm install ganache-cli web3@0.20.3 solcnode_modules/.bin/ganache-cli保持ganache运行,在新终端上我们将部署我们的智能合约。请将智能合约保存为Grades.sol。让我们编译代码。nodecode = fs.readFileSync(‘Grades.sol’).toString()solc = require(‘solc’)compiledCode = solc.compile(code)现在让我们部署智能合约。在区块链上部署合约会花费你的gas,这是为了奖励那些向你租用计算能力的人。所以我们必须指定你愿意分配的gas量。你可以使用gas计算器估算。但是,你现在无需付费,因为你正在使用个人区块链进行部署,这是你正在使用的资源。在公共以太坊区块链上部署合约时,你必须付费。Web3 = require(‘web3’)web3 = new Web3(new Web3.providers.HttpProvider(“http://localhost:8545”));abiDefinition = JSON.parse(compiledCode.contracts[’:Grades’].interface)GradesContract = web3.eth.contract(abiDefinition)byteCode = compiledCode.contracts[’:Grades’].bytecodedeployedContract = GradesContract.new([‘John’,‘James’],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000})现在让我们调用我们的函数给我们的学生John提供成绩A +。稍后,我们将使用getGradeForStudent函数检查它是否已更新。deployedContract.giveGradeToStudent(‘John’, ‘A+’, {from: web3.eth.accounts[0]})deployedContract.getGradeForStudent.call(‘John’)‘A+‘恭喜,你已经部署了智能合约。======================================================================如果你希望更进一步,分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。汇智网原创翻译,转载请标明出处。这里是原文区块链编程初学者入门指南 ...

December 27, 2018 · 1 min · jiezi

foo、bar到底是什么意思

在学习编程语言的过程中,尤其使用的是英文的书籍,我们经常发现一些foo、bar、baz之类的名字,要么是变量的名字,要么是文件的名字。。。深究起来完全不明所以。这到底是什么意思呢?示例下面是《C++17 STL Cookbook》这本书的一些用例://foo用做类名class foo {public: static std::string &standard_string() { static std::string s{“some standard string”}; return s; }};还有://用作文件名path p {“testdir/foobar.txt”};同样其他语言里也有这种情况,《The Go Programming Language》这本书使用了大量此类名字:f, err = os.Open(“foo.txt”) // 用作文件名//用做字符串fmt.Println(Equal([]string{“foo”}, []string{“bar”}))解释那这些单词传达给我们的意思是什么呢?实际上这些东西跟我们中文里的张三、李四、王五这类词属于一类,表示当前当前命名“无关紧要”,不影响当前概念的表达。其他资料维基百科里有比较专业的解释:The terms foobar (/fubr/), or foo and others are used as placeholder names (also referred to as metasyntactic variables) in computer programming or computer-related documentation.[1] They have been used to name entities such as variables, functions, and commands whose exact identity is unimportant and serve only to demonstrate a concept.直译过来就是:foobar或者foo等诸如此类的措辞在计算机编程或计算机相关文档中被用作占位符名字(也称为元语法变量)。它们通常被用来命名一些变量、函数或命令等此类实体,而这些实体通常不重要,而且仅仅用来演示一个概念。请关注我的公众号哦。 ...

December 25, 2018 · 1 min · jiezi

JavaScript基础——深入学习async/await

本文由云+社区发表本篇文章,小编将和大家一起学习异步编程的未来——async/await,它会打破你对上篇文章Promise的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解Promise后,才能更好的的驾驭async/await,因为async/await是基于Promise的。关于async / await用于编写异步程序代码书写方式和同步编码十分相似,因此代码十分简洁易读基于Promise您可以使用try-catch常规的方法捕获异常ES8中引入了async/await,目前几乎所有的现代浏览器都已支持这个特性(除了IE和Opera不支持)你可以轻松设置断点,调试更容易。从async开始学起让我们从async关键字开始吧,这个关键字可以放在函数之前,如下所示:async function f() { return 1; }在函数之间加上async意味着:函数将返回一个Promise,虽然你的代码里没有显示的声明返回一个Promise,但是编译器会自动将其转换成一个Promise,不信你可以使用Promise的then方法试试:async function f() { return 1; } f().then(alert); // 1…如果你不放心的话,你可以在代码里明确返回一个Promise,输出结果是相同的。async function f() { return Promise.resolve(1); } f().then(alert); // 1很简单吧,小编之所以说 async/await 是基于Promise是没毛病的,async函数返回一个Promise,很简单吧,不仅如此,还有一个关键字await,await只能在async中运行。等待——awaitawait的基本语法:let value=await promise;该关键字的await的意思就是让JS编译器等待Promise并返回结果。接下来我们看一段简单的示例:async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve(“done!”), 1000) }); let result = await promise; // wait till the promise resolves (*) alert(result); // “done!” } f();函数执行将会在 let result = await promise 这一行暂停,直到Promise返回结果,因此上述代码将会1秒后,在浏览器弹出“done”的提示框。小编在此强调下:await的字面意思就是让JavaScript等到Promise结束,然后输出结果。这里并不会占用CPU资源,因为引擎可以同时执行其他任务:其他脚本或处理事件。不能单独使用await,必须在async函数作用域下使用,否则将会报出异常“Error: await is only valid in async function”,示例代码如下:function f() { let promise = Promise.resolve(1); let result = await promise; // Syntax error }接下来,小编将和大家一起来亲自动手实践以下内容:async与Promise.then的结合,依次处理多个结果使用await替代Promise.then,依次处理多个结果同时等待多个结果使用Promise.all收集多个结果使用try-catch捕获异常如何捕获Promise.all中的异常使用finally确保函数执行一起动手之前,确保你安装了Node,NPM相关工具,谷歌浏览器,为了预览代码效果,小编使用 npm install http-server -g 命令快速部署了web服务环境,方便我们运行代码。接下来,我们写一个火箭发射场景的小例子(不是真的发射火箭????)。async与Promise.then的结合,依次处理多个结果通过控制台命令切换至工作区创建一个async-function-Promise-chain的文件夹在main.js中用创建一个返回随机函数的async函数getRandomNumber:async function getRandomNumber() { console.log(‘Getting random number.’); return Math.random(); } 再创建一个async函数determinReadyToLaunch:如果传入参数大于0.5将返回Trueasync function deteremineReadyToLaunch(percentage) { console.log(‘Determining Ready to launch.’); return percentage>0.5; } 创建第三个async函数reportResults,如果传入参数为True将进入倒计时发射async function reportResults(isReadyToLaunch) { if (isReadyToLaunch) { console.log(‘Rocket ready to launch. Initiate countdown: ????’); } else { console.error(‘Rocket not ready. Abort mission: ‘); } } 创建一个main函数,调用getRandomNumber函数,并且通过Promise.then方法相继调用determineReadyToLaunch和reportResults函数export function main() { console.log(‘Before Promise created’); getRandomNumber() .then(deteremineReadyToLaunch) .then(reportResults) console.log(‘After Promise created’); } 新建一个html文件引入main.js<html> <script type=“module”> import {main} from ‘./main.js’; main(); </script> <body> </body> </html>在工作区域运行 http-server 命令,你将会看到如下输出使用await替代Promise.then,依次处理多个结果上一小节,我们使用Promise.then依次处理了多个结果,本小节,小编将使用await实现同样的功能,具体操作如下:通过控制台命令切换至工作区创建一个async-function-Promise-chain的文件夹在main.js中用创建一个返回随机函数的async函数getRandomNumber:async function getRandomNumber() { console.log(‘Getting random number.’); return Math.random(); } 再创建一个async函数determinReadyToLaunch:如果传入参数大于0.5将返回Trueasync function deteremineReadyToLaunch(percentage) { console.log(‘Determining Ready to launch.’); return percentage>0.5; } 创建第三个async函数reportResults,如果传入参数为True将进入倒计时发射async function reportResults(isReadyToLaunch) { if (isReadyToLaunch) { console.log(‘Rocket ready to launch. Initiate countdown: ????’); } else { console.error(‘Rocket not ready. Abort mission: ‘); } } 创建一个main函数,调用getRandomNumber函数,并且通过Promise.then方法相继调用determineReadyToLaunch和reportResults函数export async function main() { const randomNumber = await getRandomNumber(); const ready = await deteremineReadyToLaunch(randomNumber); await reportResults(ready); } 在工作区域运行 http-server 命令,你将会看到如下输出同时等待多个结果有时候我们需要同时启动多个异步,无需依次等待结果消耗时间,接下来的例子可以使用await同时启动多个异步函数,等待多个结果。通过控制台命令切换至工作区创建一个await-concurrently的文件夹创建三个函数checkEngines,checkFlightPlan,和checkNavigationSystem用来记录信息时,这三个函数都返回一个Promise,示例代码如下:function checkEngines() { console.log(‘checking engine’); return new Promise(function (resolve) { setTimeout(function() { console.log(’engine check completed’); resolve(Math.random() < 0.9) }, 250) }); } function checkFlightPlan() { console.log(‘checking flight plan’); return new Promise(function (resolve) { setTimeout(function() { console.log(‘flight plan check completed’); resolve(Math.random() < 0.9) }, 350) }); } function checkNavigationSystem() { console.log(‘checking navigation system’); return new Promise(function (resolve) { setTimeout(function() { console.log(’navigation system check completed’); resolve(Math.random() < 0.9) }, 450) }); } 创建一个async的main函数调用上一步创建函数。将每个返回的值分配给局部变量。然后等待Promise的结果,并输出结果: export async function main() { const enginePromise = checkEngines(); const flighPlanPromise = checkFlightPlan(); const navSystemPromise = checkNavigationSystem(); const enginesOk = await enginePromise; const flighPlanOk = await flighPlanPromise; const navigationOk = await navSystemPromise; if (enginesOk && flighPlanOk && navigationOk) { console.log(‘All systems go, ready to launch: ????’); } else { console.error(‘Abort the launch: ‘); if (!enginesOk) { console.error(’engines not ready’); } if (!flighPlanOk) { console.error(’error found in flight plan’); } if (!navigationOk) { console.error(’error found in navigation systems’); } } } 在工作区域运行 http-server 命令,你将会看到如下输出使用Promise.all收集多个结果在上一小节中,我们一起学习了如何触发多个异步函数并等待多个异步函数结果。上一节我们只使用了asyc/await,本节小编和大家一起使用Promise.all来收集多个异步函数的结果,在某些情况下,尽量使用Promise相关的API,具体的代码如下:通过控制台命令切换至工作区创建一个Promise-all-collect-concurrently的文件夹创建三个函数功能checkEngines,checkFlightPlan,和checkNavigationSystem用来记录信息时,这三个函数都返回一个Promise,示例代码如下:function checkEngines() { console.log(‘checking engine’); return new Promise(function (resolve) { setTimeout(function() { console.log(’engine check completed’); resolve(Math.random() < 0.9) }, 250) }); } function checkFlightPlan() { console.log(‘checking flight plan’); return new Promise(function (resolve) { setTimeout(function() { console.log(‘flight plan check completed’); resolve(Math.random() < 0.9) }, 350) }); } function checkNavigationSystem() { console.log(‘checking navigation system’); return new Promise(function (resolve) { setTimeout(function() { console.log(’navigation system check completed’); resolve(Math.random() < 0.9) }, 450) }); } 创建一个async的main函数调用上一步创建的函数。使用Promise.all收集多个结果,将结果返回给变量,代码实现如下:export async function main() { const prelaunchChecks = [ checkEngines(), checkFlightPlan(), checkNavigationSystem() ]; const checkResults = await Promise.all(prelaunchChecks); const readyToLaunch = checkResults.reduce((acc, curr) => acc && curr); if (readyToLaunch) { console.log(‘All systems go, ready to launch: ????’); } else { console.error(‘Something went wrong, abort the launch: ‘); } } } 在工作区域运行 http-server 命令,你将会看到如下输出:Promise.all接收多个promise的数组,并整体返回一个Promise,如果和上一小节的代码进行比较,代码量少了不少。使用try-catch捕获异常并非所有的async都能成功返回,我们需要处理程序的异常,在本小节中,你将会看到如何使用try-catch捕获async函数引发的异常,具体操作的流程如下:通过控制台命令切换至工作区创建一个async-errors-try-catch的文件夹创建一个抛出错误的async函数addBoostersasync function addBoosters() { throw new Error(‘Unable to add Boosters’); } 创建一个async函数,performGuidanceDiagnostic它也会抛出一个错误:async function performGuidanceDiagnostic (rocket) { throw new Error(‘Unable to finish guidance diagnostic’)); } 创建一个async的main函数调用addBosters、performGuidanceDiagnostic两个函数 ,使用try-catch捕获异常: export async function main() { console.log(‘Before Check’); try { await addBosters(); await performGuidanceDiagnostic(); } catch (e) { console.error(e); } } console.log(‘After Check’); 在工作区域运行 http-server 命令,你将会看到如下输出:从输出看出,我们使用熟悉的try-catch捕获到了异常,如果第一个发生异常,第二个就不会执行,同时将会被记录到,并输出到控制台,在下一小节,我们将一起学习如何使用try-catch捕获Promise.all中运行的多个Promise的异常。如何捕获Promise.all中的异常在上一小节,我们使用了Promise.all来收集多个异步函数的结果。在收集异常方面,Promise.all更有趣。通常,我们在处理多个错误时,同时显示多个错误信息,我们必须编写相关的业务逻辑。但是,在这小节,你将会使用Promise.all和try-catch捕获异常,无需编写复杂的布尔逻辑处理业务,具体如何实现示例如下:通过控制台命令切换至工作区创建一个Promise-all-collect-concurrently的文件夹创建三个async函数checkEngines,checkFlightPlan以及checkNavigationSystem用来记录信息时,返回Promise,一个成功的值的信息和一个失败值的信息:function checkEngines() { console.log(‘checking engine’); return new Promise(function (resolve, reject) { setTimeout(function () { if (Math.random() > 0.5) { reject(new Error(‘Engine check failed’)); } else { console.log(‘Engine check completed’); resolve(); } }, 250) }); } function checkFlightPlan() { console.log(‘checking flight plan’); return new Promise(function (resolve, reject) { setTimeout(function () { if (Math.random() > 0.5) { reject(new Error(‘Flight plan check failed’)); } else { console.log(‘Flight plan check completed’); resolve(); } }, 350) }); } function checkNavigationSystem() { console.log(‘checking navigation system’); return new Promise(function (resolve, reject) { setTimeout(function () { if (Math.random() > 0.5) { reject(new Error(‘Navigation system check failed’)); } else { console.log(‘Navigation system check completed’); resolve(); } }, 450) }); } 创建一个async的main函数调用每个在上一步中创建的功能函数。等待结果,捕获并记录引发的异常。如果没有抛出异常,则记录成功: export async function main() { try { const prelaunchChecks = [ checkEngines, checkFlightPlan, checkNavigationSystem ]; await Promise.all(prelauchCheck.map((check) => check()); console.log(‘All systems go, ready to launch: ????’); } catch (e) { console.error(‘Aborting launch: ‘); console.error(e); } } } 在工作区域运行 http-server 命令,你将会看到如下输出Promise.all返回一个Promise,当await在错误状态下,会抛出异常。三个异步promise同时执行,如果其中一个或多个错误得到满足,则会抛出一个或多个错误;你会发现只有一个错误会被记录下来,与同步代码一样,我们的代码可能会抛出多个异常,但只有一个异常会被catch捕获并记录。使用finally确保函数执行错误处理可能会变得相当复杂。有些情况,其中你希望发生错误时继续冒泡调用堆栈以便执行其它更高级别处理。在这些情况下,您可能还需要执行一些清理任务。本小节,你将了解如何使用finally以确保执行某些代码,而不管错误状态如何,具体如何实现示例如下:通过控制台命令切换至工作区创建一个Promise-all-collect-concurrently的文件夹创建三个async函数checkEngines,checkFlightPlan、checkNavigationSystem用来记录信息,返回Promise,一个成功的值的信息和一个失败值的信息:function checkEngines() { console.log(‘checking engine’); return new Promise(function (resolve, reject) { setTimeout(function () { if (Math.random() > 0.5) { reject(new Error(‘Engine check failed’)); } else { console.log(‘Engine check completed’); resolve(); } }, 250) }); } function checkFlightPlan() { console.log(‘checking flight plan’); return new Promise(function (resolve, reject) { setTimeout(function () { if (Math.random() > 0.5) { reject(new Error(‘Flight plan check failed’)); } else { console.log(‘Flight plan check completed’); resolve(); } }, 350) }); } function checkNavigationSystem() { console.log(‘checking navigation system’); return new Promise(function (resolve, reject) { setTimeout(function () { if (Math.random() > 0.5) { reject(new Error(‘Navigation system check failed’)); } else { console.log(‘Navigation system check completed’); resolve(); } }, 450) }); } 创建一个asyncperformCheck函数,调用上一步中创建的每个函数。等待结果,并用于finally记录完整的消息:async function performChecks() { console.log(‘Starting Pre-Launch Checks’); try { const prelaunchChecks = [ checkEngines, checkFlightPlan, checkNavigationSystem ]; return Promise.all(prelauchCheck.map((check) => check()); } finally { console.log(‘Completed Pre-Launch Checks’); } } 创建一个async的main函数调该函数performChecks。等待结果,捕获并记录引发的异常。export async function main() { try { await performChecks(); console.log(‘All systems go, ready to launch: ????’); } catch (e) { console.error(‘Aborting launch: ‘); console.error(e); } } 在工作区域运行 http-server 命令,你将会看到如下输出与上一小节一样,异常在main函数中进行捕获,由于finally的存在,让我清楚的知道performChecks确保需要执行的输出完成。你可以设想,处理错误是一个重要的任务,并且async/await允许我们使用try/catch的方式同时处理异步和同步代码的错误,大大简化了我们处理错误的工作量,让代码更加简洁。用async/await改写上篇文章Promise的例子上篇文章《JavaScript基础——Promise使用指南》的最后,我们使用Promise的方法改写了回调的例子,本文的最后,我们将用今天学到的内容async/await改写这个例子,如何实现呢,代码如下:const fs = require(‘fs’); const path = require(‘path’); const postsUrl = path.join(__dirname, ‘db/posts.json’); const commentsUrl = path.join(__dirname, ‘db/comments.json’); //return the data from our file function loadCollection(url) { return new Promise(function(resolve, reject) { fs.readFile(url, ‘utf8’, function(error, data) { if (error) { reject(’error’); } else { resolve(JSON.parse(data)); } }); }); } //return an object by id function getRecord(collection, id) { return new Promise(function(resolve, reject) { const data = collection.find(function(element){ return element.id == id; }); resolve(data); }); } //return an array of comments for a post function getCommentsByPost(comments, postId) { return comments.filter(function(comment){ return comment.postId == postId; }); } async function getPost(){ try { const posts = await loadCollection(postsUrl); const post = await getRecord(posts, “001”); const comments = await loadCollection(commentsUrl); const postComments = await getCommentsByPost(comments, post.id); console.log(post); console.log(postComments); } catch (error) { console.log(error); } } getPost(); 和Promise的方式相比,async/await 的实现方式是不是更直观更容易理解呢,让我几乎能用同步的方式编写异步代码。此文已由作者授权腾讯云+社区发布 ...

December 20, 2018 · 5 min · jiezi

一起来看 rxjs

更新日志2018-05-26 校正2016-12-03 第一版翻译过去你错过的 Reactive Programming 的简介你好奇于这名为Reactive Programming(反应式编程)的新事物, 更确切地说,你想了解它各种不同的实现(比如 [Rx*], [Bacon.js], RAC 以及其它各种各样的框架或库)学习它比较困难, 因为比较缺好的学习材料(译者注: 原文写就时, RxJs 还在 v4 版本, 彼时社区对 RxJs 的探索还不够完善). 我在开始学习的时候, 试图找过教程, 不过能找到的实践指南屈指可数, 而且这些教程只不过隔靴搔痒, 并不能帮助你做真正了解 RxJs 的基本概念. 如果你想要理解其中一些函数, 往往代码库自带的文档帮不到你. 说白了, 你能一下看懂下面这种文档么:Rx.Observable.prototype.flatMapLatest(selector, [thisArg])按照将元素的索引合并的方法, 把一个 “observable 队列 " 中的作为一个新的队列加入到 “observable 队列的队列” 中, 然后把 “observable 队列的队列” 中的一个 “observable 队列” 转换成一个 “仅从最近的 ‘observable 队列’ 产生的值构成的一个新队列.“这是都是什么鬼?我读了两本书, 一本只是画了个大致的蓝图, 另一本则是一节一节教你 “如何使用 Reactive Libarary” . 最后我以一种艰难的方式来学习 Reactive Programming: 一遍写, 一遍理解. 在我就职于 Futurice 的时候, 我第一次在一个真实的项目中使用它, 我在遇到问题时, 得到了来自同事的支持.学习中最困难的地方是 以 Reactive(反应式) 的方式思考. 这意思就是, 放下你以往熟悉的编程中的命令式和状态化思维习惯, 鼓励自己以一种不同的范式去思考. 至今我还没在网上找到任何这方面的指南, 而我认为世界上应该有一个说明如何以 Reactive(反应式) 的方式思考的教程, 这样你才知道要如何开始使用它. 在阅读完本文后之后. 请继续阅读代码库自带的文档来指引你之后的学习. 我希望, 这篇文档对你有所帮助.“什么是 Reactive Programming(反应式编程)?“在网上可以找到大量对此糟糕的解释和定义. Wikipedia 的 意料之中地泛泛而谈和过于理论化. Stackoverflow 的 圣经般的答案也绝对不适合初学者. Reactive Manifesto 听起来就像是要给你公司的项目经理或者是老板看的东西. 微软的 Rx 术语 “Rx = Observables + LINQ + Schedulers” 也读起来太繁重, 太微软了, 以至于你看完后仍然一脸懵逼. 类似于 “reactive” 和 “propagation” 的术语传达出的含义给人感觉无异于你以前用过的 MV* 框架和趁手的语言已经做到的事情. 我们现有的框架视图当然是会对数据模型做出反应, 任何的变化当然也是要冒泡的. 要不然, 什么东西都不会被渲染出来嘛.所以, 让我们撇开那些无用的说辞, 尝试去了解本质.Reactive programming(反应式编程) 是在以异步数据流来编程当然, 这也不是什么新东西. 事件总线或者是典型的点击事件确实就是异步事件流, 你可以对其进行 observe(观察) 或者做些别的事情. 不过, Reactive 是比之更优秀的思维模型. 你能够创建任何事物的数据流, 而不只是从点击和悬浮事件中. “流” 是普遍存在的, 一切都可能是流: 变量, 用户输入, 属性, 缓存, 数据结构等等. 比如, 想象你的 Twitter 时间线会成为点击事件同样形式的数据流.熟练掌握该思维模型之后, 你还会接触到一个令人惊喜的函数集, 其中包含对任何的数据流进行合并、创建或者从中筛选数据的工具. 它充分展现了 “函数式” 的魅力所在. 一个流可以作为另一个流的输入. 甚至多个流可以作为另一个流的输入. 你可以合并两个流. 你可以筛选出一个仅包含你需要的数据的另一个流. 你可以从一个流映射数据值到另一个流.让我们基于 “流是 Reactive 的中心” 这个设想, 来细致地做看一下整个思维模型, 就从我们熟知的 “点击一个按钮” 事件流开始.每个流是一个按时序不间断的事件序列. 它可能派发出三个东西: (某种类型的)一个数值, 一个错误, 或者一个 “完成” 信号. 说到 “完成” , 举个例子, 当包含了这个按钮的当前窗口/视图关闭时, 也就是 “完成” 信号发生时.我们仅能异步地捕捉到这些事件: 通过定义三种函数, 分别用来捕捉派发出的数值、错误以及 “完成” 信号. 有时候后两者可以被忽略, 你只需定义用来捕捉数值的函数. 我们把对流的 “侦听” 称为订阅(subscribing), 我们定义的这三种函数合起来就是观察者, 流则是被观察的主体(或者叫"被观察者”). 这正是设计模式中的观察者模式.描述这种方式的另一种方式用 ASCII 字符来画个导图, 在本教程的后续的部分也能看到这种图形.–a—b-c—d—X—|->a, b, c, d 代表被派发出的值X 代表错误| 代表"完成"信号—> 则是时间线这些都是是老生常谈了, 为了不让你感到无聊, 现在来点新鲜的东西: 我们将原生的点击事件流进行变换, 来创建新的点击事件流.首先, 我们做一个计数流, 来指明一个按钮被点击了多少次. 在一般的 Reactive 库中, 每个流都附带了许多诸如map, filter, scan 等的方法. 当你调用这些方法之一(比如比如clickStream.map(f))时, 它返回一个基于 clickStream 的新的流. 它没有对原生的点击事件做任何修改. 这种(不对原有流作任何修改的)特性叫做immutability(不可变性), 而它和 Reactive(反应式) 这个概念的契合度之高好比班戟和糖浆(译者注: 班戟就是薄煎饼, 该称呼多见于中国广东地区. 此句意为 immutability 与 Reactive 两个概念高度契合). 这样的流允许我们进行链式调用, 比如clickStream.map(f).scan(g): clickStream: —c—-c–c—-c——c–> vvvvv map(c becomes 1) vvvv —1—-1–1—-1——1–> vvvvvvvvv scan(+) vvvvvvvvvcounterStream: —1—-2–3—-4——5–>map(f) 方法根据你提供的函数f替换每个被派发的元素形成一个新的流. 在上例中, 我们将每次点击都映射为计数 1. scan(g) 方法则在内部运行x = g(accumulated, current), 以某种方式连续聚合处理该流之前所有的值, 在该例子中, g 就是简单的加法. 然后, 一次点击发生时, counterStream 就派发一个点击数的总值.为了展示 Reactive 真正的能力, 我们假设你想要做一个 “双击事件” 的流. 或者更厉害的, 我们假设我们想要得到一个 “三击事件流” , 甚至推广到更普遍的情况, “多击流”. 现在, 深呼吸, 想象一下按照传统的命令式和状态化思维习惯要如何完成这项工作? 我敢说那会烦死你了, 它必须包含各种各样用来保持状态的变量, 以及一些对周期性工作的处理.然而, 以 Reactive 的方式, 它会非常简单. 事实上, 这个逻辑只不过是四行代码. 不过让我们现在忘掉代码.无论你是个初学者还是专家, 借助导图来思考, 才是理解和构建流最好的方法.图中的灰色方框是将一个流转换成另一个流的方法. 首先, 每经过 “250毫秒” 的 “事件静默” (简单地说, 这是在 buffer(stream.throttle(250ms)) 完成的. (现在先)不必担心对这点的细节的理解, 我们主要是演示下 Reactive 的能力.), 我们就得到了一个 “点击动作” 的列表, 即, 转换的结果是一个列表的流, 而从这个流中我们应用 map() 将每个列表映射成对应该队列的长度的整数值. 最后, 我们使用 filter(x >= 2) 方法忽略掉所有的 1. 如上: 这 3 步操作将产生我们期望的流. 我们之后可以订阅(“侦听”)它, 并按我们希望的处理方式处理流中的数据.我希望你感受到了这种方式的美妙. 这个例子只是一次不过揭示了冰山一角: 你可以将相同的操作应用到不同种类的流上, 比如 API 返回的流中. 除此以外, 还有许多有效的函数.“为什么我应该采用反应式编程?“Reactive Programming (反应式编程) 提升了你代码的抽象层次, 你可以更多地关注用于定义业务逻辑的事件之间的互相依赖, 而不必写大量的细节代码来处理事件. RP(反应式编程)的代码会更简洁明了.在现代网页应用和移动应用中, 这种好处是显而易见的, 这些场景下, 与数据事件关联的大量 UI 事件需要被高频地交互. 10 年前, 和 web 页面的交互只是很基础地提交一个长长的表单给后端, 然后执行一次简单的重新渲染. 在这 10 年间, App 逐渐变得更有实时性: 修改表单中的单个字段能够自动触发一次到后端的保存动作, 对某个内容的 “点赞” 需要实时反馈到其他相关的用户……现今的 App 有大量的实时事件, 它们共同作用, 以带给用户良好的体验. 我们要能简洁处理这些事件的工具, 而 Reactive Programming 方式我们想要的.举例说明如何以反应式编程的方式思考现在我们进入到实战. 一个真实的手把手教你如何以 RP(反应式编程) 的方式来思考的例子. 注意这里不是随处抄来的例子, 不是半吊子解释的概念. 到这篇教程结束为止, 我们会在写出真正的功能性代码的同时, 理解我们做的每个动作.我选择了 JavaScript 和 RxJS 作为工具, 原因是, JavaScript 是当下最为人熟知的语言, 而 [Rx*] 支持多数语言和平台 (.NET, Java, Scala, Clojure, JavaScript, Ruby, Python, C++, Objective-C/Cocoa, Groovy 等等). , 无论你的工具是什么, 你可以从这篇教程中收益.实现一个"建议关注"盒子在 Twitter 上, 有一个 UI 元素是建议你可以关注的其它账户.我们将着重讲解如何模仿出它的核心特性:在页面启动时, 从 API 中加载账户数据, 并展示三个推荐关注者在点击"刷新"时, 加载另外的三个推荐关注的账户, 形成新三行在点击一个账户的 “x” 按钮时, 清除该账户并展示一个新的每一行显示账户的头像和到他们主页的链接我们可以忽略其它的特性和按钮, 它们都是次要的. 另外, Twitter 最近关闭了非认证请求接口, 作为替代, 我们使用 [Github 的 API] 来构建这个关注别人 UI.(注: 到本稿的最新的校正为止, github 的该接口对非认证用户启用了一段时间内访问频次限制)如果你想尽早看一下完整的代码, 请点击[样例代码].请求和回复你如何用 Rx 处理这个问题?首先, (几乎) 万物皆可为流 .这是 “Rx 口诀”. 让我们从最容易的特性开始: “在页面启动时, 从 API 中加载账户数据”. 这没什么难得, 只需要(1) 发一个请求, (2) 读取回复, (3) 渲染回复的中的数据. 所以我们直接把我们我们的请求当做流. 一开始就用流也许颇有"杀鸡焉用牛刀"的意味, 但为了理解, 我们需要从基本的例子开始.在应用启动的时候, 我们只需要一个请求, 因此如果我们将它作为一个数据流, 它将会只有一个派发的值. 我们知道之后我们将有更多的请求, 但刚开始时只有一个.–a——|->其中 a 是字符串 ‘https://api.github.com/users'这是一个将请求的 URL 的流. 无论请求何时发生, 它会告诉我们两件事: 请求发生的时刻和内容. 请求执行之时就是事件派发之时, 请求的内容就是被派发的值: 一个 URL 字符串.创建这样一个单值流对 [Rx*] 来说非常简单, 官方对于流的术语, 是 “Observable”(可被观察者), 顾名思义它是可被观察的, 但我觉得这名字有点傻, 所以我称呼它为 流.var requestStream = Rx.Observable.just(‘https://api.github.com/users');但现在, 这只是一个字符串流, 不包含其他操作, 所以我们需要要在值被派发的时候做一些事情. 这依靠对流的订阅.requestStream.subscribe(function(requestUrl) { // 执行该请求 jQuery.getJSON(requestUrl, function(responseData) { // … });}注意我们使用了 jQuery Ajax 回调(我们假定你应已对此有了解)来处理请求操作的异步性. 但稍等, Rx 就是处理 异步 数据流的. 难道这个请求的回复不就是一个在未来某一刻会带回返回数据的流么? 从概念上讲, 它看起来就是的, 我们来尝试写一下.requestStream.subscribe(function(requestUrl) { // 执行该请求 var responseStream = Rx.Observable.create(function (observer) { jQuery.getJSON(requestUrl) .done(function(response) { observer.onNext(response); }) .fail(function(jqXHR, status, error) { observer.onError(error); }) .always(function() { observer.onCompleted(); }); }); responseStream.subscribe(function(response) { // 对回复做一些处理 });}Rx.Observable.create() 所做的是自定义一个流, 这个流会通知其每个观察者(或者说其"订阅者” )有数据产生 (onNext()) 或发生了错误 (onError()). 我们需要做的仅仅是包装 jQuery Ajax Promise. 稍等, 这难道是说 Promise 也是一个 Observable?是的. Observable 就是一个 Promise++ 对象. 在 Rx 中, 通过运行 var stream = Rx.Observable.fromPromise(promise) 你就可以把一个 Promise 转换成一个 Observable. 仅有的区别在于 Observables 不符合 Promises/A+ 标准, 但他们在概念上是不冲突的. 一个 Promise 就是一个仅派发一个值的 Observable. Rx 流就是允许多次返回值的 Promise.这个例子很可以的, 它展示了 Observable 是如何至少有 Promise 的能力. 因此如果你喜欢 Promise, 请注意 Rx Observable 也可以做到同样的事.现在回到我们的例子上, 也许你已经注意到了, 我们在一个中 subscribe() 调用了另一个 subscribe(), 这有点像回调地狱. 另外, responseStream 的创建也依赖于 requestStream. 但正如前文所述, 在 Rx 中有简单的机制来最流作变换并支持从其他流创建一个新的流, 接下来我们来做这件事.到目前为止, 你应该知道的对流进行变换的一个基础方法是 map(f), 将 “流 A” 中的每一个元素作 f()处理, 然后在 “流 B” 中生成一一对应的值. 如果我们这样处理我们的请求和回复流, 我们可以把请求 URL 映射到回复的 Promise (被当做是流) 中.var responseMetastream = requestStream .map(function(requestUrl) { return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl)); });这下我们创建了一个叫做 元流 (流的流) 的奇怪的东西. 不必对此感到疑惑, 元流, 就是其中派发值是流的流. 你可以把它想象成 指针): 每个被派发的值都是对其它另一个流的 指针 . 在我们的例子中, 每个请求的 URL 都被映射为一个指针, 指向一个个包含 URL 对应的返回数据的 promise 流.这个元流看上去有点让人迷惑, 而且对我们根本没什么用. 我们只是想要一个简单的回复流, 其中每个派发的值都应是一个 JSON 对象, 而不是一个包含 JSON 对象的 Promise. 现在来认识 Flatmap: 它类似于 map(), 但它是把 “分支” 流中派发出的的每一项值在 “主干” 流中派发出来, 如此, 它就可以对元流进行扁平化处理.(译者注: 这里, “分支” 流指的是元流中每个被派发的值, “主干” 流是指这些值有序构成的流, 由于元流中的每个值都是流, 作者不得不用 “主干” 和 “分支” 这样的比喻来描述元流与其值的关系). 在此, Flatmap 并不是起到了"修正"的作用, 元流也并不是一个 bug, 相反, 它们正是 Rx 中处理异步回复流的工具.var responseStream = requestStream .flatMap(function(requestUrl) { return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl)); });漂亮. 因为回复流是依据请求流定义的, 设想之后有更多的发生在请求流中的事件, 不难想象, 就会有对应的发生在回复流中的的回复事件:requestStream: –a—–b–c————|->responseStream: —–A——–B—–C—|->(小写的是一个请求, 大写的是一个回复)现在我们终于得到了回复流, 我们就可以渲染接收到的数据responseStream.subscribe(function(response) { // 按你设想的方式渲染 response 为 DOM});整理一下到目前为止的代码, 如下:var requestStream = Rx.Observable.just(‘https://api.github.com/users');var responseStream = requestStream .flatMap(function(requestUrl) { return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl)); });responseStream.subscribe(function(response) { // 按你设想的方式渲染 response 为 DOM});刷新按钮现在我们注意到, 回复中的 JSON 是一个包含 100 个用户的列表. [Github 的 API] 只允许我们指定一页的偏移量, 而不能指定读取的一页中的项目数量, 所以我们只用到 3 个数据对象, 剩下的 97 个只能浪费掉. 我们暂时忽略这个问题, 之后我们会看到通过缓存回复来处理它.每次刷新按钮被点击的时候, 请求流应该派发一个新的 URL, 因此我们会得到一个新的回复. 我们需要两样东西: 一个刷新按钮的点击事件流(口诀: 万物皆可成流), 并且我们需要改变请求流以依赖刷新点击流. 好在, RxJs 拥有从事件监听器产生 Observable 的工具.var refreshButton = document.querySelector(’.refresh’);var refreshClickStream = Rx.Observable.fromEvent(refreshButton, ‘click’);既然刷新点击事件自身不带任何 API URL, 我们需要映射每次点击为一个实际的 URL. 现在我们将请求流改成刷新点击流, 这个流被映射为每次带有随机的偏移参数的、到 API 的请求.var requestStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });如果我直接这样写, 也不做自动化测试, 那这段代码其实有个特性没实现. 即请求不会在页面加载完时发生, 只有当刷新按钮被点击的时候才会. 但其实, 两种行为我们都需要: 刷新按钮被点击的时候的请求, 或者是页面刚打开时的请求.两种场景下需要不同的流:var requestOnRefreshStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });var startupRequestStream = Rx.Observable.just(‘https://api.github.com/users');但我们如何才能"合并"这两者为同一个呢? 有一个 merge() 方法. 用导图来解释的话, 它看起来像是这样的.stream A: —a——–e—–o—–>stream B: —–B—C—–D——–> vvvvvvvvv merge vvvvvvvvv —a-B—C–e–D–o—–>那我们要做的事就变得很容易了:var requestOnRefreshStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });var startupRequestStream = Rx.Observable.just(‘https://api.github.com/users');var requestStream = Rx.Observable.merge( requestOnRefreshStream, startupRequestStream);也有另外一种更干净的、不需要中间流的写法:var requestStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; }) .merge(Rx.Observable.just(‘https://api.github.com/users'));甚至再短、再有可读性一点:var requestStream = refreshClickStream .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; }) .startWith(‘https://api.github.com/users');startWith() 会照你猜的那样去工作: 给流一个起点. 无论你的输入流是怎样的, 带 startWith(x) 的输出流总会以 x 作为起点. 但我这样做还不够 [DRY], 我把 API 字符串写了两次. 一种修正的做法是把 startWith() 用在 refreshClickStream 上, 这样可以从"模拟"在页面加载时一次刷新点击事件.var requestStream = refreshClickStream.startWith(‘startup click’) .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });漂亮. 如果你现在回头去看我说 “有个特性没实现” 的那一段, 你应该能看出那里的代码和这里的代码的区别仅仅是多了一个 startWith().使用流来建立"3个推荐关注者"的模型到现在为止, 我们只是写完了一个发生在回复流的 subscribe() 中的 推荐关注者 的 UI. 对于刷新按钮, 我们要解决一个问题: 一旦你点击了"刷新”, 现在的三个推荐关注者仍然没有被清理. 新的推荐关注者只在请求内回复后才能拿到, 不过为了让 UI 看上去令人舒适, 我们需要在刷新按钮被点击的时候就清理当前的推荐关注者.refreshClickStream.subscribe(function() { // 清理 3 个推荐关注者的 DOM 元素});稍等一下. 这样做不太好, 因为这样我们就有两个会影响到推荐关注者的 DOM 元素的 subscriber (另一个是 responseStream.subscribe()), 这听起来不符合 Separation of concerns. 还记得 Reactive 口诀吗?在 “万物皆可为流” 的指导下, 我们把推荐关注者构建为一个流, 其中每个派发出来的值都是一个包含了推荐关注人数据的 JSON 对象. 我们会对三个推荐关注者的数据分别做这件事. 像这样来写:var suggestion1Stream = responseStream .map(function(listUsers) { // 从列表中随机获取一个用户 return listUsers[Math.floor(Math.random()*listUsers.length)]; });至于获取另外两个用户的流, 即 suggestion2Stream 和 suggestion3Stream, 只需要把 suggestion1Stream 复制一遍就行了. 这不够 [DRY], 不过对我们的教程而言, 这样能让我们的示例简单些, 同时我认为, 思考如何在这个场景下避免重复编写 suggestion[N]Stream 也是个好的思维练习, 就留给读者去考虑吧.我们让渲染的过程发生在回复流的 subscribe() 中, 而是这样做:suggestion1Stream.subscribe(function(suggestion) { // 渲染第 1 个推荐关注者});回想之前我们说的 “刷新的时候, 清理推荐关注者”, 我们可以简单地将刷新单击事件映射为 “null” 数据(它代表当前的推荐关注者为空), 并且在 suggestion1Stream 做这项工作, 如下:var suggestion1Stream = responseStream .map(function(listUsers) { // 从列表中随机获取一个用户 return listUsers[Math.floor(Math.random()*listUsers.length)]; }) .merge( refreshClickStream.map(function(){ return null; }) );在渲染的时候, 我们把 null 解释为 “没有数据”, 隐藏它的 UI 元素.suggestion1Stream.subscribe(function(suggestion) { if (suggestion === null) { // 隐藏第 1 个推荐关注者元素 } else { // 显示第 1 个推荐关注者元素并渲染数据 }});整个情景是这样的:refreshClickStream: ———-o——–o—-> requestStream: -r——–r——–r—-> responseStream: —-R———R——R–> suggestion1Stream: —-s—–N—s—-N-s–> suggestion2Stream: —-q—–N—q—-N-q–> suggestion3Stream: —-t—–N—t—-N-t–>其中 N 表示 null(译者注: 注意, 当 refreshClickStream 产生新值, 即用户进行点击时, null 的产生总是立刻发生在 refreshClickStream 之后; 而 refreshClickStream => requestStream => responseStream, responseStream 中的值, 是发给 API 接口的异步请求的结果, 这个结果的产生往往会需要花一点时间, 必然在 null 之后, 因此可以达到 “为了让 UI 看上去令人舒适, 我们需要在刷新按钮被点击的时候就清理当前的推荐关注者” 的效果).稍微完善一下, 我们会在页面启动的时候也会渲染 “空” 推荐关注人. 为此可以 startWith(null) 放在推荐关注人的流里:var suggestion1Stream = responseStream .map(function(listUsers) { // 从列表中随机获取一个用户 return listUsers[Math.floor(Math.random()*listUsers.length)]; }) .merge( refreshClickStream.map(function(){ return null; }) ) .startWith(null);最后我们得到的流:refreshClickStream: ———-o———o—-> requestStream: -r——–r———r—-> responseStream: —-R———-R——R–> suggestion1Stream: -N–s—–N—-s—-N-s–> suggestion2Stream: -N–q—–N—-q—-N-q–> suggestion3Stream: -N–t—–N—-t—-N-t–>关闭推荐关注人, 并利用已缓存的回复数据目前还有一个特性没有实现. 每个推荐关注人格子应该有它自己的 ‘x’ 按钮来关闭它, 然后加载另一个数据来代替. 也许你的第一反应是, 用一种简单方法: 在点击关闭按钮的时候, 发起一个请求, 然后更新这个推荐人:var close1Button = document.querySelector(’.close1’);var close1ClickStream = Rx.Observable.fromEvent(close1Button, ‘click’);// close2Button 和 close3Button 重复此过程var requestStream = refreshClickStream.startWith(‘startup click’) .merge(close1ClickStream) // 把关闭按钮加在这里 .map(function() { var randomOffset = Math.floor(Math.random()500); return ‘https://api.github.com/users?since=' + randomOffset; });然而这没不对. (由于 refreshClickStream 影响了所有的推荐人流, 所以)该过程会关闭并且重新加载_所有的_推荐关注人, 而不是仅更新我们想关掉的那一个. 这里有很多方式来解决这个问题, 为了玩点炫酷的, 我们会重用之前的回复数据中别的推荐人. API 返回的数据每页包含 100 个用户, 但我们每次只用到其中的 3 个, 所以我们有很多有效的刷新数据可以用, 没必要再请求新的.再一次的, 让我们用流的思维来思考. 当一个 ‘close1’点击事件发生的时候, 我们使用 responseStream中 最近被派发的 回复来从回复的用户列表中随机获取一个用户. 如下: requestStream: –r—————> responseStream: ——R———–>close1ClickStream: ————c—–>suggestion1Stream: ——s—–s—–>在 [Rx] 中, 有一个合成器方法叫做 combineLatest, 似乎可以完成我们想做的事情. 它把两个流 A 和 B 作为其输入, 而当其中任何一个派发值的时候, combineLatest 会把两者最近派发的值 a 和 b 按照 c = f(x,y) 的方法合并处理再输出, 其中 f 是你可以定义的方法. 用图来解释也许更清楚:stream A: –a———–e——–i——–>stream B: —–b—-c——–d——-q—-> vvvvvvvv combineLatest(f) vvvvvvv —-AB—AC–EC—ED–ID–IQ—->在该例中, f 是一个转换为全大写的函数我们可以把 combineLatest() 用在 close1ClickStream 和 responseStream 上, 因此一旦 “关闭按钮1” 被点击(导致 close1ClickStream 产生新值), 我们都能得到最新的返回数据, 并在 suggestion1Stream中产生一个新的值. 由于 combineLatest() 的对称性的, 任何时候, 只要 responseStream 派发了一个新的回复, 它也将合并最新的一次 ‘关闭按钮1被点击’ 事件来产生一个新的推荐关注人. 这个特性非常有趣, 因为它允许我们简化我们之前的 suggestion1Stream , 如下:var suggestion1Stream = close1ClickStream .combineLatest(responseStream, function(click, listUsers) { return listUsers[Math.floor(Math.random()*listUsers.length)]; } ) .merge( refreshClickStream.map(function(){ return null; }) ) .startWith(null);在上述思考中, 还有一点东西被遗漏. combineLatest() 使用了两个数据源中最近的数据, 但是如果这些源中的某个从未派发过任何东西, combineLatest() 就不能产生一个数据事件到输出流. 如果你再细看上面的 ASCII 图, 你会发现当第一个流派发 a 的时候, 不会有任何输出. 只有当第二个流派发 b 的时候才能产生一个输出值.有几种方式来解决该问题, 我们仍然采取最简单的一种, 就是在页面启动的时候模拟一次对 ‘关闭按钮1’ 按钮的点击:var suggestion1Stream = close1ClickStream.startWith(‘startup click’) // 把对"关闭按钮1"的点击的模拟加在这里 .combineLatest(responseStream, function(click, listUsers) {l return listUsers[Math.floor(Math.random()*listUsers.length)]; } ) .merge( refreshClickStream.map(function(){ return null; }) ) .startWith(null);总结整理现在我们的工作完成了. 完整的代码如下所示:var refreshButton = document.querySelector(’.refresh’);var refreshClickStream = Rx.Observable.fromEvent(refreshButton, ‘click’);var closeButton1 = document.querySelector(’.close1’);var close1ClickStream = Rx.Observable.fromEvent(closeButton1, ‘click’);// close2 和 close3 是同样的逻辑var requestStream = refreshClickStream.startWith(‘startup click’) .map(function() { var randomOffset = Math.floor(Math.random()*500); return ‘https://api.github.com/users?since=' + randomOffset; });var responseStream = requestStream .flatMap(function (requestUrl) { return Rx.Observable.fromPromise($.ajax({url: requestUrl})); });var suggestion1Stream = close1ClickStream.startWith(‘startup click’) .combineLatest(responseStream, function(click, listUsers) { return listUsers[Math.floor(Math.random()listUsers.length)]; } ) .merge( refreshClickStream.map(function(){ return null; }) ) .startWith(null);// suggestion2Stream 和 suggestion3Stream 是同样的逻辑suggestion1Stream.subscribe(function(suggestion) { if (suggestion === null) { // 隐藏第 1 个推荐关注者元素 } else { // 显示第 1 个推荐关注者元素并渲染数据 }});你可以在这里查看完整的[样例代码]很惭愧, 这只是一个微小的代码示例, 但它的信息量很大: 它着重表现了, 如何对关注点进行适当的隔离, 从而对不同流进行管理, 甚至充分利用了返回数据的流. 这样的函数式风格使得代码像声明式多于像命令式: 我们并不用给出一个要执行的的结构化序列, 我们只是通过定义流之间的关系来表达系统中每件事物是什么. 举例来说, 通过 Rx, 我们告诉计算机 suggestion1Stream 就是 点击关闭按钮1 的流, 与最近一个API返回的(用户中随机选择的一个)的用户的流, 刷新时产生 null 的流, 和应用启动时产生 null 的流的合并流.回想一下那些你熟稔的流程控制的语句(比如 if, for, while), 以及 Javascript 应用中随处可见的基于回调的控制流. (只要你愿意, )你甚至可以在上文的 subscribe() 中不写 if 和 else, 而是(在 observable 上)使用 filter()(这一块我就不写实现细节了, 留给你作为练习). 在 Rx 中, 有很多流处理方法, 比如 map, filter, scan, merge, combineLatest, startWith, 以及非常多用于控制一个事件驱动的程序的流的方法. 这个工具集让你用更少的代码而写出更强大的效果.接下来还有什么?如果你愿意用 [Rx] 来做反应式编程, 请花一些时间来熟悉这个 函数列表, 其中涉及如何变换, 合并和创建 Observables (被观察者). 如果你想以图形的方式理解这些方法, 可以看一下 弹珠图解 RxJava. 一旦你对理解某物有困难的时候, 试着画一画图, 基于图来思考, 看一下函数列表, 再继续思考. 以我的经验, 这样的学习流程非常有用.一旦你熟悉了如何使用 [Rx] 进行变成, 理解冷热酸甜, 想吃就吃…哦不, 冷热 Observables 就很有必要了. 反正就算你跳过了这一节, 你也会回来重新看的, 勿谓言之不预也. 建议通过学习真正的函数式编程来磨练你的技巧, 并且熟悉影响各种议题, 比如"影响 [Rx] 的副作用"什么的.不过, 实现了反应式编程的库并非并非只有 [Rx]. [Bacon.js] 的运行机制就很直观, 理解它不像理解 [Rx] 那么难; [Elm Language] 在特定的应用场景有很强的生命里: 它是一种会编译到 Javascript + HTML + CSS 的反应式编程语言, 它的特色在于 [time travelling debugger]. 这些都很不错.Rx 在严重依赖事件的前端应用中表现优秀. 但它不只是只为客户端应用服务的, 在接近数据库的后端场景中也大有可为. 实际上, [RxJava 正是激活 Netflex 服务端并发能力的关键]. Rx 不是一个严格限于某种特定类型应用的框架或者是语言. 它其实是一种范式, 你可以在任何事件驱动的软件中实践它.本文作者:richardo2016阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 6, 2018 · 8 min · jiezi

程序员进阶之算法练习:LeetCode专场

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦本文由落影发表前言LeetCode上的题目是大公司面试常见的算法题,今天的目标是拿下5道算法题: 题目1是基于链表的大数加法,既考察基本数据结构的了解,又考察在处理加法过程中的边界处理; 题目2是求数组出现频率前k大的数字,考察思维能力,代码很短; 题目3是给出从两个数组中选择数字,组成一个最大的数字,考察的是贪心的思想; 前三个都偏向于考察想法,实现的代码都比较简单; 题目4、5是数据结构实现题,也是大部分人比较头疼的题目,因为需要较多的数据结构和STL实现,并且还有时间和空间的限制。正文1、Add Two Numbers II题目链接 题目大意:给俩个链表,节点由09的数字组成,分别表示两个数字; 求出两个数字的和,以链表的形式返回。例如Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)7243 + 564 =7807Output: 7 -> 8 -> 0 -> 7题目解析: 题目的意思很明显,就是把两个数字加起来,需要考虑进位的情况。 因为是单向的链表,遍历后很难回溯,所以先把数字存到vec中。 并且为了处理方便,vec的最低位存在vec的起始部分。 于是从0开始遍历两个vec即可,注意考虑最后进位的情况。复杂度解析: 时间复杂度是O(N) 空间复杂度是O(N)struct ListNode { int val; ListNode next; ListNode(int x) : val(x), next(NULL) {}};class Solution {public: ListNode addTwoNumbers(ListNode* l1, ListNode* l2) { ListNode ret = NULL; vector<int> vec1, vec2; sum(l1, vec1); sum(l2, vec2); int n = vec1.size(), m = vec2.size(), flag = 0; for (int i = 0; i < n || i < m; ++i) { int x = 0, y = 0; if (i < n) { x = vec1[i]; } if (i < m) { y = vec2[i]; } int s = x + y + flag; if (s > 9) { s -= 10; flag = 1; } else { flag = 0; } ListNode tmp = new ListNode(s); tmp->next = ret; ret = tmp; } if (flag) { ListNode tmp = new ListNode(1); tmp->next = ret; ret = tmp; } return ret; } void sum(ListNode list, vector<int> &vec) { if (list->next) { sum(list->next, vec); } vec.push_back(list->val); }};2.Top K Frequent Elements题目链接 题目大意:给出一个数组和一个数字k,返回按数字出现频率的前k个的数字; 1 <= k <= n, n是数组大小; example, Given [1,1,1,2,2,3] and k = 2, return [1,2].题目解析:题目分为两个步骤: 1、统计每个数字的出现次数; 2、从中选择k个次数最多的数字;一个简单的做法: 用哈希表统计每个数字的出现次数; 把每个数字的出现次数和数字组成一个pair,放入优先队列;这样从优先队列中取出k个即可。复杂度解析: 时间复杂度是O(NlogN),主要在最后的优先队列。其他解法: 有一个O(NlogK)的优化; 首先把队列变成最小有限队列, 每次pair放入优先对时,如果当前的size大于k,那么弹出top; 这样每次的操作从O(logN)变成O(logK)。class Solution {public: vector<int> topKFrequent(vector<int>& nums, int k) { unordered_map<int, int> numsHash; for (int i = 0; i < nums.size(); ++i) { ++numsHash[nums[i]]; } priority_queue<pair<int, int>> q; for (int i = 0; i < nums.size(); ++i) { if(numsHash[nums[i]]) { q.push(make_pair(numsHash[nums[i]], nums[i])); numsHash[nums[i]] = 0; } } vector<int> ret; for (int i = 0; i < k; ++i) { ret.push_back(q.top().second); q.pop(); } return ret; }}leetcode;3、create-maximum-number题目链接 题目大意: 给出两个数组,数组只包括0~9十个数字,长度分别为n、m; 从两个数组中选出k个数,组成一个长度为k的数字,要求: 1、从数组n、m选择出来的数字相对位置不变; 2、最后的数字最大; 输出最后的数字。 Example 1: nums1 = [3, 4, 6, 5] nums2 = [9, 1, 2, 5, 8, 3] k = 5 return [9, 8, 6, 5, 3] Example 2: nums1 = [6, 7] nums2 = [6, 0, 4] k = 5 return [6, 7, 6, 0, 4]题目解析:要求最后数字最大,那么尽可能把数字大的排在前面; 在都合法的前提下,99 肯定比 98要大; 那么可以按照这样的贪心策略: 先枚举t,t表示从数组nums1中选出t个数字,那么数组nums2中应该选出k-t个数字; 两个数组的所有数字组成最大的数字,因为两个数组间的数字是可以任意顺序,那么只需每次选择较大的放在前面即可。问题简化成,O(N)每次从数组中选出t个最大的数字; 这个可以用贪心解决: 假设数组当前枚举到第i个,且nums[i]=x; 从左到右遍历已经选择的数,当遇到一个数字t,t<x时,判断插入x后,后续是否存在合法解;如果存在则替换,否则直到最后,插入尾部;class Solution {public: vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k) { int n = (int)nums1.size(), m = (int)nums2.size(); vector<int> ret(k, 0); for (int i = max(0, k - m); i <= k && i <= n; ++i) { vector<int> tmp1 = maxArray(nums1, i); vector<int> tmp2 = maxArray(nums2, k - i); vector<int> tmp = merge(tmp1, tmp2, k); if (greater(tmp, 0, ret, 0)) { ret = tmp; } } return ret; } vector<int> maxArray(vector<int> &nums, int k) { int n = (int)nums.size(); vector<int> ret(k, 0); for (int i = 0, j = 0; i < n; ++i) { while (n - i + j > k && j > 0 && ret[j - 1] < nums[i]) { –j; } if (j < k) { ret[j++] = nums[i]; } } return ret; } vector<int> merge(vector<int>& nums1, vector<int>& nums2, int k) { vector<int> ret(k, 0); for (int i = 0, j = 0, r = 0; r < k; ++r) { ret[r] = greater(nums1, i, nums2, j) ? nums1[i++] : nums2[j++]; } return ret; } bool greater(vector<int> &nums1, int i, vector<int> &nums2, int j) { while (i < nums1.size() && j < nums2.size() && nums1[i] == nums2[j]) { ++i; ++j; } return j == nums2.size() || (i < nums1.size() && nums1[i] > nums2[j]); }};4、 Insert Delete GetRandom O(1) - Duplicates allowed题目链接 题目大意: 实现一个数据结构,包括以下三个方法: 1、insert(val): 插入一个数字; 2、remove(val): 移除一个数字; 3、getRandom: O(1)随机返回一个数字; Example 插入数字1; collection.insert(1); 插入数字1: collection.insert(1); 插入数字2 collection.insert(2); 随机返回数字,要求 2/3可能返回1, 1/3可能返回2; collection.getRandom();题目解析:插入和移除数字不麻烦,考虑如何在O(1)时间返回一个数字。 容易知道,放在数组里面可以,然后随机返回一个位置可以实现。 增加可以在数组最末端增加; 删除数组中间某个数字时,可以把最末端的数字放到删除的位置上;现在的问题是,如何快速找到数组中该删除的某个位置; 考虑用hash来实现。 数组就是vector<pair<int, int> >; first存val,second存出现次数; 再用一个哈希map,unordered_map<int, vector<int>> 里面存对应数字出现的位置;class RandomizedCollection {public: / Initialize your data structure here. / RandomizedCollection() { } /* Inserts a value to the collection. Returns true if the collection did not already contain the specified element. / bool insert(int val) { bool ret = hashMap.find(val) == hashMap.end(); hashMap[val].push_back(randVec.size()); randVec.push_back(make_pair(val, hashMap[val].size() - 1)); return ret; } /* Removes a value from the collection. Returns true if the collection contained the specified element. / bool remove(int val) { bool ret = hashMap.find(val) != hashMap.end(); if (ret) { auto last = randVec.back(); hashMap[last.first][last.second] = hashMap[val].back(); randVec[hashMap[val].back()] = last; hashMap[val].pop_back(); if (hashMap[val].empty()) { hashMap.erase(val); } randVec.pop_back(); } return ret; } /* Get a random element from the collection. / int getRandom() { return randVec[rand() % randVec.size()].first; } private: unordered_map<int, vector<int>> hashMap; vector<pair<int, int>> randVec;}leetcode;5、 All O`one Data Structure题目链接 题目大意:实现一个数据结构,要求: 1、Inc(Key) - Inserts a new key with value 1. Or increments an existing key by 1. Key is guaranteed to be a non-empty string. 2、Dec(Key) - If Key’s value is 1, remove it from the data structure. Otherwise decrements an existing key by 1. If the key does not exist, this function does nothing. Key is guaranteed to be a non-empty string. 3、GetMaxKey() - Returns one of the keys with maximal value. If no element exists, return an empty string “”. 4、GetMinKey() - Returns one of the keys with minimal value. If no element exists, return an empty string “".要求所有的数据结构的时间复杂度是O(1);题目解析:在不考虑复杂度的前提下,朴素做法是遍历,O(N); 简单的优化,用map来维护优先队列,操作1、2先获取key值,更新完重新插入;操作3、4直接拿队列top;每个操作的复杂度是O(LogN);题目要求是O(1),那么必然不能使用树类型的结构,应该利用题目特性,配合hash以及贪心来实现。假设有一个key-hash表,来存key的对应值。 操作1、先看keyHash里面是否有key,有则+1,无则插入; 操作2、先看keyHash里面是否有key,有则-1,无则Nothing;为了维护最值,引入链表list,里面所有的元素是从小到大;每个元素是一个桶,桶里放着值相同的key; 操作3、直接获取list头元素的值; 操作4、直接获取list尾元素的值;同时,操作1、2在操作的过程中,需要把当前key值从list对应的桶里移除,放到上一个或者下一个桶里,或者丢弃。 为了实现O(1)获取key所在位置,可以用iter-hash来维护key所对应元素的迭代器。struct Bucket { int value; unordered_set<string> keys;};class AllOne {public: list<Bucket> buckets; unordered_map<string, list<Bucket>::iterator> bucketOfKey; /* Initialize your data structure here. / AllOne() { } /* Inserts a new key <Key> with value 1. Or increments an existing key by 1. / void inc(string key) { if (bucketOfKey.find(key) == bucketOfKey.end()) { bucketOfKey[key] = buckets.insert(buckets.begin(), {0, {key}}); } auto next = bucketOfKey[key], bucket = next++; if (next == buckets.end() || next->value > bucket->value + 1) { next = buckets.insert(next, {bucket->value+1, {}}); } next->keys.insert(key); bucketOfKey[key] = next; bucket->keys.erase(key); if (bucket->keys.empty()) { buckets.erase(bucket); } } /* Decrements an existing key by 1. If Key’s value is 1, remove it from the data structure. / void dec(string key) { if (bucketOfKey.find(key) == bucketOfKey.end()) { return ; } auto pre = bucketOfKey[key], bucket = pre; if (pre != buckets.begin()) { –pre; } bucketOfKey.erase(key); if (bucket->value > 1) { if (bucket == buckets.begin() || pre->value < bucket->value - 1) { pre = buckets.insert(bucket, {bucket->value - 1, {}}); } pre->keys.insert(key); bucketOfKey[key] = pre; } bucket->keys.erase(key); if (bucket->keys.empty()) { buckets.erase(bucket); } } /* Returns one of the keys with maximal value. / string getMaxKey() { return buckets.empty() ? "” : (buckets.rbegin()->keys.begin()); } / Returns one of the keys with Minimal value. */ string getMinKey() { return buckets.empty() ? "" : *(buckets.begin()->keys.begin()); }}leetcode;总结这5个题目如果都能独立完成,那么水平已经可以足以应付国内各大企业的算法面。 算法重在勤思多练,埋怨公司出算法题是没用的,多花时间准备才是正道。此文已由作者授权腾讯云+社区发布,更多原文请点击搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包! ...

November 26, 2018 · 5 min · jiezi

学习编程并不是学习编程语言

作者:zooboole英文原文:《Learning programming is different from learning a programming language》我们都是程序员,也是学习者。令人惊讶的是,如此多的人以为自己在学习编程,却已经步入歧途。你可能正在学习编程语言,而不是编程本身大家都知道计算机科学不是研究计算机,它反倒是利用计算机研究自动解决问题的。问题解决是计算机科学,不是编程。这就许多计算机科学专业的学生似乎不理解他们为什么要学习算法或数学的原因。如果你以前上过计算机科学课,你就应该知道我在说什么。因为你会注意到编程与编程语言几乎没有关系。问问自己为什么伪代码在这些课程中如此常见。但是,大多数自以为是的程序员总是落入陷阱。在意识到进行编程时到底什么是应该要做的之前,我们学习了几十年的编程语言。我自己也是受害者。我花了十多年的时间一点一点地学习各种编程语言。我学的越多,就越难以简单的方式解决问题。我以为是没有找到合适的工具。但问题是,当我甚至还不知道这个工作要做什么时,就去寻找合适的工具,而忘记了找出真正的工作是该做什么。编程语言的奇怪之处在于它们总是在不断发展。编程语言几乎每天都在变化,跟进很难。而大多数优秀的程序只使用了编程语言的一小部分。首先,学习编程语言的问题就像在学习木工之前学习如何使用木工锯,锤子和各种切割机器。木工需要注意:想法,可行性分析,测量,测试,客户行为。资深木匠感兴趣的事物不止于锤子和钉子。在他对这项工作的研究中,还需要时间来检查钉子、着色剂、木材等的质量。学习编程和学习编程语言的区别是什么呢?编程是通过一次下达指令来设置一个系统自动运行。我们每天都这样做。我们教我们的孩子,命令我们的士兵,服务我们的客户。我们给予或收到指示,以自由/独立的方式生活。父母不需要跟随并指导你在生活中所做的每一个动作。他们可能已经在生活的许多方面为你编程了。大多数学校和教学网站都会教授编程语言的语法。他们可以添加一些设计模式(当你忽略究竟是什么设计时)、一些算术计算。教你如何声明变量以及如何使用它们;教你如何声明数据类型以及创建它们。这并不能教你推理。但后来,您将会遇见推理方法。使用那些方法来学习,会让你觉得是浪费生命或者花了很多时间来学习编程。我们用编程来解决问题,编程语言是帮助我们达到目的工具。它们就象工具箱,我们称之为框架,帮助你组织你的思维。如果你正在学习编程且仍然无法设计和编写真实应用程序,那么这就意味着你正在学习编程语言而不是编程。我们经常会遇到想知道如何创建程序的学习者。对于程序员来说,程序是一个问题求解。在使用任何编程语言之前,他通过关键分析解决了问题。当你解决任何问题时,你可以用任何编程语言来编码。我们来看看平方求解的案例。为了求解平方,我们将它与自己相乘。我们可以用各种语言实现它,例如:C语言function square(int * x) { return x * x;}PHP语言function square ($x){ return $x * $x;}Javascript语言function square(x){ return x * x}Scheme(a Lisp dialect)语言(define (square x) (* x x))您应该注意到实现中只有语法是不一样的,解决方案是一样的。这也是你几乎可以使用任何编程语言的主要原因之一,在这种语言中你可以更轻松地构建任何类型的软件。编程可以让你更容易理解一门语言通常,问题出在人类语言,它充满了局限和错误。人类语言不能用来指令机器,因为它们不理解。你学习编程时,是在学习一种新术语和工具,来帮助你以计算机或其他程序员可以理解和同意的方式编写逻辑。通常,你将从简单且类似人类语言的符号–伪代码开始。它是从人类语言到计算机编程语言的良好过渡工具。这通常是为了避免浪费时间在具体的编程语言上,这样你可以完全专注于推理。通过它,你将发现构成良好编程工具(语言)的核心部分。你知道了真正需要的是什么、掌握了编程语言的核心目标。在编程实践过程中,你会不知不觉地就学会了这门编程语言。相关文章如何学习一门计算机编程语言

November 20, 2018 · 1 min · jiezi

理解 React Hooks

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由志航发表于云+社区专栏TL;DR一句话总结 React Hooks 就是在 react 函数组件中,也可以使用类组件(classes components)的 state 和 组件生命周期,而不需要在 mixin、 函数组件、HOC组件和 render props 之间来回切换,使得函数组件的功能更加实在,更加方便我们在业务中实现业务逻辑代码的分离和组件的复用。本文将从以下几个方面介绍 hooksHooks 在解决什么问题 Hooks 的 api 介绍 和如何使用 hooks Hooks 是怎么实现的????Hooks 在解决什么问题React 一直在解决一个问题,如何实现分离业务逻辑代码,实现组件内部相关业务逻辑的复用。一般情况下,我们都是通过组件和自上而下传递的数据流将我们页面上的大型UI组织成为独立的小型UI,实现组件的重用。但是我们经常遇到很难侵入一个复杂的组件中实现重用,因为组件的逻辑是有状态的,无法提取到函数组件当中。这在处理动画和表单的时候,尤其常见,当我们在组件中连接外部的数据源,然后希望在组件中执行更多其他的操作的时候,我们就会把组件搞得特别糟糕:难以重用和共享组件中的与状态相关的逻辑,造成产生很多巨大的组件逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 localstate 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面。复杂的模式,如渲染道具和高阶组件。由于业务变动,函数组件不得不改为类组件。这时候,Hooks就派上用场了。 Hooks 允许我们将组件内部的逻辑,组织成为一个可复用的隔离模块。借用 @Sunil Pai 的两张图来说明这个问题:image.pngimage.png从 React Hooks 中体验出来的是 React 的哲学在组件内部的实现,以前我们只在组件和组件直接体现 React 的哲学,就是清晰明确的数据流和组成形式。既可以复用组件内的逻辑,也不会出现 HOC 带来的层层嵌套,更加不会出现 Mixin 的弊端。????Hooks 的 api 介绍 和如何使用 hooks@dan_abramov 在会议上给我们介绍了 hooks 的三个关键的api,分别是 State Hooks 、 Effect Hooks 、 Custom Hooks(自定义hooks)????state Hooks (useState)useState 这个方法可以为我们的函数组件带来 local state,它接收一个用于初始 state 的值,返回一对变量。 让函数组件拥有自己的组件。首先如果我们需要用 classes component 实现一个点击按钮 +1 组件应该怎么写呢?import React from ‘react’;class Example extends React.Component { constructor(props) { super(props); this.state = {count: 0}; this.clickBtn = this.clickBtn.bind(this); } clickBtn = () => { this.setState({ count: this.state.count + 1; }); } return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={this.clickBtn}> Click me </button> </div> );}那使用 useState 是怎么样的呢? 可以看见非常清晰明了。// 一个简单的点击计数import { useState } from ‘react’;function Example() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> );}????Effect Hooks (useEffect)Effect Hooks 用于处理一些带有副作用的操作,下面通过监听窗口宽度的变化代码为例,说明 effect hooks 的使用fangfaimport { useState } from ‘react’;function windowWidth() { const [width, setWithd] = useState(window.innerWidth); useEffect(() => { const handleResize = ()=>{ setWidth(window.innerWidth); } window.addEventListener(‘resize’, handleResize); }); return ( <p> window width is {width}</p> )}useEffect 可以传入第二个操作来避免性能的损耗,如果第二个参数数组中的成员变量没有变化则会跳过此次改变。如何传入一个空数组 ,那么该 effect 只会在组件 mount 和 unmount 时期执行。import { useState } from ‘react’;function windowWidth() { const [width, setWithd] = useState(window.innerWidth); useEffect(() => { const handleResize = ()=>{ setWidth(window.innerWidth); } window.addEventListener(‘resize’, handleResize); }, [width]); // width 没有变化则不处理 return ( <p> window width is {width}</p> )}useEffect 中还可以通过让函数返回一个函数来进行一些取消兼容之类的清理操作,比如取消订阅等import { useState } from ‘react’;function windowWidth() { const [width, setWithd] = useState(window.innerWidth); useEffect(() => { const handleResize = ()=>{ setWidth(window.innerWidth); } window.addEventListener(‘resize’, handleResize); return () => { // 取消监听窗口的宽度变化 window.removeEventListener(‘resize’); } }); return ( <p> window width is {width}</p> )}如上所示,内置的 React Hooks 如 useState 和 useEffect 充当基本构建块。 我们可以直接在组件中使用它们,或者我们可以将它们组合到自定义Hook中,例如useWindowWidth。使用自定义Hooks感觉就像使用React的内置API一样。????Custom Hooks 自定义组件接着上面的监听窗口大小的代码,我们接着讲自定义 hooks, 证明 react hooks 是怎么使到组件内的逻辑可复用的。Talk is cheap, show me the code.// 一个显示目前窗口大小的组件function responsiveComponent(){ // custom hooks const width = useWindowWidth(); return ( <p>当前窗口的宽度是 {width}</p> )}上面的代码只有几行,非常清晰明了说明了他的作用就是监听当前窗口的变化,这就是Hooks的目标 - 使组件真正具有声明性,即使它们包含状态和副作用。我们来看看如何实现这个自定义Hook。我们使用React本地状态来保持当前窗口宽度,并在窗口调整大小时使用副作用来设置该状态import { useState, useEffect} from ‘react’;// custom hooks to listen window width changefunction useWindowWidth(){ const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = ()=>{ setWidth(window.innerWidth); } window.addEventListener(‘resize’, handleResize); }, [width]); // width 没有变化则不处理 return width;}[在线编辑例子]⚡ React Hooks 的规则Hooks 是JavaScript函数,但它们强加了两个额外的规则:只能在顶层调用Hooks。不要在循环,条件或嵌套函数中调用Hook。仅从React功能组件调用Hooks。不要从常规JavaScript函数中调用Hook。 (还有另一个地方可以调用Hooks——你自己的定制Hooks。)???? 其他 Hooks这里有一些不常用的内置Hook。例如,useContext允许您订阅React上下文而不引入嵌套:function Example() { const locale = useContext(LocaleContext); const theme = useContext(ThemeContext); // …}发现一个很有趣的仓库,react-use, 包含了很多很有趣的自定义hooks????hooks 是如何工作的以下内容翻译自 react-hooks-not-magic-just-arrays.react hooks 其实只是一个数组,并不是奇妙的魔法。如何实现 useState() 方法让我们在这里通过一个例子来演示状态 hooks 的实现如何工作。首先让我们从一个组件开始:function RenderFunctionComponent() { const [firstName, setFirstName] = useState(“Rudi”); const [lastName, setLastName] = useState(“Yardley”); return ( <Button onClick={() => setFirstName(“Fred”)}>Fred</Button> );}hooks API背后的想法是你可以使用一个setter函数作为hook函数中的第二个数组项返回,而setter将控制由hook管理的状态。那么React与此有什么关系呢?让我们了解这在React内部如何工作。 以下内容可在执行上下文中用于呈现特定组件。 这意味着此处存储的数据位于正在渲染的组件之外。 此状态不与其他组件共享,但它保留在可以随后渲染特定组件的范围内。1)初始化创建两个空数组:setters和state将光标设置为 0 image.png初始化:两个空数组,Cursor为02) 首次渲染首次运行组件功能。每次useState()调用,当在第一次运行时,将setter函数(绑定到光标位置)推送到setter数组,然后将某个状态推送到state数组。image.png第一次渲染:作为光标增量写入数组的项目。3) 后续渲染每个后续渲染都会重置光标,并且只从每个数组中读取这些值。image.png后续渲染:从数组中读取的项目为光标增量4) 事件处理每个setter都有一个对它的光标位置的引用,因此通过触发对任何setter的调用,它将改变状态数组中该位置的状态值。image.pngSetters“记住”他们的索引并根据它设置内存。通过伪代码实现 useState 功能这是一个演示实现的代码示例:let state = [];let setters = [];let firstRun = true;let cursor = 0;function createSetter(cursor) { return function setterWithCursor(newVal) { state[cursor] = newVal; };}// useState的伪代码实现export function useState(initVal) { if (firstRun) { state.push(initVal); setters.push(createSetter(cursor)); firstRun = false; } const setter = setters[cursor]; const value = state[cursor]; cursor++; return [value, setter];}// 模拟使用useStatefunction RenderFunctionComponent() { const [firstName, setFirstName] = useState(“Rudi”); // cursor: 0 const [lastName, setLastName] = useState(“Yardley”); // cursor: 1 return ( <div> <Button onClick={() => setFirstName(“Richard”)}>Richard</Button> <Button onClick={() => setFirstName(“Fred”)}>Fred</Button> </div> );}// 模拟Reacts渲染周期function MyComponent() { cursor = 0; // 重置光标的位置 return <RenderFunctionComponent />; // render}console.log(state); // Pre-render: []MyComponent();console.log(state); // 首次渲染: [‘Rudi’, ‘Yardley’]MyComponent();console.log(state); // 后续渲染: [‘Rudi’, ‘Yardley’]// 点击’Fred’ 按钮 console.log(state); // 点击后: [‘Fred’, ‘Yardley’]总结Hooks 还处于早期阶段,但是给我们复用组件的逻辑提供了一个很好的思路,大家可以在 react-16.7.0-alpha.0 中体验。相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

November 9, 2018 · 3 min · jiezi

大佬带你深入浅出Lua虚拟机

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦本文由鹅厂优文发表于云+社区专栏作者:郑小辉 | 腾讯 游戏客户端开发高级工程师写在前面:本文所有的文字都是我手工一个一个敲的,以及本文后面分享的Demo代码都是我一行一行码的,在我之前已经有非常多的前辈研究过Lua虚拟机了,所以本文很多思想必然是踏在这些巨人的肩膀上的。 本文标题是”深入浅出Lua虚拟机”,其实重点在浅出这两字上。毕竟作者的技术水平有限。但是听说名字要起的屌一点文章才有人看,故而得名。 谨以此文奉献给那些对Lua虚拟机有兴趣的人。希望本文能达到一个抛砖引玉的效果。Lua的执行流程:Lua代码的整个流程:如下图所示:程序员编码lua文件->语法词法分析生成Lua的字节码文件(对应Lua工具链的Luac.exe)->Lua虚拟机解析字节码,并执行其中的指令集->输出结果。蓝色和绿色的部分是本文所试图去讲的内容。词法语法分析: 我不准备讲Lua的所有词法分析过程,毕竟如果浪费太多时间来写这个的话一会策划同学要提刀来问我需求的开发进度如何了,所以长话短说,我就根据自己对Lua的理解,以某一个具体的例子来做分析: Lua代码块: If a < b then a = c end 这句话咱们程序员能看懂,可是计算机就跟某些男程序员家里负责貌美如花的老婆一样,只知道这是一串用英文字符拼出来的一行没有任何意义的字符串而已。 为了让计算机能够读懂这句话,那么我们要做的第一件事情就是分词:既然你看不懂。我就先把一句话拆成一个一个单词,而且我告诉你每个单词的含义是什么。 分词的结果大概长下面这样: 分词结果 类型(意义) if Type_If (if 关键字) a Type_Var (这是一个变量) < Type_OpLess(这是一个小于号) b Type_Var(这是一个变量) then Type_Then(Then关键字) a Type_Var (这是一个变量) = Type_OpEqual(这是一个等号) c Type_Var(这是一个变量) end Type_End(End关键字) 好了。现在计算机终于明白了。原来你写的这行代码里面有9个字,而且每个字的意思我都懂了。所以现在问题是,计算机理解了这句话了吗? 计算机依然不理解。就好像“吃饭”这句话,计算机理解了 “吃”是动词,张开嘴巴的意思。“饭”是名词,指的米饭的意思。但是你把吃饭放在一起,计算机并不知道这是“张开嘴巴,把饭放进嘴里,并且咽到胃里”的意思。因为计算机只知道“张开嘴巴”和“米饭”两件事,这两件事有什么联系,计算机并不能理解。有人会说了:简单:吃+其他字 这种结构就让计算机笼统的理解为把后一个词代表的东西放进嘴巴里的意思就好了啊?这种情况适合”吃饭”这个词,但是如果这样你让计算机怎么理解“吃惊”这个词呢?所以这里引出下一个话题:语义解析。 关于语义解析这块,如果大家想要了解的更深入,可以去了解一下AST(抽象语法树)。然而对于我们这个例子,我们用简单的方式模拟着去理解就好了。 对于Lua而言,每一个关键字都有自己特别的结构。所以Lua的关键字将成为语义解析的重点。我们现在涉及到的if这个例子:我们可以简单的用伪代码表述这个解析过程: 对于if语句我们可以抽象成这种结构: If condition(条件表达式) then dosth(语句块) end 所以对if语句块进行解析的伪代码如下: ReadTokenWord(); If(tokenWord.type == Type_If) then ReadCondition() //读取条件表达式 ReadThen() //读取关键字then ReadCodeBlock() //读取逻辑代码块 ReadEnd() //读取关键字End End所以为了让计算机理解,我们还是得把这个东西变成数据结构。 因为我只是做一个Demo而已,所以我用了先验知识。也就是我假定我们的If语句块逻辑结构是这样的: If 小于条件表达式 then 赋值表达式 End 所以在我的Demo里转成C++数据结构就是IfStateMent大概是这样: OK,所以现在,我们整个词法语法分析都做完了。但是真正的Lua虚拟机并不能执行我们的ifStateMent这种东西。Lua源码里的实现也是类似这种TokenType 和 结构化的 if Statement whileStatement等等,并且Lua没有生成完整的语法树。Lua源码的实现里面,它是解析一些语句,生成临时的语法树,然后翻译成指令集的。并不会等所有的语句都解析完了再翻译的。语义解析和翻译成指令集是并行的一个过程。贴一个源码里面关于语义解析的部分实现: OK,现在咱们已经把我们程序员输入的Lua代码变成了一个数据结构(计算机能读懂)。下一步我们要把这个数据结构再变成Lua虚拟机能认识的东西,这个东西就是 Lua 指令集! 至于转换的过程,对于我们这个例子,大概是这样的: If a < b then a = c end 先理解条件 a<b:一种基于寄存器的指令设计大概是这样的: a,b均为变量。假定我们的可用的寄存器索引值从10(0-9号寄存器都已经被占用了)开始:又假定我们有一个常量索引表:0号常量:字符’a’,1号常量:字符串’b’。那么a<b可以被翻译为这样:LoadK 10,0 :将_G[ConstVar[0]]载入10号寄存器: R[10] = _G[“a”]LoadK 11,1 :将_G[ConstVar[1]]载入11号寄存器: R[11] = _G[“b”]LT 10,11 : 比较R[10]<R[11]是否成立,如果成立,则跳过下一条指令(++PC),否则执行下一条指令。LT后面跟着的一条指令必然是JMP指令。就是如果R[10]<R[11]成立,则不执行JMP,直接执行JMP后面的一条指令(a=c的语句块对应的指令集),否则直接跳过下面的一个语句块(跳过a=c的赋值过程)。 同理,继续进行a=c的翻译等等。 所以If a < b then a = c end在我写的demo里面最后被翻译成了: OK,我们现在大概明白了从Lua代码怎么变成指令集的这件事了。 现在我们来具体看一下Lua5.1的指令集: Lua的指令集是定长的,每一条指令都是32位,其中大概长这样: 每一条指令的低六位 都是指令的指令码,比如 0代表MOVE,12代表Add。Lua总共有37条指令,分别是MOVE,LOADK,LOADBOOL,LOADNIL,GETUPVAL,GETGLOBAL,GETTABLE,SETGLOBAL,SETUPVAL,SETTABLE,NEWTABLE,SELF,ADD,SUB,MUL,DIV,MOD,POW,UNM,NOT,LEN,CONCAT,JMP,EQ,LT,LE,TEST,TESTSET,CALL,TAILCALL,RETURN,FORLOOP,TFORLOOP,SETLIST,CLOSE,CLOSURE,VARARG. 我们发现图上还有iABC,iABx,iAsBx。这个意思是有的指令格式是 OPCODE,A,B,C的格式,有的指令是OPCODE A,BX格式,有的是OPCODE A,sBX格式。sBx和bx的区别是bx是一个无符号整数,而sbx表示的是一个有符号的数,也就是sbx可以是负数。 我不打算详细的讲每一条指令,我还是举个例子: 指令编码 0x 00004041 这条指令怎么解析: 0x4041 = 0000 0000 0000 0000 0100 0000 0100 0001 低六位(05)是opcode:000001 = 1 = LoadK指令(037分别对应了我上面列的38条指令,按顺序来的,0是Move,1是loadk,2是loadbool…..37是vararg)。LoadK指令格式是iABC(C没用上,仅ab有用)格式。所以我们再继续读ab。 a = 低613位 为 00000001 = 1所以a=1 b = 低1422位 为000000001 = 1所以b=1 所以0x4041 = LOADK 1, 1 指令码如何解析我也在demo里面写了,代码大概是这样: 那么Lua文件经过Luac的编译后生成的Lua字节码,Lua字节码文件里面除了包含指令集之外又有哪些东西呢?当然不会像我上面的那个词法语法解析那个demo那么弱智拉。所以下面我们就讲一下Lua字节码文件的结构: Lua字节码文件(*.lua.bytes)包含了:文件头+顶层函数: 文件头结构:顶层函数和其他普通函数都拥有同样的结构: 所以我们是可以轻松自己写代码去解析的。后文提供的Demo源码里面我也已经实现了字节码文件的解析。Demo中的例子是涉及到的Lua源代码以及最终解析字节码得到的信息分别是: OK,本文现在就剩最后一点点东西了:Lua虚拟机是怎么执行这些指令的呢? 大概是这样的: While(指令不为空) 执行指令 取下一条要执行的指令 End 每一条指令应该怎么执行呢???如果大家还有印象的话,咱们前文语义解析完之后转指令集是这样的:a < bLoadK 10,0 :将_G[ConstVar[0]]载入10号寄存器: R[10] = _G[“a”]LoadK 11,1 :将_G[ConstVar[1]]载入11号寄存器: R[11] = _G[“b”]LT 10,11 : 比较R[10]<R[11]是否成立,如果成立,则跳过下一条指令(++PC),否则执行下一条指令。LT后面跟着的一条指令必然是JMP指令。就是如果R[10]<R[11]成立,则不执行JMP,直接执行JMP后面的一条指令(a=c的语句块),否则直接跳过下面的一个语句块(跳过a=c的赋值过程)。那当然是指令后面的文字就已经详细的描述了指令的执行逻辑拉,嘿嘿。为了真正的执行起来,所以我们在数据结构上设计需要 1,寄存器:2,常量表:3,全局变量表:为了能执行我们demo里面的例子:我实现了这段代码涉及到的所有指令insExecute[(int)OP_LOADK] = &LuaVM::LoadK;insExecute[(int)OP_SETGLOBAL] = &LuaVM::SetGlobal;insExecute[(int)OP_GETGLOBAL] = &LuaVM::GetGlobal;insExecute[(int)OP_ADD] = &LuaVM::_Add;insExecute[(int)OP_SUB] = &LuaVM::_Sub;insExecute[(int)OP_MUL] = &LuaVM::_Mul;insExecute[(int)OP_DIV] = &LuaVM::_Div;insExecute[(int)OP_CALL] = &LuaVM::_Call;insExecute[(int)OP_MOD] = &LuaVM::_Mod;insExecute[(int)OP_LT] = &LuaVM::_LT;insExecute[(int)OP_JMP] = &LuaVM::_JMP;insExecute[(int)OP_RETURN] = &LuaVM::_Return;以Add为例:bool LuaVM::_Add(LuaInstrunction ins){ //R(A):=RK(B)+RK(C) ::: //Todo:必要的参数合法性检查:如果有问题则抛异常 // 将ins.bValue代表的数据和ins.cValue代表的数据相加的结果赋值给索引值为ins.aValue的寄存器 luaRegisters[ins.aValue].SetValue(0, GetBK(ins.bValue) + GetBK(ins.cValue)); return true;}下面是程序的运行效果截图: 看完整个过程,其实可以思考这个问题:为什么Lua执行效率会远远低于C程序? 个人愚见: 1,真假寄存器:Lua指令集涉及到的寄存器是模拟的寄存器,其实质还是内存上的一个数据。访问速度取决于CPU对内存的访问速度。而C程序最后可以用win32指令集or Arm指令集来执行。这里面涉及到的寄存器EBX,ESP等都是CPU上面的与非门,其访问速度=CPU的频率(和cpu访问内存的速度对比简直一个天上一个地上)。 2,指令集运行的平台:Lua指令集运行的平台是Lua虚拟机。而C程序指令集运行的直接是硬件支持的。 3,C里面的数据直接对应的就是内存地址。而Lua里面的数据对应的是一个描述这个数据的数据结构。所以隔了这么一层,效率也大打折扣了。 4,比如Lua的Gc操作等等这些东西都是C程序不需要去做的。。。。 OK,最后献上我写的这个demo的源代码:这份源代码是我在清明节在家的时候瞎写的。也就是说代码并没有经过耐心的整理,而且清明节有人找我出去喝酒,导致我有很长一段时间都处于“我艹快点码完我要出去喝了”这种心不在焉的状态,所以有些编码格式和结构设计都处处能看到随性的例子毕竟只是一个demo嘛。人生在世,要有佛性,随缘就好!如果各位真的想进一步理解关于Lua虚拟机的东西,那么我推荐诸位有空耐着性子去读一读Lua虚拟机的源代码~ 最后,诚挚感谢所有看到了最后这句话的同学。谢谢你们耐着性子看完了一个技术菜鸡的长篇废话。Demo.zip问答Lua支持Unicode吗?相关阅读Lua 性能剖析使用lua小技巧Lua 游戏开发学习 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

October 25, 2018 · 2 min · jiezi