当前位置: 首页 > 编程日记 > 正文

码农技术炒股之路——配置管理器、日志管理器

配置管理器和日志管理器是项目中最为独立的模块。我们可以很方便将其剥离出来供其他Python工程使用。文件的重点将是介绍Python单例和logging模块的使用。(转载请指明出于breaksoftware的csdn博客)

配置管理器

在《码农技术炒股之路——架构和设计》中我们介绍过,配置管理将作为一个单例而存在。我尝试过各种Python单例的实现方法,发现都存在一些问题,不能保证单例的特性。后来对一些方案进行修改,得出下面一种可靠的方式:

instances = {}
def singleton(cls, *args, **kw):global instancesdef _singleton(*args, **kw): if cls.__name__ not in instances:  instances[cls.__name__] = cls(*args, **kw)return instances[cls.__name__]  return _singleton

我们可以使用下面方法进行测试

    @singletonclass singleton_test(object):def __init__(self, s_data):print "init"self._data = s_datadef run(self):print self._dataa = singleton_test("AAAAAAA")print aa.run()b = singleton_test("BBBBBBB")print bb.run()

其结果是

init
<__main__.singleton_test object at 0x021C57D0>
AAAAAAA
<__main__.singleton_test object at 0x021C57D0>
AAAAAAA

可见我们对通过修饰符修饰后的singleton_test类“构造”两次后得到的是同一个对象。

配置管理的实现其实非常简单,它就是一个使用singleton修饰、封装了ConfigParser的单例类

import ConfigParser
from singleton import singleton@singleton
class scheduler_frame_conf_inst():def __init__(self):self._cp = Nonedef load(self, conf_path):print("load frame conf %s" % conf_path)self._cp = ConfigParser.SafeConfigParser()self._cp.read(conf_path)def has_option(self, section_name, option_name):if self._cp:return self._cp.has_option(section_name, option_name)return Falsedef get(self, section_name, option_name):if self._cp:return self._cp.get(section_name, option_name)else:print("get conf %s %s" % (section_name, option_name))

load方法用于从指定路径加载工程配置。因为子模块都有自己的配置,且可能格式不一致,所以如果这些配置都放在一个文件中会显得非常杂乱。故工程的主配置文件保存是一组子模块配置文件路径的信息。子模块通过自己的配置解释规则去解释这些文件。

[frame_job]
conf_path = ./conf/frame_job.conf[frame_log]
conf_path = ./conf/log.conf[strategy_job]
conf_path = ./conf/strategy_job.conf[mysql_manager]
conf_path = ./conf/mysql_manager.conf[regulars]
conf_path = ./conf/regulars_manager.conf

上面配置分别对应于:系统任务管理器配置、日志管理器配置、普通任务管理器配置、数据库管理配置和正则管理器配置。

日志管理器

日志管理是通过封装Python的logging实现的。官方说明并没有对如何配置logging进行详细且准确的说明,所以我在完善这个模块时进行了若干次尝试,才得出正确的使用方法。

一般来说日志可以分为如下五种等级:

  • Info。用于记录一般性日志,如执行流程或者运行中的中间结果。如果线上日志量比较大,这种日志在上线前是可以关闭的。
  • Debug。用于记录辅助调试的信息。如果线上日志量比较大,这种日志在上线前是可以关闭的。
  • Warning。用于记录运行中我们可以接受的错误。一般发生这种错误只是一种预告,预示着某些方面出现了异常。
  • Error。用于记录运行中我们可以勉强接受的错误。这种错误的发生并不代表我们整个工程不可用,而是某些功能已经受限了。
  • Fatal。用于记录运行中我们不可以接收的错误。比如我们发现内存分配失败,就可以打印Fatal错误,并退出程序。因为内存都耗尽了,之后发生的什么事都不好预测,不如记录下错误信息后退出。

Python的logging库也支持上述等级。我们先看下封装后的日志类初始化操作

@singleton
class loggingex():def __init__(self, conf_path):error = 0while True:try:logging.config.fileConfig(conf_path)except IOError as e:if error > 1:raise eif 2 == e.errno:if os.path.isdir(e.filename):os.makedirs(e.filename)else:os.makedirs(os.path.dirname(e.filename))error = error + 1except Exception as e: raise eelse:break

