前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[客户端应用程序读取的mycnf客户端部分...]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
Hive
...Hive通过HDFS读取和写入数据。如果HDFS服务出现问题,如NameNode宕机或权限设置不当,都会影响Hive对数据的访问。HDFS通过分块存储数据,并提供高吞吐量的数据访问,适合大规模数据集的存储和处理。 NameNode , HDFS的核心组件之一,负责管理文件系统的命名空间和客户端对文件的访问。在文章中,NameNode的状态直接决定了Hive能否正常访问HDFS。如果NameNode宕机或无法运行,Hive将无法读取HDFS中的数据。NameNode记录了每个文件的元信息,并维护文件系统树形结构以及文件块的位置信息。为了确保高可用性,通常会部署Secondary NameNode或启用HA(高可用)模式。
2025-04-01 16:11:37
105
幽谷听泉
转载文章
...点是支持一次写入多次读取且不支持文件修改,每个文件被划分为固定大小的块(如文中提到的128M),并分布在多台计算机节点上以实现高效的数据存储与访问。用户可以通过Hadoop提供的shell命令或客户端API进行文件上传、下载、复制、移动、删除等操作,并可以对文件权限、所有者、组以及其他元数据进行管理。 NameNode (NN) , 在HDFS架构中,NameNode是主控节点,负责整个文件系统的元数据管理。它维护着文件系统的目录树结构、文件到数据块的映射关系以及每个数据块所在的DataNode列表。当客户端发起文件操作请求时,首先会与NameNode交互获取必要的元数据信息,确保数据操作能够在正确的DataNode上执行。为了提高系统的可靠性和可用性,实际生产环境中通常会部署Secondary NameNode或启用HA高可用方案来辅助或替代NameNode工作。 Secondary NameNode (2NN) , Secondary NameNode是Hadoop早期版本中提供的一种辅助服务角色,用于减轻NameNode的工作负担,尤其是在定期合并FsImage(文件系统镜像)和EditLog(编辑日志)方面。虽然名为“Secondary”,但它并不是NameNode的实时备份节点,不能直接接管NameNode的工作。其主要职责是在预定的时间间隔内,从NameNode获取FsImage和EditLog,将它们合并成新的FsImage,然后将其推送给NameNode,这样NameNode就可以用新合并的FsImage替换旧的FsImage,从而释放一部分资源并减少系统恢复时间。随着Hadoop的发展,更先进的高可用(High Availability, HA)解决方案逐渐取代了Secondary NameNode的角色,例如使用多个Active/Standby NameNode节点。
2023-12-05 22:55:20
276
转载
转载文章
...令等不能让CGIs 程序模块获取到的东西 3.CGI配置文件cgi.cfg CGI 配置文件包含了一系列的设置,它们会影响CGIs程序模块.还有一些保存在主配置文件之中,因此CGI 程序会知道你是如何配置的Nagios并且在哪里保存了对象定义.最实际的例子就是,如果你想建立一个只有查看报警权限的用户,或者只有查看其中一些服务 器或者服务状态的权限,通过修改cfi.cfg可以灵活的控制web访问端的权限. 4.主机定义文件 定义你要监控的对象,这里定义的“host_name”被应用到其它的所有配置文件中,这个是我们配置Nagios 必须修改的配置文件. [root@test objects] vim hosts.cfg define host{ host_name Nagios-Server ; 设置主机的名字,该名字会出现在hostgroups.cfg 和services.cfg 中。注意,这个名字可以不是该服务器的主机名。 alias Nagios服务器 ; 别名 address 192.168.81.128 ; 主机的IP 地址 check_command check-host-alive ; 检查使用的命令,需要在命令定义文件定义,默认是定义好的。 check_interval 1 ; 检测的时间间隔 retry_interval 1 ; 检测失败后重试的时间间隔 max_check_attempts 3 ; 最大重试次数 check_period 24x7 ; 检测的时段 process_perf_data 0 retain_nonstatus_information 0 contact_groups sagroup ; 需要通知的联系组 notification_interval 30 ; 通知的时间间隔 notification_period 24x7 ; 通知的时间段 notification_options d,u,r ; 通知的选项 w—报警(warning),u—未知(unkown) c—严重(critical),r—从异常情况恢复正常 } define host{ host_name Nagios-Client alias Nagios客户端 address 192.168.81.129 check_command check-host-alive check_interval 1 retry_interval 1 max_check_attempts 3 check_period 24x7 process_perf_data 0 retain_nonstatus_information 0 contact_groups sagroup notification_interval 30 notification_period 24x7 notification_options d,u,r } 5.主机组定义文件 主机组定义文件,可以方便的将相同功能或者在应用上相同的服务器添加到一个主机组里,在WEB 界面可以通过HOST Group 方便的查看该组主机的状态信息. 将刚才定义的两个主机加入到主机组中,针对生产环境就像把所有的MySQL 服务器加到一个MySQL主机组里,将Oracle 服务器加到一个Oracle 主机组里,方便管理和查看,可以配置多个组. [root@test objects] vim hostgroups.cfg define hostgroup { hostgroup_name Nagios-Example ; 主机组名字 alias Nagios 主机组 ; 主机组别名 members Nagios-Server,Nagios-Client ; 主机组成员,用逗号隔开 } 6.服务定义文件 服务定义文件定义你需要监控的对象的服务,比如本例为检测主机是否存活,在后面会讲到如何监控其它服务,比如服务器负载、内存、磁盘等. [root@test objects] vim services.cfg define service { host_name Nagios-Server ; hosts.cfg 定义的主机名称 service_description check-host-alive ; 服务描述 check_period 24x7 ; 检测的时间段 max_check_attempts 3 ; 最大检测次数 normal_check_interval 3 retry_check_interval 2 contact_groups sagroup ; 发生故障通知的联系人组 notification_interval 10 notification_period 24x7 ; 通知的时间段 notification_options w,u,c,r check_command check-host-alive } define service { host_name Nagios-Client service_description check-host-alive check_period 24x7 max_check_attempts 3 normal_check_interval 3 retry_check_interval 2 contact_groups sagroup notification_interval 10 notification_period 24x7 notification_options w,u,c,r check_command check-host-alive } 7.服务组定义文件 和主机组一样,我们可以按需将相同的服务放入一个服务组,这样有规律的分类,便于我们在WEB端查看. [root@test objects] vim servicegroups.cfg define servicegroup{ servicegroup_name Host-Alive ; 组名 alias Host Alive ; 别名设置 members Nagios-Server,check-host-alive,Nagios-Client,check-host-alive } 8.联系人定义文件 定义发生故障时,需要通知的联系人信息.默认安装完成后,该配置文件已经存在,而且该文件不仅定义了联系人,也定义了联系人组,为了条理化的规划,我们把联系人定义放在contacts.cfg文件里,把联系人组放在contactgroups.cfg文件中. [root@test objects] mv contacts.cfg contacts.cfg.bak [root@test objects] vim contacts.cfg define contact{ contact_name maoxian ; 联系人的名字 alias maoxian ; 别名 service_notification_period 24x7 ; 服务报警的时间段 host_notification_period 24x7 ; 主机报警的时间段 service_notification_options w,u,c,r ; 就是在这四种情况下报警。 host_notification_options d,u,r ;同上。 服务报警发消息的命令,在command.cfg 中定义。 service_notification_commands notify-service-by-email 服务报警发消息的命令,在command.cfg 中定义。 host_notification_commands notify-host-by-email email wangyx088@gmail.com ; 定义邮件地址,也就是接收报警邮件地址。 } 9.联系人组定义文件 联系人组定义文件在实际应用中很有好处,我们可以把报警信息分级别,报联系人分级别存放在联系人组里面.例如:当发生一些警告信息的情况下,只发邮件给系统工程师联系人组即可,但是当发生重大问题,比如主机宕机了,可以发给领导联系人组. [root@test objects] vim contactgroups.cfg define contactgroup{ contactgroup_name sagroup ; 组名 alias Nagios Administrators ; 别名 members maoxian ; 联系人组成员 } 10.命令定义文件 commands.cfg 命令定义文件是Nagios中很重要的配置文件,所有在hosts.cfg还是services.cfg使用的命令都必须在命令定义文件中定义才能使用.默认情况下,范例配置文件已经配置好了日常需要使用的命令,所以一般不做修改. 11.时间段定义文件 timeperiods.cfg 我们在检测、通知、报警的时候都需要定义时间段,默认都是使用7x24,这也是默认配置文件里配置好的,如果你需要周六日不做检测,或者在制定的维护时间不做检测,都可以在该时间段定义文件定义好,这样固定维护的时候,就不会为大量的报警邮件或者短信烦恼 [root@test objects] cat timeperiods.cfg |grep -v "^" |grep -v "^$" 可以根据业务需求来更改 12.启动Nagios 1> 修改配置文件所有者 [root@test objects] chown -R nagios:nagios /usr/local/nagios/etc/objects/ 2> 检测配置是否正确 [root@test objects] /usr/local/nagios/bin/nagios -v /usr/local/nagios/etc/nagios.cfg 如果配置错误,会给出相应的报错信息,可以根据信息查找,注意,如果配置文件中有不可见字符也可以导致配置错误 3> 重载Nagios [root@test objects] service nagios restart 本文出自 “毛线的linux之路” 博客,请务必保留此出处http://maoxian.blog.51cto.com/4227070/756516 本篇文章为转载内容。原文链接:https://blog.csdn.net/gzh0222/article/details/8549202。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-16 20:48:42
483
转载
转载文章
...n系统上的开发环境及应用软件。 SSH服务(Secure Shell) , 一种网络协议,用于加密远程登录会话和命令执行过程,确保数据传输的安全性。在文中,通过启动SSH服务,用户可以在本地主机通过命令行工具安全地连接到deepin虚拟机进行远程操作和管理。 JDK(Java Development Kit) , Java开发工具包,包含了Java编译器、Java运行时环境(JRE)、以及一系列用于开发Java应用程序所需的工具和库文件。在文章中,安装JDK8是为了为deepin系统提供Java开发环境,支持基于Java语言的项目构建与运行。 Node.js , 一个开源、跨平台的JavaScript运行环境,允许开发者使用JavaScript编写服务器端代码,实现高性能、可伸缩的网络应用。文中提到安装Node.js,并配置淘宝源以优化npm包下载速度,为开发基于Node.js的后端服务或者全栈Web应用提供了基础条件。 Nginx , 一款高性能的HTTP和反向代理服务器,同时也可用作邮件代理服务器和负载均衡器。在该文场景下,Nginx被用作Web服务器,负责处理和分发来自客户端的HTTP请求,对于部署静态网站或作为Web应用的前端服务器非常适用。 PostgreSQL , 一种开源的关系型数据库管理系统,支持丰富的SQL标准和高级特性,如窗口函数、多版本并发控制等。在文中安装PostgreSQL是为了解决项目中的持久化存储需求,用于存放应用的数据。 Redis , 一个开源的、内存中的数据结构存储系统,常被用作数据库、缓存和消息中间件。在该篇文章里,Redis被安装和配置,用来提高应用的数据读写性能,尤其是在高并发场景下提供快速响应的能力。
2023-11-15 19:14:44
54
转载
转载文章
...式的Java Web应用程序框架,它主要用于简化构建企业级Java Web应用的工作。在本文中,Struts被用来实现文件上传和下载功能,通过定义Action类、配置struts.xml文件以及使用拦截器等机制,实现了对HTTP请求的接收、处理及响应。 MIME类型(Multipurpose Internet Mail Extensions) , MIME类型是一种标准,用于指定数据内容的格式类型,如文本、图像、视频或应用程序特定的数据。在Web开发中,特别是文件上传和下载场景,服务器端和客户端需要根据MIME类型来正确解析和处理不同类型的文件。例如,在Struts框架中,通过配置MIME类型可以指示浏览器如何打开或保存从服务器下载的文件。 拦截器(Interceptor) , 在Struts 2框架中,拦截器是一个可插拔的对象,它可以参与到Action执行的整个生命周期中,并在特定阶段进行预处理或后处理操作。文章中的LoginInterceptor就是一个自定义拦截器,它负责检查用户是否已经登录,只有当用户已登录时才允许继续执行后续的操作(如文件上传或下载)。通过这种方式,拦截器增强了系统的安全性,确保了只有经过验证的用户才能访问受限资源。
2023-11-12 20:53:42
140
转载
NodeJS
...。它允许服务器主动向客户端推送数据,而无需客户端发起请求,这使得实时数据传输成为可能。在本文中,WebSocket 被用来实现客户端与服务器之间的实时通信,用于推送监控数据如 CPU 使用率、内存占用等,从而避免了传统 HTTP 请求“请求-响应”模式的弊端,显著提升了数据更新的效率和用户体验。 Express , Express 是一个简洁而灵活的 Node.js Web 应用框架,提供一系列强大的特性来开发 Web 和移动应用程序。它有助于简化 HTTP 服务的搭建过程,提供路由定义、中间件支持以及模板引擎集成等功能。在本文中,Express 被用来快速搭建一个 HTTP 服务,为 WebSocket 提供基础的服务器支持,并通过 app.get() 方法处理根路径的请求,返回一个简单的 HTML 页面作为客户端入口。 进度条 , 进度条是一种常见的用户界面元素,通常用于表示某个操作的完成百分比或当前状态。在本文中,进度条被用来直观地展示从服务器接收到的监控数据,例如 CPU 使用率、内存占用和磁盘使用率。当客户端接收到 WebSocket 推送的随机生成的监控数据后,JavaScript 代码会解析数据并将对应的值设置为进度条的当前值,从而动态更新页面上的可视化效果,让用户能够清晰地了解系统的实时运行状况。
2025-05-06 16:24:48
72
清风徐来
转载文章
...术理解和屏幕阅读器的读取,从而提升网页的可访问性和用户体验。 结构元素 , 结构元素是HTML5新增或重新定义的一类标签,它们用来明确表示文档或应用程序中的逻辑部分和结构关系。例如,header元素用于标识区块的头部区域,通常包含标题和其他导航内容;footer元素则代表区块的底部,常常放置版权信息、联系信息等;nav元素专为导航链接设计,帮助构建页面内的导航菜单;而section元素用于划分不同的内容区块,增强了网页内容的模块化和组织性。 向后兼容 , 向后兼容是HTML5的一个重要特性,意味着新的HTML标准在设计时考虑到与旧版本浏览器的兼容问题,尽可能确保使用新特性的网页在不支持这些特性的老旧浏览器上也能以某种方式正确显示或至少不影响基本功能。比如,尽管HTML5引入了许多新的语义化元素,但在不支持这些元素的浏览器中,这些元素仍会被当作普通的块级元素处理,确保网页的基本结构和内容依然可以被用户看到和理解。
2023-07-16 11:42:34
252
转载
Hadoop
HDFS读取速度慢?别急,我们一起来找原因! 最近我在使用Hadoop的过程中,发现了一个让我非常头疼的问题——HDFS的读取速度慢得让人抓狂!作为一个对大数据技术充满热情的技术宅男(或者宅女),这种问题简直就像一道数学题里的“未知数”一样困扰着我。今天,我就想跟大家聊聊这个话题,希望能找到一些解决办法。 一、背景介绍 HDFS为什么重要? 首先,让我们简单回顾一下HDFS是什么。HDFS(Hadoop分布式文件系统)就像是Hadoop这个大家族里的“顶梁柱”之一,它专门用来管理海量的数据,就像一个超级大的仓库,能把成千上万的数据文件整整齐齐地存放在不同的电脑上,还能保证它们既安全又容易取用。简单来说,就是把一个大文件分成很多小块,然后把这些小块分散存储在不同的服务器上。这么做的好处嘛,简直太明显了!就算哪台机器突然“罢工”了,数据也能稳稳地保住,完全不会丢。而且呢,还能同时对这些数据进行处理,效率杠杠的! 但是,任何技术都有它的局限性。HDFS虽然功能强大,但在实际应用中也可能会遇到各种问题,比如读取速度慢。这可能是由于网络延迟、磁盘I/O瓶颈或者其他因素造成的。那么,具体有哪些原因会导致HDFS读取速度变慢呢?接下来,我们就来一一分析。 二、可能的原因及初步排查 1. 网络延迟过高 想象一下,你正在家里看电影,突然发现画面卡顿了,这是因为你的网络连接出了问题。同样地,在HDFS中,如果网络延迟过高,也会导致读取速度变慢。比如说,假如你的数据节点散落在天南海北的各种数据中心里,那数据跑来跑去就得花更多时间,就像你在城市两端都有家一样,来回折腾肯定比在同一个小区里串门费劲得多。 示例代码: java Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(conf); Path filePath = new Path("/user/hadoop/input/file.txt"); FSDataInputStream in = null; try { in = fs.open(filePath); byte[] buffer = new byte[1024]; int bytesRead = in.read(buffer); while (bytesRead != -1) { bytesRead = in.read(buffer); } } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } 这段代码展示了如何从HDFS中读取文件。如果你发现每次执行这段代码时都需要花费很长时间,那么很可能是网络延迟的问题。 2. 数据本地性不足 还记得小时候玩过的接力赛吗?如果接力棒总是从一个人传到另一个人再传回来,效率肯定不高。这就跟生活中的事儿一样啊,在HDFS里头,要是数据没分配到离客户端最近的那个数据节点上,那不是干等着嘛,多浪费时间呀! 解决方案: 可以通过调整副本策略来改善数据本地性。比如说,默认设置下,HDFS会把文件的备份分散存到集群里的不同机器上。不过呢,如果你想让这个过程变得更高效或者更适合自己的需求,完全可以去调整那个叫dfs.replication的参数! xml dfs.replication 3 3. 磁盘I/O瓶颈 磁盘读写速度是影响HDFS性能的一个重要因素。要是你的服务器用的是那些老掉牙的机械硬盘,那读文件的速度肯定就慢得像乌龟爬了。 实验验证: 为了测试磁盘I/O的影响,可以尝试将一部分数据迁移到SSD上进行对比实验。好啦,想象一下,你手头有一堆日志文件要对付。先把它们丢到普通的老硬盘(HDD)里待着,然后又挪到固态硬盘(SSD)上,看看读取速度变了多少。是不是感觉像在玩拼图游戏,只不过这次是在折腾文件呢? 三、进阶优化技巧 经过前面的分析,我们可以得出结论:要提高HDFS的读取速度,不仅仅需要关注硬件层面的问题,还需要从软件配置上下功夫。以下是一些更高级别的优化建议: 1. 增加带宽 带宽就像是高速公路的车道数量,车道越多,车辆通行就越顺畅。对于HDFS来说,增加带宽意味着可以同时传输更多的数据块。 实际操作: 联系你的网络管理员,询问是否有可能升级现有的网络基础设施,比如更换更快的交换机或者部署新的光纤线路。 2. 调整副本策略 默认情况下,HDFS会将每个文件的三个副本均匀分布在整个集群中。然而,在某些特殊场景下,这种做法并不一定是最优解。比如说,你家APP平时就爱扎堆在那几个服务器节点上干活儿,那就可以把副本都放一块儿,这样它们串门聊天、传文件啥的就方便多了,也不用跑太远浪费时间啦! 配置修改: xml dfs.block.local-path-access.enabled true 3. 使用缓存机制 缓存就像冰箱里的剩饭,拿出来就能直接吃,不用重新加热。HDFS也有类似的机制,叫做“DataNode Cache”。打开这个功能之后啊,那些经常用到的数据就会被暂时存到内存里,这样下次再用的时候就嗖的一下快多了! 启用步骤: bash hadoop dfsadmin -setSpaceQuota 100g /cachedir hadoop dfs -cache /inputfile /cachedir 四、总结与展望 通过今天的讨论,我相信大家都对HDFS读取速度慢的原因有了更深的理解。其实,无论是网络延迟、数据本地性还是磁盘I/O瓶颈,都不是不可克服的障碍。其实吧,只要咱们肯花点心思去琢磨、去试试,肯定能找出个适合自己情况的办法。 最后,我想说的是,作为一名技术人员,我们应该始终保持好奇心和探索精神。不要害怕失败,也不要急于求成,因为每一次挫折都是一次成长的机会。希望这篇文章能给大家带来启发,让我们一起努力,让Hadoop变得更加高效可靠吧! --- 以上就是我对“HDFS读取速度慢”的全部看法和建议。如果你还有其他想法或者遇到类似的问题,请随时留言交流。咱们共同进步,一起探索大数据世界的奥秘!
2025-05-04 16:24:39
103
月影清风
Netty
...件驱动的Java网络应用框架,简直是程序员的福音。话说回来,再厉害的工具也不是全能的啊,在那种超高并发、必须稳如老狗的场景里,总免不了会出点幺蛾子。今天咱们就来聊聊Netty是如何帮我们实现故障恢复的。 说到故障恢复,其实很多人可能会觉得这是个很玄乎的事情。但其实,Netty在这方面做得相当出色。它的设计思路非常人性化,既考虑了性能,也兼顾了稳定性。咱们可以从以下几个方面入手,看看它是怎么做到的。 --- 二、为什么需要故障恢复? 首先,咱们得明白一个问题:为什么我们需要故障恢复?在现实世界中,网络环境复杂多变,服务器宕机、网络抖动、数据丢失等情况随时随地可能发生。如果我们的程序没有应对这些问题的能力,那后果简直不堪设想! 想象一下,你正在做一个在线支付系统,用户刚输入完支付信息,结果服务器突然挂了,这笔交易失败了。哎呀,这要是让用户碰上了,那可真是抓狂了!所以啊,咱们得想点办法,给系统加点“容错”的本事,不然出了问题用户可就懵圈了。说白了,故障恢复不就是干这个的嘛,就是为了不让小问题变成大麻烦! Netty在这方面做得非常到位。它有一套挺管用的招数,就算网络突然“捣乱”或者出问题了,也能尽量把损失降到最低,然后赶紧恢复到正常状态,一点儿都不耽误事儿。接下来,咱们就一步步拆解这些机制。 --- 三、Netty的故障恢复机制 3.1 异常处理与重试机制 首先,咱们来看看Netty最基础的故障恢复手段:异常处理与重试机制。 Netty提供了一种优雅的方式来处理异常。好比说呗,当客户端和服务器之间的连接突然“闹别扭”了,Netty就会立刻反应过来,自动给我们发个提醒,就像是“叮咚!出问题啦!”这样,咱们就能赶紧去处理这个小麻烦了。具体代码如下: java // 定义一个ChannelFutureListener,用于监听连接状态 ChannelFuture future = channel.connect(remoteAddress); future.addListener((ChannelFutureListener) futureListen -> { if (!futureListen.isSuccess()) { System.out.println("连接失败,尝试重新连接..."); // 这里可以加入重试逻辑 scheduleRetry(); } }); 在这段代码中,我们通过addListener为连接操作添加了一个监听器。如果连接失败,我们会打印一条日志并调用scheduleRetry()方法。这个办法啊,特别适合用来搞那种简单的重试操作,比如说隔一会儿就再试试重新连上啥的,挺实用的! 当然啦,实际项目中可能需要更复杂的重试策略,比如指数退避算法。不过Netty已经为我们提供了足够的灵活性,剩下的就是根据需求去实现啦! --- 3.2 零拷贝技术与内存管理 接下来,咱们聊聊另一个关键点:零拷贝技术与内存管理。 在高并发场景下,频繁的数据传输会导致内存占用飙升,进而引发GC(垃圾回收)风暴。Netty通过零拷贝技术很好地解决了这个问题。简单说呢,零拷贝技术就像是给数据开了一条“直达通道”,不用再把数据倒来倒去地复制一遍,就能让它直接从这儿跑到那儿。 举个例子,假设我们要将文件内容发送给远程客户端,传统的做法是先将文件读取到内存中,然后再逐字节写入Socket输出流。这样不仅效率低下,还会浪费大量内存资源。Netty 这家伙可聪明了,它能用 FileRegion 类直接把文件塞进 Socket 通道里,这样就省得在内存里来回倒腾数据啦,效率蹭蹭往上涨! java // 使用FileRegion发送文件 FileInputStream fileInputStream = new FileInputStream(new File("data.txt")); FileRegion region = new DefaultFileRegion(fileInputStream.getChannel(), 0, fileSize); channel.writeAndFlush(region); 在这段代码中,我们利用DefaultFileRegion将文件内容直接传递给了Netty的通道,大大提升了传输效率。 --- 3.3 长连接复用与心跳检测 第三个重要的机制是长连接复用与心跳检测。 在高并发环境下,频繁创建和销毁TCP连接的成本是非常高的。所以啊,Netty这个家伙超级聪明,它能让一个TCP连接反复用,不用每次都重新建立新的连接。这就像是你跟朋友煲电话粥,不用每次说完一句话就挂断重拨,直接接着聊就行啦,省心又省资源! 与此同时,为了防止连接因为长时间闲置而失效,Netty还引入了心跳检测机制。简单说吧,就像你隔一会儿给对方发个“我还在线”的消息,就为了确认你们的联系没断就行啦! java // 设置心跳检测参数 Bootstrap bootstrap = new Bootstrap(); bootstrap.option(ChannelOption.SO_KEEPALIVE, true); // 开启TCP保活功能 bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); // 设置连接超时时间 在这里,我们通过设置SO_KEEPALIVE选项开启了TCP保活功能,并设置了最长的连接等待时间为5秒。这样一来,即使网络出现短暂中断,Netty也会自动尝试恢复连接。 --- 3.4 数据缓冲与批量处理 最后一个要点是数据缓冲与批量处理。 在网络通信过程中,数据的大小和频率往往不可控。要是每次传来的数据都一点点的,那老是去处理这些小碎数据,就会多花不少功夫啦。Netty通过内置的缓冲区(Buffer)解决了这个问题。 例如,我们可以使用ByteBuf来存储和处理接收到的数据。ByteBuf就像是内存管理界的“万金油”,不仅能够灵活地伸缩大小,还能轻松应对各种编码需求,简直是程序员手里的瑞士军刀! java // 创建一个ByteBuf实例 ByteBuf buffer = Unpooled.buffer(1024); buffer.writeBytes(data); // 处理数据 while (buffer.readableBytes() > 0) { byte b = buffer.readByte(); process(b); } 在这段代码中,我们首先创建了一个容量为1024字节的缓冲区,然后将接收到的数据写入其中。接着,我们通过循环逐个读取并处理缓冲区中的数据。这种方式不仅可以提高处理效率,还能更好地应对突发流量。 --- 四、总结与展望 好了,朋友们,今天的分享就到这里啦!通过上面的内容,相信大家对Netty的故障恢复机制有了更深的理解。不管是应对各种意外情况的异常处理,还是能让数据传输更高效的零拷贝技术,又或者是能重复利用长连接和设置数据缓冲这些招数,Netty可真是个实力派选手啊! 不过,技术的世界永远没有尽头。Netty虽然已经足够优秀,但在某些特殊场景下仍可能存在局限性。未来的日子啊,我超级期待能看到更多的小伙伴,在Netty的基础上大展身手,把自己的系统捯饬得既聪明又靠谱,简直就像给它装了个“智慧大脑”一样! 最后,我想说的是,技术的学习是一个不断探索的过程。希望大家能在实践中积累经验,在挑战中成长进步。如果你有任何疑问或者想法,欢迎随时留言交流哦! 祝大家都能写出又快又稳的代码,一起迈向技术巅峰吧!😎
2025-03-19 16:22:40
79
红尘漫步
Redis
... 同一时刻只能有一个客户端持有锁。 2. 可靠性 即使某个客户端崩溃了,锁也必须自动释放,避免死锁。 3. 公平性 排队等待的客户端应该按照请求顺序获取锁。 4. 可重入性(可选) 允许同一个客户端多次获取同一个锁。 现在我们就来一步步实现这些功能。 示例代码 1:最基本的分布式锁实现 python import redis import time def acquire_lock(redis_client, lock_key, timeout=10): 尝试加锁,设置过期时间为timeout秒 result = redis_client.set(lock_key, "locked", nx=True, ex=timeout) return bool(result) def release_lock(redis_client, lock_key): 使用Lua脚本来保证解锁的安全性 script = """ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ redis_client.eval(script, keys=[lock_key], args=["locked"]) 这段代码展示了最基础的分布式锁实现方式。我们用set命令设置了两个参数:一个是NX,意思是“只在key不存在的时候才创建”,这样就能避免重复创建;另一个是EX,给这个锁加了个过期时间,相当于设了个倒计时,万一客户端挂了或者出问题了,锁也能自动释放,就不会一直卡在那里变成死锁啦。最后,解锁的时候我们用了Lua脚本,这样可以保证操作的原子性。 --- 4. 如何解决锁的隔离性问题? 诶,说到这里,问题来了——如果两个不同的业务逻辑都需要用到同一个锁怎么办?比如订单系统和积分系统都想操作同一个用户的数据,这时候就需要考虑锁的隔离性了。换句话说,我们需要确保不同业务逻辑之间的锁不会互相干扰。 示例代码 2:基于命名空间的隔离策略 python def acquire_namespace_lock(redis_client, namespace, lock_name, timeout=10): 构造带命名空间的锁名称 lock_key = f"{namespace}:{lock_name}" result = redis_client.set(lock_key, "locked", nx=True, ex=timeout) return bool(result) def release_namespace_lock(redis_client, namespace, lock_name): lock_key = f"{namespace}:{lock_name}" script = """ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ redis_client.eval(script, keys=[lock_key], args=["locked"]) 在这个版本中,我们在锁的名字前面加上了命名空间前缀,比如orders:place_order和points:update_score。这样一来,不同业务逻辑就可以使用独立的锁,避免相互影响。 --- 5. 进阶 如何处理锁竞争与性能优化? 当然啦,现实中的分布式锁并不会总是那么顺利,有时候会出现大量请求同时争抢同一个锁的情况。这时我们可能需要引入队列机制或者批量处理的方式来降低系统的压力。 示例代码 3:使用Redis的List模拟队列 python def enqueue_request(redis_client, queue_key, request_data): redis_client.rpush(queue_key, request_data) def dequeue_request(redis_client, queue_key): return redis_client.lpop(queue_key) def process_queue(redis_client, lock_key, queue_key): while True: 先尝试获取锁 if not acquire_lock(redis_client, lock_key): time.sleep(0.1) 等待一段时间再重试 continue 获取队列中的第一个请求并处理 request = dequeue_request(redis_client, queue_key) if request: handle_request(request) 释放锁 release_lock(redis_client, lock_key) 这段代码展示了如何利用Redis的List结构来管理请求队列。想象一下,好多用户一起抢同一个东西,场面肯定乱哄哄的对吧?这时候,咱们就让他们老老实实排成一队,然后派一个专门的小哥挨个儿去处理他们的请求。这样一来,大家就不会互相“打架”了,事情也能更顺利地办妥。 --- 6. 总结与反思 兄弟们,通过今天的讨论,我相信大家都对如何在Redis中实现分布式锁有了更深刻的理解了吧?虽然Redis本身已经足够强大,但我们仍然需要根据实际需求对其进行适当的扩展和优化。比如刚才提到的命名空间隔离、队列机制等,这些都是非常实用的小技巧。 不过呢,我也希望大家能记住一点——技术永远不是一成不变的。业务越做越大,技术也日新月异的,咱们得不停地充电,学点新鲜玩意儿,试试新招数才行啊!就像今天的分布式锁一样,也许明天就会有更高效、更优雅的解决方案出现。所以,保持好奇心,勇于探索未知领域,这才是程序员最大的乐趣所在! 好了,今天就聊到这里啦,祝大家在编程的路上越走越远!如果有任何疑问或者想法,欢迎随时找我交流哦~
2025-04-22 16:00:29
58
寂静森林
转载文章
...解了GRUB引导加载程序、Windows系统安装和网络配置的详细内容后,读者可以进一步探索以下领域的最新动态与技术解析: 首先,对于GRUB及MBR/PBR相关的更深入研究,近期Linux内核社区对UEFI Secure Boot功能的安全性进行了强化升级,通过改进GRUB2的引导流程以适应最新的安全规范。同时,针对新型存储设备如NVMe SSDs,开发者正在优化GRUB对新硬件的支持,并研究如何确保在不同启动环境下MBR与GPT分区表的有效兼容。 其次,在Windows安装与部署方面,微软于今年发布了Windows 11新版操作系统,不仅引入了全新的用户界面设计,还在安装流程中融入了智能化安装选项和更快捷的驱动识别机制。此外,随着云技术和虚拟化技术的发展,Windows To Go和Azure Sphere等新型安装方式为系统部署提供了更多可能。 在网络配置方面,IPv6在全球范围内的普及速度加快,许多网络设备厂商正致力于提升产品对IPv6协议栈的支持。与此同时,Windows操作系统也在不断更新其网络功能,包括DHCPv6客户端功能增强、DNS-over-HTTPS(DoH)支持以及更完善的组播服务管理工具。此外,Windows防火墙已新增多项高级策略设置,以满足日益复杂的网络安全需求。 最后,关于Office软件中的实用技巧,Microsoft Office 365定期发布更新,提供更丰富的协作工具和智能功能,例如Excel的数据预处理和分析能力得到显著提升,Outlook则集成了更多智能邮件管理和日程安排助手。这些实时更新和新增功能有助于用户提高工作效率,应对各种办公场景挑战。 总之,随着科技不断发展,无论是操作系统的基础架构、网络配置的复杂度还是办公应用的智能化程度都在持续演进,关注行业动态和技术前沿将帮助我们更好地理解和运用文中提及的相关知识。
2023-09-10 16:27:10
270
转载
转载文章
...ption {// 设置编码格式response.setCharacterEncoding(utf8);//获取文件路径String fileName=request.getParameter("fileName");String drive=request.getParameter("drive");//参数校验log.info(fileName,drive);//完整路径(路径拼接待优化-前端传输优化-后端从新格式化 )String pathAll=drive+":\\"+fileName;log.info("pathAll{}",pathAll);Optional<String> pathFlag = Optional.ofNullable(pathAll);File file=null;if (pathFlag.isPresent()){//根据文件名,读取file流file = new File(pathAll);log.info("文件路径是{}",pathAll);if (!file.exists()){log.warn("文件不存在");return;} }else {//请输入文件名log.warn("请输入文件名!");return;}InputStream is = null;OutputStream os = null;try {//分片下载long fSize = file.length();//获取长度response.setContentType("application/x-download");String file_Name = URLEncoder.encode(file.getName(),"UTF-8");response.addHeader("Content-Disposition","attachment;filename="+fileName);//根据前端传来的Range 判断支不支持分片下载response.setHeader("Accept-Range","bytes");//获取文件大小//response.setHeader("fSize",String.valueOf(fSize));response.setHeader("fName",file_Name);//定义断点long pos = 0,last = fSize-1,sum = 0;//判断前端需不需要分片下载if (null != request.getHeader("Range")){response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);String numRange = request.getHeader("Range").replaceAll("bytes=","");String[] strRange = numRange.split("-");if (strRange.length == 2){pos = Long.parseLong(strRange[0].trim());last = Long.parseLong(strRange[1].trim());//若结束字节超出文件大小 取文件大小if (last>fSize-1){last = fSize-1;} }else {//若只给一个长度 开始位置一直到结束pos = Long.parseLong(numRange.replaceAll("-","").trim());} }long rangeLenght = last-pos+1;String contentRange = new StringBuffer("bytes").append(pos).append("-").append(last).append("/").append(fSize).toString();response.setHeader("Content-Range",contentRange);// response.setHeader("Content-Lenght",String.valueOf(rangeLenght));os = new BufferedOutputStream(response.getOutputStream());is = new BufferedInputStream(new FileInputStream(file));is.skip(pos);//跳过已读的文件(重点,跳过之前已经读过的文件)byte[] buffer = new byte[1024];int lenght = 0;//相等证明读完while (sum < rangeLenght){lenght = is.read(buffer,0, (rangeLenght-sum)<=buffer.length? (int) (rangeLenght - sum) :buffer.length);sum = sum+lenght;os.write(buffer,0,lenght);}log.info("下载完成");}finally {if (is!= null){is.close();}if (os!=null){os.close();} }} } 启动成功 Vue <html xmlns:th="http://www.thymeleaf.org"><head><meta charset="utf-8"/><title>狂神说Java-ES仿京东实战</title><link rel="stylesheet" th:href="@{/css/style.css}"/></head><body class="pg"><div class="page" id="app"><div id="mallPage" class=" mallist tmall- page-not-market "><!-- 头部搜索 --><div id="header" class=" header-list-app"><div class="headerLayout"><div class="headerCon "><!-- Logo--><h1 id="mallLogo"><img th:src="@{/images/jdlogo.png}" alt=""></h1><div class="header-extra"><!--搜索--><div id="mallSearch" class="mall-search"><form name="searchTop" class="mallSearch-form clearfix"><fieldset><legend>天猫搜索</legend><div class="mallSearch-input clearfix"><div class="s-combobox" id="s-combobox-685"><div class="s-combobox-input-wrap"><input v-model="keyword" type="text" autocomplete="off" value="java" id="mq"class="s-combobox-input" aria-haspopup="true"></div></div><button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button></div></fieldset></form><ul class="relKeyTop"><li><a>狂神说Java</a></li><li><a>狂神说前端</a></li><li><a>狂神说Linux</a></li><li><a>狂神说大数据</a></li><li><a>狂神聊理财</a></li></ul></div></div></div></div></div><el-button @click="download" id="download">下载</el-button><!-- <el-button @click="concurrenceDownload" >并发下载测试</el-button>--><el-button @click="stop">停止</el-button><el-button @click="start">开始</el-button>{ {fileFinalOffset} }{ {contentList} }<el-progress type="circle" :percentage="percentage"></el-progress></div><!--前端使用Vue,实现前后端分离--><script th:src="@{/js/axios.min.js}"></script><script th:src="@{/js/vue.min.js}"></script><!-- 引入样式 --><link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"><!-- 引入组件库 --><script src="https://unpkg.com/element-ui/lib/index.js"></script><script>new Vue({ el: 'app',data: {keyword: '', //搜索关键字results: [] ,//搜索结果percentage: 0, // 下载进度filesCurrentPage:0,//文件开始偏移量fileFinalOffset:0, //文件最后偏移量stopRecursiveTags:true, //停止递归标签,默认是true 继续进行递归contentList: [], // 文件流数组breakpointResumeTags:false, //断点续传标签,默认是false 不进行断点续传temp:[],fileMap:new Map(),timer:null, //定时器名称},methods: {//根据关键字搜索商品信息searchKey(){var keyword=this.keyword;axios.get('/search/JD/search/'+keyword+"/1/10").then(res=>{this.results=res.data;//绑定数据console.log(this.results)console.table(this.results)})},//停止下载stop(){//改变递归标签为falsethis.stopRecursiveTags=false;},//开始下载start(){//重置递归标签为true 最后进行合并this.stopRecursiveTags=true;//重置断点续传标签this.breakpointResumeTags=true;//重新调用下载方法this.download();},// 分段下载需要后端配合download() {// 下载地址const url = "/down?fileName="+this.keyword.trim()+"&drive=E";console.log(url)const chunkSize = 1024 1024 50; // 单个分段大小,这里测试用100Mlet filesTotalSize = chunkSize; // 安装包总大小,默认100Mlet filesPages = 1; // 总共分几段下载//计算百分比之前先清空上次的if(this.percentage==100){this.percentage=0;}let sentAxios = (num) => {let rande = chunkSize;//判断是否开启了断点续传(断点续传没法并行-需要上次请求的结果作为参数)if (this.breakpointResumeTags){rande = ${Number(this.fileFinalOffset)+1}-${num chunkSize + 1};}else {if (num) {rande = ${(num - 1) chunkSize + 2}-${num chunkSize + 1};} else {// 第一次0-1方便获取总数,计算下载进度,每段下载字节范围区间rande = "0-1";} }let headers = {range: rande,};axios({method: "get",url: url.trim(),async: true,data: {},headers: headers,responseType: "blob"}).then((response) => {if (response.status == 200 || response.status == 206) {//检查了下才发现,后端对文件流做了一层封装,所以将content指向response.data即可const content = response.data;//截取文件总长度和最后偏移量let result= response.headers["content-range"].split("/");// 获取文件总大小,方便计算下载百分比filesTotalSize =result[1];//获取最后一片文件位置,用于断点续传this.fileFinalOffset=result[0].split("-")[1]// 计算总共页数,向上取整filesPages = Math.ceil(filesTotalSize / chunkSize);// 文件流数组this.contentList.push(content);// 递归获取文件数据(判断是否要继续递归)if (this.filesCurrentPage < filesPages&&this.stopRecursiveTags==true) {this.filesCurrentPage++;//计算下载百分比 当前下载的片数/总片数this.percentage=Number((this.contentList.length/filesPages)100).toFixed(2);sentAxios(this.filesCurrentPage);//结束递归return;}//递归标签为true 才进行下载if (this.stopRecursiveTags){// 文件名称const fileName =decodeURIComponent(response.headers["fname"]);//构造一个blob对象来处理数据const blob = new Blob(this.contentList);//对于<a>标签,只有 Firefox 和 Chrome(内核) 支持 download 属性//IE10以上支持blob但是依然不支持downloadif ("download" in document.createElement("a")) {//支持a标签download的浏览器const link = document.createElement("a"); //创建a标签link.download = fileName; //a标签添加属性link.style.display = "none";link.href = URL.createObjectURL(blob);document.body.appendChild(link);link.click(); //执行下载URL.revokeObjectURL(link.href); //释放urldocument.body.removeChild(link); //释放标签} else {//其他浏览器navigator.msSaveBlob(blob, fileName);} }} else {//调用暂停方法,记录当前下载位置console.log("下载失败")} }).catch(function (error) {console.log(error);});};// 第一次获取数据方便获取总数sentAxios(this.filesCurrentPage);this.$message({message: '文件开始下载!',type: 'success'});} }})</script></body></html> 本篇文章为转载内容。原文链接:https://blog.csdn.net/kangshihang1998/article/details/129407214。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-19 08:12:45
546
转载
转载文章
... 22 5.1 前台部分 22 5.1.1 前台总体框架 22 5.1.2 商城首页 22 5.1.3 产品详情页 23 5.1.4 评价 23 5.2 后台部分 24 5.2.1 后台主页 24 5.2.2 后台评价管理 25 5.2.3 商品管理 25 5.2.4 商品修改 26 5.2.5 分类管理 26 5.2.6 订单管理 27 5.2.7 腕表购物车管理 27 6系统测试 28 6.1系统测试的意义 28 6.2性能测试 29 6.3测试分析 29 总 结 30 致 谢 31 参考文献 31 3系统分析 3.1 可行性分析 腕表交易系统主要目标是实现网上展示腕表交易系统信息,购买腕表产品。在确定了目标后,我们从以下四方面对能否实现本系统目标进行可行性分析。 3.1.1 技术可行性 腕表交易系统主要采用Java技术,基于B/S结构,MYSQL数据库,主要包括前端应用程序的开发以及后台数据库的建立和维护两个方面。对于应用程序的开发要求具备功能要完备、使用应简单等特点,而对于数据库的建立和维护则要求建立一个数据完整性强、数据安全性好、数据稳定性高的库。腕表交易系统的开发技术具有很高可行性,且开发人员掌握了一定的开发技术,所以系统的开发具有可行性。 3.1.2 操作可行性 腕表交易系统的登录界面简单易于操作,采用常见的界面窗口来登录界面,通过电脑进行访问操作,会员只要平时使用过电脑都能进行访问操作。此系统的开发采用PHP语言开发,基于B/S结构,这些开发环境使系统更加完善。本系统具有易操作、易管理、交互性好的特点,在操作上是非常简单的。因此本系统可以进行开发。 3.1.3 经济可行性 腕表交易系统是基于B/S模式,采用MYSQL数据库储存数据,所要求的硬件和软件环境,市场上都很容易购买,程序开发主要是管理系统的开发和维护。所以程序在开发人力、财力上要求不高,而且此系统不是很复杂,开发周期短,在经济方面具有较高的可行性。 3.1.4 法律可行性 此腕表交易系统是自己设计的管理系统,具有很大的实际意义。开发环境软件和使用的数据库都是开源代码,因此对这个系统进行开发与普通的系统软件设计存在很大不同,没有侵权等问题,在法律上完全具有可行性。 综上所述,腕表交易系统在技术、经济、操作和法律上都具有很高的可行性,开发此程序是很必要的。 3.2 腕表交易系统功能需求分析 此基于SSM的腕表交易系统分前台功能和后台功能: 1)前台部分由用户使用,主要包括用户注册,腕表购物车管理,订单管理,个人资料管理,留言板管理 2)后台部分由管理员使用,主要包括管理员身份验证,商品管理,处理订单,用户信息管理,连接信息管理 3.3 数据库需求分析 数据库的设计通常是以一个已经存在的数据库管理系统为基础的,常用的数据库管理系统有MYSQL,SQL,Oracle等。我采用了Mysql数据库管理系统,建立的数据库名为db_business。 整个系统功能需要以下数据项: 用户:用户id、用户名称、登录密码、用户真实姓名、性别、邮箱地址、联系地址、联系电话、密码问题、答案、注册时间。 留言:主题id、作者姓名、Email、主题名称、留言内容、发布时间。 商品:商品id、名称、价格、图片路径、类型、简要介绍、存储地址、上传人姓名、发布时间、是否推荐。 订单:订单号、用户名、真实姓名、订购日期、Email、地址、邮编、付款方式、联系方式、运送方式、订单核对、其他。 管理员:管理员id、管理员名称、管理员密码。 公告:公告内容、公告时间。 4系统设计 4.1 系统功能模块设计 功能结构图如下: 图9 功能模块设计图 从图中可以看出,网上腕表交易系统可以分为前台和后台两个部分,前台部分由用户使用,主要包括用户注册,生成订单,腕表购物车管理,查看腕表购物车,查看留言,订购产品,订单查询和发布留言7个模块;本文转载自http://www.biyezuopin.vip/onews.asp?id=11975后台部分由管理员使用,主要包括管理员身份验证,商品管理,处理订单,用户信息管理,连接信息管理5个模块。 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><base href="<%=basePath%>"/><title>腕表商城</title><meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"><meta http-equiv="description" content="This is my page"><meta name="viewport" content="width=device-width, initial-scale=1"><!-- Favicon --><link rel="shortcut icon" type="image/x-icon" href="img/favicon.png"><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/font-awesome.min.css" /><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/bootstrap.css" /><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/style.css"><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/magnific-popup.css"><link rel="stylesheet" type="text/css" href="<%=basePath%>home/css/owl.carousel.css"><script type="text/javascript">function getprofenlei(){ var html = ""; $.ajax({url: "leixing.action?list&page=0&rows=30",type: "POST",async: false, contentType: "application/x-www-form-urlencoded;charset=UTF-8",success: function (data) { $.each(data.rows, function (i, val) { html += ' <li ><a href="home/search.jsp?fenlei='+val.id+'" >'+val.a1+' </a></li>';})} }); $("fenlei").html(html);}function gettop1(){var html = "";$.ajax({url: "leixing.action?list&page=0&rows=10",type: "POST",async: false,success: function (data) {var total='';//<div class="tab-pane active" id="nArrivals">// <div class="nArrivals owl-carousel" id="top1">$.each(data.rows, function (i, valmm) { html+='<div class="nArrivals owl-carousel" id="'+valmm.id+'">';$.ajax({url: "shangpin.action?list&page=0&rows=10",type: "POST",async: false,data: { fenlei:valmm.id },success: function (data) { $.each(data.rows, function (i, val) { html+='<div class="product-grid">'+'<div class="item">'+' <div class="product-thumb">'+' <div class="image product-imageblock"> <a href="home/details.jsp?ids='+val.id+'"> <img data-name="product_image" style="width:223px;height:285px;" src="<%=basePath%>'+val.tupian1+'" alt="iPod Classic" title="iPod Classic" class="img-responsive"> <img style="width:223px;height:285px;" src="<%=basePath%>'+val.tupian1+'" alt="iPod Classic" title="iPod Classic" class="img-responsive"> </a> </div>'+' <div class="caption product-detail text-left">'+' <h6 data-name="product_name" class="product-name mt_20"><a href="home/details.jsp?ids='+val.id+'" title="Casual Shirt With Ruffle Hem">'+val.biaoti+'</a></h6>'+' <div class="rating"> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-1x"></i></span> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-1x"></i></span> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-1x"></i></span> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-1x"></i></span> <span class="fa fa-stack"><i class="fa fa-star-o fa-stack-1x"></i><i class="fa fa-star fa-stack-x"></i></span> </div>'+'<span class="price"><span class="amount"><span class="currencySymbol">$</span>'+val.jiage+'</span>'+'</span>'+'<div class="button-group text-center">'+' <div class="wishlist"><a href="home/details.jsp?ids='+val.id+'"><span>wishlist</span></a></div>'+'<div class="quickview"><a href="home/details.jsp?ids='+val.id+'"><span>Quick View</span></a></div>'+'<div class="compare"><a href="home/details.jsp?ids='+val.id+'"><span>Compare</span></a></div>'+'<div class="add-to-cart"><a href="home/details.jsp?ids='+val.id+'"><span>Add to cart</span></a></div>'+'</div>'+'</div>'+'</div>'+'</div>'+' </div>'; })html+='</div>'; } })}) $("nArrivals").html(html); } }); 本篇文章为转载内容。原文链接:https://blog.csdn.net/newlw/article/details/127608579。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-21 18:24:50
66
转载
Netty
...a的异步事件驱动网络应用框架。它可以帮助开发者快速构建可扩展的服务器端应用程序。想象一下,你正在开发一个需要处理海量数据的大数据流处理平台,这时候Netty就显得尤为重要了。它不仅能够帮助我们高效地管理网络连接,还能让我们轻松应对高并发场景。 我第一次接触Netty的时候,真的被它的灵活性震撼到了。哎,说到程序员的烦心事,那肯定得提一提怎么让程序在被成千上万的人同时戳的时候还能稳如老狗啊!这事儿真心让人头大,尤其是看着服务器指标噌噌往上涨,心里直打鼓,生怕哪一秒就崩了。而Netty通过非阻塞I/O模型,完美解决了这个问题。这就像是一个超级能干的服务员,能够在同一时间同时服务上万个客人,而且就算有个客人纠结半天点菜(也就是某个请求拖拉),也不会耽误其他客人的服务,更不会让整个餐厅都停下来等他。 举个栗子: java EventLoopGroup bossGroup = new NioEventLoopGroup(); // 主线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 工作线程组 try { ServerBootstrap b = new ServerBootstrap(); // 启动辅助类 b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 使用NIO通道 .childHandler(new ChannelInitializer() { // 子处理器 @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new StringDecoder()); // 解码器 ch.pipeline().addLast(new StringEncoder()); // 编码器 ch.pipeline().addLast(new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Received message: " + msg); ctx.writeAndFlush("Echo: " + msg); // 回显消息 } }); } }); ChannelFuture f = b.bind(8080).sync(); // 绑定端口并同步等待完成 f.channel().closeFuture().sync(); // 等待服务关闭 } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } 这段代码展示了如何用Netty创建一个简单的TCP服务器。话说回来,Netty这家伙简直太贴心了,它的API设计得特别直观,想设置啥处理器或者监听事件都超简单,用起来完全没压力,感觉开发效率直接拉满! 2. 大数据流处理平台中的挑战 接下来,我们聊聊大数据流处理平台面临的挑战。在这个领域,我们通常会遇到以下几个问题: - 高吞吐量:我们需要处理每秒数百万条甚至更多的数据记录。 - 低延迟:对于某些实时应用场景(如股票交易),毫秒级的延迟都是不可接受的。 - 可靠性:数据不能丢失,必须保证至少一次投递。 - 扩展性:随着业务增长,系统需要能够无缝扩容。 这些问题听起来是不是很让人头大?但别担心,Netty正是为此而生的! 让我分享一个小故事吧。嘿,有次我正忙着弄个日志收集系统,结果一测试才发现,这传统的阻塞式I/O模型简直是“人形瓶颈”啊!流量一大就直接崩溃,完全hold不住那个高峰时刻,简直让人头大!于是,我开始研究Netty,并将其引入到项目中。哈哈,结果怎么样?系统的性能直接翻了三倍!这下我可真服了,选对工具真的太重要了,感觉像是找到了开挂的装备一样爽。 为了更好地理解这些挑战,我们可以看看下面这段代码,这是Netty中用来实现高性能读写的示例: java public class HighThroughputHandler extends ChannelInboundHandlerAdapter { private final ByteBuf buffer; public HighThroughputHandler() { buffer = Unpooled.buffer(1024); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < 1024; i++) { buffer.writeByte((byte) i); } ctx.writeAndFlush(buffer.retain()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 在这段代码中,我们创建了一个自定义的处理器HighThroughputHandler,它能够在每次接收到数据后立即转发出去,从而实现高吞吐量的传输。 3. Netty如何优化大数据流处理平台? 现在,让我们进入正题——Netty是如何具体优化大数据流处理平台的呢? 3.1 异步非阻塞I/O Netty的核心优势在于其异步非阻塞I/O模型。这就相当于,当有请求进来的时候,Netty可不会给每个连接都专门安排一个“服务员”,而是让这些连接共用一个“服务团队”。这样既能节省人手,又能高效处理各种任务,多划算啊!这样做的好处是显著减少了内存占用和上下文切换开销。 假设你的大数据流处理平台每天要处理数十亿条数据记录,采用传统的阻塞式I/O模型,很可能早就崩溃了。而Netty则可以通过单线程处理数千个连接,极大地提高了资源利用率。 3.2 零拷贝技术 另一个让Netty脱颖而出的特点是零拷贝技术。嘿,咱们就拿快递打个比方吧!想象一下,你在家里等着收快递,但这个快递特别麻烦——它得先从仓库(相当于内核空间)送到快递员手里(用户空间),然后快递员再把东西送回到你家(又回到内核空间)。这就像是数据在网络通信里来回折腾了好几趟,一会儿在系统深处待着,一会儿又被搬出来给应用用,真是费劲啊!这种操作不仅耗时,还会消耗大量CPU资源。 Netty通过ZeroCopy机制,直接将数据从文件系统传递到网络套接字,避免了不必要的内存拷贝。这种做法不仅加快了数据传输速度,还降低了系统的整体负载。 这里有一个实际的例子: java FileRegion region = new DefaultFileRegion(fileChannel, 0, fileSize); ctx.write(region); 上述代码展示了如何利用Netty的零拷贝功能发送大文件,无需手动加载整个文件到内存中。 3.3 灵活的消息编解码 在大数据流处理平台中,数据格式多种多样,可能包括JSON、Protobuf、Avro等。Netty提供了一套强大的消息编解码框架,允许开发者根据需求自由定制解码逻辑。 例如,如果你的数据是以Protobuf格式传输的,可以这样做: java public class ProtobufDecoder extends MessageToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { byte[] data = new byte[in.readableBytes()]; in.readBytes(data); MyProtoMessage message = MyProtoMessage.parseFrom(data); out.add(message); } } 通过这种方式,我们可以轻松解析复杂的数据结构,同时保持代码的整洁性和可维护性。 3.4 容错与重试机制 最后但同样重要的是,Netty内置了强大的容错与重试机制。在网上聊天或者传输文件的时候,有时候会出现消息没发出去、对方迟迟收不到的情况,就像快递丢了或者送慢了。Netty这个小助手可机灵了,它会赶紧发现这些问题,然后试着帮咱们把没送到的消息重新发一遍,就像是给快递员多派一个人手,保证咱们的信息能安全顺利地到达目的地。 java RetryHandler retryHandler = new RetryHandler(maxRetries); ctx.pipeline().addFirst(retryHandler); 上面这段代码展示了如何添加一个重试处理器到Netty的管道中,让它在遇到错误时自动重试。 4. 总结与展望 经过这一番探讨,相信大家已经对Netty及其在大数据流处理平台中的应用有了更深入的理解。Netty可不只是个工具库啊,它更像是个靠谱的小伙伴,陪着咱们一起在高性能网络编程的大海里劈波斩浪、寻宝探险! 当然,Netty也有它的局限性。比如说啊,遇到那种超级复杂的业务场景,你可能就得绞尽脑汁写一堆专门定制的代码,不然根本搞不定。还有呢,这门技术的学习难度有点大,刚上手的小白很容易觉得晕头转向,不知道该怎么下手。但我相信,只要坚持实践,总有一天你会爱上它。 未来,随着5G、物联网等新技术的发展,大数据流处理的需求将会更加旺盛。而Netty凭借其卓越的性能和灵活性,必将在这一领域继续发光发热。所以,不妨大胆拥抱Netty吧,它会让你的开发之旅变得更加精彩! 好了,今天的分享就到这里啦!如果你有任何疑问或者想法,欢迎随时交流。记住,编程之路没有终点,只有不断前进的脚步。加油,朋友们!
2025-04-26 15:51:26
46
青山绿水
转载文章
...的工作感悟,分享内容部分来自成功总结,更多是来自失败的反思,希望我踩过的坑大家可以不用再踩。 “提出问题”难于“解决问题” 作为技术人员,我们已经习惯于作为问题的解决者给出设计方案,而很少以问题提出者的身份去思考设计方案。团队中常见的典型矛盾,就是产品团队和研发团队之间的矛盾。作为研发团队,我们常吐槽产品团队的需求不合理、不懂技术等。其实我们可以试着把自己的工作再往前移一下,不仅仅是去设计架构、实现产品的需求,同时也试着去实现客户的需求,甚至发现潜在的需求。 这时我们就变成了在设计上提出问题的人,你会发现提出问题的同时,在很多时候也需要同样深入的思考。设计一个好的问题,甚至比解决问题更难。 其实即便是软件开发领域的大神 Frederick P. Brooks Jr.(《人月神话》的作者)也会有同样的感叹。 “The hardest part of design is deciding what to design.” – 《The design of design》, by Frederick P. Brooks Jr. 决定“不要什么”比“要什么”更难 也许是由于人性的贪婪,对于软件系统我们同样想要更多:更多功能、更好的性能、更好的伸缩性、扩展性等等。作为软件架构师要明白软件架构设计就是一种取舍或平衡。当大家都在往里面加东西的时候,架构师更应该来做这个说“不”的人。 软件设计和定义过程中存在很多取舍,例如: 完善功能和尽早发布的取舍。 伸缩性和性能的取舍。 著名的 CAP 原则,就是一个很好的取舍指导策略。为了更好的取舍,保持架构风格的一致性,在一开始架构师就应该根据系统的实际需求来定义一些取舍的原则,如: 数据一致性拥有最高优先级。 提前发布核心功能优于完整发布等。 非功能性需求决定架构 因为软件是为了满足客户的功能性需求的,所以很多设计人员可能会认为架构是由要实现的功能性需求决定的。但实际上真正决定软件架构的其实是非功能性需求。 架构师要更加关注非功能性需求,常见的非功能性包括:性能,伸缩性,扩展性和可维护性等,甚至还包括团队技术水平和发布时间要求。能实现功能的设计总是有很多,考虑了非功能性需求后才能筛选出最合适的设计。 以上架构模式来自《面向模式的软件架构》的第一卷,这套书多年来一直是架构师的必读经典。面向架构的模式就是为不同的非功能性需求提供了很好的参考和指导。图中的 Micro-Kernel 模式,更加关注可扩展性和可用性(错误隔离)。 “简单”并不“容易” 很多架构师都会常常提到保持简单,但是有时候我们会混淆简单和容易。简单和容易在英语里也是两个词“simple”和“easy”。 “Simple can be harder than complex: You have to work hard to get your thinking clean to make it simple. But it’s worth it in the end because once you get there, you can move mountains. To be truly simple, you have to go really deep.” –SteveJobs 真正的一些简单的方法其实来自于对问题和技术更深入的理解。这些方案往往不是容易获得的、表面上的方法。简单可以说蕴含着一种深入的技巧在其中。 下面我来举一个例子。 首先我们来回顾一下软件生命周期中各个阶段的成本消耗占比。以下是来一个知名统计机构的分析报告。我们可以看到占比最大的是维护部分,对于这一部分的简化将最具有全局意义。 我曾经开发过一个设备管理系统,移动运营商通过这个系统来管理移动设备,实现包括设备的自动注册、固件和软件的同步等管理功能。这些功能是通过一些管理系统与移动设备间的预定义的交互协议来完成的。 电信专家们会根据业务场景及需求来调整和新增这些交互协议。起初我们采用了一种容易实现的方式,即团队中的软件工程会根据电信专家的说明,将协议实现为对应代码。 之后我们很快发现这样的方式,让我们的工作变得没那么简单。 “I believe that the hardest part of software projects, the most common source of project failure, is communication with the customers and users of that software.” –Martin Fowler 正如软件开发大师 MartinFowler 提到的,“沟通”往往是导致软件项目失败的主要原因。前面这个项目最大的问题是在系统上线后的运行维护阶段,电信专家和开发工程师之间会不断就新的协议修改和增加进行持续的沟通,而他们的领域知识和词汇都有很大的差别,这会大大影响沟通的效率。因此这期间系统的运行维护(协议的修改)变得十分艰难,不仅协议更新上线时间慢,而且由于软件工程对于电信协议理解程度有限,很多问题都要在实际上线使用后才能被电信专家发现,导致了很多的交换和反复。 针对上面提到的问题,后来我们和电信专家一起设计了一种协议设计语言(并提供可视化的工具),这种设计语言使用的电信专家所熟悉的词汇。然后通过一个类似于编译器的程序将电信专家定义好的协议模型转换为内存中的 Java 结构。这样整个项目的运行和维护就变得简单高效了,省去了低效的交流和不准确人工转换。 我们可以看到一开始按电信专家的说明直接实现协议是更为容易的办法,但就整个软件生命周期来看却并不是一个简单高效的方法。 永远不要停止编码 架构师也是程序员,代码是软件的最终实现形态,停止编程会逐渐让你忘记作为程序员的感受,更重要的是忘记其中的“痛”,从而容易产生一些不切实际的设计。 大家可能听说过在 Amazon,高级副总裁级别的 Distinguish Engineer(如:James Gosling,Java 之父),他们每年的编码量也非常大,常在 10 万行以上。 风险优先 架构设计很重要的一点是识别可能存在的风险,尤其是非功能性需求实现的风险。因为这些风险往往没有功能性需求这么容易在初期被发现,但修正的代价通常要比修正功能性需求大非常多,甚至可能导致项目的失败,前面我们也提到了非功能性需求决定了架构,如数据一致性要求、响应延迟要求等。 我们应该通过原型或在早期的迭代中确认风险能够通过合理的架构得以解决。 绝对不要把风险放到最后,就算是一个项目要失败也要让它快速失败,这也是一种敏捷。 从“问题”开始,而不是“技术” 技术人员对于新技术的都有着一种与身俱来的激情,总是乐于去学习新技术,同时也更有激情去使用新技术。但是这也同样容易导致一个通病,就是“当我们有一个锤子的时候看什么都是钉子”,使用一些不适合的技术去解决手边的问题,常常会导致简单问题复杂化。 我曾经的一个团队维护过这样一个简单的服务,起初就是一个用 MySQL 作数据存储的简单服务,由团队的一个成员来开发和维护。后来,这位成员对当时新出的 DynamoDB 产生了兴趣,并学习了相关知识。 然后就发生下面这样的事: 用DynamoDB替换了MySQL。 很快发现DynamoDB并不能很好的支持事务特性,在当时只有一个性能极差的客户端类库来支持事物,由于采用客户端方式,引入了大量的额外交互,导致性能差别达7倍之多。这时候,这个同学就采用了当时在NoSQL领域广泛流行的最终一致技术,通过一个Pub-Sub消息队列来实现最终一致(即当某对象的值发生改变后会产生一个事件,然后关注这一改变的逻辑,就会订阅这个通知,并改变于其相关数据,从而实现不同数据的最终一致)。 接着由于DynamoDB无法提供SQL那样方便的查询机制,为了实现数据分析就又引入了EMR/MapReduceJob。 到此,大家可以看到实现一样的功能,但是复杂性大大增加,维护工作也由一个人变成了一个团队。 过度忙碌使你落后 对于 IT 人而言忙碌已成为了习惯,加班常挂在嘴边。“996”工作制似乎也变成了公司高效的标志。而事实上过度的忙碌使你落后。经常遇见一些朋友,在一个公司没日没夜的干了几年,没有留一点学习时间给自己。几年之后倒是对公司越来越“忠诚”了,但忙碌的工作同时也导致了没有时间更新知识,使得自己已经落后了,连跳槽的能力和勇气都失去了。 过度忙碌会导致没有时间学习和更新自己的知识,尤其在这个高速发展的时代。我在工作经历中发现过度繁忙通常会带来以下问题: 缺乏学习导致工作能力没有提升,而面对的问题却变得日益复杂。 技术和业务上没有更大的领先优势,只能被动紧紧追赶。试想一下,要是你都领先同行业五年了,还会在乎通过加班来早一个月发布吗? 反过来上面这些问题会导致你更加繁忙,进而更没有时间提高自己的技术技能,很快就形成了一个恶性循环。 练过健身的朋友都知道,光靠锻炼是不行的,营养补充和锻炼同样重要。个人技术成长其实也一样,实践和学习是一样重要的,当你在一个领域工作了一段时间以后,工作对你而言就主要是实践了,随着你对该领域的熟悉,能学习的到技术会越来越少。所以每个技术人员都要保证充足的学习时间,否则很容易成为井底之蛙,从而陷入前面提到的恶性循环。 最后,以伟大诗人屈原的诗句和大家共勉:“路漫漫其修远兮,吾将上下而求索“。希望我们大家都可以不忘初心,保持匠心! 作者简介: 蔡超,Mobvista 技术 VP 兼首席架构师,SpotMax 云服务创始人。拥有超过 15 年的软件开发经验,其中 9 年任世界级 IT 公司软件架构师/首席软件架构师。2017 年加入 Mobvista,任公司技术副总裁及首席架构师,领导公司的数字移动营销平台的开发,该平台完全建立于云计算技术之上,每天处理来自全球不同 region 的超过 600 亿次的请求。 在加入 Mobvista 之前,曾任亚马逊全球直运平台首席架构师,亚马逊(中国)首席架构师,曾领导了亚马逊的全球直运平台的开发,并领导中国团队通过 AI 及云计算技术为中国客户打造更好的本地体验;曾任 HP(中国)移动设备管理系统首席软件架构师,该系统曾是全球最大的无线设备管理系统(OMA DM)(客户包括中国移动,中国联通,中国电信等);曾任北京天融信网络安全技术公司,首席软件架构师,领导开发的网络安全管理系统(TopAnalyzer)至今仍被政府重要部门及军队广为采用,该系统也曾成功应用于 2008 北京奥运,2010 上海世博等重要事件的网络安全防护。 本篇文章为转载内容。原文链接:https://blog.csdn.net/Honnyee/article/details/111896981。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-19 14:55:26
78
转载
转载文章
...且我还发现我们公司的客户端软件在启动的时候比较慢,通过主动调研和测试,最后给领导提交了一个客户端启动加速的方案。现在能想起来的方式其中一个技术方式是 DLL 的基地址重定位。 02 入职腾讯 在 2011 年下半年,工作了一年多的时候,感觉广播电视领域整体的盘子还是太小了,当时领头企业的营业额一年也就才十个亿左右。再通过和自己在腾讯的同学交流,还是觉得互联网的空间更大。所以也婉拒了领导给的副组长的提拔挽留,又毅然跳到了北京腾讯。 我是 2011 年 11 月加入腾讯的。在项目上,仍然保持和第一家公司时工作类似的风格,全力以赴。不仅仅局限于完成自己手头的工作,主动做一切可能有价值的事情。其中一件事情就是我发现在当时的项目中,存在很多运营后台的开发需求。每次开发一个后台都得有人力去投入。 后来我就在老大的所开发的一套 PHP 框架的基础上进行改进。实现了只要指定一张 Mysql 数据库中的表,就可以自动生成 bootstrap 样式的管理后台界面。支持列表展示、搜索、删除、批量删除、文本框、时间控件等等一切基础功能。再以后涉及管理后台的功能,只需要在这个基础上改造就行了,人力投入降低了很多,风格也得到了统一。这个工具现在在我们团队内部仍然还在广泛地使用。 还有个故事我也讲过,就是老大分配给我一个图片下载的任务。我不局限于完成完成任务,而且还把文件系统、磁盘工作原理都深入整理了一遍,就是这篇《Linux文件系统十问》 03 转战搜狗 2013 下半年的时候,我第一次感受到了工作岗位的震荡。我还专注解决某一个 bug,花了不少精力都还没查到 bug 的原因。这时候,部门助理突然招呼我们所有人都下楼,在银科腾讯的 Image 印象店集合。在那里,见到了腾讯的总裁 Martin。这还是第一次离大老板只有一米远的距离。 所有人都是一脸困惑,突然把大家召集下来是干嘛呢。原来就在几个小时前,腾讯总办已经和搜狗达成了协议。腾讯收购搜狗的一部分股份,并把我们连人带业务一起注入到了搜狗。 没想到,是老板用一种更牛逼的方式帮我把 bug 给解决了。 14 年 1 月正式到了搜狗以后,我们没有继续做搜索了。而是内部 Transfer 到了另外一个部门。做起了搜狗网址导航、搜狗手机助手、搜狗浏览器等业务。我也是从那个时间点,开始带团队的,也是从那以后慢慢开始从个人贡献者到带团队集体输出的角色的转变。 在搜狗工作的这 7 年的时间里,我仍然也是延续之前的风格。不拘泥于完成工作中的产品需求,以及老大交付的任务。而是主动去探索各种项目中有价值的事情。 比如在手机助手的推广中,我琢磨了新用户的安装流程的各个环节后,找出影响用户安装率提升的关键因素。然后对新版本安装包采用了多种技术方案,将单用户获取成本削减了20%+,这一年下来就是千万级别的成本节约。 我们还主动在手机助手的搜索模块中应用了简单的学习算法。采用了用户协同,标签相似,点击反馈等方法将手机助手的搜索转化率提升了数个百分点。 除了用技术提升业务以外,我还结合工作中的问题进行了很多的深度技术思考。 如有一次我们自己维护了一个线上的redis(当时工程部还没有redis平台,redis服务要业务自己维护)。为了优化性能,我把后端的请求由短连接改成了长连接。虽然看效果性能确实是优化了,但是我的思考并没有停止。我们所有的后端机都会连接这个redis。这样在这个redis实例上可能得有6000多条并发连接存在。我就开始疑惑,Linux 最多能有多少个TCP连接呢,我这 6000 条长连接会不会把这个服务器玩坏? 再比如,我们组的服务器遭遇过几次连接相关的线上问题。其中一次是因为端口紧张而导致 CPU 消耗飙升。后来我又深入研究了一下。 最近,由于 Docker 的广泛应用。底层的网络工作方式已经在悄悄地发生变化了。所以我又开辟了一个网络虚拟化的坑,来一点一点地填。 现在我们的「开发内功修炼」公众号和 Github 就是在作为一个我和大家分享我的技术思考的一个窗口。 04 重回腾讯 时隔 7 年,我又以一种奇特的方式变回了腾讯人的身份。 腾讯再一次收购了搜狗的股份,这一次不再是控股,而是全资。 在离开腾讯的这 7 年多的时间里,腾讯的内部技术工作方式已经发生了翻天覆地的变化。 所以在刚转回腾讯的这一段时间里,我花了大量的精力来熟悉腾讯基于 tRPC 的各种技术生态。除了工作日,也投入了不少周末的精力。 05 再叨叨几句 最后,水文里挤干货,通过我今天的文章我想给大家分享这么几点经验。 第一,是要学会抬头看路,选择一个好的赛道进去。我非常庆幸我当年从广电赛道切换到了互联网,获得了更大的舞台。不过其实我自己在这点上做的也不是特别好,2013年底入职搜狗前拒绝了字节大把期权的offer,要不然我我早就财务自由了。 第二,不要光被动接收领导的指令干活。要主动积极思考项目中哪些地方是待改进的,想到了你就去做。领导都非常喜欢积极主动的员工。我自己也是喜欢招一些能主动思考,积极推进的同学。这些人能创造意外的价值。 第三,工作中除了业务以外还要主动技术的深度思考。毕竟技术仍然是开发的立命之本。在晋升考核的时候,业务数据做的再好也代替不了技术实力的核心位置。把工作中的技术点总结一下,在公司内分享出来。不涉及机密的话在外网分享一下更好。对你自己,对你的团队,都是好事。 技术交流群 最近有很多人问,有没有读者交流群,想知道怎么加入。 最近我创建了一些群,大家可以加入。交流群都是免费的,只需要大家加入之后不要随便发广告,多多交流技术就好了。 目前创建了多个交流群,全国交流群、北上广杭深等各地区交流群、面试交流群、资源共享群等。 有兴趣入群的同学,可长按扫描下方二维码,一定要备注:全国 Or 城市 Or 面试 Or 资源,根据格式备注,可更快被通过且邀请进群。 ▲长按扫描 往期推荐 武大94年博士年薪201万入职华为!学霸日程表曝光,简直降维打击! 腾讯三面:40亿个QQ号码如何去重? 我被开除了。。只因为看了骂公司的帖子 如果你喜欢本文, 请长按二维码,关注 Hollis. 转发至朋友圈,是对我最大的支持。 点个 在看 喜欢是一种感觉 在看是一种支持 ↘↘↘ 本篇文章为转载内容。原文链接:https://blog.csdn.net/hollis_chuang/article/details/121738393。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-06 11:38:24
232
转载
转载文章
...-> 在打开的应用之间切换 Alt + F4 -> 关闭活动项,或者退出活动应用 Shift + F10 -> 显示选定项的快捷菜单 Shift 加任意箭头键 -> 在窗口中或桌面上选择多个项目,或者在文档中选择文本 Shift + Delete -> 无需先将选定项移动到“回收站”,直接将其删除 三、win(windows徽标的简称)相关 win + L -> 锁定电脑 win + D -> 显示和隐藏桌面 win + E -> 打开“文件资源管理器” win + I -> 打开“设置” win + M -> 最小化所有窗口 win + Shift + M -> 将最小化的窗口还原到桌面 win + P -> 选择演示显示模式 win + K -> 打开“连接”快速操作 win + L -> 锁定电脑或切换帐户 win + Tab -> 打开“任务视图” win + R -> 打开运行窗口 四、其他快捷键 End -> 显示活动窗口的底端 Home -> 显示活动窗口的顶端 F11 -> 最大化或最小化活动窗口 五、运行窗口快捷命令 先输入win+ R 本小结转载地址:https://blog.csdn.net/qq_42402854/article/details/93162387 1.calc:启动计算器 2.appwiz.cpl:程序和功能 3.certmgr.msc:证书管理实用程序 4.charmap:启动字符映射表 5.chkdsk.exe:Chkdsk磁盘检查(管理员身份运行命令提示符) 6.cleanmgr: 打开磁盘清理工具 7.cliconfg:SQL SERVER 客户端网络实用工具 8.cmstp:连接管理器配置文件安装程序 9.cmd:CMD命令提示符 10.自动关机命令 Shutdown -s -t 600:表示600秒后自动关机 shutdown -a :可取消定时关机 Shutdown -r -t 600:表示600秒后自动重启 rundll32 user32.dll,LockWorkStation:表示锁定计算机 11.colorcpl:颜色管理,配置显示器和打印机等中的色彩 12.CompMgmtLauncher:计算机管理 13.compmgmt.msc:计算机管理 14.credwiz:备份或还原储存的用户名和密码 15.comexp.msc:打开系统组件服务 16.control:控制面版 17.dcomcnfg:打开系统组件服务 18.Dccw:显示颜色校准 19.devmgmt.msc:设备管理器 20.desk.cpl:屏幕分辨率 21.dfrgui:优化驱动器 Windows 7→dfrg.msc:磁盘碎片整理程序 22.dialer:电话拨号程序 23.diskmgmt.msc:磁盘管理 24.dvdplay:DVD播放器 25.dxdiag:检查DirectX信息 26.eudcedit:造字程序 27.eventvwr:事件查看器 28.explorer:打开资源管理器 29.Firewall.cpl:Windows防火墙 30.FXSCOVER:传真封面编辑器 31.fsmgmt.msc:共享文件夹管理器 32.gpedit.msc:组策略 33.hdwwiz.cpl:设备管理器 34.inetcpl.cpl:Internet属性 35.intl.cpl:区域 36.iexpress:木马捆绑工具,系统自带 37.joy.cpl:游戏控制器 38.logoff:注销命令 39.lusrmgr.msc:本地用户和组 40.lpksetup:语言包安装/删除向导,安装向导会提示下载语言包 41.lusrmgr.msc:本机用户和组 42.main.cpl:鼠标属性 43.mmsys.cpl:声音 44.magnify:放大镜实用程序 45.mem.exe:显示内存使用情况(如果直接运行无效,可以先管理员身份运行命令提示符,在命令提示符里输入mem.exe>d:a.txt 即可打开d盘查看a.txt,里面的就是内存使用情况了。当然什么盘什么文件名可自己决定。) 46.MdSched:Windows内存诊断程序 47.mmc:打开控制台 48.mobsync:同步命令 49.mplayer2:简易widnows media player 50.Msconfig.exe:系统配置实用程序 51.msdt:微软支持诊断工具 52.msinfo32:系统信息 53.mspaint:画图 54.Msra:Windows远程协助 55.mstsc:远程桌面连接 56.NAPCLCFG.MSC:客户端配置 57.ncpa.cpl:网络连接 58.narrator:屏幕“讲述人” 59.Netplwiz:高级用户帐户控制面板,设置登陆安全相关的选项 60.netstat : an(TC)命令检查接口 61.notepad:打开记事本 62.Nslookup:IP地址侦测器 63.odbcad32:ODBC数据源管理器 64.OptionalFeatures:打开“打开或关闭Windows功能”对话框 65.osk:打开屏幕键盘 66.perfmon.msc:计算机性能监测器 67.perfmon:计算机性能监测器 68.PowerShell:提供强大远程处理能力 69.printmanagement.msc:打印管理 70.powercfg.cpl:电源选项 71.psr:问题步骤记录器 72.Rasphone:网络连接 73.Recdisc:创建系统修复光盘 74.Resmon:资源监视器 75.Rstrui:系统还原 76.regedit.exe:注册表 77.regedt32:注册表编辑器 78.rsop.msc:组策略结果集 79.sdclt:备份状态与配置,就是查看系统是否已备份 80.secpol.msc:本地安全策略 81.services.msc:本地服务设置 82.sfc /scannow:扫描错误并复原/windows文件保护 83.sfc.exe:系统文件检查器 84.shrpubw:创建共享文件夹 85.sigverif:文件签名验证程序 86.slui:Windows激活,查看系统激活信息 87.slmgr.vbs -dlv :显示详细的许可证信息 88.snippingtool:截图工具,支持无规则截图 89.soundrecorder:录音机,没有录音时间的限制 90.StikyNot:便笺 91.sysdm.cpl:系统属性 92.sysedit:系统配置编辑器 93.syskey:系统加密,一旦加密就不能解开,保护系统的双重密码 94.taskmgr:任务管理器(旧版) 95.TM任务管理器(新版) 96.taskschd.msc:任务计划程序 97.timedate.cpl:日期和时间 98.UserAccountControlSettings用户账户控制设置 99.utilman:辅助工具管理器 100.wf.msc:高级安全Windows防火墙 101.WFS:Windows传真和扫描 102.wiaacmgr:扫描仪和照相机向导 103.winver:关于Windows 104.wmimgmt.msc:打开windows管理体系结构(WMI) 105.write:写字板 106.wscui.cpl:操作中心 107.wuapp:Windows更新 108.wscript:windows脚本宿主设置 六、小结 键盘快捷键会大大提高使用效率,让你在外行面前显得更酷。持续更新中…感谢点赞,评论与转发,谢谢! 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_44168588/article/details/121208530。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-01 13:38:26
91
转载
转载文章
...limiter | 设置分隔符,要不然好像只会执行begin与and之间的第一条sql语句. 查看触发器: 1.show triggers; 语句查看触发器信息.(查询所有的触发器) 2.在triggers表中查看触发器信息.(在数据库原始表triggers中可以查看) 删除触发器: Drop trigger 触发器名称 ; 27.函数: (1)日期函数: CURDATE()当前日期,CURTIME()当前时间,MONTH(d):返回日期d中的月份值,范围试1-12 (2)字符串函数:CHAR_LENGTH(s) 计算字段s值->字符串的长度.UPPER(s) 把该字段的值中所有英文都变成大写,LOWER(s) 和相面相反->把英文都变成小写. (3)数学函数:sum():求和,ABS(s) 求绝对值,SQRT(s):求平方根,mod(x,y),求余x/y (4)加密函数:PASSWORD(STR) 一般对密码加密 不可逆… MD5(STR) 普通加密 ,不可逆. ENCODE(str,pswd_str) 加密函数,结果是一个二进制文件,用blob类型的字段保存,pswd_str类似一个加密的钥匙,可以随便写. DECODE(被加密的值,pswd_str)–>对encode进行解密. 28.存储过程: (1)存储过程和函数:两者是在数据库中定义一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句.存储过程和函数可以避免重复的写一些sql语句,而且存储过程是在mysql服务器中存储和执行的,减少客户端和服务器端的数据传输.(类似于java代码写的工具类.) (2)创建存储过程和函数: Create procedure 关键字 pro_book 存储过程名称, in 输入 bT 输入参数名称 int 输入参数类型 out 输出 count_num 输出参数名称 int 输入参数类型 Begin 过程开始 end过程结束 中间是sql语句, Delimiter 默认是分号,而他的作用就是若是遇见分号时就开始执行该过程(语句),但是一个存储过程可能有很多sql语句且以分号结束,若这样的情况下当第一条sql语句结束后就会开始执行该过程,产生的后果是创建过程时,执行到第一个分号就会开始创建,导致存储过程创建错误.(若是有多个参数,在多条sql中均有参数,第一条设置完执行了,而这时第二条的参数有可能还么有设置完成,导致sql执行失败.)因此,需要把默认执行过程的demiliter关键字的默认值改为其他的字符,例如上面的就是改为&&,(当然我认为上面就一条sql语句,改不改默认的demiliter的默认值都一样.) . 使用navicat的话不使用delimiter好像也是可以的. Reads sql data则是上面图片所提到的参数指定存储过程的特性.(这个是指读数据,当然还有写输入与读写数据专用的参数类型.)看下图 经常用contains sql (应该是可以读,) 这个是调用上面的存储过程,1为入参,@total相当于全局变量,为出参. 这是一个存储函数,create function 为关键字,fun_book为函数名称, 括号里面为传入的参数名(值)以及入参的类型.RETURNS 为返回的关键字,后面接返回的类型. BEGIN函数开始,END函数结束.中间是return 以及查询数据的sql语句, 这里是指把bookId 传进去,通过存储函数返回对应的书本名字, ---------存储函数的调用和调用系统函数一样 例如:select 存储函数名称(入参值) Select 为查询 func_book 为存储函数名 2为入参值. (3)变量的使用:declaer:声明变量的值 Delimiter && Create procedure user() Begin Declare a,b varchar2(20) ; — a,b有默认的值,为空 Insert into user values(a,b); End && Delimiter ; Set 可以用来赋值,例如: 可以从其他表中查询出对应的值插入到另一个表中.例如: 从t_user2中查询出username2与password2放入到变量a,b中,然后再插入到t_user表中.(当然这只是创建存储过程),创建完以后,需要用CALL 存储过程名(根据过程参数描写.)来调用存储过程.注意:这一种的写法只可以插入单笔数据,若是select查询出多笔数据,因为无循环故而会插入不进去语句,会导致倒致存储过程时出错.下面的游标也是如此. (4)游标的使用.查询语句可能查询出多条记录,在存储过程和函数中使用游标逐条读取查询结果集中的记录.游标的使用包括声明游标,打开游标,使用游标和关闭游标.游标必须声明到处理程序之前,并且声明在变量和条件之后. 声明:declare 游标名 curson for 查询sql语句. 打开:open 游标名 使用:fetch 游标名 into x, 关闭:close 游标名 ----- 游标只能保存单笔数据. 类似于这一个,意思就是先查询出来username2,与password2的值放入到cur_t_user2的游标中(声明,类似于赋值),然后开启->使用.使用的意思就是把游标中存储的值分别赋值到a,b中,然后执行sql语句插入到t_user表中.最后关闭游标. (5)流程控制的使用:mysql可以使用:IF 语句 CASE语句 LOOP语句 LEAVE语句 ITERATE 语句 REPEAT语句与WHILE语句. 这个过程的意思是,查询t_user表中是否存在id等于我们入参时所写的id,若有的情况下查出有几笔这样的数据并且把数值给到全局变量@num中,if判断是否这样的数据是否存在,若是存在执行THEN后面的语句,即使更新该id对应的username,若没有则插入一条新的数据,最后注意END IF. 相当于java中的switch case.例如: 这里想当然于,while(ture){ break; } 这里的意思是,参数一个int类型的参数,loop aaa循环,把参数当做主键id插入到t_user表中,每循环一次参入的参数值减一,直到参数值为0,跳出循环(if判断,leave实现.) 相当于java的continue. 比上面的多了一个当totalNum = 3时,结束本次循环,下面的语句不在执行,直接执行下一次循环,也即是说插入的数据没有主键为3的数据. 和上面的差不多,只不过当执行到UNTIL时满足条件时,就跳出循环.就如上面那一个意思就是当执行到totalNum = 1时,跳出循环,也就是说不会插入主键为0的那一笔数据 当while条件判断为true时,执行do后面的语句,否则就不再执行. (6)调用存储过程和函数 CALL 存储过程名字(参数值1,参数值2,…) 存储函数名称(参数值1,参数值2,…) (7)查看存储过程和函数. Show procedure status like ‘存储过程名’ --只能查看状态 Show create procedure ‘存储过程名’ – 查看定义(使用频率高). 存储函数查看也和上面的一样. 当然还可以从information_schema.Routines中(系统数据库表)查看存储过程与函数. (8)修改存储过程与函数: 修改存储过程comment属性的值 ALTER procedure 存储过程名 comment ‘新值’; (9)删除存储过程与函数: DROP PROCEDURE 存储过程名; DROP function 存储函数名; 29.数据备份与还原: (1)数据备份:数据备份可以保证数据库表的安全性,数据库管理员需要定期的进行数据库备份. 命令:使用mysqldump(下图),或者使用图形工具 Mysqldump在msql文件夹+bin+mysqldump.exe中,相当于一个小软件.执行的话是在dos命令窗操作的. 其实就是导出数据库数据,在navacat中可以如下图导出 (2)数据还原: 若是从navacat中就是把外部的.sql文件数据导入到数据库中去.如下图 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_42847571/article/details/102686087。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-26 19:09:16
83
转载
转载文章
...的地址空间之内,这些应用程序就可以直接使用输入输出的地址空间,从而提高读写的效率。Linux提供了mmap()函数,用来映射物理内存。在驱动程序中,应用程序以设备文件为对象,调用mmap()函数,内核进行内存映射的准备工作,生成vm_area_struct结构体,然后调用设备驱动程序中定义的mmap函数。 2.mmap系统调用 mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。 当使用mmap映射文件到进程后,就可以直接操作这段虚拟地址进行文件的读写等操作,不必再调用read,write等系统调用.但需注意,直接对该段内存写时不会写入超过当前文件大小的内容. 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。 基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime可能被更新。如果st_atime字段在前述的情况下没有得到更新,首次对映射区的第一个页索引时会更新该字段的值。用PROT_WRITE 和 MAP_SHARED标志建立起来的文件映射,其st_ctime 和 st_mtime在对映射区写入之后,但在msync()通过MS_SYNC 和 MS_ASYNC两个标志调用之前会被更新。 用法: include <sys/mman.h> void mmap(void start, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void start, size_t length); 返回说明: 成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void )-1],munmap返回-1。errno被设为以下的某个值 EACCES:访问出错 EAGAIN:文件已被锁定,或者太多的内存已被锁定 EBADF:fd不是有效的文件描述词 EINVAL:一个或者多个参数无效 ENFILE:已达到系统对打开文件的限制 ENODEV:指定文件所在的文件系统不支持内存映射 ENOMEM:内存不足,或者进程已超出最大内存映射数量 EPERM:权能不足,操作不允许 ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志 SIGSEGV:试着向只读区写入 SIGBUS:试着访问不属于进程的内存区 参数: start:映射区的开始地址。 length:映射区的长度。 prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起 PROT_EXEC //页内容可以被执行 PROT_READ //页内容可以被读取 PROT_WRITE //页可以被写入 PROT_NONE //页不可访问 flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体 MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。 MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。 MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。 MAP_DENYWRITE //这个标志被忽略。 MAP_EXECUTABLE //同上 MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。 MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。 MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。 MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。 MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。 MAP_FILE //兼容标志,被忽略。 MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。 MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。 MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。 fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。 offset:被映射对象内容的起点。 3.munmap系统调用 include <sys/mman.h> int munmap( void addr, size_t len ) 该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。 4.msync系统调用 include <sys/mman.h> int msync ( void addr , size_t len, int flags) 一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。 二 系统调用mmap()用于共享内存的两种方式 (1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下: [cpp] view plaincopy fd=open(name, flag, mode); if(fd<0) ... ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方 (2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可. 三 mmap进行内存映射的原理 mmap系统调用的最终目的是将,设备或文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接读写,这个任务可以分为以下三步: 1.在用户虚拟地址空间中寻找空闲的满足要求的一段连续的虚拟地址空间,为映射做准备(由内核mmap系统调用完成) 每个进程拥有3G字节的用户虚存空间。但是,这并不意味着用户进程在这3G的范围内可以任意使用,因为虚存空间最终得映射到某个物理存储空间(内存或磁盘空间),才真正可以使用。 那么,内核怎样管理每个进程3G的虚存空间呢?概括地说,用户进程经过编译、链接后形成的映象文件有一个代码段和数据段(包括data段和bss段),其中代码段在下,数据段在上。数据段中包括了所有静态分配的数据空间,即全局变量和所有申明为static的局部变量,这些空间是进程所必需的基本要求,这些空间是在建立一个进程的运行映像时就分配好的。除此之外,堆栈使用的空间也属于基本要求,所以也是在建立进程时就分配好的,如图3.1所示: 图3.1 进程虚拟空间的划分 在内核中,这样每个区域用一个结构struct vm_area_struct 来表示.它描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。可以使用 cat /proc/<pid>/maps来查看一个进程的内存使用情况,pid是进程号.其中显示的每一行对应进程的一个vm_area_struct结构. 下面是struct vm_area_struct结构体的定义: [cpp] view plaincopy struct vm_area_struct { struct mm_struct vm_mm; / The address space we belong to. / unsigned long vm_start; / Our start address within vm_mm. / unsigned long vm_end; / The first byte after our end address within vm_mm. / / linked list of VM areas per task, sorted by address / struct vm_area_struct vm_next, vm_prev; pgprot_t vm_page_prot; / Access permissions of this VMA. / unsigned long vm_flags; / Flags, see mm.h. / struct rb_node vm_rb; / For areas with an address space and backing store, linkage into the address_space->i_mmap prio tree, or linkage to the list of like vmas hanging off its node, or linkage of vma in the address_space->i_mmap_nonlinear list. / union { struct { struct list_head list; void parent; / aligns with prio_tree_node parent / struct vm_area_struct head; } vm_set; struct raw_prio_tree_node prio_tree_node; } shared; / A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma list, after a COW of one of the file pages. A MAP_SHARED vma can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack or brk vma (with NULL file) can only be in an anon_vma list. / struct list_head anon_vma_chain; / Serialized by mmap_sem & page_table_lock / struct anon_vma anon_vma; / Serialized by page_table_lock / / Function pointers to deal with this struct. / const struct vm_operations_struct vm_ops; / Information about our backing store: / unsigned long vm_pgoff; / Offset (within vm_file) in PAGE_SIZE units, not PAGE_CACHE_SIZE / struct file vm_file; / File we map to (can be NULL). / void vm_private_data; / was vm_pte (shared mem) / unsigned long vm_truncate_count;/ truncate_count or restart_addr / ifndef CONFIG_MMU struct vm_region vm_region; / NOMMU mapping region / endif ifdef CONFIG_NUMA struct mempolicy vm_policy; / NUMA policy for the VMA / endif }; 通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个vm_area_struct结构来描述。在vm_area_struct结构的数目较少的时候,各个vm_area_struct按照升序排序,以单链表的形式组织数据(通过vm_next指针指向下一个vm_area_struct结构)。但是当vm_area_struct结构的数据较多的时候,仍然采用链表组织的化,势必会影响到它的搜索速度。针对这个问题,vm_area_struct还添加了vm_avl_hight(树高)、vm_avl_left(左子节点)、vm_avl_right(右子节点)三个成员来实现AVL树,以提高vm_area_struct的搜索速度。 假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。 图3.2 进程虚拟地址示意图 因此,mmap系统调用所完成的工作就是准备这样一段虚存空间,并建立vm_area_struct结构体,将其传给具体的设备驱动程序 2 建立虚拟地址空间和文件或设备的物理地址之间的映射(设备驱动完成) 建立文件映射的第二步就是建立虚拟地址和具体的物理地址之间的映射,这是通过修改进程页表来实现的.mmap方法是file_opeartions结构的成员: int (mmap)(struct file ,struct vm_area_struct ); linux有2个方法建立页表: (1) 使用remap_pfn_range一次建立所有页表. int remap_pfn_range(struct vm_area_struct vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot); 返回值: 成功返回 0, 失败返回一个负的错误值 参数说明: vma 用户进程创建一个vma区域 virt_addr 重新映射应当开始的用户虚拟地址. 这个函数建立页表为这个虚拟地址范围从 virt_addr 到 virt_addr_size. pfn 页帧号, 对应虚拟地址应当被映射的物理地址. 这个页帧号简单地是物理地址右移 PAGE_SHIFT 位. 对大部分使用, VMA 结构的 vm_paoff 成员正好包含你需要的值. 这个函数影响物理地址从 (pfn<<PAGE_SHIFT) 到 (pfn<<PAGE_SHIFT)+size. size 正在被重新映射的区的大小, 以字节. prot 给新 VMA 要求的"protection". 驱动可(并且应当)使用在vma->vm_page_prot 中找到的值. (2) 使用nopage VMA方法每次建立一个页表项. struct page (nopage)(struct vm_area_struct vma, unsigned long address, int type); 返回值: 成功则返回一个有效映射页,失败返回NULL. 参数说明: address 代表从用户空间传过来的用户空间虚拟地址. 返回一个有效映射页. (3) 使用方面的限制: remap_pfn_range不能映射常规内存,只存取保留页和在物理内存顶之上的物理地址。因为保留页和在物理内存顶之上的物理地址内存管理系统的各个子模块管理不到。640 KB 和 1MB 是保留页可能映射,设备I/O内存也可以映射。如果想把kmalloc()申请的内存映射到用户空间,则可以通过mem_map_reserve()把相应的内存设置为保留后就可以。 (4) remap_pfn_range与nopage的区别 remap_pfn_range一次性建立页表,而nopage通过缺页中断找到内核虚拟地址,然后通过内核虚拟地址找到对应的物理页 remap_pfn_range函数只对保留页和物理内存之外的物理地址映射,而对常规RAM,remap_pfn_range函数不能映射,而nopage函数可以映射常规的RAM。 3 当实际访问新映射的页面时的操作(由缺页中断完成) (1) page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。 (2) 文件与 address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。 (3) 进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。 (4) 对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。 注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新. (5) 所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。 四 总结 1.对于mmap的内存映射,是将物理内存映射到进程的虚拟地址空间中去,那么进程对文件的访问就相当于直接对内存的访问,从而加快了读写操作的效率。在这里,remap_pfn_range函数是一次性的建立页表,而nopage函数是根据page fault产生的进程虚拟地址去找到内核相对应的逻辑地址,再通过这个逻辑地址去找到page。完成映射过程。remap_pfn_range不能对常规内存映射,只能对保留的内存与物理内存之外的进行映射。 2.在这里,要分清几个地址,一个是物理地址,这个很简单,就是物理内存的实际地址。第二个是内核虚拟地址,即内核可以直接访问的地址,如kmalloc,vmalloc等内核函数返回的地址,kmalloc返回的地址也称为内核逻辑地址。内核虚拟地址与实际的物理地址只有一个偏移量。第三个是进程虚拟地址,这个地址处于用户空间。而对于mmap函数映射的是物理地址到进程虚拟地址,而不是把物理地址映射到内核虚拟地址。而ioremap函数是将物理地址映射为内核虚拟地址。 3.用户空间的进程调用mmap函数,首先进行必要的处理,生成vma结构体,然后调用remap_pfn_range函数建立页表。而用户空间的mmap函数返回的是映射到进程地址空间的首地址。所以mmap函数与remap_pfn_range函数是不同的,前者只是生成mmap,而建立页表通过remap_pfn_range函数来完成。 本篇文章为转载内容。原文链接:https://blog.csdn.net/wh8_2011/article/details/52373213。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-20 22:49:12
464
转载
转载文章
... 该不该捕获异常? 应用程序全局处理异常 抛出哪些异常? 异常的分类 其他 一些常见异常的原因和解决方法 AccessViolationException 快速了解 .NET 的异常机制 Exception 类 我们大多数小伙伴可能更多的使用 Exception 的类型、Message 属性、StackTrace 以及内部异常来定位问题,但其实 Exception 类型还有更多的信息可以用于辅助定位问题。 Message 用来描述异常原因的详细信息 如果你捕捉到了异常,一般使用这段描述能知道发生的大致原因。 如果你准备抛出异常,在这个信息里面记录能帮助调试问题的详细文字信息。 StackTrace 包含用来确定错误位置的堆栈跟踪(当有调试信息如 PDB 时,这里就会包含源代码文件名和源代码行号) InnerException 包含内部异常信息 Source 这个属性包含导致错误的应用程序或对象的名称 Data 这是一个字典,可以存放基于键值的任意数据,帮助在异常信息中获得更多可以用于调试的数据 HelpLink 这是一个 url,这个 url 里可以提供大量用于说明此异常原因的信息 如果你自己写一个自定义异常类,那么你可以在自定义的异常类中记录更多的信息。然而大多数情况下我们都考虑使用 .NET 中自带的异常类,因此可以充分利用 Exception 类中的已有属性在特殊情况下报告更详细的利于调试的异常信息。 捕捉异常 捕捉异常的基本语法是: try{// 可能引发异常的代码。}catch (FileNotFoundException ex){// 处理一种类型的异常。}catch (IOException ex){// 处理另一种类的异常。} 除此之外,还有 when 关键字用于筛选异常: try{// 可能引发异常的代码。}catch (FileNotFoundException ex) when (Path.GetExtension(ex.FileName) is ".png"){// 处理一种类型的异常,并且此文件扩展名为 .png。}catch (FileNotFoundException ex){// 处理一种类型的异常。} 无论是否有带 when 关键字,都是前面的 catch 块匹配的时候执行匹配的 catch 块而无视后面可能也匹配的 catch 块。 如果 when 块中抛出异常,那么此异常将被忽略,when 中的表达式值视为 false。有个但是,请看:.NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃 - walterlv。 引发异常 引发异常使用 throw 关键字。只是注意如果要重新抛出异常,请使用 throw; 语句或者将原有异常作为内部异常。 创建自定义异常 如果你只是随便在业务上创建一个异常,那么写一个类继承自 Exception 即可: public class MyCustomException : Exception{public string MyCustomProperty { get; }public MyCustomException(string customProperty) => MyCustomProperty = customProperty;} 不过,如果你需要写一些比较通用抽象的异常(用于被继承),或者在底层组件代码中写自定义异常,那么就建议考虑写全异常的所有构造函数,并且加上可序列化: [Serializable]public class InvalidDepartmentException : Exception{public InvalidDepartmentException() : base() { }public InvalidDepartmentException(string message) : base(message) { }public InvalidDepartmentException(string message, Exception innerException) : base(message, innerException) { }// 如果异常需要跨应用程序域、跨进程或者跨计算机抛出,就需要能被序列化。protected InvalidDepartmentException(SerializationInfo info, StreamingContext context) : base(info, context) { } } 在创建自定义异常的时候,建议: 名称以 Exception 结尾 Message 属性的值是一个句子,用于描述异常发生的原因。 提供帮助诊断错误的属性。 尽量写全四个构造函数,前三个方便使用,最后一个用于序列化异常(新的异常类应可序列化)。 finally 异常堆栈跟踪 堆栈跟踪从引发异常的语句开始,到捕获异常的 catch 语句结束。 利用这一点,你可以迅速找到引发异常的那个方法,也能找到是哪个方法中的 catch 捕捉到的这个异常。 异常处理原则 try-catch-finally 我们第一个要了解的异常处理原则是——明确 try catch finally 的用途! try 块中,编写可能会发生异常的代码。 最好的情况是,你只将可能会发生异常的代码放到 try 块中,当然实际应用的时候可能会需要额外放入一些相关代码。但是如果你将多个可能发生异常的代码放到一个 try 块中,那么将来定位问题的时候你就会很抓狂(尤其是多个异常还是一个类别的时候)。 catch 块的作用是用来 “恢复错误” 的,是用来 “恢复错误” 的,是用来 “恢复错误” 的。 如果你在 try 块中先更改了类的状态,随后出了异常,那么最好能将状态改回来——这可以避免这个类型或者应用程序的其他状态出现不一致——这很容易造成应用程序“雪崩”。举一个例子:我们写一个程序有简洁模式和专业模式,在从简洁模式切换到专业模式的时候,我们设置 IsProfessionalMode 为 true,但随后出现了异常导致没有成功切换为专业模式;然而接下来所有的代码在执行时都判断 IsProfessionalMode 为 true 状态不正确,于是执行了一些非预期的操作,甚至可能用到了很多专业模式中才会初始化的类型实例(然而没有完成初始化),产生大量的额外异常;我们说程序雪崩了,多数功能再也无法正常使用了。 当然如果任务已全部完成,仅仅在对外通知的时候出现了异常,那么这个时候不需要恢复状态,因为实际上已经完成了任务。 你可能会有些担心如果我没有任何手段可以恢复错误怎么办?那这个时候就不要处理异常!——如果不知道如何恢复错误,请不要处理异常!让异常交给更上一层的模块处理,或者交给整个应用程序全局异常处理模块进行统一处理(这个后面会讲到)。 另外,异常不能用于在正常执行过程中更改程序的流程。异常只能用于报告和处理错误条件。 finally 块的作用是清理资源。 虽然 .NET 的垃圾回收机制可以在回收类型实例的时候帮助我们回收托管资源(例如 FileStream 类打开的文件),但那个时机不可控。因此我们需要在 finally 块中确保资源可被回收,这样当重新使用这个文件的时候能够立刻使用而不会被占用。 一段异常处理代码中可能没有 catch 块而有 finally 块,这个时候的重点是清理资源,通常也不知道如何正确处理这个错误。 一段异常处理代码中也可能 try 块留空,而只在 finally 里面写代码,这是为了“线程终止”安全考虑。在 .NET Core 中由于不支持线程终止因此可以不用这么写。详情可以参考:.NET/C 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions) - walterlv。 该不该引发异常? 什么情况下该引发异常?答案是——这真的是一个异常情况! 于是,我们可能需要知道什么是“异常情况”。 一个可以参考的判断方法是——判断这件事发生的频率: 如果这件事并不常见,当它发生时确实代表发生了一个错误,那么这件事情就可以认为是异常。 如果这件事经常发生,代码中正常情况就应该处理这件事情,那么这件事情就不应该被认为是异常(而是正常流程的一部分)。 例如这些情况都应该认为是异常: 方法中某个参数不应该传入 null 时但传入了 null 这是开发者使用这个方法时没有遵循此方法的契约导致的,让开发者改变调用此方法的代码就可以完全避免这件事情发生 而下面这些情况则不应该认为是异常: 用户输入了一串字符,你需要将这串字符转换为数字 用户输入的内容本身就千奇百怪,出现非数字的输入再正常不过了,对非数字的处理本就应该成为正常流程的一部分 对于这些不应该认为是异常的情况,编写的代码就应该尽可能避免异常。 有两种方法来避免异常: 先判断再使用。 例如读取文件之前,先判断文件是否存在;例如读取文件流时先判断是否已到达文件末尾。 如果提前判断的成本过高,可采用 TryDo 模式来完成,例如字符串转数字中的 TryParse 方法,字典中的 TryGetValue 方法。 对极为常见的错误案例返回 null(或默认值),而不是引发异常。极其常见的错误案例可被视为常规控制流。通过在这些情况下返回 NULL(或默认值),可最大程度地减小对应用的性能产生的影响。(后面会专门说 null) 而当存在下列一种或多种情况时,应引发异常: 方法无法完成其定义的功能。 根据对象的状态,对某个对象进行不适当的调用。 请勿有意从自己的源代码中引发 System.Exception、System.SystemException、System.NullReferenceException 或 System.IndexOutOfRangeException。 该不该捕获异常? 在前面 try-catch-finally 小节中,我们提到了 catch 块中应该写哪些代码,那里其实已经说明了哪些情况下应该处理异常,哪些情况下不应该处理异常。一句总结性的话是——如果知道如何从错误中恢复,那么就捕获并处理异常,否则交给更上层的业务去捕获异常;如果所有层都不知道如何处理异常,就交给全局异常处理模块进行处理。 应用程序全局处理异常 对于 .NET 程序,无论是 .NET Framework 还是 .NET Core,都有下面这三个可以全局处理的异常。这三个都是事件,可以自行监听。 AppDomain.UnhandledException 应用程序域未处理的异常,任何线程中未处理掉的异常都会进入此事件中 当这里能够收到事件,意味着应用程序现在频临崩溃的边缘(从设计上讲,都到这里了,也再没有任何代码能够使得程序从错误中恢复了) 不过也可以配置 legacyUnhandledExceptionPolicy 防止后台线程抛出的异常让程序崩溃退出 建议在这个事件中记录崩溃日志,然后对应用程序进行最后的拯救恢复操作(例如保存用户的文档数据) AppDomain.FirstChanceException 应用程序域中的第一次机会异常 我们前面说过,一个异常被捕获时,其堆栈信息将包含从 throw 块到 catch 块之间的所有帧,而在第一次机会异常事件中,只是刚刚 throw 出来,还没有被任何 catch 块捕捉,因此在这个事件中堆栈信息永远只会包含一帧(不过可以稍微变通一下在第一次机会异常 FirstChanceException 中获取比较完整的异常堆栈) 注意第一次机会异常事件即便异常会被 catch 也会引发,因为它引发在 catch 之前 不要认为异常已经被 catch 就万事大吉可以无视这个事件了。前面我们说过异常仅在真的是异常的情况才应该引发,因此如果这个事件中引发了异常,通常也真的意味着发生了错误(差别只是我们能否从错误中恢复而已)。如果你经常在正常的操作中发现可以通过此事件监听到第一次机会异常,那么一定是应用程序或框架中的异常设计出了问题(可能把正常应该处理的流程当作了异常,可能内部实现代码错误,可能出现了使用错误),这种情况一定是要改代码修 Bug 的。而一些被认为是异常的情况下收到此事件则是正常的。 TaskScheduler.UnobservedTaskException 在使用 async / await 关键字编写异步代码的时候,如果一直有 await 传递,那么异常始终可以被处理到;但中间有异步任务没有 await 导致异常没有被传递的时候,就会引发此事件。 如果在此事件中监听到异常,通常意味着代码中出现了不正确的 async / await 的使用(要么应该修改实现避免异常,要么应该正确处理异常并从中恢复错误) 对于 GUI 应用程序,还可以监听 UI 线程上专属的全局异常: WPF:Application.DispatcherUnhandledException 或者 Dispatcher.UnhandledException Windows Forms:Application.ThreadException 关于这些全局异常的处理方式和示例代码,可以参阅博客: WPF UnhandledException - Iron 的博客 - CSDN博客 抛出哪些异常? 任何情况下都不应该抛出这些异常: 过于抽象,以至于无法表明其含义 Exception 这可是顶级基类,这都抛出来了,使用者再也无法正确地处理此异常了 SystemException 这是各种异常的基类,本身并没有明确的意义 ApplicationException 这是各种异常的基类,本身并没有明确的意义 由 CLR 引发的异常 NullReferenceException 试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了 IndexOutOfRangeException 使用索引的时候超出了边界 InvalidCastException 表示试图对某个类型进行强转但类型不匹配 StackOverflow 表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归 OutOfMemoryException 表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了 AccessViolationException 这说明使用非托管内存时发生了错误 BadImageFormatException 这说明了加载的 dll 并不是期望中的托管 dll TypeLoadException 表示类型初始化的时候发生了错误 .NET 设计失误 FormatException 因为当它抛出来时无法准确描述到底什么错了 首先是你自己不应该抛出这样的异常。其次,你如果在运行中捕获到了上面这些异常,那么代码一定是写得有问题。 如果是捕获到了上面 CLR 的异常,那么有两种可能: 你的代码编写错误(例如本该判空的代码没有判空,又如索引数组超出界限) 你使用到的别人写的代码编写错误(那你就需要找到它改正,或者如果开源就去开源社区中修复吧) 而一旦捕获到了上面其他种类的异常,那就找到抛这个异常的人,然后对它一帧狂扁即可。 其他的异常则是可以抛出的,只要你可以准确地表明错误原因。 另外,尽量不要考虑抛出聚合异常 AggregateException,而是优先使用 ExceptionDispatchInfo 抛出其内部异常。详见:使用 ExceptionDispatchInfo 捕捉并重新抛出异常 - walterlv。 异常的分类 在 该不该引发异常 小节中我们说到一个异常会被引发,是因为某个方法声称的任务没有成功完成(失败),而失败的原因有四种: 方法的使用者用错了(没有按照方法的契约使用) 方法的执行代码写错了 方法执行时所在的环境不符合预期 简单说来,就是:使用错误,实现错误、环境错误。 使用错误: ArgumentException 表示参数使用错了 ArgumentNullException 表示参数不应该传入 null ArgumentOutOfRangeException 表示参数中的序号超出了范围 InvalidEnumArgumentException 表示参数中的枚举值不正确 InvalidOperationException 表示当前状态下不允许进行此操作(也就是说存在着允许进行此操作的另一种状态) ObjectDisposedException 表示对象已经 Dispose 过了,不能再使用了 NotSupportedException 表示不支持进行此操作(这是在说不要再试图对这种类型的对象调用此方法了,不支持) PlatformNotSupportedException 表示在此平台下不支持(如果程序跨平台的话) NotImplementedException 表示此功能尚在开发中,暂时请勿使用 实现错误: 前面由 CLR 抛出的异常代码主要都是实现错误 NullReferenceException 试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了 IndexOutOfRangeException 使用索引的时候超出了边界 InvalidCastException 表示试图对某个类型进行强转但类型不匹配 StackOverflow 表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归 OutOfMemoryException 表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了 AccessViolationException 这说明使用非托管内存时发生了错误 BadImageFormatException 这说明了加载的 dll 并不是期望中的托管 dll TypeLoadException 表示类型初始化的时候发生了错误 环境错误: IOException 下的各种子类 Win32Exception 下的各种子类 …… 另外,还剩下一些不应该抛出的异常,例如过于抽象的异常和已经过时的异常,这在前面一小结中有说明。 其他 一些常见异常的原因和解决方法 在平时的开发当中,你可能会遇到这样一些异常,它不像是自己代码中抛出的那些常见的异常,但也不包含我们自己的异常堆栈。 这里介绍一些常见这些异常的原因和解决办法。 AccessViolationException 当出现此异常时,说明非托管内存中发生了错误。如果要解决问题,需要从非托管代码中着手调查。 这个异常是访问了不允许的内存时引发的。在原因上会类似于托管中的 NullReferenceException。 参考资料 Handling and throwing exceptions in .NET - Microsoft Docs Exceptions and Exception Handling - C Programming Guide - Microsoft Docs 我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。 如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。 本篇文章为转载内容。原文链接:https://blog.csdn.net/WPwalter/article/details/94610764。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-13 13:38:26
59
转载
转载文章
...类型内置一系列获取和设置日期时间信息的方法。 Date 类型 ECMAScript 中的 Date 类型是在早期 Java 中 java.util.Date 类基础上构建的。为此,Date 类型使用 UTC(Coordinated Universal Time,国际协调时间[又称世界统一时间])1970 年 1 月 1 日午夜(零时)开始经过的毫秒来保存日期。在使用这种数据存储格式的条件下,Date 类型保存的日期能够精确到 1970 年 1 月 1 日之前或之后的 285616 年。 创建一个日期对象,使用 new 运算符和 Date 构造方法(构造函数)即可。 var box = new Date(); // 创建一个日期对象 在调用 Date 构造方法而不传递参数的情况下,新建的对象自动获取当前的时间和日期。 alert(box); // 不同浏览器显示不同 ECMAScript 提供了两个方法,Date.parse()和 Date.UTC()。Date.parse()方法接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应的毫秒数。ECMA-262 没有定义 Date.parse()应该支持哪种日期格式,因此方法的行为因实现而异,因地区而异。默认通常接收的日期格式如下: ‘月/日/年’,如 6/13/2011; ‘英文月名 日, 年’,如 May 25, 2004; ‘英文星期几 英文月名 日 年 时:分:秒 时区’,如 Tue May 25 2004 00:00:00 GMT-070 alert(Date.parse('6/13/2011')); // 1307894400000 如果 Date.parse()没有传入或者不是标准的日期格式,那么就会返回 NaN。 alert(Date.parse()); // NaN 如果想输出指定的日期,那么把 Date.parse()传入 Date 构造方法里。 var box = new Date(Date.parse('6/13/2011')); // Mon Jun 13 2011 00:00:00 GMT+0800var box = new Date('6/13/2011'); // 直接传入,Date.parse()后台被调用 Date 对象及其在不同浏览器中的实现有许多奇怪的行为。其中有一种倾向是将超出的范围的值替换成当前的值,以便生成输出。例如,在解析“January 32, 2007”时,有的浏览器会将其解释为“February 1, 2007”。而 Opera 则倾向与插入当前月份的当前日期。 Date.UTC()方法同样也返回表示日期的毫秒数,但它与 Date.parse()在构建值时使用不同的信息。(年份,基于 0 的月份[0 表示 1 月,1 表示 2 月],月中的哪一天[1-31],小时数[0-23] ,分钟,秒以及毫秒)。只有前两个参数是必须的。如果没有提供月数,则天数为 1;如果省略其他参数,则统统为 0。 alert(Date.UTC(2011,11)); // 1322697600000 如果 Date.UTC()参数传递错误,那么就会出现负值或者 NaN 等非法信息。 alert(Date.UTC()); // 负值或者 NaN 如果要输出指定日期,那么直接把 Date.UTC()传入 Date 构造方法里即可。 var box = new Date(Date.UTC(2011,11, 5, 15, 13, 16)); 通用的方法 与其他类型一样,Date 类型也重写了 toLocaleString()、toString()和 valueOf()方法;但这些方法返回值与其他类型中的方法不同。 var box = new Date(Date.UTC(2011,11, 5, 15, 13, 16));alert('toString:' + box.toString());alert('toLocaleString:' + box.toLocaleString()); // 按本地格式输出 这两个方法在不同浏览器显示的效果又不一样,但不用担心,这两个方法只是在调试比较有用,在显示时间和日期上,没什么价值。valueOf()方法显示毫秒数。 日期格式化方法 Date 类型还有一些专门用于将日期格式化为字符串的方法。 var box = new Date();alert(box.toDateString()); // 以特定的格式显示星期几、月、日和年alert(box.toTimeString()); // 以特定的格式显示时、分、秒和时区alert(box.toLocaleDateString()); // 以特定地区格式显示星期几、月、日和年alert(box.toLocaleTimeString()); // 以特定地区格式显示时、分、秒和时区alert(box.toUTCString()); // 以特定的格式显示完整的 UTC 日期 组件方法 组件方法,是为我们单独获取你想要的各种时间/日期而提供的方法。需要注意的时候 ,这些方法中,有带 UTC 的,有不带 UTC 的。UTC 日期指的是在没有时区偏差的情况下的日期值。 alert(box.getTime()); // 获取日期的毫秒数,和 valueOf()返回一致alert(box.setTime(100)); // 以毫秒数设置日期,会改变整个日期alert(box.getFullYear()); // 获取四位年份alert(box.setFullYear(2012)); // 设置四位年份,返回的是毫秒数alert(box.getMonth()); // 获取月份,没指定月份,从 0 开始算起alert(box.setMonth(11)); // 设置月份alert(box.getDate()); // 获取日期alert(box.setDate(8)); // 设置日期,返回毫秒数alert(box.getDay()); // 返回星期几,0 表示星期日,6 表示星期六alert(box.setDay(2)); // 设置星期几alert(box.getHours()); // 返回时alert(box.setHours(12)); // 设置时alert(box.getMinutes()); // 返回分钟alert(box.setMinutes(22)); // 设置分钟alert(box.getSeconds()); // 返回秒数alert(box.setSeconds(44)); // 设置秒数alert(box.getMilliseconds()); // 返回毫秒数alert(box.setMilliseconds()); // 设置毫秒数alert(box.getTimezoneOffset()); // 返回本地时间和 UTC 时间相差的分钟数 以上方法除了 getTimezoneOffset(),其他都具有 UTC 功能,例如 setDate()及 getDate()获取星期几,那么就会有 setUTCDate()及getUTCDate(),表示世界协调时间。 2、正则表达式 假设用户需要在 HTML 表单中填写姓名、地址、出生日期等。那么在将表单提交到服务器进一步处理前,JavaScript 程序会检查表单以确认用户确实输入了信息并且这些信息是符合要求的。 什么是正则表达式 正则表达式(regular expression)是一个描述字符模式的对象。ECMAScript 的 RegExp 类表示正则表达式,而 String 和 RegExp 都定义了使用正则表达式进行强大的模式匹配和文本检索与替换的函数。 正则表达式主要用来验证客户端的输入数据。用户填写完表单单击按钮之后,表单就会被发送到服务器,在服务器端通常会用 PHP、ASP.NET 等服务器脚本对其进行进一步处理 。因为客户端验证,可以节约大量的服务器端的系统资源,并且提供更好的用户体验。 创建正则表达式 创建正则表达式和创建字符串类似,创建正则表达式提供了两种方法,一种是采用 new 运算符,另一个是采用字面量方式。 两种创建方式 var box = new RegExp('box'); // 第一个参数字符串var box = new RegExp('box', 'ig'); // 第二个参数可选模式修饰符 模式修饰符的可选参数 参数 含义 i 忽略大小写 g 全局匹配 m 多行匹配 var box = /box/; // 直接用两个反斜杠var box = /box/ig; // 在第二个斜杠后面加上模式修饰符 测试正则表达式 RegExp 对象包含两个方法:test()和 exec(),功能基本相似,用于测试字符串匹配。test()方法在字符串中查找是否存在指定的正则表达式并返回布尔值,如果存在则返回 true,不存在则返回 false。exec()方法也用于在字符串中查找指定正则表达式,如果 exec()方法执行成功,则返回包含该查找字符串的相关信息数组。如果执行失败,则返回 null。 RegExp 对象的方法 方法 功能 test 在字符串中测试模式匹配,返回 true 或 false exec 在字符串中执行匹配搜索,返回结果数组 // 使用 new 运算符的 test 方法示例var pattern = new RegExp('box', 'i'); // 创建正则模式,不区分大小写var str = 'This is a Box!'; // 创建要比对的字符串alert(pattern.test(str)); // 通过 test()方法验证是否匹配// 使用字面量方式的 test 方法示例var pattern = /box/i; // 创建正则模式,不区分大小写var str = 'This is a Box!';alert(pattern.test(str));// 使用一条语句实现正则匹配alert(/box/i.test('This is a Box!')); // 模式和字符串替换掉了两个变量// 使用 exec 返回匹配数组var pattern = /box/i;var str = 'This is a Box!';alert(pattern.exec(str)); // 匹配了返回数组,否则返回 null 使用字符串的正则表达式方法 除了 test()和 exec()方法,String 对象也提供了 4 个使用正则表达式的方法。 String 对象中的正则表达式方法 方法 含义 match(pattern) 返回 pattern 中的子串或 null replace(pattern, replacement) 用 replacement 替换 pattern search(pattern) 返回字符串中 pattern 开始位置 split(pattern) 返回字符串按指定 pattern 拆分的数组 // 使用 match 方法获取获取匹配数组var pattern = /box/ig; // 全局搜索var str = 'This is a Box!,That is a Box too';alert(str.match(pattern)); // 匹配到两个 Box,Boxalert(str.match(pattern).length); // 获取数组的长度// 使用 search 来查找匹配数据var pattern = /box/ig;var str = 'This is a Box!,That is a Box too';alert(str.search(pattern)); // 查找到返回位置,否则返回-1 因为 search 方法查找到即返回,也就是说无需 g 全局。 // 使用 replace 替换匹配到的数据var pattern = /box/ig;var str = 'This is a Box!,That is a Box too';alert(str.replace(pattern, 'Tom')); // 将 Box 替换成了 Tom// 使用 split 拆分成字符串数组var pattern = / /ig;var str = 'This is a Box!,That is a Box too';alert(str.split(pattern)); // 将空格拆开分组成数组 RegExp 对象的静态属性 属性 短名 含义 input $_ 当前被匹配的字符串 lastMatch $& 最后一个匹配字符串 lastParen $+ 最后一对圆括号内的匹配子串 leftContext $ 最后一次匹配前的子串 multiline $ 用于指定是否所有的表达式都用于多行的布尔值 rightContext $’ 在上次匹配之后的子串 // 使用静态属性var pattern = /(g)oogle/;var str = 'This is google!';pattern.test(str); // 执行一下alert(RegExp.input); // This is google!alert(RegExp.leftContext); // This isalert(RegExp.rightContext); // !alert(RegExp.lastMatch); // googlealert(RegExp.lastParen); // galert(RegExp.multiline); // false Opera 不支持 input、lastMatch、lastParen 和 multiline 属性。IE 不支持 multiline 属性。所有的属性可以使用短名来操作。RegExp.input 可以改写成 RegExp['$_'],依次类推。但 RegExp.input 比较特殊,它还可以写成 RegExp.$_。 RegExp 对象的实例属性 属性 含义 global Boolean 值,表示 g 是否已设置 ignoreCase Boolean 值,表示 i 是否已设置 lastIndex 整数,代表下次匹配将从哪里字符位置开始 multiline Boolean 值,表示 m 是否已设置 Source 正则表达式的源字符串形式 // 使用实例属性var pattern = /google/ig;alert(pattern.global); // true,是否全局了alert(pattern.ignoreCase); // true,是否忽略大小写alert(pattern.multiline); // false,是否支持换行alert(pattern.lastIndex); // 0,下次的匹配位置alert(pattern.source); // google,正则表达式的源字符串var pattern = /google/g;var str = 'google google google';pattern.test(str); // google,匹配第一次alert(pattern.lastIndex); // 6,第二次匹配的位 以上基本没什么用。并且 lastIndex 在获取下次匹配位置上 IE 和其他浏览器有偏差 ,主要表现在非全局匹配上。lastIndex 还支持手动设置,直接赋值操作。 获取控制 正则表达式元字符是包含特殊含义的字符。它们有一些特殊功能,可以控制匹配模式的方式。反斜杠后的元字符将失去其特殊含义。 字符类:单个字符和数字 元字符/元符号 匹配情况 . 匹配除换行符外的任意字符 [a-z0-9] 匹配括号中的字符集中的任意字符 [^a-z0-9] 匹配任意不在括号中的字符集中的字符 \d 匹配数字 \D 匹配非数字,同[^0-9]相同 \w 匹配字母和数字及_ \W 匹配非字母和数字及_ 字符类:空白字符 元字符/元符号 匹配情况 \0 匹配 null 字符 \b 匹配空格字符 \f 匹配进纸字符 \n 匹配换行符 \r 匹配回车字符 \t 匹配制表符 \s 匹配空白字符、空格、制表符和换行符 \S 匹配非空白字符 字符类:锚字符 元字符/元符号 匹配情况 ^ 行首匹配 $ 行尾匹配 \A 只有匹配字符串开始处 \b 匹配单词边界,词在[]内时无效 \B 匹配非单词边界 \G 匹配当前搜索的开始位置 \Z 匹配字符串结束处或行尾 \z 只匹配字符串结束处 字符类:重复字符 元字符/元符号 匹配情况 x? 匹配 0 个或 1 个 x x 匹配 0 个或任意多个 x x+ 匹配至少一个 x (xyz)+ 匹配至少一个(xyz) x{m,n} 匹配最少 m 个、最多 n 个 x 字符类:替代字符 元字符/元符号 匹配情况 this where 字符类:记录字符 元字符/元符号 匹配情况 (string) 用于反向引用的分组 \1 或$1 匹配第一个分组中的内容 \2 或$2 匹配第二个分组中的内容 \3 或$3 匹配第三个分组中的内容 // 使用点元字符var pattern = /g..gle/; // .匹配一个任意字符var str = 'google';alert(pattern.test(str));// 重复匹配var pattern = /g.gle/; // .匹配 0 个一个或多个var str = 'google'; //,?,+,{n,m}alert(pattern.test(str));// 使用字符类匹配var pattern = /g[a-zA-Z_]gle/; // [a-z]表示任意个 a-z 中的字符var str = 'google';alert(pattern.test(str));var pattern = /g[^0-9]gle/; // [^0-9]表示任意个非 0-9 的字符var str = 'google';alert(pattern.test(str));var pattern = /[a-z][A-Z]+/; // [A-Z]+表示 A-Z 一次或多次var str = 'gOOGLE';alert(pattern.test(str));// 使用元符号匹配var pattern = /g\wgle/; // \w匹配任意多个所有字母数字_var str = 'google';alert(pattern.test(str));var pattern = /google\d/; // \d匹配任意多个数字var str = 'google444';alert(pattern.test(str));var pattern = /\D{7,}/; // \D{7,}匹配至少 7 个非数字var str = 'google8';alert(pattern.test(str));// 使用锚元字符匹配var pattern = /^google$/; // ^从开头匹配,$从结尾开始匹配var str = 'google';alert(pattern.test(str));var pattern = /goo\sgle/; // \s 可以匹配到空格var str = 'goo gle';alert(pattern.test(str));var pattern = /google\b/; // \b 可以匹配是否到了边界var str = 'google';alert(pattern.test(str));// 使用或模式匹配var pattern = /google|baidu|bing/; // 匹配三种其中一种字符串var str = 'google';alert(pattern.test(str));// 使用分组模式匹配var pattern = /(google){4,8}/; // 匹配分组里的字符串 4-8 次var str = 'googlegoogle';alert(pattern.test(str));var pattern = /8(.)8/; // 获取 8..8 之间的任意字符var str = 'This is 8google8';str.match(pattern);alert(RegExp.$1); // 得到第一个分组里的字符串内容var pattern = /8(.)8/;var str = 'This is 8google8';var result = str.replace(pattern,'<strong>$1</strong>'); // 得到替换的字符串输出document.write(result);var pattern = /(.)\s(.)/;var str = 'google baidu';var result = str.replace(pattern, '$2 $1'); // 将两个分组的值替换输出document.write(result); 贪婪 惰性 + +? ? ?? ? {n} {n}? {n,} {n,}? {n,m} {n,m}? // 关于贪婪和惰性var pattern = /[a-z]+?/; // ?号关闭了贪婪匹配,只替换了第一个var str = 'abcdefjhijklmnopqrstuvwxyz';var result = str.replace(pattern, 'xxx');alert(result);var pattern = /8(.+?)8/g; // 禁止了贪婪,开启的全局var str = 'This is 8google8, That is 8google8, There is 8google8';var result = str.replace(pattern,'<strong>$1</strong>');document.write(result);var pattern = /8([^8])8/g; // 另一种禁止贪婪var str = 'This is 8google8, That is 8google8, There is 8google8';var result = str.replace(pattern,'<strong>$1</strong>');document.write(result);// 使用 exec 返回数组var pattern = /^[a-z]+\s[0-9]{4}$/i;var str = 'google 2012';alert(pattern.exec(str)); // 返回整个字符串var pattern = /^[a-z]+/i; // 只匹配字母var str = 'google 2012';alert(pattern.exec(str)); // 返回 googlevar pattern = /^([a-z]+)\s([0-9]{4})$/i; // 使用分组var str = 'google 2012';alert(pattern.exec(str)[0]); // google 2012alert(pattern.exec(str)[1]); // googlealert(pattern.exec(str)[2]); // 2012// 捕获性分组和非捕获性分组var pattern = /(\d+)([a-z])/; // 捕获性分组var str = '123abc';alert(pattern.exec(str));var pattern = /(\d+)(?:[a-z])/; // 非捕获性分组var str = '123abc';alert(pattern.exec(str));// 使用分组嵌套var pattern = /(A?(B?(C?)))/; // 从外往内获取var str = 'ABC';alert(pattern.exec(str));// 使用前瞻捕获var pattern = /(goo(?=gle))/; // goo 后面必须跟着 gle 才能捕获var str = 'google';alert(pattern.exec(str));// 使用特殊字符匹配var pattern = /\.\[\/b\]/; // 特殊字符,用\符号转义即可var str = '.[/b]';alert(pattern.test(str));// 使用换行模式var pattern = /^\d+/mg; // 启用了换行模式var str = '1.baidu\n2.google\n3.bing';var result = str.replace(pattern, '');alert(result); 常用的正则 检查邮政编码 var pattern = /[1-9][0-9]{5}/; // 共 6 位数字,第一位不能为 0var str = '224000';alert(pattern.test(str)); 检查文件压缩包 var pattern = /[\w]+\.zip|rar|gz/; // \w 表示所有数字和字母加下划线var str = '123.zip'; // \.表示匹配.,后面是一个选择alert(pattern.test(str)); 删除多余空格 var pattern = /\s/g; // g 必须全局,才能全部匹配var str = '111 222 333';var result = str.replace(pattern,''); // 把空格匹配成无空格alert(result); 删除首尾空格 var pattern = /^\s+/; // 强制首var str = ' goo gle ';var result = str.replace(pattern, '');pattern = /\s+$/; // 强制尾result = result.replace(pattern, '');alert('|' + result + '|');var pattern = /^\s(.+?)\s$/; // 使用了非贪婪捕获var str = ' google ';alert('|' + pattern.exec(str)[1] + '|');var pattern = /^\s(.+?)\s$/;var str = ' google ';alert('|' + str.replace(pattern, '$1') + '|'); // 使用了分组获取 简单的电子邮件验证 var pattern = /^([a-zA-Z0-9_\.\-]+)@([a-zA-Z0-9_\.\-]+)\.([a-zA-Z]{2,4})$/;var str = 'yc60.com@gmail.com';alert(pattern.test(str));var pattern = /^([\w\.\-]+)@([\w\.\-]+)\.([\w]{2,4})$/;var str = 'yc60.com@gmail.com';alert(pattern.test(str)); 3、Function类型 在 ECMAScript 中,Function(函数)类型实际上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针。 函数的声明方式 普通的函数声明 function box(num1, num2) {return num1+ num2;} 使用变量初始化函数 var box= function(num1, num2) {return num1 + num2;}; 使用 Function 构造函数 var box= new Function('num1', 'num2' ,'return num1 + num2'); 第三种方式我们不推荐,因为这种语法会导致解析两次代码(第一次解析常规 ECMAScript 代码,第二次是解析传入构造函数中的字符串),从而影响性能。但我们可以通过这种语法来理解"函数是对象,函数名是指针"的概念。 作为值的函数 ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。 function box(sumFunction, num) {return sumFunction(num); // someFunction}function sum(num) {return num + 10;}var result = box(sum, 10); // 传递函数到另一个函数里 函数内部属性 在函数内部,有两个特殊的对象:arguments 和 this。arguments 是一个类数组对象,包含着传入函数中的所有参数,主要用途是保存函数参数。但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。 function box(num) {if (num <= 1) {return 1;} else {return num box(num-1); // 一个简单的的递归} } 对于阶乘函数一般要用到递归算法,所以函数内部一定会调用自身;如果函数名不改变是没有问题的,但一旦改变函数名,内部的自身调用需要逐一修改。为了解决这个问题,我们可以使用 arguments.callee 来代替。 function box(num) {if (num <= 1) {return 1;} else {return num arguments.callee(num-1); // 使用 callee 来执行自身} } 函数内部另一个特殊对象是 this,其行为与 Java 和 C中的 this 大致相似。换句话说 ,this 引用的是函数据以执行操作的对象,或者说函数调用语句所处的那个作用域。当在全局作用域中调用函数时,this 对象引用的就是 window。 // 便于理解的改写例子window.color = '红色的'; // 全局的,或者 var color = '红色的';也行alert(this.color); // 打印全局的 colorvar box = {color : '蓝色的', // 局部的 colorsayColor : function () {alert(this.color); // 此时的 this 只能 box 里的 color} };box.sayColor(); // 打印局部的 coloralert(this.color); // 还是全局的// 引用教材的原版例子window.color = '红色的'; // 或者 var color = '红色的';也行var box = {color : '蓝色的'};function sayColor() {alert(this.color); // 这里第一次在外面,第二次在 box 里面}getColor();box.sayColor = sayColor; // 把函数复制到 box 对象里,成为了方法box.sayColor(); 函数属性和方法 ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性 :length 和 prototype。其中,length 属性表示函数希望接收的命名参数的个数。 function box(name, age) {alert(name + age);}alert(box.length); // 2 对于 prototype 属性,它是保存所有实例方法的真正所在,也就是原型。这个属性 ,我们将在面向对象一章详细介绍。而 prototype 下有两个方法:apply()和 call(),每个函数都包含这两个非继承而来的方法。这两个方法的用途都在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。 function box(num1, num2) {return num1 + num2; // 原函数}function sayBox(num1, num2) {return box.apply(this, [num1, num2]); // this 表示作用域,这里是 window} // []表示 box 所需要的参数function sayBox2(num1, num2) {return box.apply(this, arguments); // arguments 对象表示 box 所需要的参数}alert(sayBox(10,10)); // 20alert(sayBox2(10,10)); // 20 call()方法于 apply()方法相同,他们的区别仅仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是作用域,没有变化,变化只是其余的参数都是直接传递给函数的。 function box(num1, num2) {return num1 + num2;}function callBox(num1, num2) {return box.call(this, num1, num2); // 和 apply 区别在于后面的传参}alert(callBox(10,10)); 事实上,传递参数并不是 apply()和 call()方法真正的用武之地;它们经常使用的地方是能够扩展函数赖以运行的作用域。 var color = '红色的'; // 或者 window.color = '红色的';也行var box = {color : '蓝色的'};function sayColor() {alert(this.color);}sayColor(); // 作用域在 windowsayColor.call(this); // 作用域在 windowsayColor.call(window); // 作用域在 windowsayColor.call(box); // 作用域在 box,对象冒充 这个例子是之前作用域理解的例子修改而成,我们可以发现当我们使用 call(box)方法的时候,sayColor()方法的运行环境已经变成了 box 对象里了。 使用 call()或者 apply()来扩充作用域的最大好处,就是对象不需要与方法发生任何耦合关系(耦合,就是互相关联的意思,扩展和维护会发生连锁反应)。也就是说,box 对象和 sayColor()方法之间不会有多余的关联操作,比如 box.sayColor = sayColor;。 本篇文章为转载内容。原文链接:https://blog.csdn.net/gongxifacai_believe/article/details/108286196。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-24 13:01:25
529
转载
转载文章
...,SSL证书能够确保客户端与服务器之间传输数据的安全性,防止信息被窃取或篡改。在本文的语境中,当网络爬虫遇到未安装有效SSL证书的网站时,会触发SSLError,此时可以通过设置requests库中的verify参数为False来忽略证书验证,从而继续爬取数据。 cookie , Cookie是一种由服务器发送到用户浏览器并存储在用户本地的小型文本文件。它通常包含识别用户会话或持久化用户状态的信息,如登录凭证、用户偏好等。在网络爬虫应用中,模拟用户登录状态常需要利用cookie,通过在HTTP请求头中携带已登录用户的cookie信息,使得爬虫可以像真实用户那样访问受权限保护的内容。 session , Session是指在一次用户会话过程中,服务器为该用户维护的状态信息集合。与cookie不同的是,session数据存储在服务器端,而客户端仅存储一个会话标识符(session ID),通常也以cookie的形式存在。在网络爬虫中使用session对象(如requests库中的requests.session()方法创建的对象),可以帮助爬虫维持与服务器之间的状态信息,实现连续操作间的关联和认证,这对于处理需要保持登录状态或进行多次交互的网页抓取任务尤为关键。
2023-03-01 12:40:55
563
转载
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
sed 's/old/new/g' file.txt
- 替换文件中的文本。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"