最近发布的 Elasticsearch 6.3 包含了大家期待已久的 SQL 特性,今天给大家介绍一下具体的使用方法。
首先看看接口的支持情况
目前支持的 SQL 只能进行数据的查询只读操作,不能进行数据的修改,所以我们的数据插入还是要走之前的常规索引接口。
目前 Elasticsearch 的支持 SQL 命令只有以下几个:
命令 说明
DESC table 用来描述索引的字段属性 SHOW COLUMNS 功能同上,只是别名 SHOW FUNCTIONS 列出支持的函数列表,支持通配符?过滤 SHOW TABLES SELECT .. FROM table_name WHERE .. GROUP BY .. HAVING .. ORDER BY .. LIMIT .. 返回索引列表 用来执行查询的命令
我们分别来看一下各自怎么用,以及有什么效果吧,自己也可以动手试一下,看看。 首先,我们创建一条数据: POST twitter /doc/ { "name" : "medcl" , "twitter" : "sql is awesome" , "date" : "2018-07-27" , "id" : 123 } RESTful下调用SQL 在 ES 里面执行 SQL 语句,有三种方式,第一种是 RESTful 方式,第二种是 SQL-CLI 命令行工具,第三种是通过 JDBC 来连接 ES,执行的 SQL 语句其实都一样,我们先以 RESTful 方式来说明用法。 RESTful 的语法如下: POST /_xpack/sql? format =txt { "query" : "SELECT * FROM twitter" } 因为 SQL 特性是 xpack 的免费功能,所以是在 _xpack 这个路径下面,我们只需要把 SQL 语句传给 query 字段就行了,注意最后面不要加上 ; 结尾,注意是不要! 我们执行上面的语句,查询返回的结果如下: date | id | name | twitter ------------------------+---------------+---------------+--------------- 2018-07-27T00:00:00.000Z| 123 |medcl | sql is awesome ES 俨然已经变成 SQL 数据库了,我们再看看如何获取所有的索引列表: POST /_xpack/sql? format =txt { "query" : "SHOW tables" } 返回如下: name | type ---------------------------------+--------------- .kibana | BASE TABLE .monitoring-alerts- 6 |BASE TABLE .monitoring-es-6-2018.06.21 | BASE TABLE .monitoring-es- 6 - 2018.06 . 26 |BASE TABLE .monitoring-es-6-2018.06.27 | BASE TABLE .monitoring-kibana- 6 - 2018.06 . 21 |BASE TABLE .monitoring-kibana-6-2018.06.26 | BASE TABLE .monitoring-kibana- 6 - 2018.06 . 27 |BASE TABLE .monitoring-logstash-6-2018.06.20| BASE TABLE .reporting- 2018.06 . 24 |BASE TABLE .triggered_watches | BASE TABLE .watcher-history- 7 - 2018.06 . 20 |BASE TABLE .watcher-history-7-2018.06.21 | BASE TABLE .watcher-history- 7 - 2018.06 . 26 |BASE TABLE .watcher-history-7-2018.06.27 | BASE TABLE .watches |BASE TABLE apache_elastic_example | BASE TABLE forum-mysql |BASE TABLE twitter 有点多,我们可以按名称过滤,如 twitt 开头的索引,注意通配符只支持 % 和 _ ,分别表示多个和单个字符(什么,不记得了,回去翻数据库的书去!): POST /_xpack/sql? format =txt { "query" : "SHOW TABLES 'twit%'" } POST /_xpack/sql? format =txt { "query" : "SHOW TABLES 'twitte_'" } 上面返回的结果都是: name | type ---------------+--------------- twitter |BASE TABLE 如果要查看该索引的字段和元数据,如下: POST /_xpack/sql? format =txt { "query" : "DESC twitter" } 返回: column | type ---------------+--------------- date |TIMESTAMP id |BIGINT name |VARCHAR name.keyword |VARCHAR twitter |VARCHAR twitter.keyword|VARCHAR 都是动态生成的字段,包含了 .keyword 字段。 还能使用下面的命令来查看,主要是兼容 SQL 语法。 POST /_xpack/sql? format =txt { "query" : "SHOW COLUMNS IN twitter" } 另外,如果不记得 ES 支持哪些函数,只需要执行下面的命令,即可得到完整列表: SHOW FUNCTIONS 返回结果如下,也就是当前6.3版本支持的所有函数,如下: name | type ----------------+--------------- AVG |AGGREGATE COUNT |AGGREGATE MAX |AGGREGATE MIN |AGGREGATE SUM |AGGREGATE STDDEV_POP |AGGREGATE VAR_POP |AGGREGATE PERCENTILE |AGGREGATE PERCENTILE_RANK |AGGREGATE SUM_OF_SQUARES |AGGREGATE SKEWNESS |AGGREGATE KURTOSIS |AGGREGATE DAY_OF_MONTH |SCALAR DAY |SCALAR DOM |SCALAR DAY_OF_WEEK |SCALAR DOW |SCALAR DAY_OF_YEAR |SCALAR DOY |SCALAR HOUR_OF_DAY |SCALAR HOUR |SCALAR MINUTE_OF_DAY |SCALAR MINUTE_OF_HOUR |SCALAR MINUTE |SCALAR SECOND_OF_MINUTE|SCALAR SECOND |SCALAR MONTH_OF_YEAR |SCALAR MONTH |SCALAR YEAR |SCALAR WEEK_OF_YEAR |SCALAR WEEK |SCALAR ABS |SCALAR ACOS |SCALAR ASIN |SCALAR ATAN |SCALAR ATAN2 |SCALAR CBRT |SCALAR CEIL |SCALAR CEILING |SCALAR COS |SCALAR COSH |SCALAR COT |SCALAR DEGREES |SCALAR E |SCALAR EXP |SCALAR EXPM1 |SCALAR FLOOR |SCALAR LOG |SCALAR LOG10 |SCALAR MOD |SCALAR PI |SCALAR POWER |SCALAR RADIANS |SCALAR RANDOM |SCALAR RAND |SCALAR ROUND |SCALAR SIGN |SCALAR SIGNUM |SCALAR SIN |SCALAR SINH |SCALAR SQRT |SCALAR TAN |SCALAR SCORE |SCORE 同样支持通配符进行过滤: POST /_xpack/sql? format =txt { "query" : "SHOW FUNCTIONS 'S__'" } 结果: name | type ---------------+--------------- SUM |AGGREGATE SIN |SCALAR 那如果要进行模糊搜索呢,Elasticsearch 的搜索能力大家都知道,强!在 SQL 里面,可以用 match 关键字来写,如下: POST /_xpack/sql? format =txt { "query" : "SELECT SCORE(), * FROM twitter WHERE match(twitter, 'sql is') ORDER BY id DESC" } 最后,还能试试 SELECT 里面的一些其他操作,如过滤,别名,如下: POST /_xpack/sql? format =txt { "query" : "SELECT SCORE() as score,name as myname FROM twitter as mytable where name = 'medcl' OR name ='elastic' limit 5" } 结果如下: score | myname ---------------+--------------- 0.2876821 |medcl 或是分组和函数计算: POST /_xpack/sql? format =txt { "query" : "SELECT name,max(id) as max_id FROM twitter as mytable group by name limit 5" } 结果如下: name | max_id ---------------+--------------- medcl |123.0 SQL-CLI下的使用 上面的例子基本上把 SQL 的基本命令都介绍了一遍,很多情况下,用 RESTful 可能不是很方便,那么可以试试用 CLI 命令行工具来执行 SQL 语句,妥妥的 SQL 操作体验。 切换到命令行下,启动 cli 程序即可进入命令行交互提示界面,如下: ➜ elasticsearch- 6.3 . 0 ./bin/elasticsearch-sql-cli .sssssss. ` .sssssss. .:sXXXXXXXXXXo` `ohXXXXXXXXXho. .yXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXX- .XXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXXXXX. .XXXXXXXXXXXXXXXXXXXXo. .oXXXXXXXXXXXXXXXXXXXXh .XXXXXXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXXXXXXXXXy ` yXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX. `oXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXXXXXXXXXXXXXXXo` .XXXXXXXXXXXXXXXXXXXXXXXXXo ` .oXXXXXXXXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXXXXXXXXXXXo` `odo` `oXXXXXXXXXXXXXXXXXXXXXXXXo` `oXXXXXo` `oXXXXXXXXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXo` `oXXXXXXXXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXo` `yXXXXXXXXXXXXXXXXXXXXXXXo` oXXXXXXXXXXXXXXXXX. .XXXXXXXXXXXXXXXXXXXXXXo ` ` oXXXXXXXXXXXXXXXXXXXy .XXXXXXXXXXXXXXXXXXXXo ` /XXXXXXXXXXXXXXXXXXXXX .XXXXXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXXXXX- -XXXXXXXXXXXXXXXo` `oXXXXXXXXXXXXXXXo` .oXXXXXXXXXXXo ` ` oXXXXXXXXXXXo. `.sshXXyso` SQL `.sshXhss.` sql> 当你看到一个硕大的创口贴,表示 SQL 命令行已经准备就绪了,查看一下索引列表,不,数据表的列表: [attach]2546[/attach] 各种操作妥妥的,上面已经测试过的命令就不在这里重复了,只是体验不一样罢了。 如果要连接远程的 ES 服务器,只需要启动命令行工具的时候,指定服务器地址,如果有加密,指定 keystone 文件,完整的帮助如下: ➜ elasticsearch-6.3.0 ./bin/elasticsearch-sql-cli --help Elasticsearch SQL CLI Non-option arguments: uri Option Description ------ ----------- -c, --check Enable initial connection check on startup (default: true) -d, --debug Enable debug logging -h, --help show help -k, --keystore_location Location of a keystore to use when setting up SSL. If specified then the CLI will prompt for a keystore password. If specified when the uri isn't https then an error is thrown. -s, --silent show minimal output -v, --verbose show verbose output JDBC 对接 JDBC 对接的能力,让我们可以与各个 SQL 生态系统打通,利用众多现成的基于 SQL 之上的工具来使用 Elasticsearch,我们以两个工具来举例。 和其他数据库一样,要使用 JDBC,要下载该数据库的 JDBC 的驱动,我们打开: https://www.elastic.co/downloads/jdbc-client 只有一个 zip 包下载链接,下载即可。 然后,我们这里使用 DbVisualizer 来连接 ES 进行操作,这是一个数据库的操作和分析工具,DbVisualizer 下载地址是: https://www.dbvis.com/ 。 下载安装启动之后的程序主界面如下图: 我们如果要使用 ES 作为数据源,我们第一件事需要把 ES 的 JDBC 驱动添加到 DbVisualizer 的已知驱动里面。我们打开 DbVisualizer 的菜单【Tools】-> 【Driver Manager】,打开如下设置窗口: 点击绿色的加号按钮,新增一个名为 Elasticsearch-SQL 的驱动,url format 设置成 jdbc:es: ,如下图: 然后点击上图黄色的文件夹按钮,添加我们刚刚下载好且解压之后的所有 jar 文件,如下: 添加完成之后,如下图: 就可以关闭这个 JDBC 驱动的管理窗口了。下面我们来连接到 ES 数据库。 选择主程序左侧的新建连接图标,打开向导,如下: 选择刚刚加入的 Elasticsearch-SQL 驱动: 设置连接字符串,此处没有登录信息,如果有可以对应的填上: 点击 Connect ,即可连接到 ES,左侧导航可以展开看到对应的 ES 索引信息: 同样可以查看相应的库表结果和具体的数据: 用他自带的工具执行 SQL 也是不在话下: 同理,各种 ETL 工具和基于 SQL 的 BI 和可视化分析工具都能把 Elasticsearch 当做 SQL 数据库来连接获取数据了。 最后一个小贴士,如果你的索引名称包含横线,如 logstash-201811,只需要做一个用双引号包含,对双引号进行转义即可,如下: 关于 SQL 操作的文档在这里: https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-jdbc.html 本文列出53个Python面试问题,并且提供了答案,供数科学家和软件工程师们参考。 不久前,我作为“数据科学家”开始担任一个新的角色,实际上就是一位“Python工程师”。 如果我在面试前提前了解一下Python的线程生命周期,而不是它的Recommender System(推荐系统)的话,我可能会在面试中表现得更好。 为了帮助大家通过面试,下面我整理了我为Python面试/工作准备的问题,并提供了答案。大多数数据科学家都会编写大量的代码,所以这些问题/答案对科学家和工程师都同样适用。 无论你是一位面试官、还是准备应聘一份工作、或者只是想提高你的Python技能,这份清单对你来说都将是无价之宝。 问题是无序的。我们开始吧。 1. 列表(list)和元组(tuple)有什么区别? 在我每一次应聘Python数据科学家的面试中,这个问题都会被问到。所以对这个问题的答案,我可以说是了如指掌。 列表是可变的。创建后可以对其进行修改。 元组是不可变的。元组一旦创建,就不能对其进行更改。 列表表示的是顺序。它们是有序序列,通常是同一类型的对象。比如说按创建日期排序的所有用户名,如 ["Seth", "Ema", "Eli"] 。 元组表示的是结构。可以用来存储不同数据类型的元素。比如内存中的数据库记录,如 (2, "Ema", "2020–04–16")(#id, 名称,创建日期) 。 2. 如何进行字符串插值? 在不导入Template类的情况下,有3种方法进行字符串插值。 name = 'Chris'# 1. f stringsprint(f'Hello {name}')# 2. % operatorprint('Hey %s %s' % (name, name))# 3. formatprint( "My name is {}".format((name))) 3. “is”和“==”有什么区别? 在我的Python职业生涯的早期,我认为它们是相同的,因而制造了一些bug。所以请大家听好了,“is”用来检查对象的标识(id),而“==”用来检查两个对象是否相等。 我们将通过一个例子说明。创建一些列表并将其分配给不同的名字。请注意,下面的b指向与a相同的对象。 a = [1,2,3]b = ac = [1,2,3] 下面来检查是否相等,你会注意到结果显示它们都是相等的。 print(a == b)print(a == c)#=> True#=> True 但是它们具有相同的标识(id)吗?答案是不。 print(a is b)print(a is c)#=> True#=> False 我们可以通过打印他们的对象标识(id)来验证这一点。 print(id(a))print(id(b))print(id(c))#=> 4369567560#=> 4369567560#=> 4369567624 你可以看到:c和a和b具有不同的标识(id)。 4. 什么是装饰器(decorator)? 这是每次面试我都会被问到的另一个问题。它本身就值得写一篇文章。如果你能自己用它编写一个例子,那么说明你已经做好了准备。 装饰器允许通过将现有函数传递给装饰器,从而向现有函数添加一些额外的功能,该装饰器将执行现有函数的功能和添加的额外功能。 我们将编写一个装饰器,该装饰器会在调用另一个函数时记录日志。 编写装饰器函数logging。它接受一个函数func作为参数。它还定义了一个名为log_function_called的函数,它先执行打印出一些“函数func被调用”的信息(print(f'{func} called.')),然后调用函数func。最后返回定义的函数。 def logging(func): def log_function_called: print(f'{func} called.') func return log_function_called 让我们编写其他两个函数,我们最终会将装饰器添加到其中(但还没有)。 def my_name: print('chris')def friends_name: print('naruto')my_namefriends_name#=> chris#=> naruto 现在将装饰器添加到上面编写的两个函数之中。 @loggingdef my_name: print('chris')@loggingdef friends_name: print('naruto')my_namefriends_name#=> called.#=> chris#=> called.#=> naruto 现在,你了解了如何仅仅通过在其上面添加@logging(装饰器),就能够轻松地将日志添加到我们编写的任何函数中。 5. 解释Range函数 Range函数可以用来创建一个整数列表,一般用在for循环中。它有3种使用方法。 Range函数可以接受1到3个参数,参数必须是整数。 请注意:我已经将range的每种用法包装在一个递推式构造列表(list comprehension)中,以便我们可以看到生成的值。 用法1 - range(stop):生成从0到参数“stop”之间的整数。 [i for i in range(10)]#=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 用法2 - range(start, stop) : 生成从参数“start”到“stop”之间的整数 [i for i in range(2,10)]#=> [2, 3, 4, 5, 6, 7, 8, 9] 用法3 - range(start, stop, step):以参数“step”为步长,生成从“start”到“stop”之间的整数。 [i for i in range(2,10,2)]#=> [2, 4, 6, 8] 6. 定义一个名为car的类,它有两个属性:“color”和“speed”。然后创建一个实例并返回“speed”。 class Car : def __init__(self, color, speed): self.color = color self.speed = speedcar = Car('red','100mph')car.speed#=> '100mph' 7. Python中的实例方法、静态方法和类方法有什么区别? 实例方法:接受self参数,并且与类的特定实例相关。 静态方法:使用装饰器 @staticmethod ,与特定实例无关,并且是自包含的(不能修改类或实例的属性)。 类方法:接受cls参数,并且可以修改类本身。 我们将通过一个虚构的CoffeeShop类来说明它们之间的区别。 class CoffeeShop: specialty = 'espresso' def __init__(self, coffee_price): self.coffee_price = coffee_price # instance method def make_coffee(self): print(f'Making {self.specialty} for ${self.coffee_price}') # static method @staticmethod def check_weather: print('Its sunny') # class method @classmethod def change_specialty(cls, specialty): cls.specialty = specialty print(f'Specialty changed to {specialty}') CoffeeShop类有一个属性specialty,默认值设为“espresso”。CoffeeShop类的每个实例初始化时都使用了coffee_price这个属性。同时,它还有3个方法,一个实例方法,一个静态方法和一个类方法。 让我们将coffee_price的值设为5,来初始化CoffeeShop的一个实例。然后调用实例方法make_coffee。 coffee_shop = CoffeeShop('5')coffee_shop.make_coffee#=> Making espresso for $5 现在我们来调用静态方法。静态方法无法修改类或实例状态,因此通常用于工具函数,例如,把2个数字相加。我们这里用它来检查天气。天气晴朗。太好了! coffee_shop.check_weather#=> Its sunny 现在让我们使用类方法修改CoffeeShop的属性specialty,然后调用make_coffee方法来制作咖啡。 coffee_shop.change_specialty('drip coffee')#=> Specialty changed to drip coffeecoffee_shop.make_coffee#=> Making drip coffee for $5 注意,make_coffee过去是用来做意式浓缩咖啡(espresso)的,但现在用来做滴滤咖啡(drip coffee)了! 8. “func”和“ func”有什么区别? 这个问题的目的是想看看你是否理解所有函数也是Python中的对象。 def func: print('Im a function') func#=> function __main__.func>func #=> Im a function func是表示函数的对象,它可以被分配给变量或传递给另一个函数。带圆括号的func调用该函数并返回其输出。 9. 解释map函数的工作原理。 Map函数返回一个列表,该列表由对序列中的每个元素应用一个函数时返回的值组成。 def add_three(x): return x + 3li = [1,2,3][i for i in map(add_three, li)]#=> [4, 5, 6] 上面,我对列表中的每个元素的值加了3。 10. 解释reduce函数的工作原理。 这个问题很棘手,在你使用过它几次之前,你得努力尝试自己能够理解它。 reduce接受一个函数和一个序列,然后对序列进行迭代。在每次迭代中,当前元素和前一个元素的输出都传递给函数。最后,返回一个值。 from functools import reducedef add_three(x,y): return x + y li = [1,2,3,5] reduce(add_three, li)#=> 11 返回11,它是1 + 2 + 3 + 5的总和。 11.解释filter函数的工作原理 Filter函数顾名思义,是用来按顺序过滤元素。 每个元素都被传递给一个函数,如果函数返回True,则在输出序列中返回该元素;如果函数返回False,则将其丢弃。 def add_three(x): if x % 2 == 0: return True else: return Falseli = [1,2,3,4,5,6,7,8][i for i in filter(add_three, li)]#=> [2, 4, 6, 8] 注意上面所有不能被2整除的元素如何被删除的。 12. Python是按引用调用还是按值调用? 如果你在谷歌上搜索这个问题并阅读前几页,你就要准备好进入语义的迷宫了。你最好只是了解它的工作原理。 不可变对象(如字符串、数字和元组等)是按值调用的。请注意下面的例子,当在函数内部修改时,name的值在函数外部不会发生变化。name的值已分配给内存中该函数作用域的新块。 name = 'chr'def add_chars(s): s += 'is' print(s)add_chars(name) print(name)#=> chris#=> chr 可变对象(如列表等)是通过引用调用的。注意下面的例子中,函数外部定义的列表在函数内部的修改是如何影响到函数外部的。函数中的参数指向内存中存储li值的原始块。 li = [1,2]def add_element(seq): seq.append(3) print(seq)add_element(li) print(li)#=> [1, 2, 3]#=> [1, 2, 3] 13. 如何使用reverse函数反转一个列表? 下面的代码对一个列表调用reverse函数,对其进行修改。该方法没有返回值,但是会对列表的元素进行反向排序。 li = ['a','b','c']print(li)li.reverseprint(li)#=> ['a', 'b', 'c']#=> ['c', 'b', 'a'] 14. 字符串乘法是如何工作的? 让我们看看将字符串" cat"乘以3的结果。 'cat' * 3#=> 'catcatcat' 该字符串将自身连接3次。 15. 列表乘法是如何工作的? 我们来看看将列表[1,2,3]乘以2的结果。 [1,2,3] * 2#=> [1, 2, 3, 1, 2, 3] 输出的列表包含了重复两次的列表[1,2,3]的内容。 16. 类中的“self”指的是什么? “self”引用类本身的实例。这就是我们赋予方法访问权限并且能够更新方法所属对象的能力。 下面,将self传递给__init__,使我们能够在初始化时设置实例的颜色。 class Shirt: def __init__(self, color): self.color = color s = Shirt('yellow')s.color#=> 'yellow' 17. 如何在Python中连接列表? 将2个列表相加,就是将它们连接在一起。但请注意,数组的工作方式不是这样的。 a = [1,2]b = [3,4,5] a + b#=> [1, 2, 3, 4, 5] 18. 浅拷贝和深拷贝之间有什么区别? 我们将在一个可变对象(列表)的上下文中讨论这个问题,对于不可变的对象,浅拷贝和深拷贝的区别并不重要。 我们将介绍三种情况。 1、引用原始对象。这将新对象li2指向li1所指向的内存中的同一位置。因此,我们对li1所做的任何更改也会在li2中发生。 li1 = [['a'],['b'],['c']]li2 = li1li1.append(['d'])print(li2)#=> [['a'], ['b'], ['c'], ['d']] 2、创建原始对象的浅拷贝副本。我们可以使用list构造函数来实现这一点。浅拷贝创建一个新对象,但是用对原始对象的引用填充它。因此,向原始列表li3中添加新对象不会传播到li4中,但是修改li3中的一个对象将传播到li4中。 li3 = [['a'],['b'],['c']]li4 = list(li3)li3.append([4])print(li4)#=> [['a'], ['b'], ['c']]li3[0][0] = ['X']print(li4)#=> [[['X']], ['b'], ['c']] 3、创建一个深拷贝副本。这是用copy.deepcopy完成的。现在,这两个对象是完全独立的,并且对其中一个对象所做的更改不会对另外一个对象产生影响。 import copyli5 = [['a'],['b'],['c']]li6 = copy.deepcopy(li5)li5.append([4])li5[0][0] = ['X']print(li6)#=> [['a'], ['b'], ['c']] 19. 列表和数组有什么区别? 注意:Python的标准库有一个array(数组)对象,但在这里,我特指常用的Numpy数组。 列表存在于python的标准库中。数组由Numpy定义。 列表可以在每个索引处填充不同类型的数据。数组需要同构元素。 列表上的算术运算可从列表中添加或删除元素。数组上的算术运算按照线性代数方式工作。 列表还使用更少的内存,并显著具有更多的功能。 20. 如何连接两个数组? 记住,数组不是列表。数组来自Numpy和算术函数,例如线性代数。 我们需要使用Numpy的连接函数concatenate来实现。 import numpy as npa = np.array([1,2,3])b = np.array([4,5,6])np.concatenate((a,b))#=> array([1, 2, 3, 4, 5, 6]) 21. 你喜欢Python的什么? Python可读性很强,并且有一种Python方式可以处理几乎所有事情,这意味着它有一种简洁明了的首选方法。 我将Python与Ruby进行对比,Ruby通常有很多种方法来做某事,但是没有指南说哪种方法是首选。 22. 你最喜欢Python的哪个库? 在处理大量数据时,没有什么比Pandas(熊猫)更有帮助了,因为Pandas让操作和可视化数据变得轻而易举。 23. 举出几个可变和不可变对象的例子? 不可变意味着创建后不能修改状态。例如:int、float、bool、string和tuple。 可变意味着可以在创建后修改状态。例如列表(list)、字典(dict)和集合(set)。 24. 如何将一个数字四舍五入到小数点后三位? 使用round(value, decimal_places)函数。 a = 5.12345round(a,3)#=> 5.123 25. 如何分割一个列表? 分割语法使用3个参数, list[start:stop:step] ,其中step是返回元素的间隔。 a = [0,1,2,3,4,5,6,7,8,9]print(a[:2])#=> [0, 1]print(a[8:])#=> [8, 9]print(a[2:8])#=> [2, 3, 4, 5, 6, 7]print(a[2:8:2])#=> [2, 4, 6] 26. 什么是pickling? Pickling是Python中序列化和反序列化对象的常用方法。 在下面的示例中,我们对一个字典列表进行序列化和反序列化。 import pickleobj = [ {'id':1, 'name':'Stuffy'}, {'id':2, 'name': 'Fluffy'}]with open('file.p', 'wb') as f: pickle.dump(obj, f)with open('file.p', 'rb') as f: loaded_obj = pickle.load(f)print(loaded_obj)#=> [{'id': 1, 'name': 'Stuffy'}, {'id': 2, 'name': 'Fluffy'}] 27. 字典和JSON有什么区别? Dict是Python的一种数据类型,是经过索引但无序的键和值的集合。 JSON只是一个遵循指定格式的字符串,用于传输数据。 28. 你在Python中使用了哪些ORM? ORM(对象关系映射)将数据模型(通常在应用程序中)映射到数据库表,并简化了数据库事务。 SQLAlchemy通常用于Flask的上下文中,而Django拥有自己的ORM。 29. any和all如何工作? Any接受一个序列,如果序列中的任何元素为true,则返回true。 All只有当序列中的所有元素都为true时,才返回true。 a = [False, False, False]b = [True, False, False]c = [True, True, True]print( any(a) )print( any(b) )print( any(c) )#=> False#=> True#=> Trueprint( all(a) )print( all(b) )print( all(c) )#=> False#=> False#=> True 30. 字典和列表的查找速度哪个更快? 在列表中查找一个值需要O(n)时间,因为需要遍历整个列表,直到找到值为止。 在字典中查找一个值只需要O(1)时间,因为它是一个哈希表。 如果有很多值,这会造成很大的时间差异,因此通常建议使用字典来提高速度。但字典也有其他限制,比如需要唯一键。 31. 模块(module)和包(package)有什么区别? 模块是可以一起导入的文件(或文件集合)。 import sklearn 包是模块的目录。 from sklearn import cross_validation 因此,包是模块,但并非所有模块都是包。 32. 如何在Python中递增和递减一个整数? 可以使用“+=”和“-=”对整数进行递增和递减。 value = 5value += 1print(value)#=> 6value -= 1value -= 1print(value)#=> 4 33. 如何返回一个整数的二进制值? 使用bin函数。 bin(5)#=> '0b101' 34. 如何从列表中删除重复的元素? 可以通过将一个列表先转化为集合,然后再转化回列表来完成。 a = [1,1,1,2,3]a = list(set(a))print(a)#=> [1, 2, 3] 35. 如何检查一个值是不是在列表中存在? 使用“in”。 'a' in ['a','b','c'] #=> True'a' in [1,2,3]#=> False 36. append和extend有什么区别? Append将一个值添加到一个列表中,而extend将另一个列表的值添加到一个列表中。 a = [1,2,3]b = [1,2,3]a.append(6)print(a)#=> [1, 2, 3, 6]b.extend([4,5])print(b)#=> [1, 2, 3, 4, 5] 37. 如何取一个整数的绝对值? 这可以通过abs函数来实现。 abs(2#=> 2 abs(-2)#=> 2 38. 如何将两个列表组合成一个元组列表? 可以使用zip函数将列表组合成一个元组列表。这不仅仅限于使用两个列表。也适合3个或更多列表的情况。 a = ['a','b','c']b = [1,2,3] [(k,v) for k,v in zip(a,b)]#=> [('a', 1), ('b', 2), ('c', 3)] 39. 如何按字母顺序对字典进行排序? 你不能对字典进行排序,因为字典没有顺序,但是你可以返回一个已排序的元组列表,其中包含字典中的键和值。 d = {'c':3, 'd':4, 'b':2, 'a':1} sorted(d.items)#=> [('a', 1), ('b', 2), ('c', 3), ('d', 4)] 40. 一个类如何继承Python的另一个类? 在下面的示例中,Audi继承自Car。继承带来了父类的实例方法。 class Car: def drive(self): print('vroom')class Audi(Car): passaudi = Audiaudi.drive 41. 如何删除字符串中的所有空白? 最简单的方法是使用空白拆分字符串,然后将拆分成的字符串重新连接在一起。 s = 'A string with white space'''.join(s.split)#=> 'Astringwithwhitespace' 42. 在迭代序列时,为什么要使用enumerate? enumerate允许在序列上迭代时跟踪索引。它比定义和递增一个表示索引的整数更具Python感。 li = ['a','b','c','d','e']for idx,val in enumerate(li): print(idx, val)#=> 0 a#=> 1 b#=> 2 c#=> 3 d#=> 4 e 43. pass、continue和break之间有什么区别? pass意味着什么都不做。我们之所以通常使用它,是因为Python不允许在没有代码的情况下创建类、函数或if语句。 在下面的例子中,如果在i>3中没有代码的话,就会抛出一个错误,因此我们使用pass。 a = [1,2,3,4,5]for i in a: if i > 3: pass print(i)#=> 1#=> 2#=> 3#=> 4#=> 5 Continue会继续到下一个元素并停止当前元素的执行。所以当i<3时,永远不会达到print(i)。 for i in a: if i < 3: continue print(i)#=> 3#=> 4#=> 5 break会中断循环,序列不再重复下去。所以不会被打印3以后的元素。 for i in a: if i == 3: break print(i) #=> 1#=> 2 44. 如何将for循环转换为使用递推式构造列表(list comprehension)? For循环如下: a = [1,2,3,4,5] a2 = for i in a: a2.append(i + 1)print(a2)#=> [2, 3, 4, 5, 6] 用递推式构造列表来修改这个for循环,代码如下: a a3 = [i+1 for i in a] print(a3)#=> [2, 3, 4, 5, 6] 递推式构造列表通常被认为更具Python风格,同时仍易于阅读。 45. 举一个使用三元运算符的例子。 三元运算符是一个单行的if/else语句。 语法看起来像“if 条件 else b”。 x = 5y = 10'greater' if x > 6 else 'less'#=> 'less''greater' if y > 6 else 'less'#=> 'greater' 46. 检查一个字符串是否仅仅包含数字? 可以使用isnumeric方法。 '123abc...'.isalnum#=> False' 123abc'.isalnum#=> True 47. 检查一个字符串是否仅仅包含字母? 你可以使用isalpha。 '123a'.isalpha#=> False'a'.isalpha#=> True 48. 检查字符串是否只包含数字和字母? 你可以使用isalnum。 '123abc...'.isalnum#=> False'123abc'.isalnum#=> True 49. 从字典返回键列表 这可以通过将字典传递给Python的list构造函数list来完成。 d = {'id':7, 'name':'Shiba', 'color':'brown', 'speed':'very slow'}list(d)#=> ['id', 'name', 'color', 'speed'] 50. 如何将一个字符串转化为全大写和全小写? 你可以使用upper和lower字符串方法。 small_word = 'potatocake'big_word = 'FISHCAKE'small_word.upper#=> 'POTATOCAKE'big_word.lower#=> 'fishcake' 51. remove、del和pop有什么区别? remove 删除第一个匹配的值。 li = ['a','b','c','d'] li.remove('b')li#=> ['a', 'c', 'd'] del按索引删除元素。 li = ['a','b','c','d'] del li[0]li#=> ['b', 'c', 'd'] pop 按索引删除一个元素并返回该元素。 li = ['a','b','c','d'] li.pop(2)#=> 'c' li#=> ['a', 'b', 'd'] 52. 举一个递推式构造字典(dictionary comprehension)的例子 下面我们将创建一个字典,其中字母表中的字母作为键,并以字母索引作为值。 # creating a list of lettersimport stringlist(string.ascii_lowercase)alphabet = list(string.ascii_lowercase)# list comprehensiond = {val:idx for idx,val in enumerate(alphabet)}d#=> {'a': 0,#=> 'b': 1,#=> 'c': 2,#=> ...#=> 'x': 23,#=> 'y': 24,#=> 'z': 25} 53. Python中的异常处理是如何进行的? Python提供了3个关键字来处理异常,try、except和finally。 语法如下: try: # try to do thisexcept: # if try block fails then do thisfinally: # always do this 在下面的简单示例中,try块失败,因为我们不能将字符串添加到整数中。except块设置val=10,然后finally块打印出“complete”。 try: val = 1 + 'A'except: val = 10finally: print('complete') print(val)#=> complete#=> 10 你永远不知道面试中会出现什么问题,最好的准备方法是拥有很多编写代码的经验。 也就是说,这个列表应该涵盖Python所要求的数据科学家或初级/中级Python开发人员角色的大部分内容。 我希望这对你一样有帮助。 如果我漏掉了什么好问题,请让我知道。 作者 | Chris 原文 | https://towardsdatascience.com/53-python-interview-questions-and-answers-91fa311eec3f 文源网络,仅供学习之用,如有侵权请联系删除。 在学习Python的道路上肯定会遇见困难,别慌,我这里有一套学习资料,包含40+本电子书,800+个教学视频,涉及Python基础、爬虫、框架、数据分析、机器学习等,不怕你学不会! https://shimo.im/docs/JWCghr8prjCVCxxK/ 《Python学习资料》 关注公众号【Python圈子】,优质文章每日送达。
>作者 谢恩铭,公众号「程序员联盟」(微信号:coderhub)。 转载请注明出处。 原文: https://www.jianshu.com/p/bbce8f04faf1 > 《C语言探索之旅》 全系列 内容简介 前言 变量的大小 内存的动态分配 动态分配一个数组 总结 第二部分第九课预告 1. 前言 上一课是 C语言探索之旅 | 第二部分第七课:文件读写 。 经历了第二部分的一些难点课程,我们终于来到了这一课,一个听起来有点酷酷的名字: 动态分配 。 >“万水千山总是情,分配也由系统定”。 到目前为止,我们创建的变量都是系统的编译器为我们自动构建的,这是简单的方式。 其实还有一种更偏手动的创建变量的方式,我们称为“动态分配”(Dynamic Allocation)。dynamic 表示“动态的”,allocation 表示“分配”。 动态分配的一个主要好处就是可以在内存中“预置”一定空间大小,在编译时还不知道到底会用多少。 使用这个技术,我们可以创建大小可变的数组。到目前为止我们所创建的数组都是大小固定不可变的。而学完这一课后我们就会创建所谓“动态数组”了。 学习这一章需要对指针有一定了解,如果指针的概念你还没掌握好,可以回去复习 C语言探索之旅 | 第二部分第二课:进击的指针,C语言的王牌! 那一课。 我们知道当我们创建一个变量时,在内存中要为其分配一定大小的空间。例如: int number = 2; 当程序运行到这一行代码时,会发生几件事情: 应用程序询问 操作系统 (Operating System,简称 OS。例如Windows,Linux,macOS,Android,iOS,等)是否可以使用一小块内存空间。 操作系统回复我们的程序,告诉它可以将这个变量存储在内存中哪个地方(给出分配的内存地址)。 当函数结束后,你的变量会自动从内存中被删除。你的程序对操作系统说:“我已经不需要内存中的这块地址了,谢谢!” (当然,实际上你的程序不可能对操作系统说一声“谢谢”,但是确实是操作系统在掌管一切,包括内存,所以对它还是客气一点比较好...)。 可以看到,以上的过程都是自动的。当我们创建一个变量,操作系统就会自动被程序这样调用。 那么什么是手动的方式呢?说实在的,没人喜欢把事情复杂化,如果自动方式可行,何必要大费周章来使用什么手动方式呢?但是要知道,很多时候我们是不得不使用手动方式。 这一课中,我们将会: 探究内存的机制(是的,虽然以前的课研究过,但是还是要继续深入),了解不同变量类型所占用的内存大小。 接着,探究这一课的主题,来学习如何向操作系统动态请求内存。也就是所谓的“动态内存分配”。 最后,通过学习如何创建一个在编译时还不知道其大小(只有在程序运行时才知道)的数组来了解动态内存分配的好处。 准备好了吗?Let's Go ! 2. 变量的大小 根据我们所要创建的变量的类型(char,int,double,等等),其所占的内存空间大小是不一样的。 事实上,为了存储一个大小在 -128 至 127 之间的数(char 类型),只需要占用一个字节(8 个二进制位)的内存空间,是很小的。 然而,一个 int 类型的变量就要占据 4 个字节了;一个 double 类型要占据 8 个字节。 问题是:并不总是这样。 什么意思呢? 因为类型所占内存的大小还与操作系统有关系。不同的操作系统可能就不一样,32 位和 64 位的操作系统的类型大小一般会有区别。 这一节中我们的目的是学习如何获知变量所占用的内存大小。 有一个很简单的方法:使用 sizeof() 。 虽然看着有点像函数,但其实 sizeof 不是一个函数,而是一个 C语言的关键字,也算是一个运算符吧。 我们只需要在 sizeof 的括号里填入想要检测的变量类型,sizeof 就会返回所占用的字节数了。 例如,我们要检测 int 类型的大小,就可以这样写: sizeof(int) 在编译时, sizeof(int) 就会被替换为 int 类型所占用的字节数了。 在我的电脑上, sizeof(int) 是 4,也就是说 int 类型在我的电脑的内存中占据 4 个字节。在你的电脑上,也许是 4,但也可能是其他的值。 我们用一个例子来测试一下吧: // octet 是英语“字节”的意思,和 byte 类似 printf("char : %d octets\n", sizeof(char)); printf("int : %d octets\n", sizeof(int)); printf("long : %d octets\n", sizeof(long)); printf("double : %d octets\n", sizeof(double)); 在我的电脑(64 位)运行,输出: char : 1 octets int : 4 octets long : 8 octets double : 8 octets 我们并没有测试所有已知的变量类型,你也可以课后自己去测试一下其他的类型,例如:short,float。 曾几何时,当电脑的内存很小的年代,有这么多不同大小的变量类型可供选择是一件很好的事,因为我们可以选“够用的最小的”那种变量类型,以节约内存。 现在,电脑的内存一般都很大,“有钱任性”么。所以我们在编程时也没必要太“拘谨”。不过在嵌入式领域,内存大小一般是有限的,我们就得斟酌着使用变量类型了。 既然 sizeof 这么好用,我们可不可以用它来显示我们自定义的变量类型的大小呢?例如 struct,enum,union。 是可以的。写一个程序测试一下: #include typedef struct Coordinate { int x; int y; } Coordinate; int main(int argc, char *argv[]) { printf("Coordinate 结构体的大小是 : %d 个字节\n", sizeof(Coordinate)); return 0; } 运行输出: Coordinate 结构体的大小是 : 8 个字节 对于内存的全新视角 之前,我们在绘制内存图示时,还是比较不精准的。现在,我们知道了每个变量所占用的大小,我们的内存图示就可以变得更加精准了。 假如我定义一个 int 类型的变量: int age = 17; 我们用 sizeof 测试后得知 int 的大小为 4。假设我们的变量 age 被分配到的内存地址起始是 1700,那么我们的内存图示就如下所示: 我们看到,我们的 int 型变量 age 在内存中占用 4 个字节,起始地址是 1700(它的内存地址),一直到 1703。 如果我们对一个 char 型变量(大小是一个字节)同样赋值: char number = 17; 那么,其内存图示是这样的: 假如是一个 int 型的数组: int age[100]; 用 sizeof() 测试一下,就可以知道在内存中 age 数组占用 400 个字节。4 * 100 = 400。 即使这个数组没有赋初值,但是在内存中仍然占据 400 个字节的空间。变量一声明,在内存中就为它分配一定大小的内存了。 那么,如果我们创建一个类型是 Coordinate 的数组呢? Coordinate coordinate[100]; 其大小就是 8 * 100 = 800 个字节了。 3. 内存的动态分配 好了,现在我们就进入这一课的关键部分了,重提一次这一课的目的:学会如何手动申请内存空间。 我们需要引入 stdlib.h 这个标准库头文件,因为接下来要使用的函数是定义在这个库里面。 这两个函数是什么呢?就是: malloc:是 Memory Allocation 的缩写,表示“内存分配”。询问操作系统能否预支一块内存空间来使用。 free:表示“解放,释放,自由的”。意味着“释放那块内存空间”。告诉操作系统我们不再需要这块已经分配的空间了,这块内存空间会被释放,另一个程序就可以使用这块空间了。 当我们手动分配内存时,须要按照以下三步顺序来: 调用 malloc 函数来申请内存空间。 检测 malloc 函数的返回值,以得知操作系统是否成功为我们的程序分配了这块内存空间。 一旦使用完这块内存,不再需要时,必须用 free 函数来释放占用的内存,不然可能会造成内存泄漏。 以上三个步骤是不是让我们回忆起关于上一课“文件读写”的内容了? 这三个步骤和文件指针的操作有点类似,也是先申请内存,检测是否成功,用完释放。 malloc 函数:申请内存 malloc 分配的内存是在堆上,一般的局部变量(自动分配的)大多是在栈上。 关于堆和栈的区别,还有内存的其他区域,如静态区等,大家可以自己延伸阅读。 之前“字符串”那一课里已经给出过一张图表了。再来回顾一下吧: 名称 内容
代码段 可执行代码、字符串常量 数据段 已初始化全局变量、已初始化全局静态变量、局部静态变量、常量数据 BSS段 未初始化全局变量,未初始化全局静态变量 栈 堆 局部变量、函数参数 动态内存分配
给出 malloc 函数的原型,你会发现有点滑稽: void* malloc(size_t numOctetsToAllocate); 可以看到,malloc 函数有一个参数 numOctetsToAllocate,就是需要申请的内存空间大小(用字节数表示),这里的 size_t(之前的课程有提到过)其实和 int 是类似的,就是一个 define 宏定义,实际上很多时候就是 int。 对于我们目前的演示程序,可以将 sizeof(int) 置于 malloc 的括号中,表示要申请 int 类型的大小的空间。 真正引起我们兴趣的是 malloc 函数的返回值: void* 如果你还记得我们在函数那章所说的,void 表示“空”,我们用 void 来表示函数没有返回值。 所以说,这里我们的函数 malloc 会返回一个指向 void 的指针,一个指向“空”(void 表示“虚无,空”)的指针,有什么意义呢?malloc 函数的作者不会搞错了吧? 不要担心,这么做肯定是有理由的。 >难道有人敢质疑老爷子 Dennis Ritchie(C语言的作者)的智商? 来人呐,拖出去... 罚写 100 个 C语言小游戏。 事实上,这个函数返回一个指针,指向操作系统分配的内存的首地址。 如果操作系统在 1700 这个地址为你开辟了一块内存的话,那么函数就会返回一个包含 1700 这个值的指针。 但是,问题是:malloc 函数并不知道你要创建的变量是什么类型的。 实际上,你只给它传递了一个参数: 在内存中你需要申请的字节数。 如果你申请 4 个字节,那么有可能是 int 类型,也有可能是 long 类型。 正因为 malloc 不知道自己应该返回什么变量类型(它也无所谓,只要分配了一块内存就可以了),所以它会返回 void* 这个类型。这是一个可以表示任意指针类型的指针。 void* 与其他类型的指针之间可以通过强制转换来相互转换。例如: int *i = (int *)p; // p 是一个 void* 类型的指针 void *v = (void *)c; // c 是一个 char* 类型的指针 实践 如果我实际来用 malloc 函数分配一个 int 型指针: int *memoryAllocated = NULL; // 创建一个 int 型指针 memoryAllocated = malloc(sizeof(int)); // malloc 函数将分配的地址赋值给我们的指针 memoryAllocated 经过上面的两行代码,我们的 int 型指针 memoryAllocated 就包含了操作系统分配的那块内存地址的首地址值。 假如我们用之前我们的图示来举例,这个值就是 1700。 检测指针 既然上面我们用两行代码使得 memoryAllocated 这个指针包含了分配到的地址的首地址值,那么我们就可以通过检测 memoryAllocated 的值来判断申请内存是否成功了: 如果为 NULL,则说明 malloc 调用没有成功。 否则,就说明成功了。 一般来说内存分配不会失败,但是也有极端情况: 你的内存(堆内存)已经不够了。 你申请的内存值大得离谱(比如你申请 64 GB 的内存空间,那我想大多数电脑都是不可能分配成功的)。 希望大家每次用 malloc 函数时都要做指针的检测,万一真的出现返回值为 NULL 的情况,那我们需要立即停止程序,因为没有足够的内存,也不可能进行下面的操作了。 为了中断程序的运行,我们来使用一个新的函数: exit() exit 函数定义在 stdlib.h 中,调用此函数会使程序立即停止。 这个函数也只有一个参数,就是返回值,这和 return 函数的参数是一样原理的。实例: int main(int argc, char *argv[]) { int *memoryAllocated = NULL; memoryAllocated = malloc(sizeof(int)); if (memoryAllocated == NULL) // 如果分配内存失败 { exit(0); // 立即停止程序 } // 如果指针不为 NULL,那么可以继续进行接下来的操作 return 0; } 另外一个问题:用 malloc 函数申请 0 字节内存会返回 NULL 指针吗? 可以测试一下,也可以去查找关于 malloc 函数的说明文档。 >申请 0 字节内存,函数并不返回 NULL,而是返回一个正常的内存地址。 但是你却无法使用这块大小为 0 的内存! 这就好比尺子上的某个刻度,刻度本身并没有长度,只有某两个刻度一起才能量出长度。 对于这一点一定要小心,因为这时候 if(NULL != p) 语句校验将不起作用。 free函数:释放内存 记得上一课我们使用 fclose 函数来关闭一个文件指针,也就是释放占用的内存。 free 函数的原理和 fclose 是类似的,我们用它来释放一块我们不再需要的内存。原型: void free(void* pointer); free 函数只有一个目的:释放 pointer 指针所指向的那块内存。 实例程序: int main(int argc, char *argv[]) { int* memoryAllocated = NULL; memoryAllocated = malloc(sizeof(int)); if (memoryAllocated == NULL) // 如果分配内存失败 { exit(0); // 立即停止程序 } // 此处添加使用这块内存的代码 free(memoryAllocated); // 我们不再需要这块内存了,释放之 return 0; } 综合上面的三个步骤,我们来写一个完整的例子: #include #include int main(int argc, char *argv[]) { int* memoryAllocated = NULL; memoryAllocated = malloc(sizeof(int)); // 分配内存 if (memoryAllocated == NULL) // 检测是否分配成功 { exit(0); // 不成功,结束程序 } // 使用这块内存 printf("您几岁了 ? "); scanf("%d", memoryAllocated); printf("您已经 %d 岁了\n", *memoryAllocated); free(memoryAllocated); // 释放这块内存 return 0; } 运行输出: 您几岁了 ? 32 您已经 32 岁了 以上就是我们用动态分配的方式来创建了一个 int 型变量,使用它,释放它所占用的内存。 但是,我们也完全可以用以前的方式来实现,如下: int main(int argc, char *argv[]) { int myAge = 0; // 分配内存 (自动) // 使用这块内存 printf("您几岁了 ? "); scanf("%d", &myAge); printf("你已经 %d 岁了\n", myAge); return 0; } // 释放内存 (在函数结束后自动释放) 在这个简单使用场景下,两种方式(手动和自动)都是能完成任务的。 总结说来,创建一个变量(说到底也就是分配一块内存空间)有两种方式:自动和手动。 自动:我们熟知并且一直使用到现在的方式。 手动(动态):这一课我们学习的内容。 你可能会说:“我发现动态分配内存的方式既复杂又没什么用嘛!” 复杂么?还行吧,确实相对自动的方式要考虑比较多的因素。 没有用么?绝不! 因为很多时候我们不得不使用手动的方式来分配内存。 接下来我们就来看一下手动方式的必要性。 4. 动态分配一个数组 暂时我们只是用手动方式来创建了一个简单的变量。 然而,一般说来,我们的动态分配可不是这样“大材小用”的。 如果只是创建一个简单的变量,我们用自动的方式就够了。 那你会问:“啥时候须要用动态分配啊?” 问得好。动态分配最常被用来创建在运行时才知道大小的变量,例如动态数组。 假设我们要存储一个用户的朋友的年龄列表,按照我们以前的方式(自动方式),我们可以创建一个 int 型的数组: int ageFriends[18]; 很简单对吗?那问题不就解决了? 但是以上方式有两个缺陷: 你怎么知道这个用户只有 18 个朋友呢?可能他有更多朋友呢。 你说:“那好,我就创建一个数组: int ageFriends[10000]; 足够储存 1 万个朋友的年龄。” 但是问题是:可能我们使用到的只是这个大数组的很小一部分,岂不是浪费内存嘛。 最恰当的方式是询问用户他有多少朋友,然后创建对应大小的数组。 而这样,我们的数组大小就只有在运行时才能知道了。 Voila,这就是动态分配的优势了: 可以在运行时才确定申请的内存空间大小。 不多不少刚刚好,要多少就申请多少,不怕不够或过多。 所以借着动态分配,我们就可以在运行时询问用户他到底有多少朋友。 如果他说有 20 个,那我们就申请 20 个 int 型的空间;如果他说有 50 个,那就申请 50 个。经济又环保。 我们之前说过,C语言中禁止用变量名来作为数组大小,例如不能这样: int ageFriends[numFriends]; // numFriends 是一个变量 尽管有的 C编译器可能允许这样的声明,但是我们不推荐。 我们来看看用动态分配的方式如何实现这个程序: #include #include int main(int argc, char *argv[]) { int numFriends = 0, i = 0; int *ageFriends= NULL; // 这个指针用来指示朋友年龄的数组 // 询问用户有多少个朋友 printf("请问您有多少朋友 ? "); scanf("%d", &numFriends); if (numFriends > 0) // 至少得有一个朋友吧,不然也太惨了 :P { ageFriends = malloc(numFriends * sizeof(int)); // 为数组分配内存 if (ageFriends== NULL) // 检测分配是否成功 { exit(0); // 分配不成功,退出程序 } // 逐个询问朋友年龄 for (i = 0 ; i < numFriends; i++) { printf("第%d位朋友的年龄是 ? ", i + 1); scanf("%d", &ageFriends[i]); } // 逐个输出朋友的年龄 printf("\n\n您的朋友的年龄如下 :\n"); for (i = 0 ; i < numFriends; i++) { printf("%d 岁\n", ageFriends[i]); } // 释放 malloc 分配的内存空间,因为我们不再需要了 free(ageFriends); } return 0; } 运行输出: 请问您有多少朋友 ? 7 第1位朋友的年龄是 ? 25 第2位朋友的年龄是 ? 21 第3位朋友的年龄是 ? 27 第4位朋友的年龄是 ? 18 第5位朋友的年龄是 ? 14 第6位朋友的年龄是 ? 32 第7位朋友的年龄是 ? 30 您的朋友的年龄如下 : 25岁 21岁 27岁 18岁 14岁 32岁 30岁 当然了,这个程序比较简单,但我向你保证以后的课程会使用动态分配来做更有趣的事。 5. 总结 不同类型的变量在内存中所占的大小不尽相同。 借助 sizeof 这个关键字(也是运算符)可以知道一个类型所占的字节数。 动态分配就是在内存中手动地预留一块空间给一个变量或者数组。 动态分配的常用函数是 malloc(当然,还有 calloc,realloc,可以查阅使用方法,和 malloc 是类似的),但是在不需要这块内存之后,千万不要忘了使用 free 函数来释放。而且,malloc 和 free 要一一对应,不能一个 malloc 对应两个 free,会出错;或者两个 malloc 对应一个 free,会内存泄露! 动态分配使得我们可以创建动态数组,就是它的大小在运行时才能确定。 6. 第二部分第九课预告 今天的课就到这里,一起加油吧! 下一课: C语言探索之旅 | 第二部分第九课: 实战"悬挂小人"游戏 >我是 谢恩铭 ,公众号「程序员联盟」(微信号:coderhub)运营者,慕课网精英讲师 Oscar 老师 ,终生学习者。 热爱生活,喜欢游泳,略懂烹饪。 人生格言:「向着标杆直跑」 在国外某社交网站上有一个关于迁移 Spring Boot 迁移 Maven 至 Gradle 的帖子:
该贴子上也有很多人质疑:Maven 用的好好的,为什么要迁移至 Gradle?
虽然该贴子只是说 Gradle 牛逼,但并没有说迁移至 Gradle 所带来的影响和价值。
所以,Spring Boot 官方对此也发了博文作了解释: https://spring.io/blog/2020/06/08/migrating-spring-boot-s-build-to-gradle
栈长简单概括一下。
没错,Spring Boot 做了一个重大调整:
在 Spring Boot 2.3.0.M1 中,将首次使用 Gradle 代替 Maven 来构建 Spring Boot 项目。
为什么要迁移?
Spring Boot 团队给出的主要原因是,迁移至 Gradle 可以 减少构建项目所花费的时间 。
因为使用 Maven 构建,回归测试时间太长了,等待项目构建大大增加了修复 bug 和实现新特性的时间。
而 Gradle 的宗旨是减少构建工作量,它可以根据需要构建任何有变化的地方或者并行构建。
当然,Spring Boot 团队也花了很多时间来尝试用 Maven 进行 并行构建,但因为构建 Spring Boot 项目的复杂性,最终失败了。
另外,Spring Boot 团队也看到了在其他 Spring 项目中使用 Gradle 以及并行构建所带来的提升,并且还可以使用 Gradle 在一些第三方项目上的构建缓存,这些优势都促使 Gradle 带到构建 Spring Boot 项目中来。
迁移有什么好处?
栈长使用 Maven,哪怕只改一个代码也是构建全部,构建项目确实要花不少时间。
Spring Boot 官方也给出了数据,一次完整的 Maven 项目构建一般需要一个小时或者以上,而在过去的 4 周时间内,使用 Gradle 构建的平均时间只用了 9 分 22 秒!!!
如下面截图所示:
光从构建时间来看,效率真是倍数级的。 https://github.com/spring-projects/spring-boot/tree/v2.3.0.RELEASE
栈长特意去看了下,在 Spring Boot 2.2.8 中使用的是 Maven:
而最新发布的 Spring Boot 2.3.1 已经是切换到 Gradle 了:
会带来什么影响?
也许会有小伙伴质疑,Spring Boot 迁移到了 Gradle,会不会对公司现有的 Maven 项目或者后续的版本升级造成影响?
如果你只是使用 Spring Boot 框架来搭建系统,那还是可以继续使用 Maven 来管理依赖的,Spring Boot 会继续在 Maven 中央仓库提交。
如下面所示:
org.springframework.boot spring-boot 2.3.1.RELEASE 因为当版本确定之后,这个 Maven 构建只是一次性的,不会影响 Spring Boot 团队的日常迭代效率。
但是,如果我们需要在本地构建 Spring Boot 源码,或者你正在学习最新 Spring Boot 源码,就需要掌握 Gradle 构建了。
题外话,Gradle 肯定是未来的趋势,但也不一定非得迁移至 Gradle,只有适合自己的才是最好的,毕竟现在 Maven 和 Gradle 都是主流,但是 Maven 更占有市场,很多主流开源项目都是以 Maven 依赖来作为示例演示的。
栈长也会陆续关注 Spring Boot 动态,后续也会给大家带来各方面的教程,获取历史教程可以在Java技术栈公众号后台回复:boot,掌握 Spring Boot 问题不大。
学习、从不止步。
推荐去我的博客阅读更多:
1. Java JVM、集合、多线程、新特性系列教程
2. Spring MVC、Spring Boot、Spring Cloud 系列教程
3. Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4. Java、后端、架构、阿里巴巴等大厂最新面试题
觉得不错,别忘了点赞+转发哦!
【Part1——理论篇】 试想一个问题,如果我们要抓取某个微博大V微博的评论数据,应该怎么实现呢?最简单的做法就是找到微博评论数据接口,然后通过改变参数来获取最新数据并保存。首先从微博api寻找抓取评论的接口,如下图所示。 但是很不幸,该接口频率受限,抓不了几次就被禁了,还没有开始起飞,就凉凉了。 接下来小编又选择微博的移动端网站,先登录,然后找到我们想要抓取评论的微博,打开浏览器自带流量分析工具,一直下拉评论,找到评论数据接口,如下图所示。 之后点击“参数”选项卡,可以看到参数为下图所示的内容: 可以看到总共有4个参数,其中第1、2个参数为该条微博的id,就像人的身份证号一样,这个相当于该条微博的“身份证号”,max_id是变换页码的参数,每次都要变化,下次的max_id参数值在本次请求的返回数据中。 【Part2——实战篇】 有了上文的基础之后,下面我们开始撸代码,使用Python进行实现。 1、首先区分url,第一次不需要max_id,第二次需要用第一次返回的max_id。 2、请求的时候需要带上cookie数据,微博cookie的有效期比较长,足够抓一条微博的评论数据了,cookie数据可以从浏览器分析工具中找到。 3、然后将返回数据转换成json格式,取出评论内容、评论者昵称和评论时间等数据,输出结果如下图所示。 4、为了保存评论内容,我们要将评论中的表情去掉,使用正则表达式进行处理,如下图所示。 5、之后接着把内容保存到txt文件中,使用简单的open函数进行实现,如下图所示。 6、重点来了,通过此接口最多只能返回16页的数据(每页20条),网上也有说返回50页的,但是接口不同、返回的数据条数也不同,所以我加了个for循环,一步到位,遍历还是很给力的,如下图所示。 7、这里把函数命名为job。为了能够一直取出最新的数据,我们可以用schedule给程序加个定时功能,每隔10分钟或者半个小时抓1次,如下图所示。 8、对获取到的数据,做去重处理,如下图所示。如果评论已经在里边的话,就直接pass掉,如果没有的话,继续追加即可。 这项工作到此就基本完成了。 【Part3——总结篇】 这种方法虽然抓不全数据,但在这种微博的限制条件下,也是一种比较有效的方法。 最后如果您需要本文代码的话,请在后台回复“ 微博 ”二字,觉得不错,记得给个star噢~ 看完本文有收获?请转发分享给更多的人 IT共享之家 入群请在微信后台回复【入群】 想学习更多Python网络爬虫与数据挖掘知识,可前往专业网站: http://pdcfighting.com/
简单版 创建一张测试表 创建对应的JavaBean 创建mybatis配置文件,sql映射文件 测试
MyBatis操作数据库 创建MyBatis全局配置文件 MyBatis的全局配置文件包含了影响MyBatis行为甚深的设置(settings)和属性(properties)信息、如数据库连接池信息等。指导着MyBatis进行工作。我们可以参照官方文件的配置示例。 创建SQL映射文件 映射文件的作用就是定义Dao接口的实现类如何工作。这是我们使用MyBatis时编写的最多的文件。 根据全局配置文件,利用 SqlSessionFactoryBuilder 创建 SqlSessionFactory 。 String resource="mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resource); SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(is); 使用 SqlSessionFactory 获取 SqlSession 对象。一个 SqlSession 对象代表和数据库的一次会话。 SqlSession sqlSession = sf.openSession();
使用SqlSession:根据Id查询 SqlSession sqlSession = sf.openSession(); try { Employee employee = sqlSession.selectOne("helloworld.EmployeeMapper.getEmployeeById", 1); System.out.println(employee); } finally { sqlSession.close(); }
完整代码 加入相关的maven依赖
mysql mysql-connector-java 5.1.19 runtime org.mybatis mybatis 3.2.7 编写数据库脚本 create table t_employee( id int auto_increment, last_name varchar(255), email varchar(255), gender varchar(255), primary key(id) ); insert into t_employee (last_name,email,gender)values('tom','tom@qq.com','male'); select * from t_employee; 编写实体类 public class Employee { private Integer id; private String lastName; private String email; private String gender; // getter and setter // toString } 编写EmployeeMapper.xml
select id, last_name lastName, email,gender from t_employee where id = #{id} 编写MyBatis的全局配置文件 mybatis-config.xml
编写主程序 public class Test { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resource); SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = sf.openSession(); try { Employee employee = sqlSession.selectOne("helloworld.EmployeeMapper.getEmployeeById", 1); System.out.println(employee); } finally { sqlSession.close(); } } }
接口式编程 创建一个Dao接口 public interface EmployeeMapper { Employee getEmployeeById(Integer id); } 测试 SqlSession sqlSession = sf.openSession(); try { // 使用SqlSession获取映射器进行操作 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = mapper.getEmployeeById(1); System.out.println(employee); } finally { sqlSession.close(); }
SqlSession SqlSession的实例 是非线程安全的 ,因此是 不能被共享的 。 SqlSession每次使用完成后需要正确关闭,这个 关闭操作是必须的 。 SqlSession可以直接调用方法的id进行数据库操作。 但推荐使用SqlSession获取到Dao接口的代理类。执行代理对象的方法,可以更安全的进行类型检查操作。
什么是条件语句 JavaScript 语言中,条件语句(if 语句)常用于基于不同条件执行不同的动作。简单来讲就是判断给出的某个条件是否是正确的,如果条件正确要如何做,条件错误要如何做。举一个例子,例如现在有一个变量 age,给定一个条件语句为 “age是否大于18”,如果大于18 则可以玩游戏,否则不可以玩游戏。 var age = 20; if(age > 18){ console.log("你可以玩游戏哟"); }else{ console.log("未成年不可以玩游戏"); } 在 JavaScript 中,我们可以使用的 if 条件语句有如下几种: if 语句:当指定条件为 true 时,使用该语句来执行代码。 if-else 语句:当指定条件为 true 时执行 if 后面的代码,为false执行 else 后面的代码。 else if 语句:当要指定多个条件时,可以在 if 语句后面加 else if 语句。 if 语句 if 语句是最基本的条件语句,规定假如条件为 true 时,则执行花括号 {} 中的代码块。 语法如下所示: if (condition) { // 条件为 true 时要执行的代码块 } 其中 condition 表示条件,并且 if 只能小写,后面必须接英文的花括号 {} ,如果不按照语言要求写代码会报错。 示例: 例如我们给定一个条件,当变量 num 大于10,输出“嘻嘻嘻”,我们可以这样写: var num = 15; if(num > 10){ console.log("嘻嘻嘻"); } 在 VSCode 中执行上述代码,输出结果如下: 执行代码时,我们直接在 .js 文件中编写好代码,在 VSCode 的终端中使用 node test.js 命令来执行这段代码,其中 test.js 是文件名。 如果我们是在 HTML 中编写 JavaScript 代码,则需要将 JavaScript 代码写在