其最核心的就是logging.config.fileConfig(conf_path)这行。它让logging库通过一个配置文件进行初始化,其中包括日志类型、日志格式和日志输出方式等信息。这些配置如何编写,以及如何结合代码使用,将是后文介绍的重点。

为了让封装的日志管理器有更强大的功能。我提出以下设计要求:

  1. Debug等级日志只打印在Console中。
  2. Info等级日志只打印在普通日志文件中。按小时切分。
  3. Warning、Error和Fatal等级日志只打印在错误日志文件中。按小时切分。

我们先看下针对Debug等级日志的配置方式。

打印在Console中的Debug等级日志

首先我们需要定义日志输出的格式。我们希望日志可以打印出:时间、等级、进程ID、线程ID和用户自定义消息。这样在配置文件中我们需要加入如下的内容

[formatter_LogFormatter]
format=%(asctime)s ^ %(levelname)s ^ %(process)d ^ %(thread)d ^ %(message)s
datefmt=
class=logging.Formatter

注意一下,这个节的名称是formatter_LogFormatter。但是实际我们之后要使用的名称只有下划线之后一节——LogFormatter。“formatter_”是格式配置名的固有信息,即任何格式配置都要使用它来开头。

然后我们要声明一个叫formatters节,其下keys包含了之前声明的格式配置名称

[formatters]
keys=LogFormatter

下一步我们要声明日志输出方式。因为Debug日志是输出到Console中的,所以我们使用的类是StreamHandler

[handler_ConsoleHandler]
class=StreamHandler
formatter=LogFormatter
level=DEBUG
args=(sys.stdout,)

上面一节记录了日志输出所使用的类名、所使用的格式、日志等级和输出参数。注意一下节的名称——handler_ConsoleHandler,和格式配置节名要以“formatter_”开始类似,输出方式的节名要以“handler_”开头,而实际的名称则是下划线之后的ConsoleHandler。

接下来我们需要声明一个叫handlers的节,其下keys包含了之前声明的输出方式配置名称

[handlers]
keys=ConsoleHandler

最后我们要声明一个叫loggers的节,其下keys字段它包含了日志对象的名称。这些名称用逗号分隔。在定义Debug等级日志对象名称前,我们先要定义一个叫root的日志对象

[loggers]
keys=root,LogDebug

root日志对象的配置要包含所有声明的初始方式信息,当前我们只有ConsoleHandler,于是这样配置

[logger_root]
level=NOTSET
handlers=ConsoleHandler

LogDebug的配置如下

[logger_LogDebug]
handlers=ConsoleHandler
qualname=logger_LogDebug
level=DEBUG
propagate=0

节名是以logger_开头,其后跟着在Loggers中声明的Debug日志对象名称LogDebug。handler指向向Console输出的输出方式名ConsoleHandler;qualname指定为该节节名。level设置为DEBUG。

在Python中,我们可以通过下面的方式使用该日志对象

    def log_debug(self, msg):log_debug = logging.getLogger('logger_LogDebug')        #https://docs.python.org/2/howto/logging.htmllog_debug.debug(msg)

打印在文件中、按时间切分的Info等级日志

数据的内容格式我们还是借用LogFormatter定义。因为这次是要往文件中输出,所以我们需要重新定义一种输出方式——FileNomalHandler。

[handler_FileNomalHandler]
class=logging.handlers.TimedRotatingFileHandler
formatter=LogFormatter
level=INFO
args=('./log/nomal.log.i', 'H', 1, 60)

因为要按时间维度切分,所以这次使用的类是logging.handlers.TimedRotatingFileHandler。然后我们再args中指定文件生成的路径和通用名,以及按多久时间切分一次。上述写法,将导致logging在工程的log目录下生成nomal.log.i.2017-01-01_23这样格式的数据

        别忘记修改handlers下的keys信息,要把新增的handler给加进去

[handlers]
keys=ConsoleHandler,FileNomalHandler,

以及在logger_root加入它

[logger_root]
level=NOTSET
handlers=ConsoleHandler,FileNomalHandler

相应的我们需要定义一个日志对象配置

[logger_LogInfo]
handlers=FileNomalHandler
qualname=logger_LogInfo
level=INFO
propagate=0

并在loggers下的keys中新增该对象名称

[loggers]
keys=root,LogDebug,LogInfo

打印在文件中、按时间切分的Warning、Error和Fatal等级日志

相应的配置修改和上面类似,当时要注意文件名称需要换一下。我把整个配置放在这面区域中

###############################################################################
[loggers]
keys=root,LogDebug,LogInfo,LogWarningErrorCritical,SQL_ERROR[logger_root]
level=NOTSET
handlers=ConsoleHandler,FileNomalHandler,FileErrorHandler[logger_LogDebug]
handlers=ConsoleHandler
qualname=logger_LogDebug
level=DEBUG
propagate=0[logger_LogInfo]
handlers=FileNomalHandler
qualname=logger_LogInfo
level=INFO
propagate=0[logger_LogWarningErrorCritical]
handlers=FileErrorHandler
qualname=logger_LogWarningErrorCritical
level=WARNING
propagate=0[logger_SQL_ERROR]
handlers=SaveErrorSQL_FileHandler
qualname=logger_SQL_ERROR
level=WARNING
propagate=0
##############################################################################################################################################################
[handlers]
keys=ConsoleHandler,FileNomalHandler,FileErrorHandler,SaveErrorSQL_FileHandler[handler_ConsoleHandler]
class=StreamHandler
formatter=LogFormatter
level=DEBUG
args=(sys.stdout,)[handler_FileNomalHandler]
class=logging.handlers.TimedRotatingFileHandler
formatter=LogFormatter
level=INFO
args=('./log/nomal.log.i', 'H', 1, 60)[handler_FileErrorHandler]
class=logging.handlers.TimedRotatingFileHandler
formatter=LogFormatter
level=WARNING
args=('./log/nomal.log.wec', 'H', 1, 60)[handler_SaveErrorSQL_FileHandler]
class=logging.handlers.TimedRotatingFileHandler
formatter=SQLLogFormatter
level=WARNING
args=('./log/sql_error.log', 'H', 1, 60)##############################################################################################################################################################
[formatters]
keys=LogFormatter,SQLLogFormatter[formatter_LogFormatter]
format=%(asctime)s ^ %(levelname)s ^ %(process)d ^ %(thread)d ^ %(message)s
datefmt=
class=logging.Formatter[formatter_SQLLogFormatter]
format=%(asctime)s ^ %(message)s
datefmt=
class=logging.Formatter
###############################################################################

上面配置中,我新增了一个打印SQL的日志对象配置。因为我们之后维护时可能需要把执行失败的SQL重新执行一遍,所以需要一个尽量简洁的文件格式。
        因为在日志中,我需要知道是哪个文件哪行出错,所以需要使用inspect库进行栈回溯。于是在用户自定义消息的基础上,在调用日志方法前,对原消息做些修改(除了SQL日志)以扩充信息。

完整的代码如下:

import os
import sys
import inspect
import logging
import logging.config
from singleton import singleton@singleton
class loggingex():def __init__(self, conf_path):error = 0while True:try:logging.config.fileConfig(conf_path)except IOError as e:if error > 1:raise eif 2 == e.errno:if os.path.isdir(e.filename):os.makedirs(e.filename)else:os.makedirs(os.path.dirname(e.filename))error = error + 1except Exception as e: raise eelse:breakdef log_debug(self, msg):log_debug = logging.getLogger('logger_LogDebug')        #https://docs.python.org/2/howto/logging.htmllog_debug.debug(msg)def log_info(self, msg):log_info = logging.getLogger('logger_LogInfo')log_info.info(msg)def log_warning(self, msg):log_warning_error_critical = logging.getLogger('logger_LogWarningErrorCritical')log_warning_error_critical.warning(msg)def log_error(self, msg):log_warning_error_critical = logging.getLogger('logger_LogWarningErrorCritical')log_warning_error_critical.error(msg)      def log_critical(self, msg):log_warning_error_critical = logging.getLogger('logger_LogWarningErrorCritical')log_warning_error_critical.critical(msg)def log_error_sql(self, msg):log_error_sql = logging.getLogger('logger_SQL_ERROR')log_error_sql.critical(msg)def LOG_INIT(conf_path):global logger_objlogger_obj = loggingex(conf_path)def modify_msg(msg):stack_info = inspect.stack()if len(stack_info) > 2:file_name = inspect.stack()[2][1]line = inspect.stack()[2][2]function_name = inspect.stack()[2][3]new_msg = file_name + " ^ " + function_name + " ^ " + str(line) + " ^ " + msgreturn new_msgdef LOG_DEBUG(msg):new_msg = modify_msg(msg)try:logger_obj.log_debug(new_msg)except Exception as e:print new_msgdef LOG_INFO(msg):new_msg = modify_msg(msg)try:logger_obj.log_info(new_msg)except Exception as e:print new_msgdef LOG_WARNING(msg):new_msg = modify_msg(msg)try:logger_obj.log_warning(new_msg)except Exception as e:print new_msgdef LOG_ERROR(msg):new_msg = modify_msg(msg)try:logger_obj.log_error(new_msg)except Exception as e:print new_msgdef LOG_CRITICAL(msg):new_msg = modify_msg(msg)try:logger_obj.log_critical(new_msg)except Exception as e:print new_msgdef LOG_ERROR_SQL(msg):try:logger_obj.log_error_sql(msg)except Exception as e:print msgif __name__ == "__main__":LOG_INIT("../../conf/log.conf")LOG_DEBUG('LOG_DEBUG')LOG_INFO('LOG_INFO')LOG_WARNING('LOG_WARNING')LOG_ERROR('LOG_ERROR')LOG_CRITICAL('LOG_CRITICAL')LOG_ERROR_SQL("Create XXX Error")

相关文章:

“数学不好,干啥都不行!”资深程序员:别再瞎努力了!

之前很多程序员读者向我们反馈&#xff1a;1&#xff09;做算法优化时&#xff0c;只能现搬书里的算法&#xff0c;遇到不一样的问题&#xff0c;就不会了。2&#xff09;面试一旦涉及到算法和数据结构&#xff0c;如果数学不行&#xff0c;面试基本就凉凉了。3&#xff09;算法…

受限列表 队列与栈

2019独角兽企业重金招聘Python工程师标准>>> 队列与栈为受限列表&#xff0c;队列为先入先出型列表&#xff0c;而栈为先入后出型列表&#xff0c;有关列表的实现可以查看 http://my.oschina.net/u/2011113/blog/514713 。 结构图为 Queue实现了IQueue接口&#xff…

码农技术炒股之路——数据库管理器、正则表达式管理器

我选用的数据库是Mysql。选用它是因为其可以满足我的需求&#xff0c;而且资料多。因为作为第三方工具&#xff0c;难免有一些配置问题。所以本文也会讲解一些和Mysql配置及开发相关的问题。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 数据库管理器 Mysq…

Overview of ISA and TMG Networking and ISA Networking Case Study (Part 1)

老方说&#xff1a;此篇文章摘自ISASERVER.ORG网站&#xff0c;出自Thomas Shinder达人之手。严重建议ISA爱好者看看。Published: Dec 16, 2008 Updated: Jan 21, 2009 Author: Thomas Shinder What ISA/TMG firewall Networks are about and how the firewall uses these ne…

阿里云免费开放一切AI算力,加速新型冠状病毒新药和疫苗研发

近日&#xff0c;阿里云宣布&#xff0c;为了帮助加速新药和疫苗研发&#xff0c;将向全球公共科研机构免费开放一切AI算力。目前&#xff0c;中国疾控中心已成功分离病毒&#xff0c;疫苗研发和药物筛选仍在争分夺秒地进行。新药和疫苗研发期间&#xff0c;需要进行大量的数据…

ASP.net(C#)批量上传图片(完整版)

来自&#xff1a;http://blog.itpub.net/9869521/viewspace-667955/ 这篇关于ASP.Net批量上传图片的文章写得非常好&#xff0c;偶尔在网上看到想转载到这里&#xff0c;却费劲了周折。为了更新这篇文章&#xff0c;我用了近半个小时&#xff0c;网上的转载都残缺不全&#xff…

码农技术炒股之路——任务管理器

系统任务和普通任务都是通过任务管理器调度的。它们的区别是&#xff1a;系统任务在程序运行后即不会被修改&#xff0c;而普通任务则会被修改。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 为什么要有这样的设计&#xff1f;因为我希望它是一个可以不用停…

面对新型肺炎疫情,AI能做什么?

作者 | 马超出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;根据最新的新型冠状病毒疫情通报&#xff0c;截至1月30日24时&#xff0c;国家卫生健康委公布确诊病例9692例&#xff0c;重症病例1527例&#xff0c;累计死亡病例213例&#xff0c;另有疑似病例15238例。为…

大家帮忙.谢谢!..(急急急急急)

大家帮忙.谢谢!..(急急急急急) Delphi / Windows SDK/APIhttp://www.delphi2007.net/DelphiDB/html/delphi_20061218224617231.htmlprocedure TForm1.Button4Click(Sender: TObject); var P : pstring; i, j : integer; begin GetMem(p, sizeof(stri…

HDU4866 Shooting (要持久段树)

意甲冠军&#xff1a; 给你一些并行x行轴。总是询问坐标x的顶部之前&#xff0c;k一个段高度&#xff0c;。标题是必须在线。思路&#xff1a; 首先要会可持久化线段树(又称主席树和函数式线段树)。不会的能够去做下POJ 2104。 把全部线段高度离散化&#xff0c;作为结点建线段…

C++过去的这一年

作者 | Bartek译者 | 苏本如&#xff0c;责编 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;【导读】本文旨在让我们回顾 C 2019年里的变化和发展&#xff01;我们将重点关注本年度里 C 上发生的重大事件&#xff0c;标准的发展&#xff0c;工具的变化等等…

码农技术炒股之路——抓取股票基本信息、实时交易信息、主力动向信息

从本节开始&#xff0c;我们开始介绍各个抓取和备份业务。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 因为我们数据库很多&#xff0c;数据库中表也很多&#xff0c;所以我们需要一个自动检测并创建数据库和表的功能。在《码农技术炒股之路——数据库管理…

TemplateBuilder

http://msdn.microsoft.com/zh-cn/vstudio/system.web.ui.templatebuilder_members(VS.85).aspx TemplateBuilder 成员TemplateBuilder 成员支持在生成模板及其包含的子控件时使用的页分析器。 下表列出了由 TemplateBuilder 类型公开的成员。 公共构造函数 名称 说明 Templat…

【iOS UI】iOS 9 GUI 资源分享

分享的内容包括一个【DesignCode-iOS-9-GUI】Sketch 文件&#xff0c; 和苹果官方释出的【SF-UI、SF-Compact】两种字体的安装包。 以上内容是正版、免费的 <a href "https://itunes.apple.com/cn/app/sketch-3/id852320343?mt12">Sketch</a> 是收费软…

反向R?削弱显著特征为细粒度分类带来提升 | AAAI 2020

作者 | VincentLee来源 | 晓飞的算法工程笔记导读&#xff1a;论文提出了类似于dropout作用的diversification block&#xff0c;通过抑制特征图的高响应区域来反向提高模型的特征提取能力&#xff0c;在损失函数方面&#xff0c;提出专注于top-k类别的gradient-boosting loss来…

C#初学——doWhile

继续上面的学习&#xff0c;这次的是流程控制&#xff0c;用dowhile&#xff0c;代码如下&#xff0c;还是用语言选择来作为事例的。using System; using System.Collections.Generic; using System.Text; namespace ConsoleApplication9 { class Program { static void Main(s…

码农技术炒股之路——实时交易信息、主力动向信息分库备份

一般来说&#xff0c;一个股票信息应该保存在一张表中。但是由于我机器资源限制&#xff0c;且我希望尽快频率的抓取数据。所以每天我将所有股票的实时交易信息放在daily_temp库中的一个以日期命名的表中。主力动向信息也是如此。但是盘后分析股票时&#xff0c;我们会以单只股…

数据预处理(完整步骤)

原文&#xff1a;http://dataunion.org/5009.html 一&#xff1a;为什么要预处理数据&#xff1f;&#xff08;1&#xff09;现实世界的数据是肮脏的&#xff08;不完整&#xff0c;含噪声&#xff0c;不一致&#xff09;&#xff08;2&#xff09;没有高质量的数据&#xff0c…

码农技术炒股之路——抓取日线数据、计算均线和除权数据

日线数据是股票每日收盘后的信息。这块数据不用实时抓取&#xff0c;所以并不占用宝贵的交易时间的资源。于是我们抓取完数据后直接往切片后的数据库中保存。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 抓取日线数据 我们先要获取今天有交易信息的股票代…

茫茫碌碌的日子

一连很好多天&#xff0c;都在为公司数据库基础构架升级的事情忙活着。升级的事情还是比较棘手的。需要升级硬件服务器&#xff0c;相关的存储&#xff0c;操作系统&#xff0c;数据库产品&#xff0c;涉及面非常多。当然烦心的事情就很多。作为线上生产系统&#xff0c;升级和…

Python PK C++,究竟谁更胜一筹?

作者 | Farhad Malik译者 | 弯月&#xff0c;编辑 | 屠敏来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;在编程生涯的早期阶段&#xff0c;我参与过一款C数学优化应用程序的开发&#xff0c;这个程序对性能的要求很高。至今我依然记得那段艰难的经历。在那个项目中…

oracle--查看表空间大小以及修改表空间大小

为什么80%的码农都做不了架构师&#xff1f;>>> 一.修改表空间大小 解决以上问题的办法&#xff1a;通过增大表空间即可解决&#xff0c;如下&#xff1a; Sql代码 使用dba用户登陆 sqlplus / as sysdba; 执行如下命令&#xff1a; SQL >…

同步、异步、堵塞、非堵塞和函数调用及I/O之间的组合概念

在我们工作和学习中&#xff0c;经常会接触到“同步”、“异步”、“堵塞”和“非堵塞”这些概念&#xff0c;但是并不是每个人都能将它们的关系和区别说清楚。本文将对这些基本概念进行讨论&#xff0c;以期让大家有更清楚的认识。&#xff08;转载请指明出于breaksoftware的c…

“抗击”新型肺炎!阿里达摩院研发AI算法,半小时完成疑似病例基因分析

利用技术辅助抗击疫情&#xff0c;阿里巴巴、百度等科技巨头各显身手。此前&#xff0c;AI科技大本营采访报道了阿里达摩院《数十名工程师作战5天&#xff0c;阿里达摩院连夜研发智能疫情机器人》一文&#xff0c;后者为了解决客服人力不足的局面&#xff0c;快速响应政府需求开…

反编译工具jad简单用法

反编译工具jad简单用法 下载地址&#xff1a;[url]http://58.251.57.206/down1?cidB99584EFA6154A13E5C0B273C3876BD4CC8CE672&t2&fmt&usrinput[/url]反编译工具jad &dt2002000一. 不用安装&#xff0c;只要解压就行&#xff08;有这样两个文件jad.exe&#x…

ubuntu 系统设置bugzilla制

随着时间的推移。在大脑中形成的记忆总会慢慢的淡去。人的记忆力就是这样。所以最好的办法就是形成博客去记录下来&#xff0c;一方面给自己以后回想用。一方面也算是自己的一个积累。所以一旦选择了一个行业&#xff0c;最好不要轻 易转行&#xff0c;由于非常多知识须要不断的…

静态分析C语言生成函数调用关系的利器——cflow

除了《静态分析C语言生成函数调用关系的利器——calltree》一文中介绍的calltree&#xff0c;我们还可以借助cflow辅助我们阅读理解代码。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; cflow的说明和安装cflow是一款静态分析C语言代码的工具&#xff0c;通过…

我在MongoDB年终大会上获二等奖文章:由数据迁移至MongoDB导致的数据不一致问题及解决方案...

作者 | 上海小胖来源 | Python专栏&#xff08;ID:xpchuiit)故事背景企业现状2019年年初&#xff0c;我接到了一个神秘电话&#xff0c;电话那头竟然准确的说出了我的昵称&#xff1a;上海小胖。我想这事情不简单&#xff0c;就回了句&#xff1a;您好&#xff0c;我是小胖&…

注意String.Split的几个重载形式

String.Split应该是经常用到的一个函数了,经常的有下面两种形式 public string[] Split(char[] separator, StringSplitOptions options); public string[] Split(string[] separator, StringSplitOptions options); 1. 多数情况下我们会使用第一种,代码里可能这…

如何让猎头找到你

如何让猎头找到你