前端技术
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
[内存地址与对象内容的区别]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_45630258/article/details/124785912。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 一、部署Nginx 1、下载nginx镜像(这一步可以省略,直接进入第二步会自动下载的) pull 2、启动容器 run 3、进入容器 exec 1、镜像: docker hub官网搜索nginx 下载:docker pull nginx 查看下载情况:docker images 2、容器: 创建容器命令:docker run [-d 后台启动] [–name nginx01 起别名] [-p 3344:80 端口:协议] [镜像(包含版本)] (创建)启动容器实例:docker run -d --name nginx01 -p 3344:80 nginx 查看容器运行状况:docker ps 本机访问测试一下:curl localhost:3344 ■ 端口暴露 -p 宿主机端口:容器内部端口 浏览器输入: http://服务器ip地址:3344/ 3344 是暴露的端口 ----接下来: 进入(正在运行的)容器内部:docker exec -it nginx01 /bin/bash [root@iZwz9535z41cmgcpkm7i81Z /] docker exec -it nginx01 /bin/bashroot@d1a29e4791e3:/ whereis nginxnginx: /usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginxroot@d1a29e4791e3:/ cd /etc/nginxroot@d1a29e4791e3:/etc/nginx lsconf.d fastcgi_params mime.types modules nginx.conf scgi_params uwsgi_paramsroot@d1a29e4791e3:/etc/nginx ■ /bin/bash 是Linux的一种常用shell脚本,用于解释执行Linux命令,根据镜像支持的shell的不同,可以使用不同的的shell脚本。 容器,也是和虚拟机一样是虚拟技术呀,通过脚本执行/bin/bash实现,创建并进入容器内部docker ● 思考问题:每次改动nginx配置文件,都需要进入容器内部,十分麻烦: 要是可以在容器外部提供一个映射路径,达到在容器修改文件名,容器内部就可以自动修改?-v 数据卷技术! 二、部署tomcat docker run 可以不用pull,能自动下载 ctrl+c退出 docker pull tomcat:9.0 启动运行,应该加上版本号: docker run -d -p 3355:8080 --name tomcat01 tomcat:9.0 进入容器 docker exec -it tomcat01 /bin/bash ● 部署tomcat,发现问题: 1、linux命令少了 2、没有webapps 这是阿里云镜像的原因:默认使用最小镜像,所有不必要的都剔除了,保证最小可运行环境 可以通过拷贝的方式,解决没有webapps的问题: 在浏览器中输入:http://服务器ip地址:3355/ 进行访问 ● 思考问题:我们以后部署项目,如果每次都要进入容器很麻烦? 要是可以在容器外部提供一个映射路径,webapps,我们在外部放置项目,容器内部就可以自动修改?-v 数据卷技术! 三、部署es+kibana ● Elasticsearch 的问题: es 暴露的端口很多 es 十分耗内存 es 的数据一般需要放置到安全目录!挂载 1、问题1:es 十分耗内存 下载启动运行elastissearch 之后,Linux系统就变得特别卡 # 启动了 linux就卡住了docker stats# 查看 cpu的状态 #es 是十分耗内存的,1.xG# 1核2G(学生机)! # 查看 docker stats 2、问题2:es 需要暴露的端口很多 -p (下载)启动 elasticsearch$ docker run -d --name elasticsearch01 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.6.2 查看内存占用情况docker stats 先感觉stop一下docker stop ba18713ca536 3、es 十分耗内存的解决:增加内存的限制,修改配置文件 -e 环境配置修改 通过 -e 限制内存docker run -d --name elasticsearch02 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m" elasticsearch:7.6.2 [root@iZwz9535z41cmgcpkm7i81Z /] curl localhost:9200/{"name" : "14329968b00f","cluster_name" : "docker-cluster","cluster_uuid" : "0iDu-G_KTo-4X8KORDj1XQ","version" : {"number" : "7.6.2","build_flavor" : "default","build_type" : "docker","build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f","build_date" : "2020-03-26T06:34:37.794943Z","build_snapshot" : false,"lucene_version" : "8.4.0","minimum_wire_compatibility_version" : "6.8.0","minimum_index_compatibility_version" : "6.0.0-beta1"},"tagline" : "You Know, for Search"} 4、思考:用kibana连接elasticsearch? 思考(kibana连接elasticsearch)网络如何连接过去 ☺ 参考来源: 狂神的B站视频《【狂神说Java】Docker最新超详细版教程通俗易懂》 https://www.bilibili.com/video/BV1og4y1q7M4 如果本文对你有帮助的话记得给一乐点个赞哦,感谢! 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_45630258/article/details/124785912。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-12 10:54:44
65
转载
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_34253126/article/details/94304775。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 6月分了,又该更新一篇博客了。由于老婆换工作,最近找房子,换地方住,感受就是房价贵,身体累。 最近在工作技术上印像较深的应该就是任务的划分,专业一点就是WBS的分解,如何分解得好,不同的分解都能把任务分解下来,而且表面上也是满足要求的,但是可以说不同的分解在时间或者理解或者沟通成本等方面都会有影响。 做为程序员,我们先看看下面代码 一 for(int i=0;i<1000;i++){for(int j=0;j<10;j++){//do something } } 二 for(int j=0;j<10;j++){for(int i=0;i<1000;i++){//do something } } 针对这两段代码 都是以 i,j为参数做一些事情,但是两个的效果是否一样呢?没有区别,对在程序上面什么区别,结果也基本上没有什么区别。但是我今天的文章中是认为这个是有区别的。你现在要把10000箱东西搬上1楼,现在有两种方案,第一种是 每次搬10箱,搬1000次,第二种是 每次搬1000箱,搬10次。所以这里看出来就是有区别的了,这个我们就要看什么成本高,比如一次搬10箱 成本为X,每增加一箱会增加小x的成本,但是上一次楼的成本是Y,那么两种方案会得到如下成本公式。 第一种:成本=X+1000Y 第二种:成本=X+990x+10Y 最后通过计算是能选出来个成本最低的方案来执行的。 回到工作分解结构上来的。比如3个功能要分解,每个功能有3部分,1.接收数据,2.处理数据,3.写入数据库,当然三个功能是不同的内容,只是大体结构相同。我目前见得最多的是这样分,直接按3个功能分成3个任务,一种是一个功能的一部分分成一个任务,也就是分下来有6个任务。 这里我有点微微的吐嘲一下分成6个任务的坏处。我们先说一下好处。 1.3个人每个人拿3个小任务,任务显得小,对他们压力小一些。 2.每个人处理自己的3个任务类似,可能处理整速度快,而且分配时按善长哪一块分配哪一块的方式,较为合理。 下面说一下坏处,我认为还是弊大于利,下面列一些坏处(因为目前公司就是很多这样分配的任务) 1.3部分功能,3个文档,如果分给3个人来做,那么每个人都要求很精确的理解文档的意思,然后找出自己要做的部分来处理。 2.3个人看3个文档,假设每个文档由一个设计人员设计,那么这3个设计人员都要与3个开发人员产生沟通(所以沟通成本约为第一种方安的3倍,可能小于3倍) 3.开发人员在这种做多个相似(我们假设相似,其实这些问题因该由一个好的架构设计来处理)的编码情况下容易厌倦,产生复制修改代码的情况。 4.还有一部分成本前面3点都没有说到,也是沟通的成本,也就是一个功能里面的三个部分的衔接问题,也就是每个功能模块多了2个开发人员的沟通,也就是多出6个单位沟通成本。 先就说这么几点吧。但是我觉得已经很致命了,公司经常出现重复的沟通,就是上面所说的一个设计人员要同多个开发说明一件事情,而且不是在一起说,是开发在参与到开发过程中时,反馈回去,然后只有同这个开发沟通,可能与每个开发沟通的内容有一部分不是重复的,但是他们的设计内容都是一个模块当中的。而且公司经常出来开发与开发的衔接部分的沟通,有分歧时也会叫设计人员参与进来。所以这样分配的最大的成本就是沟通上面的成本,或者是变更方面的成本最大,比如一个功能模块有要变动,那么可能要通知3个开发人员。要是第一种方案可能就通知一个开发人员就行了。这里也不是说其他的人员不通知,我这里的意思是通知的力度是不一样的,如果是一个责任矩阵(Responsibility Matrix)来看的话,可能这种一点的方案会3个开发人员A,一个组长R,其它人员I。如果是上面一种方案那么可能是1个开发人员A,一个组长R,其它人员I.这里我也就是想说明他们的力度是不一样的。当然成本肯定也不一样。 插入:(我打算在以后的文章中加入插入系列,主要用于解释一些我认为比较有趣,或者有用,或者对我对大家来说可能陌生,但是有印像,本人也是通过查询总结出来的一些东西,多数为一些名词解释) 插入: 责任矩阵 责任矩阵是以表格形式表示完成工作分解结构中工作细目的个人责任方法。这是在项目管理中一个十分重要的工具,因为他强调每一项工作细目由谁负责,并表明每个人的角色在整个项目中的地位。制定责任色(RACI)(R=Responsible,A=Accountable,C=Consulted,I=Informed)。 插入后面继续说,刚才已经吐槽了一下一种方案的坏处,所以我认为对于分解还是逃不过模块,一个人做不下来的大模块,分解成小模块,每个模块主要就是IPO,输入什么,做什么事,出输什么,模块接口要设计好,这样一个一个的装配上就是一个大的系统,而不是把一个模块的类似部分或者说一个独立的功能模块再来分开。最小的模块我们就是函数,或者现在面向对象可以说类,但是细化下来的思想面向过程还是有用处的。这里我就强调一点,现代的设计中多用接口这个东西吧,你慢慢会发现他有很大的用处的。 总结:从昨天下午开始写这个,今天才完成中间有断开,所以可能思路不太清析,但是主要说的一点就是工作分解结构里面的一小部分内容,说了说两种分解方式的优劣。建议大家以接口设计,功能模块,类等去处理分解任务。 转载于:https://www.cnblogs.com/gw2010/p/3781447.html 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_34253126/article/details/94304775。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-29 21:22:45
111
转载
Apache Lucene
...on { // 创建内存中的目录,用于存储索引 Directory directory = new RAMDirectory(); // 创建索引配置 IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer()); // 创建索引写入器 IndexWriter indexWriter = new IndexWriter(directory, config); // 创建文档对象 Document doc = new Document(); doc.add(new Field("content", "Hello Lucene!", Field.Store.YES, Field.Index.ANALYZED)); // 添加文档到索引 indexWriter.addDocument(doc); // 关闭索引写入器 indexWriter.close(); } } 在这个例子中,我们首先创建了一个内存中的目录(RAMDirectory),这是为了方便演示。接着,我们定义了索引配置,并使用StandardAnalyzer对文本进行分析。最后,我们创建了一个文档,并将它添加到了索引中。是不是很简单呢? 2.2 解决NullPointerException:预防胜于治疗 现在,让我们回到那个恼人的NullPointerException问题上。在用Lucene做索引的时候,经常会被空指针异常坑到,特别是当你试图去访问那些还没被初始化的对象或者字段时。为了避免这种情况,我们需要养成良好的编程习惯,比如: - 检查null值:在访问任何对象前,先检查是否为null。 - 初始化变量:确保所有对象在使用前都被正确初始化。 - 使用Optional类:Java 8引入的Optional类可以帮助我们更好地处理可能为空的情况。 例如,假设我们在处理索引文档时遇到了一个可能为空的字段,我们可以这样处理: java // 假设我们有一个可能为空的内容字段 String content = getContent(); // 这里可能会返回null if (content != null) { doc.add(new Field("content", content, Field.Store.YES, Field.Index.ANALYZED)); } else { System.out.println("内容字段为空!"); } 三、深入探索 Lucene的高级特性 3.1 搜索:不仅仅是查找 除了创建索引外,Lucene还提供了强大的搜索功能。让我们来看一个简单的搜索示例: java import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; public class SimpleSearcher { public static void main(String[] args) throws Exception { Directory directory = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer()); IndexWriter indexWriter = new IndexWriter(directory, config); Document doc = new Document(); doc.add(new Field("content", "Hello Lucene!", Field.Store.YES, Field.Index.ANALYZED)); indexWriter.addDocument(doc); indexWriter.close(); DirectoryReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); QueryParser parser = new QueryParser("content", new StandardAnalyzer()); Query query = parser.parse("lucene"); TopDocs results = searcher.search(query, 10); for (ScoreDoc scoreDoc : results.scoreDocs) { System.out.println(searcher.doc(scoreDoc.doc).get("content")); } reader.close(); } } 这段代码展示了如何使用QueryParser解析查询字符串,并使用IndexSearcher执行搜索操作。通过这种方式,我们可以轻松地从索引中检索出相关的文档。 3.2 高级搜索技巧:优化你的查询 当你开始构建更复杂的搜索逻辑时,Lucene提供了许多高级功能来帮助你优化搜索结果。比如说,你可以用布尔查询把好几个搜索条件拼在一起,或者用模糊匹配让搜索变得更灵活一点。这样找东西就方便多了! java import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.FuzzyQuery; // 构建布尔查询 BooleanQuery booleanQuery = new BooleanQuery(); booleanQuery.add(new TermQuery(new Term("content", "hello")), BooleanClause.Occur.MUST); booleanQuery.add(new FuzzyQuery(new Term("content", "lucen")), BooleanClause.Occur.SHOULD); TopDocs searchResults = searcher.search(booleanQuery, 10); 在这个例子中,我们创建了一个布尔查询,其中包含两个子查询:一个是必须满足的精确匹配查询,另一个是可选的模糊匹配查询。这种组合可以显著提升搜索的准确性和相关性。 四、结语 享受编码的乐趣 通过这篇文章,我们不仅学习了如何使用Apache Lucene来创建和搜索索引,还一起探讨了如何有效地避免NullPointerException。希望这些示例代码和技巧能对你有所帮助。记住,编程不仅仅是一门技术,更是一种艺术。尽情享受编程的乐趣吧,一路探索和学习,你会发现自己的收获多到让人惊喜!如果你有任何问题或想法,欢迎随时与我交流! --- 以上就是关于Apache Lucene与javalangNullPointerException: null的讨论。希望能通过这篇文章点燃你对Lucene的热情,让你在实际开发中游刃有余,玩得更嗨!让我们一起继续探索更多有趣的技术吧!
2024-10-16 15:36:29
88
岁月静好
Hive
...文件里写着HDFS的地址是hdfs://namenode:9000/,但是实际上NameNode所在的机器根本不在网络范围内,那Hive当然会报错啦。 解决方法:检查一下网络连接是否正常。你可以试着ping一下HDFS的NameNode地址,看看能不能通。如果不行的话,赶紧找网络管理员帮忙修一下。 2.2 权限问题 其次,权限问题也是常见的原因。HDFS对文件和目录是有严格权限控制的,如果你的用户没有足够的权限去读取某个文件,那么Hive自然也无能为力。 举个栗子,假如你有一个HDFS路径/user/hive/warehouse/my_table,但是这个目录的权限设置成了只有root用户才能访问,而你的Hive用户不是root,那肯定就悲剧了。 解决方法:检查HDFS上的文件和目录权限。如果你想看看某个文件的权限,可以用这个命令:hadoop fs -ls /path/to/file。看完之后,要是觉得权限不对劲,就动手改一下呗,比如说用hadoop fs -chmod 755 /path/to/file,给它整成合适的权限就行啦! 2.3 HDFS服务未运行 还有一种可能是HDFS服务本身挂掉了。比如说,NameNode突然罢工了,DataNode也闹起了情绪,甚至整个集群都瘫痪了,啥都不干了。哎呀糟糕了,这情况有点悬啊!HDFS直接罢工了,完全不干活,任凭Hive使出浑身解数也无济于事。这下可好,整个系统像是瘫了一样,啥也跑不起来了。 解决方法:检查HDFS的服务状态。可以通过命令jps查看是否有NameNode和DataNode进程在运行。如果没有,那就得赶紧启动它们,或者重启整个HDFS服务。 三、实战演练 Hive访问HDFS的具体操作 接下来,我们通过一些实际的例子来看看如何用Hive操作HDFS。 3.1 创建表并加载数据到HDFS 假设我们现在要创建一个简单的表,并将数据加载到HDFS中。我们可以先创建一个本地文件data.txt,内容如下: id,name,age 1,Alice,25 2,Bob,30 3,Charlie,35 然后上传到HDFS: bash hadoop fs -put data.txt /user/hive/warehouse/my_table/ 接着在Hive中创建表: sql CREATE TABLE my_table ( id INT, name STRING, age INT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' STORED AS TEXTFILE; 最后加载数据: sql LOAD DATA INPATH '/user/hive/warehouse/my_table/data.txt' INTO TABLE my_table; 这样,我们的数据就成功存到了HDFS上,并且Hive也能读取到了。 3.2 查询数据 现在我们可以试试查询数据: sql SELECT FROM my_table; 如果一切正常,你应该能看到类似这样的结果: OK 1 Alice 25 2 Bob 30 3 Charlie 35 Time taken: 0.077 seconds, Fetched: 3 row(s) 但如果之前出现了访问不了HDFS的情况,这里就会报错。所以我们要确保每一步都正确无误。 四、总结与展望 总之,Hive无法访问HDFS的问题虽然看起来很复杂,但实际上只要找到根本原因,解决起来并不难。无论是网络问题、权限问题还是服务问题,都有相应的解决办法。嘿,大家听我说啊!以后要是再碰到这种事儿,别害怕,也别乱了阵脚。就当是玩个解谜游戏,一步一步慢慢来,肯定能找出办法搞定它! 未来,随着大数据技术的发展,Hive和HDFS的功能也会越来越强大。说不定哪天它们还能像人类一样交流感情呢!(开玩笑啦) 好了,今天的分享就到这里啦。如果你还有什么疑问或者经验想要分享,欢迎随时留言讨论哦!让我们一起进步,一起探索大数据的奥秘吧!
2025-04-01 16:11:37
105
幽谷听泉
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_73892801/article/details/129181633。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 Java的特点:1、面向对象;它对对象中的类、对象、继承、封装、多态、接口、包等均有很好支持。2、平台无关性;在引入虚拟机之后,Java语言在不同的平台上运行不需要重新编译。3、简单性。4、解释执行;程序在Java平台运行时会被编译成字节码文件,然后可以在有Java环境的操作系统上运行。5、支持多线程,并提供多线程之间的同步机制;6、分布式;7、健壮性;8、高性能;9、安全性。 什么是Java语言 简单地说,Java 是由 Sun Microsystems 公司于 1995 年推出的一门面向对象程序设计语言。2010 年 Oracle 公司收购 Sun Microsystems,之后由 Oracle 公司负责 Java 的维护和版本升级。 其实,Java 还是一个平台。Java 平台由 Java 虚拟机(Java Virtual Machine,JVM)和 Java 应用编程接口(Application Programming Interface,API)构成。Java 应用编程接口为此提供了一个独立于操作系统的标准接口,可分为基本部分和扩展部分。在硬件或操作系统平台上安装一个 Java 平台之后,Java 应用程序就可运行。 Java 平台已经嵌入了几乎所有的操作系统。这样 Java 程序只编译一次,就可以在各种系统中运行。Java 应用编程接口已经从 1.1x 版本发展到 1.2 版本。 Java语言的特点 Java 语言的风格很像 C 语言和 C++ 语言,是一种纯粹的面向对象语言,它继承了 C++ 语言面向对象的技术核心,但是拋弃了 C++ 的一些缺点,比如说容易引起错误的指针以及多继承等,同时也增加了垃圾回收机制,释放掉不被使用的内存空间,解决了管理内存空间的烦恼。 Java 语言是一种分布式的面向对象语言,具有面向对象、平台无关性、简单性、解释执行、多线程、安全性等很多特点,下面针对这些特点进行逐一介绍。 1. 面向对象 Java 是一种面向对象的语言,它对对象中的类、对象、继承、封装、多态、接口、包等均有很好的支持。为了简单起见,Java 只支持类之间的单继承,但是可以使用接口来实现多继承。使用 Java 语言开发程序,需要采用面向对象的思想设计程序和编写代码。 2. 平台无关性 平台无关性的具体表现在于,Java 是“一次编写,到处运行(Write Once,Run any Where)”的语言,因此采用 Java 语言编写的程序具有很好的可移植性,而保证这一点的正是 Java 的虚拟机机制。在引入虚拟机之后,Java 语言在不同的平台上运行不需要重新编译。 Java 语言使用 Java 虚拟机机制屏蔽了具体平台的相关信息,使得 Java 语言编译的程序只需生成虚拟机上的目标代码,就可以在多种平台上不加修改地运行。 3. 简单性 Java 语言的语法与 C 语言和 C++ 语言很相近,使得很多程序员学起来很容易。对 Java 来说,它舍弃了很多 C++ 中难以理解的特性,如操作符的重载和多继承等,而且 Java 语言不使用指针,加入了垃圾回收机制,解决了程序员需要管理内存的问题,使编程变得更加简单。 4. 解释执行 Java 程序在 Java 平台运行时会被编译成字节码文件,然后可以在有 Java 环境的操作系统上运行。在运行文件时,Java 的解释器对这些字节码进行解释执行,执行过程中需要加入的类在连接阶段被载入到运行环境中。 5. 多线程 Java 语言是多线程的,这也是 Java 语言的一大特性,它必须由 Thread 类和它的子类来创建。Java 支持多个线程同时执行,并提供多线程之间的同步机制。任何一个线程都有自己的 run() 方法,要执行的方法就写在 run() 方法体内。 6. 分布式 Java 语言支持 Internet 应用的开发,在 Java 的基本应用编程接口中就有一个网络应用编程接口,它提供了网络应用编程的类库,包括 URL、URLConnection、Socket 等。Java 的 RIM 机制也是开发分布式应用的重要手段。 7. 健壮性 Java 的强类型机制、异常处理、垃圾回收机制等都是 Java 健壮性的重要保证。对指针的丢弃是 Java 的一大进步。另外,Java 的异常机制也是健壮性的一大体现。 8. 高性能 Java 的高性能主要是相对其他高级脚本语言来说的,随着 JIT(Just in Time)的发展,Java 的运行速度也越来越高。 9. 安全性 Java 通常被用在网络环境中,为此,Java 提供了一个安全机制以防止恶意代码的攻击。除了 Java 语言具有许多的安全特性以外,Java 还对通过网络下载的类增加一个安全防范机制,分配不同的名字空间以防替代本地的同名类,并包含安全管理机制。 Java 语言的众多特性使其在众多的编程语言中占有较大的市场份额,Java 语言对对象的支持和强大的 API 使得编程工作变得更加容易和快捷,大大降低了程序的开发成本。Java 的“一次编写,到处执行”正是它吸引众多商家和编程人员的一大优势。 扩展知识: 按应用范围,Java 可分为 3 个体系,即 Java SE、Java EE 和 Java ME。下面简单介绍这 3 个体系。 1. Java SE Java SE(Java Platform Standard Edition,Java 平台标准版)以前称为 J2SE,它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为 Java EE 提供基础,如 Java 语言基础、JDBC 操作、I/O 操作、网络通信以及多线程等技术。图 1 所示为 Java SE 的体系结构。 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_73892801/article/details/129181633。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-25 09:18:50
84
转载
Go Gin
...,当客户端访问这个地址时,会返回JSON格式的数据{"message": "pong"}。 个人感悟 刚接触这段代码的时候,我有点被惊到了——这么少的代码竟然能完成如此多的功能!当然,这也得益于Gin的设计理念:尽可能简化开发流程,让程序员专注于业务逻辑而不是框架细节。 --- 三、实时处理的核心 WebSocket支持 既然我们要讨论实时处理,那么就不得不提WebSocket。WebSocket就像是一个永不掉线的“聊天热线”,能让浏览器和服务器一直保持着畅通的联系。跟传统的请求-响应模式不一样,它可以让双方随时自由地“唠嗑”,想发啥就发啥,特别适合那些需要实时互动的应用,比如聊天室里你一言我一语,或者股票行情那种分分钟都在变化的东西,用它简直太合适了! Gin内置了对WebSocket的支持,我们可以直接通过中间件来实现这一功能。下面是一个完整的WebSocket示例: go package main import ( "log" "net/http" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r http.Request) bool { return true // 允许跨域 }, } func handleWebSocket(c gin.Context) { ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { log.Println("Failed to upgrade:", err) return } defer ws.Close() for { messageType, msg, err := ws.ReadMessage() if err != nil { log.Println("Error reading message:", err) break } log.Printf("Received: %s\n", string(msg)) err = ws.WriteMessage(messageType, msg) if err != nil { log.Println("Error writing message:", err) break } } } func main() { r := gin.Default() r.GET("/ws", handleWebSocket) r.Run(":8080") } 在这段代码中,我们利用gorilla/websocket包实现了WebSocket升级,并在handleWebSocket函数中处理了消息的读取与发送。你可以试着在浏览器里输入这个地址:ws://localhost:8080/ws,然后用JavaScript发个消息试试,看能不能马上收到服务器的回应。 深入探讨 说实话,刚开始写这部分代码的时候,我还担心WebSocket的兼容性问题。后来发现,只要正确设置了CheckOrigin方法,大多数现代浏览器都能正常工作。这让我更加坚定了对Gin的信心——它虽然简单,但足够强大! --- 四、进阶技巧 并发与性能优化 在实际项目中,我们可能会遇到高并发的情况。为了保证系统的稳定性,我们需要合理地管理线程池和内存分配。Gin提供了一些工具可以帮助我们做到这一点。 例如,我们可以使用sync.Pool来复用对象,减少垃圾回收的压力。下面是一个示例: go package main import ( "sync" "time" "github.com/gin-gonic/gin" ) var pool sync.Pool func init() { pool = &sync.Pool{ New: func() interface{} { return make([]byte, 1024) }, } } func handler(c gin.Context) { data := pool.Get().([]byte) defer pool.Put(data) copy(data, []byte("Hello World!")) time.Sleep(100 time.Millisecond) // 模拟耗时操作 c.String(http.StatusOK, string(data)) } func main() { r := gin.Default() r.GET("/", handler) r.Run(":8080") } 在这个例子中,我们定义了一个sync.Pool来存储临时数据。每次处理请求时,从池中获取缓冲区,处理完毕后再放回池中。这样可以避免频繁的内存分配和释放,从而提升性能。 反思与总结 其实,刚开始学习这段代码的时候,我对sync.Pool的理解还停留在表面。直到后来真正用它解决了性能瓶颈,我才意识到它的价值所在。这也让我明白,优秀的框架只是起点,关键还是要结合实际需求去探索和实践。 --- 五、未来展望 Gin与实时处理的无限可能 Gin的强大之处不仅仅在于它的易用性和灵活性,更在于它为开发者提供了广阔的想象空间。无论是构建大型分布式系统,还是打造小型实验项目,Gin都能胜任。 如果你也想尝试用Gin构建实时处理系统,不妨从一个小目标开始——比如做一个简单的在线聊天室。相信我,当你第一次看到用户实时交流的画面时,那种成就感绝对会让你欲罢不能! 最后的话 写这篇文章的过程,其实也是我自己重新审视Gin的过程。其实这个东西吧,说白了挺简单的,但让我学到了一个本事——用最利索的办法搞定事情。希望能这篇文章也能点醒你,让你在今后的开发路上,慢慢琢磨出属于自己的那套玩法!加油吧,程序员们!
2025-04-07 16:03:11
65
时光倒流
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/MCKZX/article/details/127630566。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 英特尔oneAPI——异构计算学习总结 oneAPI编程模型 多架构编程面临的挑战 SYCL DPC++ 编译和运行DPC++程序 编程实例 实现矢量加法 queue类 Parallel kernel 通用的并行编程模板 Host Accessor 矢量相加源代码 统一共享内存 (Unified Shared Memory USM) USM语法 数据依赖 wait() depends_on in_order queue property 练习1:事件依赖 练习2:事件依赖 UMS实验 oneAPI编程模型 oneAPI编程模型提供了一个全面、统一的开发人员工具组合,可用于各种硬件设备,其中包括跨多个工作负载领域的一系列性能库。这些库包括面向各目标架构而定制化代码的函数,因此相同的函数调用可为各种支持的架构提供优化的性能。DPC++基于行业标准和开放规范,旨在鼓励生态系统的协作和创新。 多架构编程面临的挑战 在以数据为中心的环境中,专用工作负载的数量不断增长。专用负载通常因为没有通用的编程语言或API而需要使用不同的语言和库进行编程,这就需要维护各自独立的代码库。 由于跨平台的工具支持不一致,因此开发人员必须学习和使用一整套不同的工具。单独投入精力给每种硬件平台开发软件。 oneAPI则可以利用一种统一的编程模型以及支持并行性的库,支持包括CPU、GPU、FPGA等硬件等同于原生高级语言的开发性能,并且可以与现有的HPC编程模型交互。 SYCL SYCL支持C++数据并行编程,SYCL和OpenCL一样都是由Khronos Group管理的,SYCL是建立在OpenCL之上的跨平台抽象层,支持用C++用单源语言方式编写用于异构处理器的与设备无关的代码。 DPC++ DPC++(Data Parallel C++)是一种单源语言,可以将主机代码和异构加速器内核写在同一个文件当中,在主机中调用DPC++程序,计算由加速器执行。DPC++代码简洁且效率高,并且是开源的。现有的CUDA应用、Fortran应用、OpenCL应用都可以用不同方式很方便地迁移到DPC++当中。 下图显示了原来使用不同架构的HPC开发人员的一些推荐的转换方法。 编译和运行DPC++程序 编译和运行DPC++程序主要包括三步: 初始化环境变量 编译DPC++源代码 运行程序 例如本地运行,在本地系统上安装英特尔基础工具套件,使用以下命令编译和运行DPC++程序。 source /opt/intel/inteloneapi/setvars.shdpcpp simple.cpp -o simple./simple 编程实例 实现矢量加法 以下实例描述了使用DPC++实现矢量加法的过程和源代码。 queue类 queue类用来提交给SYCL执行的命令组,是将作业提交到运算设备的一种机制,多个queue可以映射到同一个设备。 Parallel kernel Parallel kernel允许代码并行执行,对于一个不具有相关性的循环数据操作,可以用Parallel kernel并行实现 在C++代码中的循环实现 for(int i=0; i < 1024; i++){a[i] = b[i] + c[i];}); 在Parallel kernel中的并行实现 h.parallel_for(range<1>(1024), [=](id<1> i){A[i] = B[i] + C[i];}); 通用的并行编程模板 h.parallel_for(range<1>(1024), [=](id<1> i){// CODE THAT RUNS ON DEVICE }); range用来生成一个迭代序列,1为步长,在循环体中,i表示索引。 Host Accessor Host Accessor是使用主机缓冲区访问目标的访问器,它使访问的数据可以在主机上使用。通过构建Host Accessor可以将数据同步回主机,除此之外还可以通过销毁缓冲区将数据同步回主机。 buf是存储数据的缓冲区。 host_accessor b(buf,read_only); 除此之外还可以将buf设置为局部变量,当系统超出buf生存期,buf被销毁,数据也将转移到主机中。 矢量相加源代码 根据上面的知识,这里展示了利用DPC++实现矢量相加的代码。 //第一行在jupyter中指明了该cpp文件的保存位置%%writefile lab/vector_add.cppinclude <CL/sycl.hpp>using namespace sycl;int main() {const int N = 256;// 初始化两个队列并打印std::vector<int> vector1(N, 10);std::cout<<"\nInput Vector1: "; for (int i = 0; i < N; i++) std::cout << vector1[i] << " ";std::vector<int> vector2(N, 20);std::cout<<"\nInput Vector2: "; for (int i = 0; i < N; i++) std::cout << vector2[i] << " ";// 创建缓存区buffer vector1_buffer(vector1);buffer vector2_buffer(vector2);// 提交矢量相加任务queue q;q.submit([&](handler &h) {// 为缓存区创建访问器accessor vector1_accessor (vector1_buffer,h);accessor vector2_accessor (vector2_buffer,h);h.parallel_for(range<1>(N), [=](id<1> index) {vector1_accessor[index] += vector2_accessor[index];});});// 创建主机访问器将设备中数据拷贝到主机当中host_accessor h_a(vector1_buffer,read_only);std::cout<<"\nOutput Values: ";for (int i = 0; i < N; i++) std::cout<< vector1[i] << " ";std::cout<<"\n";return 0;} 运行结果 统一共享内存 (Unified Shared Memory USM) 统一共享内存是一种基于指针的方法,是将CPU内存和GPU内存进行统一的虚拟化方法,对于C++来说,指针操作内存是很常规的方式,USM也可以最大限度的减少C++移植到DPC++的代价。 下图显示了非USM(左)和USM(右)的程序员开发视角。 类型 函数调用 说明 在主机上可访问 在设备上可访问 设备 malloc_device 在设备上分配(显式) 否 是 主机 malloc_host 在主机上分配(隐式) 是 是 共享 malloc_shared 分配可以在主机和设备之间迁移(隐式) 是 是 USM语法 初始化: int data = malloc_shared<int>(N, q); int data = static_cast<int >(malloc_shared(N sizeof(int), q)); 释放 free(data,q); 使用共享内存之后,程序将自动在主机和运算设备之间隐式移动数据。 数据依赖 使用USM时,要注意数据之间的依赖关系以及事件之间的依赖关系,如果两个线程同时修改同一个内存区,将产生不可预测的结果。 我们可以使用不同的选项管理数据依赖关系: 内核任务中的 wait() 使用 depends_on 方法 使用 in_queue 队列属性 wait() q.submit([&](handler &h) {h.parallel_for(range<1>(N), [=](id<1> i) { data[i] += 2; });}).wait(); // <--- wait() will make sure that task is complete before continuingq.submit([&](handler &h) {h.parallel_for(range<1>(N), [=](id<1> i) { data[i] += 3; });}); depends_on auto e = q.submit([&](handler &h) { // <--- e is event for kernel taskh.parallel_for(range<1>(N), [=](id<1> i) { data[i] += 2; });});q.submit([&](handler &h) {h.depends_on(e); // <--- waits until event e is completeh.parallel_for(range<1>(N), [=](id<1> i) { data[i] += 3; });}); in_order queue property queue q(property_list{property::queue::in_order()}); // <--- this will make sure all the task with q are executed sequentially 练习1:事件依赖 以下代码使用 USM,并有三个提交到设备的内核。每个内核修改相同的数据阵列。三个队列之间没有数据依赖关系 为每个队列提交添加 wait() 在第二个和第三个内核任务中实施 depends_on() 方法 使用 in_order 队列属性,而非常规队列: queue q{property::queue::in_order()}; %%writefile lab/usm_data.cppinclude <CL/sycl.hpp>using namespace sycl;static const int N = 256;int main() {queue q{property::queue::in_order()};//用队列限制执行顺序std::cout << "Device : " << q.get_device().get_info<info::device::name>() << "\n";int data = static_cast<int >(malloc_shared(N sizeof(int), q));for (int i = 0; i < N; i++) data[i] = 10;q.parallel_for(range<1>(N), [=](id<1> i) { data[i] += 2; });q.parallel_for(range<1>(N), [=](id<1> i) { data[i] += 3; });q.parallel_for(range<1>(N), [=](id<1> i) { data[i] += 5; });q.wait();//wait阻塞进程for (int i = 0; i < N; i++) std::cout << data[i] << " ";std::cout << "\n";free(data, q);return 0;} 执行结果 练习2:事件依赖 以下代码使用 USM,并有三个提交到设备的内核。前两个内核修改了两个不同的内存对象,第三个内核对前两个内核具有依赖性。三个队列之间没有数据依赖关系 %%writefile lab/usm_data2.cppinclude <CL/sycl.hpp>using namespace sycl;static const int N = 1024;int main() {queue q;std::cout << "Device : " << q.get_device().get_info<info::device::name>() << "\n";//设备选择int data1 = malloc_shared<int>(N, q);int data2 = malloc_shared<int>(N, q);for (int i = 0; i < N; i++) {data1[i] = 10;data2[i] = 10;}auto e1 = q.parallel_for(range<1>(N), [=](id<1> i) { data1[i] += 2; });auto e2 = q.parallel_for(range<1>(N), [=](id<1> i) { data2[i] += 3; });//e1,e2指向两个事件内核q.parallel_for(range<1>(N),{e1,e2}, [=](id<1> i) { data1[i] += data2[i]; }).wait();//depend on e1,e2for (int i = 0; i < N; i++) std::cout << data1[i] << " ";std::cout << "\n";free(data1, q);free(data2, q);return 0;} 运行结果 UMS实验 在主机中初始化两个vector,初始数据为25和49,在设备中初始化两个vector,将主机中的数据拷贝到设备当中,在设备当中并行计算原始数据的根号值,然后将data1_device和data2_device的数值相加,最后将数据拷贝回主机当中,检验最后相加的和是否是12,程序结束前将内存释放。 %%writefile lab/usm_lab.cppinclude <CL/sycl.hpp>include <cmath>using namespace sycl;static const int N = 1024;int main() {queue q;std::cout << "Device : " << q.get_device().get_info<info::device::name>() << "\n";//intialize 2 arrays on hostint data1 = static_cast<int >(malloc(N sizeof(int)));int data2 = static_cast<int >(malloc(N sizeof(int)));for (int i = 0; i < N; i++) {data1[i] = 25;data2[i] = 49;}// STEP 1 : Create USM device allocation for data1 and data2int data1_device = static_cast<int >(malloc_device(N sizeof(int),q));int data2_device = static_cast<int >(malloc_device(N sizeof(int),q));// STEP 2 : Copy data1 and data2 to USM device allocationq.memcpy(data1_device, data1, sizeof(int) N).wait();q.memcpy(data2_device, data2, sizeof(int) N).wait();// STEP 3 : Write kernel code to update data1 on device with sqrt of valueauto e1 = q.parallel_for(range<1>(N), [=](id<1> i) { data1_device[i] = std::sqrt(25); });auto e2 = q.parallel_for(range<1>(N), [=](id<1> i) { data2_device[i] = std::sqrt(49); });// STEP 5 : Write kernel code to add data2 on device to data1q.parallel_for(range<1>(N),{e1,e2}, [=](id<1> i) { data1_device[i] += data2_device[i]; }).wait();// STEP 6 : Copy data1 on device to hostq.memcpy(data1, data1_device, sizeof(int) N).wait();q.memcpy(data2, data2_device, sizeof(int) N).wait();// verify resultsint fail = 0;for (int i = 0; i < N; i++) if(data1[i] != 12) {fail = 1; break;}if(fail == 1) std::cout << " FAIL"; else std::cout << " PASS";std::cout << "\n";// STEP 7 : Free USM device allocationsfree(data1_device, q);free(data1);free(data2_device, q);free(data2);// STEP 8 : Add event based kernel dependency for the Steps 2 - 6return 0;} 运行结果 本篇文章为转载内容。原文链接:https://blog.csdn.net/MCKZX/article/details/127630566。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-22 10:28:50
321
转载
MemCache
...作为一款流行的分布式内存对象缓存系统,以其高效性和灵活性赢得了广大开发者的青睐。哎呀,用着用着,咱们可能会碰到一些意料之外的小麻烦,比如说MutexException。这事儿可不简单,它通常说明在咱们同时操作好几个线程的时候,遇到了锁的冲突,或者是怎么也拿不到那个关键的锁。就像是在厨房里,好几个人都想同时用同一把刀切菜,结果就乱了套,谁都得等着。这可得小心点,不然程序就可能卡住不动了。这篇文章将带你深入理解MemCache的工作原理,并探讨如何解决此类问题。 2. MemCache基础概念 MemCache通过在内存中存储数据来提供快速访问。哎呀,这个家伙可真能玩转各种数据类型啊!不管是那些字母串、一长串的数字清单,还是乱七八糟的集合,它都能轻松驾驭。而且,它还提供了一套超简单的操作工具,就像给小孩子们准备的玩具一样,简单易懂,轻轻松松就能搞定这些数据,真是太贴心了!MemCache这种玩意儿啊,就像是你跟朋友玩游戏,你负责喊口号出招,朋友负责听你的指挥去打怪兽或者抢金币。这游戏里头,MemCache的服务器就是那个强大的后盾,它负责把所有东西都记下来,还有找你要的东西。所以,简单来说,你就是客户端,是操作者;MemCache服务器呢,就是那个后台,负责处理一切数据的事情。这样子,你们俩配合起来,游戏玩得又快又好! 3. MutexException问题剖析 当多个线程同时尝试访问或修改同一数据时,MutexException的出现往往是因为互斥锁管理不当。哎呀,互斥锁就像是共享空间的门神,它负责在任何时候只让一个小伙伴进入这个共享区域,比如图书馆或者厨房,这样大家就不会抢着用同一本书或者同一把锅啦。这样就能避免发生混乱和冲突,保证大家都能平平安安地享受公共资源。在MemCache中,这种冲突可能发生在读取、写入或删除数据的操作上。 4. 实战案例 MemCache使用示例 为了更好地理解MemCache的工作流程及其可能出现的问题,我们通过一个简单的示例来展示其基本用法: python from pymemcache.client import base 创建MemCache客户端连接 client = base.Client(('localhost', 11211)) 缓存一个值 client.set('key', 'value') 从缓存中获取值 print(client.get('key')) 删除缓存中的值 client.delete('key') 5. 避免MutexException的策略 解决MutexException的关键在于正确管理互斥锁。以下是一些实用的策略: a. 使用原子操作 MemCache提供了原子操作,如add、replace、increment等,可以安全地执行更新操作而无需额外的锁保护。 b. 线程安全编程 确保所有涉及到共享资源的操作都是线程安全的。这意味着避免在多线程环境中直接访问全局变量或共享资源,而是使用线程本地存储或其他线程安全的替代方案。 c. 锁优化 合理使用锁。哎呀,你懂的,有时候网站或者应用里头有些东西经常被大家看,但是实际上内容变动不多。这时候,为了不让系统在处理这些信息的时候卡壳太久,我们可以用个叫做“读锁”的小技巧。简单来说,读锁就像是图书馆里的书,大家都想翻阅,但是不打算乱动它,所以不需要特别紧锁起来,这样能提高大家看书的效率,也避免了不必要的等待。此外,考虑使用更高效的锁实现,比如使用更细粒度的锁或非阻塞算法。 d. 锁超时 在获取锁时设置超时时间,避免无限等待。哎呀,如果咱们在规定的时间内没拿到钥匙(这里的“锁”就是需要获得的权限或资源),那咱们就得想点别的办法了。比如说,咱们可以先把手头的事情放一放,退一步海阔天空嘛,回头再试试;或者干脆来个“再来一次”,看看运气是不是转了一把。别急,总有办法解决问题的! 6. 结语 MemCache的未来与挑战 随着技术的发展,MemCache面临着更多的挑战,包括更高的并发处理能力、更好的跨数据中心一致性以及对新兴数据类型的支持。然而,通过持续优化互斥锁管理策略,我们可以有效地避免MutexException等并发相关问题,让MemCache在高性能缓存系统中发挥更大的作用。嘿,小伙伴们!在咱们的编程路上,要记得跟紧时代步伐,多看看那些最棒的做法和新出炉的技术。这样,咱们就能打造出既稳固又高效的超级应用了!别忘了,技术这玩意儿,就像个不停奔跑的小兔子,咱们得时刻准备着,跟上它的节奏,不然可就要被甩在后面啦!所以,多学习,多实践,咱们的编程技能才能芝麻开花节节高!
2024-09-02 15:38:39
38
人生如戏
转载文章
...posable接口的对象生命周期,以确保其Dispose方法在适当的时候被调用,从而释放非托管资源或执行其他清理任务。在本文中,通过将SqlConnection对象置于using语句中,可以自动在离开using代码块时关闭数据库连接,即使在执行过程中遇到异常也能确保资源得到释放。 SqlDataReader , SqlDataReader是.NET框架中System.Data.SqlClient命名空间下的一个类,它提供了一种只进、只读、高性能的方式从SQL Server数据库获取查询结果。在文中,SqlDataReader被用来执行SQL命令并逐行读取返回的数据集,进而将这些数据转换为CategoryInfo对象,并添加到IList集合中进行后续操作。它的特点是按需读取数据,而不是一次性加载所有数据到内存,因此适用于处理大量数据的情形。 CommandBehavior.CloseConnection , 这是SqlCommand.ExecuteReader方法的一个可选参数,当设置此标志时,在SqlDataReader关闭时,会同时关闭与之关联的SqlConnection。在文章中,作者建议通过设置CommandBehavior.CloseConnection,确保在完成数据读取后能自动关闭数据库连接,从而简化了代码并降低了资源泄漏的风险。
2023-03-18 20:09:36
89
转载
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_50662680/article/details/128449401。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用。RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数据的时候不对链表进行耗时的加锁操作。这样在同一时间可以有多个线程同时读取该链表,并且允许一个线程对链表进行修改(修改的时候,需要加锁)。RCU适用于需要频繁的读取数据,而相应修改数据并不多的情景,例如在文件系统中,经常需要查找定位目录,而对目录的修改相对来说并不多,这就是RCU发挥作用的最佳场景。 Linux内核源码当中,关于RCU的文档比较齐全,你可以在 /DocumentaTIon/RCU/ 目录下找到这些文件。Paul E. McKenney 是内核中RCU源码的主要实现者,他也写了很多RCU方面的文章。今天我们就主要来说说linux内核rcu的机制详解。 在RCU的实现过程中,我们主要解决以下问题: 在读取过程中,另外一个线程删除了一个节点。删除线程可以把这个节点从链表中移除,但它不能直接销毁这个节点,必须等到所有的线程读取完成以后,才进行销毁操作。RCU中把这个过程称为宽限期(Grace period)。 在读取过程中,另外一个线程插入了一个新节点,而读线程读到了这个节点,那么需要保证读到的这个节点是完整的。这里涉及到了发布-订阅机制(Publish-Subscribe Mechanism)。 保证读取链表的完整性。新增或者删除一个节点,不至于导致遍历一个链表从中间断开。但是RCU并不保证一定能读到新增的节点或者不读到要被删除的节点。 宽限期 通过这个例子,方便理解这个内容。以下例子修改于Paul的文章。 struct foo {int a;char b;long c;};DEFINE_SPINLOCK(foo_mutex);struct foo gbl_foo;void foo_read (void){foo fp = gbl_foo;if ( fp != NULL )dosomething(fp-》a, fp-》b , fp-》c );}void foo_update( foo new_fp ){spin_lock(&foo_mutex);foo old_fp = gbl_foo;gbl_foo = new_fp;spin_unlock(&foo_mutex);kfee(old_fp);} 如上的程序,是针对于全局变量gbl_foo的操作。假设以下场景。有两个线程同时运行 foo_ read和foo_update的时候,当foo_ read执行完赋值操作后,线程发生切换;此时另一个线程开始执行foo_update并执行完成。当foo_ read运行的进程切换回来后,运行dosomething 的时候,fp已经被删除,这将对系统造成危害。为了防止此类事件的发生,RCU里增加了一个新的概念叫宽限期(Grace period)。 如下图所示: 图中每行代表一个线程,最下面的一行是删除线程,当它执行完删除操作后,线程进入了宽限期。宽限期的意义是,在一个删除动作发生后,它必须等待所有在宽限期开始前已经开始的读线程结束,才可以进行销毁操作。这样做的原因是这些线程有可能读到了要删除的元素。图中的宽限期必须等待1和2结束;而读线程5在宽限期开始前已经结束,不需要考虑;而3,4,6也不需要考虑,因为在宽限期结束后开始后的线程不可能读到已删除的元素。为此RCU机制提供了相应的API来实现这个功能。 void foo_read(void){rcu_read_lock();foo fp = gbl_foo;if ( fp != NULL )dosomething(fp-》a,fp-》b,fp-》c);rcu_read_unlock();}void foo_update( foo new_fp ){spin_lock(&foo_mutex);foo old_fp = gbl_foo;gbl_foo = new_fp;spin_unlock(&foo_mutex);synchronize_rcu();kfee(old_fp);} 其中foo_read中增加了rcu_read_lock和rcu_read_unlock,这两个函数用来标记一个RCU读过程的开始和结束。其实作用就是帮助检测宽限期是否结束。 foo_update增加了一个函数synchronize_rcu(),调用该函数意味着一个宽限期的开始,而直到宽限期结束,该函数才会返回。我们再对比着图看一看,线程1和2,在synchronize_rcu之前可能得到了旧的gbl_foo,也就是foo_update中的old_fp,如果不等它们运行结束,就调用kfee(old_fp),极有可能造成系统崩溃。而3,4,6在synchronize_rcu之后运行,此时它们已经不可能得到old_fp,此次的kfee将不对它们产生影响。 宽限期是RCU实现中最复杂的部分,原因是在提高读数据性能的同时,删除数据的性能也不能太差。 订阅——发布机制 当前使用的编译器大多会对代码做一定程度的优化,CPU也会对执行指令做一些优化调整,目的是提高代码的执行效率,但这样的优化,有时候会带来不期望的结果。如例: void foo_update( foo new_fp ){spin_lock(&foo_mutex);foo old_fp = gbl_foo;new_fp-》a = 1;new_fp-》b = ‘b’;new_fp-》c = 100;gbl_foo = new_fp;spin_unlock(&foo_mutex);synchronize_rcu();kfee(old_fp);} 这段代码中,我们期望的是6,7,8行的代码在第10行代码之前执行。但优化后的代码并不会对执行顺序做出保证。在这种情形下,一个读线程很可能读到 new_fp,但new_fp的成员赋值还没执行完成。单独线程执行dosomething(fp-》a, fp-》b , fp-》c ) 的 这个时候,就有不确定的参数传入到dosomething,极有可能造成不期望的结果,甚至程序崩溃。可以通过优化屏障来解决该问题,RCU机制对优化屏障做了包装,提供了专用的API来解决该问题。这时候,第十行不再是直接的指针赋值,而应该改为 : rcu_assign_pointer(gbl_foo,new_fp);rcu_assign_pointer的实现比较简单,如下:define rcu_assign_pointer(p, v) \__rcu_assign_pointer((p), (v), __rcu)define __rcu_assign_pointer(p, v, space) \do { \smp_wmb(); \(p) = (typeof(v) __force space )(v); \} while (0) 我们可以看到它的实现只是在赋值之前加了优化屏障 smp_wmb来确保代码的执行顺序。另外就是宏中用到的__rcu,只是作为编译过程的检测条件来使用的。 在DEC Alpha CPU机器上还有一种更强悍的优化,如下所示: void foo_read(void){rcu_read_lock();foo fp = gbl_foo;if ( fp != NULL )dosomething(fp-》a, fp-》b ,fp-》c);rcu_read_unlock();} 第六行的 fp-》a,fp-》b,fp-》c会在第3行还没执行的时候就预先判断运行,当他和foo_update同时运行的时候,可能导致传入dosomething的一部分属于旧的gbl_foo,而另外的属于新的。这样会导致运行结果的错误。为了避免该类问题,RCU还是提供了宏来解决该问题: define rcu_dereference(p) rcu_dereference_check(p, 0)define rcu_dereference_check(p, c) \__rcu_dereference_check((p), rcu_read_lock_held() || (c), __rcu)define __rcu_dereference_check(p, c, space) \({ \typeof(p) _________p1 = (typeof(p)__force )ACCESS_ONCE(p); \rcu_lockdep_assert(c, “suspicious rcu_dereference_check()” \usage”); \rcu_dereference_sparse(p, space); \smp_read_barrier_depends(); \(typeof(p) __force __kernel )(_________p1)); \})staTIc inline int rcu_read_lock_held(void){if (!debug_lockdep_rcu_enabled())return 1;if (rcu_is_cpu_idle())return 0;if (!rcu_lockdep_current_cpu_online())return 0;return lock_is_held(&rcu_lock_map);} 这段代码中加入了调试信息,去除调试信息,可以是以下的形式(其实这也是旧版本中的代码): define rcu_dereference(p) ({ \typeof(p) _________p1 = p; \smp_read_barrier_depends(); \(_________p1); \}) 在赋值后加入优化屏障smp_read_barrier_depends()。我们之前的第四行代码改为 foo fp = rcu_dereference(gbl_foo);,就可以防止上述问题。 数据读取的完整性 还是通过例子来说明这个问题: 如图我们在原list中加入一个节点new到A之前,所要做的第一步是将new的指针指向A节点,第二步才是将Head的指针指向new。这样做的目的是当插入操作完成第一步的时候,对于链表的读取并不产生影响,而执行完第二步的时候,读线程如果读到new节点,也可以继续遍历链表。如果把这个过程反过来,第一步head指向new,而这时一个线程读到new,由于new的指针指向的是Null,这样将导致读线程无法读取到A,B等后续节点。从以上过程中,可以看出RCU并不保证读线程读取到new节点。如果该节点对程序产生影响,那么就需要外部调用来做相应的调整。如在文件系统中,通过RCU定位后,如果查找不到相应节点,就会进行其它形式的查找,相关内容等分析到文件系统的时候再进行叙述。 我们再看一下删除一个节点的例子: 如图我们希望删除B,这时候要做的就是将A的指针指向C,保持B的指针,然后删除程序将进入宽限期检测。由于B的内容并没有变更,读到B的线程仍然可以继续读取B的后续节点。B不能立即销毁,它必须等待宽限期结束后,才能进行相应销毁操作。由于A的节点已经指向了C,当宽限期开始之后所有的后续读操作通过A找到的是C,而B已经隐藏了,后续的读线程都不会读到它。这样就确保宽限期过后,删除B并不对系统造成影响。 小结 RCU的原理并不复杂,应用也很简单。但代码的实现确并不是那么容易,难点都集中在了宽限期的检测上,后续分析源代码的时候,我们可以看到一些极富技巧的实现方式。 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_50662680/article/details/128449401。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-25 09:31:10
105
转载
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_65485112/article/details/122007938。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 《C primer plus》专为零基础读者撰写,历经30余年,成为C语言学习的翘楚。众多新手通过自学,已在各大技术公司担任要职。这本书无论是技术细节,还是技术广度、深度,以及讲解方式,都是针对自学用户设计的,所以敬请放心大胆地拿起这本书来自学成才吧。书名中Primer这个单词,就是“启蒙读本”“入门书”的意思。 非常经典的教材,与国内的那些不入流的教材相比,具有了更灵活的方法,更系统的介绍,更详细的讲解。每一个知识点都深入到位,完全解开了C的面纱……如果想学好C,成为真正的C程序员,这本书就非看不可。 三、Python编程从入门到实践 《Python编程从入门到实践》书如其名,本书简明清晰地讲解了入门Python所需学习的基本知识,同时在讲解过程中穿插实战演练,使读者对Python有更加深刻的理解,是一本入门Python的难得好书,推荐给大家学习。 我想说,Python是否值得学,已经不再是值得怀疑的问题了。但是,如何能高效学会Python,永远是个值得思考的重要问题。这个问题的答案,是绕不开本书的。 四、Java编程思想 《Java编程思想(第4版)》赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形。从Java的基础语法到最高级特性(深入的面向对象概念、多线程、自动项目构建、单元测试和调试等),《Java编程思想(第4版)》都能逐步指导你轻松掌握。从java编程思想这本书获得的各项大奖以及来自世界各地的读者评论中,不难看出这是一本经典之作。 五、算法导论 《算法导论》提供了对当代计算机算法研究的一个全面、综合性的介绍。全书共八部分,内容涵盖基础知识、排序和顺序统计量、数据结构、高级设计和分析技术、高级数据结构、图算法、算法问题选编,以及数学基础知识。书中深入浅出地介绍了大量的算法及相关的数据结构,以及用于解决一些复杂计算问题的高级策略(如动态规划、贪心算法、摊还分析等),重点在于算法的分析与设计。对于每一个专题,作者都试图提供目前最新的研究成果及样例解答,并通过清晰的图示来说明算法的执行过程。 六、深入理解计算机系统 《深入理解计算机系统》是将计算机软件和硬件理论结合讲述的经典教程,内容覆盖计算机导论、体系结构和处理器设计等多门课程。本书的大优点是为程序员描述计算机系统的实现细节,通过描述程序是如何映射到系统上,以及程序是如何执行的,使读者更好地理解程序的行为为什么是这样的,以及造成效率低下的原因。 七、鸟哥的Linux私房菜 《鸟哥的Linux私房菜基础学习篇》全面而详细地介绍了Linux操作系统。着重说明计算机的基础知识、Linux的学习方法,如何规划和安装Linux主机以及CentOS 7.x的安装、登录与求助方法;介绍Linux的文件系统、文件、目录与磁盘的管理;文字模式接口shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;对于系统安全非常重要的Linux账号的管理、磁盘配额、高级文件系统管理、计划任务以及进程管理,系统管理员(root)的管理事项。 本书内容丰富全面,基本概念的讲解非常细致,深入浅出。各种功能和命令的介绍,都配以大量的实例操作和详尽的解析。本书是初学者学习Linux不可多得的一本入门好书。 八、计算机网络自顶向下方法 《计算机网络自顶向下方法》是经典的计算机网络教材,采用作者独创的自顶向下方法来讲授计算机网络的原理及其协议,自第1版出版以来已经被数百所大学和学院选作教材,被译为14种语言。 新版保持了以前版本的特色,继续关注因特网和计算机网络的现代处理方式,注重原理和实践,为计算机网络教学提供一种新颖和与时俱进的方法。同时,第7版进行了相当多的修订和更新,首次改变了各章的组织结构,将网络层分成两章(第4章关注网络层的数据平面,第5章关注网络层的控制平面) 九、MySQL是怎样运行的 《MySQL是怎样运行的》采用诙谐幽默、通俗易懂的写作风格,针对上面这些问题给出了相应的解答方案。尽管本书的表达方式与司空见惯的学术派、理论派IT图书有显著区别,但本书的确是相当正经的专业技术图书,内容涵盖了使用MySQL的同学在求职面试和工作中常见的一些核心概念。无论是身居MySQL专家身份的技术人员,还是技术有待进一步提升的DBA,甚至是刚投身于数据库行业的“萌新”人员,本书都是他们彻底了解MySQL运行原理的优秀图书。 十、编程珠玑 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_65485112/article/details/122007938。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-11 11:49:14
119
转载
Javascript
...rror与其他错误的区别 说到错误,难免要和其他错误比较一番。比如说嘛,就有人会好奇地问:“AbortError跟一般的错误到底有啥不一样呀?”说实话呢,这个问题我也琢磨了好久好久,头都快想大了! 首先,AbortError是一种特殊的错误类型,专门用于表示操作被人为中断的情况。其实很多小错误啊,就是程序员自己不小心搞出来的,像打字打错了变量名,或者一激动让数组越界了之类的,都是挺常见的乌龙事件。简单来说呢,这俩的区别就是——AbortError就像是个“计划内”的小插曲,咱们事先知道它可能会发生,也能提前做好准备去应对;但普通的错误嘛,就好比是突然从天而降的小麻烦,压根儿没得防备,让人措手不及! 举个例子: javascript function divide(a, b) { if (b === 0) { throw new Error('除数不能为零'); } return a / b; } try { console.log(divide(10, 0)); // 抛出普通错误 } catch (error) { console.error(error.message); // 输出 "除数不能为零" } 在这个例子中,divide函数因为传入了非法参数(即分母为0)而抛出了一个普通错误。而如果我们换成AbortError呢? javascript const controller = new AbortController(); function process() { setTimeout(() => { console.log('处理完成'); }, 5000); } process(); controller.abort(); // 中断处理 这里虽然也有中断操作的意思,但并没有抛出任何错误。这就像是说,AbortError不会自己偷偷跑出来捣乱,得咱们主动去点那个abort()按钮才行。就好比你得自己动手去按开关,灯才不会自己亮起来一样。 --- 四、深入探讨AbortError的优缺点 说到优点嘛,我觉得AbortError最大的好处就是它让我们的代码更加健壮和可控。比如说啊,在面对一堆同时涌来的请求时, AbortError 就像一个神奇的开关,能帮我们把那些没用的请求一键关掉,这样就不会白白浪费资源啦!对了,它还能帮咱们更贴心地照顾用户体验呢!比如说,当用户等得花儿都快谢了,就给个机会让他们干脆放弃这事儿,省得干着急。 但是呢,凡事都有两面性。AbortError也有它的局限性。首先,它只适用于那些支持AbortSignal接口的操作,比如fetch、XMLHttpRequest之类。如果你尝试在一个不支持AbortSignal的操作上使用它,那就会直接报错。另外啊,要是随便乱用 AbortError 可不好,比如说老是取消请求的话,系统可能就会被折腾得够呛,负担越来越重,你说是不是? 说到这里,我想起了之前开发的一个项目,当时为了优化性能,我给每个API请求都加了AbortController,结果发现有时候会导致页面加载速度反而变慢了。后来经过反复调试,我才意识到,频繁地取消请求其实是得不偿失的。所以啊,大家在使用AbortError的时候一定要权衡利弊,不能盲目追求“安全”。 --- 五、总结与展望 总的来说,AbortError是一个非常实用且有趣的错误类型。它不仅能让我们更轻松地搞定那些乱七八糟的异步任务,还能让代码变得更好懂、更靠谱!不过,就像任何工具一样,它也需要我们在实践中不断摸索和完善。 未来,随着前端开发越来越复杂,我相信AbortError会有更多的应用场景。不管是应对一大堆同时进行的任务,还是让咱们跟软件互动的时候更顺畅、更开心,它都绝对是我们离不开的得力助手!所以,各位小伙伴,不妨多尝试用它来解决实际问题,说不定哪天你会发现一个全新的解决方案呢! 好了,今天的分享就到这里啦。希望能给大家打开一点思路,也期待大家在评论区畅所欲言,分享你的想法!最后,祝大家coding愉快,早日成为编程界的高手!
2025-03-27 16:22:54
106
月影清风
Apache Lucene
...kenStream 对象 org.apache.lucene.search.IndexSearcher.SearchContext context = searcher.createSearchContext(); org.apache.lucene.analysis.standard.StandardAnalyzer analyzer = new org.apache.lucene.analysis.standard.StandardAnalyzer(Version.LATEST); org.apache.lucene.analysis.TokenStream tokenStream = analyzer.tokenStream("content", context.reader().getTermVector(0, 0).getPayload().toString()); // 检查是否有异常抛出 while (tokenStream.incrementToken()) { System.out.println("Token: " + tokenStream.getAttribute(CharTermAttribute.class).toString()); } // 关闭 TokenStream 和 IndexReader tokenStream.end(); reader.close(); } } 在这段代码中,我们首先创建了一个 RAMDirectory,并使用它来构建一个索引。接着,我们添加了一个包含测试文本的文档到索引中。之后,我们创建了 IndexSearcher 来搜索文档,并使用 StandardAnalyzer 来创建 TokenStream。在循环中,我们逐个输出令牌,直到遇到 EOFException,这通常意味着已经到达了文本的末尾。 第二部分:深入分析 EOFException 的原因与解决策略 在实际应用中,EOFException 通常意味着 TokenStream 已经到达了文本的结尾,这可能是由于以下原因: - 文本过短:如果输入的文本长度不足以产生足够的令牌,TokenStream 可能会过早地报告结束。 - 解析问题:在复杂的文本结构下,解析器可能未能正确地分割文本,导致部分文本未被识别为有效的令牌。 为了应对这种情况,我们可以采取以下策略: - 增加文本长度:确保输入的文本足够长,以生成多个令牌。 - 优化解析器配置:根据特定的应用场景调整分析器的配置,例如使用不同的分词器(如 CJKAnalyzer)来适应不同语言的需求。 - 错误处理机制:在代码中加入适当的错误处理逻辑,以便在遇到 EOFException 时进行相应的处理,例如记录日志、提示用户重新输入更长的文本等。 结语:拥抱挑战,驾驭全文检索 面对 org.apache.lucene.analysis.TokenStream$EOFException: End of stream 这样的挑战,我们的目标不仅仅是解决问题,更是通过这样的经历深化对 Lucene 工作原理的理解。哎呀,你猜怎么着?咱们在敲代码、调参数的过程中,不仅技术越来越溜,还能在处理那些乱七八糟的数据时,感觉自己就像个数据处理的小能手,得心应手的呢!就像是在厨房里,熟练地翻炒各种食材,做出来的菜品色香味俱全,让人赞不绝口。编程也是一样,每一次的实践和调试,都是在给我们的技能加料,让我们的作品越来越美味,越来越有营养!嘿!兄弟,听好了,每次遇到难题都像是在给咱的成长加个buff,咱们得一起揭开全文检索的神秘面纱,掌控技术的大棒,让用户体验到最棒、最快的搜索服务,让每一次敲击键盘都能带来惊喜! --- 以上内容不仅涵盖了理论解释与代码实现,还穿插了人类在面对技术难题时的思考与探讨,旨在提供一种更加贴近实际应用、充满情感与主观色彩的技术解读方式。
2024-07-25 00:52:37
391
青山绿水
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/u010098331/article/details/53485539。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 这篇文章希望能够帮助读者深入理解Docker的命令,还有容器(container)和镜像(image)之间的区别,并深入探讨容器和运行中的容器之间的区别。 当我对Docker技术还是一知半解的时候,我发现理解Docker的命令非常困难。于是,我花了几周的时间来学习Docker的工作原理,更确 切地说,是关于Docker统一文件系统(the union file system)的知识,然后回过头来再看Docker的命令,一切变得顺理成章,简单极了。 题外话:就我个人而言,掌握一门技术并合理使用它的最好办法就是深入理解这项技术背后的工作原理。通常情况 下,一项新技术的诞生常常会伴随着媒体的大肆宣传和炒作,这使得用户很难看清技术的本质。更确切地说,新技术总是会发明一些新的术语或者隐喻词来帮助宣 传,这在初期是非常有帮助的,但是这给技术的原理蒙上了一层砂纸,不利于用户在后期掌握技术的真谛。 Git就是一个很好的例子。我之前不能够很好的使用Git,于是我花了一段时间去学习Git的原理,直到这时,我才真正明白了Git的用法。我坚信只有真正理解Git内部原理的人才能够掌握这个工具。 Image Definition 镜像(Image)就是一堆只读层(read-only layer)的统一视角,也许这个定义有些难以理解,下面的这张图能够帮助读者理解镜像的定义。 从左边我们看到了多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是Docker内部的实现细节,并且能够 在主机(译者注:运行Docker的机器)的文件系统上访问到。统一文件系统(union file system)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。 我们可以在图片的右边看到这个视角的形式。 你可以在你的主机文件系统上找到有关这些层的文件。需要注意的是,在一个运行中的容器内部,这些层是不可见的。在我的主机上,我发现它们存在于/var/lib/docker/aufs目录下。 sudo tree -L 1 /var/lib/docker/ /var/lib/docker/├── aufs├── containers├── graph├── init├── linkgraph.db├── repositories-aufs├── tmp├── trust└── volumes7 directories, 2 files Container Definition 容器(container)的定义和镜像(image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。 细心的读者可能会发现,容器的定义并没有提及容器是否在运行,没错,这是故意的。正是这个发现帮助我理解了很多困惑。 要点:容器 = 镜像 + 可读层。并且容器的定义并没有提及是否要运行容器。 接下来,我们将会讨论运行态容器。 Running Container Definition 一个运行态容器(running container)被定义为一个可读写的统一文件系统加上隔离的进程空间和包含其中的进程。下面这张图片展示了一个运行中的容器。 正是文件系统隔离技术使得Docker成为了一个前途无量的技术。一个容器中的进程可能会对文件进行修改、删除、创建,这些改变都将作用于可读写层(read-write layer)。下面这张图展示了这个行为。 我们可以通过运行以下命令来验证我们上面所说的: docker run ubuntu touch happiness.txt 即便是这个ubuntu容器不再运行,我们依旧能够在主机的文件系统上找到这个新文件。 find / -name happiness.txt /var/lib/docker/aufs/diff/860a7b...889/happiness.txt Image Layer Definition 为了将零星的数据整合起来,我们提出了镜像层(image layer)这个概念。下面的这张图描述了一个镜像层,通过图片我们能够发现一个层并不仅仅包含文件系统的改变,它还能包含了其他重要信息。 元数据(metadata)就是关于这个层的额外信息,它不仅能够让Docker获取运行和构建时的信息,还包括父层的层次信息。需要注意,只读层和读写层都包含元数据。 除此之外,每一层都包括了一个指向父层的指针。如果一个层没有这个指针,说明它处于最底层。 Metadata Location: 我发现在我自己的主机上,镜像层(image layer)的元数据被保存在名为”json”的文件中,比如说: /var/lib/docker/graph/e809f156dc985.../json e809f156dc985...就是这层的id 一个容器的元数据好像是被分成了很多文件,但或多或少能够在/var/lib/docker/containers/<id>目录下找到,<id>就是一个可读层的id。这个目录下的文件大多是运行时的数据,比如说网络,日志等等。 全局理解(Tying It All Together) 现在,让我们结合上面提到的实现细节来理解Docker的命令。 docker create <image-id> docker create 命令为指定的镜像(image)添加了一个可读写层,构成了一个新的容器。注意,这个容器并没有运行。 docker start <container-id> Docker start命令为容器文件系统创建了一个进程隔离空间。注意,每一个容器只能够有一个进程隔离空间。 docker run <image-id> 看到这个命令,读者通常会有一个疑问:docker start 和 docker run命令有什么区别。 从图片可以看出,docker run 命令先是利用镜像创建了一个容器,然后运行这个容器。这个命令非常的方便,并且隐藏了两个命令的细节,但从另一方面来看,这容易让用户产生误解。 题外话:继续我们之前有关于Git的话题,我认为docker run命令类似于git pull命令。git pull命令就是git fetch 和 git merge两个命令的组合,同样的,docker run就是docker create和docker start两个命令的组合。 docker ps docker ps 命令会列出所有运行中的容器。这隐藏了非运行态容器的存在,如果想要找出这些容器,我们需要使用下面这个命令。 docker ps –a docker ps –a命令会列出所有的容器,不管是运行的,还是停止的。 docker images docker images命令会列出了所有顶层(top-level)镜像。实际上,在这里我们没有办法区分一个镜像和一个只读层,所以我们提出了top-level 镜像。只有创建容器时使用的镜像或者是直接pull下来的镜像能被称为顶层(top-level)镜像,并且每一个顶层镜像下面都隐藏了多个镜像层。 docker images –a docker images –a命令列出了所有的镜像,也可以说是列出了所有的可读层。如果你想要查看某一个image-id下的所有层,可以使用docker history来查看。 docker stop <container-id> docker stop命令会向运行中的容器发送一个SIGTERM的信号,然后停止所有的进程。 docker kill <container-id> docker kill 命令向所有运行在容器中的进程发送了一个不友好的SIGKILL信号。 docker pause <container-id> docker stop和docker kill命令会发送UNIX的信号给运行中的进程,docker pause命令则不一样,它利用了cgroups的特性将运行中的进程空间暂停。具体的内部原理你可以在这里找到:https://www.kernel.org/doc/Doc ... m.txt,但是这种方式的不足之处在于发送一个SIGTSTP信号对于进程来说不够简单易懂,以至于不能够让所有进程暂停。 docker rm <container-id> docker rm命令会移除构成容器的可读写层。注意,这个命令只能对非运行态容器执行。 docker rmi <image-id> docker rmi 命令会移除构成镜像的一个只读层。你只能够使用docker rmi来移除最顶层(top level layer)(也可以说是镜像),你也可以使用-f参数来强制删除中间的只读层。 docker commit <container-id> docker commit命令将容器的可读写层转换为一个只读层,这样就把一个容器转换成了不可变的镜像。 docker build docker build命令非常有趣,它会反复的执行多个命令。 我们从上图可以看到,build命令根据Dockerfile文件中的FROM指令获取到镜像,然后重复地1)run(create和start)、2)修改、3)commit。在循环中的每一步都会生成一个新的层,因此许多新的层会被创建。 docker exec <running-container-id> docker exec 命令会在运行中的容器执行一个新进程。 docker inspect <container-id> or <image-id> docker inspect命令会提取出容器或者镜像最顶层的元数据。 docker save <image-id> docker save命令会创建一个镜像的压缩文件,这个文件能够在另外一个主机的Docker上使用。和export命令不同,这个命令为每一个层都保存了它们的元数据。这个命令只能对镜像生效。 docker export <container-id> docker export命令创建一个tar文件,并且移除了元数据和不必要的层,将多个层整合成了一个层,只保存了当前统一视角看到的内容(译者注:expoxt后 的容器再import到Docker中,通过docker images –tree命令只能看到一个镜像;而save后的镜像则不同,它能够看到这个镜像的历史镜像)。 docker history <image-id> docker history命令递归地输出指定镜像的历史镜像。 参考: http://www.cnblogs.com/bethal/p/5942369.html 本篇文章为转载内容。原文链接:https://blog.csdn.net/u010098331/article/details/53485539。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-26 15:47:20
538
转载
ZooKeeper
...占用一定的资源,包括内存和CPU。要是连上的用户太多了,但服务器的“体力”又不够强(比如内存、CPU之类的资源有限),那它就很容易“忙不过来”,导致请求都排着队等着,根本处理不完。 说到这里,我忍不住想吐槽一下自己曾经犯过的错误。嘿,有次我在测试环境里弄了个能扛大流量的程序,结果发现ZooKeeper老是蹦出个叫“CommitQueueFullException”的错误,烦得不行!我当时就纳闷了:“我明明设了个挺合理的线程池大小啊,怎么还出问题了呢?”后来一查才发现,坏事了,是客户端的连接数配少了,结果请求都堵在那儿了,就像高速公路堵车一样。真是教训深刻啊! --- 三、如何优雅地处理CommitQueueFullException? 既然知道了问题的根源,那接下来就要谈谈具体的解决办法了。我觉得可以从以下几个方面入手: 1. 调整队列大小 最直接的办法当然是增大队列的容量。通过修改zookeeper.commitlog.capacity参数,可以让ZooKeeper拥有更大的缓冲空间。其实嘛,这个方法也不是啥灵丹妙药,毕竟咱们手头的硬件资源就那么多,要是傻乎乎地把队列弄得太长,说不定反而会惹出别的麻烦,比如让系统跑得更卡之类的。 代码示例: properties zookeeper.commitlog.capacity=10485760 上面这段配置文件的内容表示将队列大小调整为10MB。你可以根据实际情况进行调整。 2. 优化客户端逻辑 很多时候,CommitQueueFullException并不是因为服务器的问题,而是客户端的请求模式不合理造成的。比如说,你是否可以合并多个小请求为一个大请求?或者是否可以采用批量操作的方式减少请求次数? 举个例子,假设你在做一个日志采集系统,每天需要向ZooKeeper写入成千上万个临时节点。与其每次都往一个节点里写东西,不如一口气往多个节点里写,这样能大大减少你发出的请求次数,省事儿又高效! 代码示例: java List nodesToCreate = Arrays.asList("/node1", "/node2", "/node3"); List createdNodes = zk.create("/batch/", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, nodesToCreate.size()); System.out.println("Created nodes: " + createdNodes); 在这段代码中,我们一次性创建了三个临时节点,而不是分别调用三次create()方法。这样的做法不仅减少了请求次数,还提高了效率。 3. 增加服务器资源 如果以上两种方法都不能解决问题,那么可能就需要考虑升级服务器硬件了。比如增加内存、提升CPU性能,甚至更换更快的磁盘。当然,这通常是最后的选择,因为它涉及到成本和技术难度。 4. 使用异步API ZooKeeper提供了同步和异步两种API,其中异步API可以在一定程度上缓解CommitQueueFullException的问题。异步API可酷了!你提交个请求,它立马给你返回结果,根本不用傻等那个响应回来。这样一来啊,就相当于给任务队列放了个假,压力小了很多呢! 代码示例: java import org.apache.zookeeper.AsyncCallback.StringCallback; public class AsyncExample implements StringCallback { @Override public void processResult(int rc, String path, Object ctx, String name) { if (rc == 0) { System.out.println("Node created successfully at path: " + name); } else { System.err.println("Failed to create node with error code: " + rc); } } public static void main(String[] args) throws Exception { ZooKeeper zk = new ZooKeeper("localhost:2181", 5000, null); zk.createAsync("/asyncTest", "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new AsyncExample(), null); } } 在这段代码中,我们使用了createAsync()方法来异步创建节点。相比于同步版本,这种方式不会阻塞主线程,从而降低了队列满的风险。 --- 四、总结与展望 通过今天的探讨,我相信大家都对CommitQueueFullException有了更深刻的理解。嘿,别被这个错误吓到!其实啊,它也没那么可怕。只要你找到对的方法,保证分分钟搞定,就跟玩儿似的! 回顾整个过程,我觉得最重要的是要保持冷静和耐心。遇到技术难题的时候啊,别慌!先搞清楚它到底是个啥问题,就像剥洋葱一样,一层层搞明白本质。接着呢,就一步一步地去找解决的办法,慢慢来,总能找到出路的!就像攀登一座高山一样,每一步都需要脚踏实地。 最后,我想鼓励大家多动手实践。理论固然重要,但真正的成长来自于不断的尝试和失败。希望大家能够在实际项目中运用今天学到的知识,创造出更加优秀的应用! 好了,今天的分享就到这里啦!如果你还有什么疑问或者想法,欢迎随时交流哦~
2025-03-16 15:37:44
10
林中小径
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/l363130002/article/details/126121255。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 yum和源码编译安装的区别 1.路径区别-yum安装的软件是他自定义的,源码安装的软件./configure --preifx=软件安装的绝对路径 2.yum仓库的软件,版本可能比较低,而源码编译安装,版本可控 3.编译安装的软件,支持第三方功能扩展./configure 这里可以加上很多参数,定制功能 1.安装mariadb,配置官方的mariadb的yum源,手动创建 mariadb.repo仓库文件 添加MariaDB源 vi /etc/yum.repos.d/MariaDB.repo 粘贴官方的或者阿里云的镜像: [mariadb]name = MariaDBbaseurl = http://yum.mariadb.org/10.3/centos7-amd64gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDBgpgcheck=1[mariadb]name = MariaDBbaseurl = https://mirrors.aliyun.com/mariadb/yum/10.4/centos7-amd64/gpgkey=https://mirrors.aliyun.com/mariadb/yum/RPM-GPG-KEY-MariaDBgpgcheck=1 2.如果下载速度太慢,请删除 mariadb.repo,只是为了使用阿里云的yum源中的mariadb rm -rf /etc/yum.repos.d/Mariadb.repo然后清空yum 缓存yum clean all 3.通过yum安装mariadb软件,安装mariadb服务端和客户端 官方 yum install MariaDB-server MariaDB-client -y阿里云 yum install mariadb mariadb-server -y 4.安装完成后,启动mariadb服务端 systemctl start/stop/restart/status mariadbsystemctl enable mariadb 开机启动mariadb 5. mariadb初始化 这条命令可以初始化mysql,删除匿名用户,设置root密码等等....mysql_secure_installation1.输入当前密码,初次安装后是没有密码的,直接回车2.询问是否使用 'unix_socket' 进行身份验证: n3.为 root 设置密码:y4.输入 root 的新密码: root5.确认输入 root 的新密码: root6.是否移除匿名用户,这个随意,建议删除: y7.拒绝用户远程登录,这个建议开启:n8.删除 test 库,可以保留:n9.重新加载权限表:y 6. 设置mysql的中文编码支持,修改/etc/my.cnf 1.vi /etc/my.cnf在[mysqld]中添加参数,使得mariadb服务端支持中文[mysqld]character-set-server=utf8collation-server=utf8_general_ci2.重启mariadb服务,读取my.cnf新配置systemctl restart mariadb 3.登录数据库,查看字符编码mysql -uroot -p输入 \s 查看编码 7. mysql常用命 desc 查看表结构create database 数据库名create table 表名查看如何创建db的show create database 库名 查看如何创建table结构的show create table 表名; 修改mysql的密码set password = PASSWORD('redhat'); 创建mysql的普通用户,默认权限非常低create user zhang@'%' identified by '123456'; 查询mysql数据库中的用户信息use mysql;select host,user,password from user; 7. 给用户添加权限命令 对所有库和所有表授权所有权限grant all privileges on . to 账户@主机名 给zhang用户授予所有权限grant all privileges on . to zhang@'%'; 刷新授权表flush privileges; 8. 给用户添加权限命令 给zhangsan用户授予所有权限grant all privileges on . to zhangsan@'%'; 给与root权限授予远程登录的命令 'centos这是密码随意设置grant all privileges on . to root@'%' identified by '123456'; 此时可以在windows登录linux的数据库 连接服务器的mysqlmysql -uyining -p -h 服务器的地址 9. 数据备份与恢复 导出当前数据库的所有db,到一个文件中1.mysqldump -u root -p --all-databases > /data/AllMysql.dump2.登录mysql 导入数据mysql -u root -p> source /data/AllMysql.dump3.通过命令导入数据 在登录时候,导入数据文件,一样可以写入数据mysql -uroot -p < /data/AllMysql.dump 10. 修改Mariadb存储路径 10.1 首先确定MariaDB数据库能正常运行,确定正常后关闭服务 systemctl stop mariadb 10.2 建立要更改数据存放的目录,如:我这单独分了一个区/data存放MariaDB的数据 mkdir /data/mysql_data chown -R mysql:mysql /data/mysql_data 10.3 复制默认数据存放文件夹到/data/mysql_data cp -a /var/lib/mysql /data/mysql_data 10.4 修改/etc/my.cnf.d/server.cnf vim /etc/my.cnf.d/server.cnf 在[mysqld]标签下添加如下内容 datadir=/data/mysql_data/mysqlsocket=/var/lib/mysql/mysql.sockdefault-character-set=utf8character_set_server=utf8slow_query_log=onslow_query_log_file=/data/mysql_data/slow_query_log.loglong_query_time=2 10.5 配置MariaDB慢查询 touch /data/mysql_data/slow_query_log.logchown mysql:mysql /data/mysql_data/slow_query_log.log 10.6 重启数据库 systemctl start mariadb 10.7 注意: 1、配置文件my.cnf存在,但是修改的并不是my.cnf,而是/etc/my.cnf.d/server.cnf; 2、并没有更改mysql.sock的路径配置; 3、没有修改/etc/init.d/mysql中的内容; 4、没有修改mysql_safe中的内容; 5、增加了数据库的慢查询配置。 11. Mariadb主从复制 11.1 主从库初始化 这条命令可以初始化mysql,删除匿名用户,设置root密码等等....mysql_secure_installation1.输入当前密码,初次安装后是没有密码的,直接回车2.询问是否使用 'unix_socket' 进行身份验证: n3.为 root 设置密码:y4.输入 root 的新密码: root5.确认输入 root 的新密码: root6.是否移除匿名用户,这个随意,建议删除: y7.拒绝用户远程登录,这个建议开启:n8.删除 test 库,可以保留:n9.重新加载权限表:y 11.2 修改主库配置 [root@mster mysql] grep -Ev "^$|^" /etc/my.cnf.d/server.cnf[server][mysqld]character-set-server=utf8collation-server=utf8_general_ciserver_id = 13 一组主从组里的每个id必须是唯一值。推荐用ip位数log-bin= mysql-bin 二进制日志,后面指定存放位置。如果只是指定名字,默认存放在/var/lib/mysql下lower_case_table_names=1 不区分大小写binlog-format=ROW 二进制日志文件格式log-slave-updates=True slave更新是否记入日志sync-master-info=1 值为1确保信息不会丢失slave-parallel-threads=3 同时启动多少个复制线程,最多与要复制的数据库数量相等即可binlog-checksum=CRC32 效验码master-verify-checksum=1 启动主服务器效验slave-sql-verify-checksum=1 启动从服务器效验[galera][embedded][mariadb][mariadb-10.6][root@mster-k8s mysql] 11.2 修改从库配置 [mysqld]character-set-server=utf8collation-server=utf8_general_ciserver_id=14log-bin= mysql-bin log-bin是二进制文件relay_log = relay-bin 中继日志, 后面指定存放位置。如果只是指定名字,默认存放在/var/lib/mysql下lower_case_table_names=1 11.3 重启主库和从库服务 systemctl restart mariad 11.4 master节点配置 MariaDB [huawei]> grant replication slave, replication client on . to 'liu'@'%' identified by '123456';Query OK, 0 rows affected (0.001 sec)MariaDB [huawei]> show master status;+------------------+----------+--------------+------------------+| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |+------------------+----------+--------------+------------------+| mysql-bin.000001 | 4990 | | |+------------------+----------+--------------+------------------+1 row in set (0.000 sec)MariaDB [huawei]> select binlog_gtid_pos('mysql-bin.000001', 4990 );+-------------------------------------------+| binlog_gtid_pos('mysql-bin.000001', 4990) |+-------------------------------------------+| 0-13-80 |+-------------------------------------------+1 row in set (0.000 sec)MariaDB [huawei]> flush privileges; 11.5 slave节点配置 MariaDB [(none)]> set global gtid_slave_pos='0-13-80';Query OK, 0 rows affected (0.004 sec)MariaDB [(none)]> change master to master_host='101.34.141.216',master_user='liu',master_password='123456',master_use_gtid=slave_pos;Query OK, 0 rows affected (0.008 sec)MariaDB [(none)]> start slave;Query OK, 0 rows affected (0.005 sec)MariaDB [(none)]> 11.6 验证salve状态 MariaDB [(none)]> show slave status\G 1. row Slave_IO_State: Waiting for master to send eventMaster_Host: 101.34.141.216Master_User: liuMaster_Port: 3306Connect_Retry: 60Master_Log_File: mysql-bin.000001Read_Master_Log_Pos: 13260Relay_Log_File: relay-bin.000002Relay_Log_Pos: 10246Relay_Master_Log_File: mysql-bin.000001Slave_IO_Running: YesSlave_SQL_Running: YesReplicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0Last_Error: Skip_Counter: 0Exec_Master_Log_Pos: 13260Relay_Log_Space: 10549Until_Condition: NoneUntil_Log_File: Until_Log_Pos: 0Master_SSL_Allowed: NoMaster_SSL_CA_File: 本篇文章为转载内容。原文链接:https://blog.csdn.net/l363130002/article/details/126121255。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-12 10:11:01
310
转载
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/sinat_41915844/article/details/79770973。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 MySql02 复习 显示所有数据 show databases; 创建新数据库,设置编码方式utf8 create database demo2 default charset utf8; 显示创建数据的语句 show create database demo2; 删除数据库 drop database demo2; 选择使用指定的数据库 use demo1; 查看库中所有表 show tables; 创建表 create table book(bid int(4) primary key comment '书id', bname varchar(50) comment '书名',pub varchar(50) comment '出版社',author varchar(50) comment '作者' )engine=myisam charset=utf8; 所有字段名,使用,所有的字符串使用''或者"" 查看建表语句 show create table book; 查看表结构 desc book; 修改表名 rename table book to book1; 修改表属性 ,引擎和字符集 alter table book1 engine=innodb charset=utf8; 添加字段 first after alter table book1 add(type varchar(20) comment '类型',numinput int(10) comment '进货量',numstore int(10) comment '库存量'); 修改字段名 bid bno alter table book1 change bid bno int(4); 修改顺序 pub 放到author后面 alter table book1 modify pub varchar(50) after author; 修改数据类型 bno int(4) -->int(10) alter table book1 modify bno int(10); 删除字段 alter table book1 drop 字段名; 删除表 drop table 表名; 插入语句 insert into book1(bno,bname,author,type) values(1001,'斗破苍穹','天蚕土豆','玄幻');insert into book1(bno,bname,author,type) values(1002,'全职高手','蝴蝶兰','网游竞技');insert into book1(bno,bname,author,type) values(1003,'鬼吹灯','天下霸唱','恐怖');insert into book1(bno,bname,author,type)values(1004,'西游记','吴承恩','4大名著');insert into book1(bno,bname,author,type)values(1005,'java基础','王克晶','达内学习手册'); update语句 把1005号书,修改成'天线宝宝',作者不详,类型少儿 把1004号书修改成'天龙八部',作者金庸,类型武侠 update book1 set bname="天线宝宝",author="作者不详",type="少儿" where bno=1005; 删除类型是'恐怖'的所有书籍 删除全表记录 删除表格 修改book名称为book_item rename table book to book_item; 在表格尾部添加字段price double(7,2) alter table book_item add price double(7,2); 把price字段的位置放到author之后 alter table book_item modify price double(7,2) after author; 把表中存在的数据添加价格,每本书都在100~1000之间,自定 update book_item set price=199 where bno=1001; 修改1001的价格为500元 把所有字段的null字段补全 update book_item set pub="达内出版社",numinput=500,numstore=100 where pub is null; 删除价格小于150的所有条目 删除所有数据 SQL分类 数据定义语言 DDL 重点 数据操纵语言 DML 重点 增 删 改 数据查询语言 DQL select 查 事务控制语言 TCL 数据库控制语言 DCL 数据定义语言 DDL - 负责数据结构定义,与创建数据库对象的语言- 常用create alter drop- DDL不支持事务,DDL语句执行之后,不能回滚 数据操纵语言 DML - 对数据库中更改数据操作的语句- select insert update delete--> CRUD 增删改查- 通常把select相关操作,单独出来,称之为DQL- DML支持事务,在非自动提交模式时,可以利用rollback回滚操作. 数据查询语言 DQL - 筛选,分组,连表查询 面试重点 TCL 和 DCL - 事务控制语句TCL- 负责实现数据库中事务支持的语言,commit rollback savepoint等指令- DCL数据库控制语言- 管理数据库的授权,角色控制等,grant(授权),revoke(取消授权) 练习: 案例:创建一张表customer(顾客) create table customer(cid int(4) primary key comment '顾客编号',cname varchar(50) comment '顾客姓名',sex char(5) comment '顾客性别',address varchar(50) comment '地址',phone varchar(11) comment '手机',email varchar(50) comment '邮箱'); show create table customer; 插入5条数据 insert into customer values(1001,'小明','男','楼上18号','123','123@163.com');insert into customer values(1002,'小红','女','楼上17号','1234','1234@163.com');insert into customer values(1003,'老王','男','楼上18号隔壁','1234','1234@163.com');insert into customer values(1004,'老宋','男','楼上17号隔壁','1234','1234@163.com');insert into customer values(1005,'小马','女','楼上17号隔壁','1234','1234@163.com'); -1 修改一条数据的姓名 小红的姓名 -2 修改一条数据的性别 老王的性别 -3 修改一条数据的电话 1001号的电话 -4 修改一条数据的邮箱 邮箱为123@163.com,改成323@163.com -5 查询性别为 男的所有数据 select from customer where sex="男"; -6 自定义DDL操作的需求,5道题,可以同上面book表的操作 数据库数据类型 主要包括5大类 整数类型 int, big int 浮点数类型 double decimal 字符串类型 char varchar text 日期类型 date datetime timestamp time year... 其他数据类型 set.... 字符串 - char(固定长度) 定长字符串 最多255个字节- 定多少长度,就占用多少长度- 多了放不进去,少了用空格补全- 不认识内容尾部的空格- varchar(最大长度) 变长字符串 最大65535字节,但是使用一般不超过255- 只要不超过定的长度,都可以放进去- 以内容真实长度为准- 认识内容尾部的空格- text 最大65535字节- blob 大数据对象,以二进制(字节)的方式存储 整数 tinyint 1字节 smallint 2字节 int 4字节 bigint 8字节 int(6)影响的是查询时显示长度(zerofill)不影响数据的保存长度 create table t1(id1 int,id2 int(5)); insert into t1 values(111111,111111); alter table t1 modify id1 int zerofill; alter table t1 modify id2 int(5) zerofill; insert into t1 values (1,1); float 4字节 double 8字节 double(8,2) 可能会产生精度的缺失 10.0/3 3.3333333336 decimal 不会缺失精度,但是使用的时候需要指定总长度和小数位数 日期 - date 年月日- time 时分秒- datetime 年月日时分秒,到9999年,而且需要手动输入,如果没有手动输入,就显示null.- timestamp 年月日时分秒,在没有数据手动插入时,自动填入当前时间.最大值2038- bigint 1970-1-1 0:0:0 格林威治时间 案例:创建表t,字段d1 date,d2 time,d3 datetime,d4 timestamp create table t(id int,d1 date,d2 time,d3 datetime,d4 timestamp);insert into t (d1,d2) values ('1910-01-10','12:32:12');insert into t values(1,'2018-12-21','15:12:00','1995-02-10 12:08:12','2030-10-10 15:19:32');insert into t values(2,'3018-01-25','15:12:34','9234-12-31 12:12:12','2030-12-31 12:12:12');insert into t values(2,'3018-01-25','15:12:34','9999-12-31 23:59:59','2030-12-31 12:12:12'); 练习 创建人物表,插入,修改,查询 create table person(id int(4) primary key,name varchar(50),age int(3));insert into person values(1,"梅超风",36);insert into person values(2,"洪七公",96);insert into person values(3,"杨过",40);insert into person values(4,"令狐冲",28);insert into person values(5,"张三丰",100);insert into person values(6,"张翠山",27);insert into person values(7,"张无忌",27);insert into person values(8,"赵敏",18);insert into person values(9,"独孤求败",250);insert into person values(10,"楚留香",36);1.案例:修改张三丰的name为刘备,id为11update person set name="刘备",id=11 where name="张三丰";2.案例:修改2号人物的的name为夏侯渊update person set name="夏侯渊" where id=2;3.案例:根据条件修改person表中的数据,修改id是6的数据中,姓名改为'任我行', 年龄改为39update person set name="任我行",age=39 where id=6;4.案例:修改姓名是‘楚留香'的数据,把id改为20,年龄改为19update person set id=20,age=19 where name="楚留香";5.案例:把person所有的数据的年龄全部改为20 update person set age=20;6.案例:修改id为7的数据,把id改为100,姓名改为杨过,年龄改为21update person set id=100,name="杨过",age=21 where id=7;7.案例:修改姓名是独孤求败,把年龄改为35update person set age=35 where name="独孤求败";8.案例:修改id=8的信息,把姓名改为房玄龄update person set name="房玄龄" where id=8;9.案例 :修改id为20并且年龄为20的人的姓名为刘德华(郑少秋也行)提示 where...and...update person set name="郑少秋" where id=20 and age=20; 查询 没有条件的简单查询 select from 表名;查询表中所有的数据 select from person; select from t; select from emp; select from dept; 查询某些列中的值 select name as '姓名' from person; select name as '姓名',age as '年龄' from person; select id as '编号',name as '姓名',age as '年龄' from person; 学习过程的编程习惯select from 表; 工作中的编程习惯select id,name,age from person; 查询emp表中所有员工的姓名,上级领导的编号,职位,工资 select ename,mgr,job,sal from emp; 查询emp表中所有员工的编号,姓名,所属部门编号,工资 select empno,ename,deptno,sal from emp; 查询dept表中所有部门的名称和地址 select dname,loc from dept; 如果忘记了mysql的用户名和密码怎么办 卸载重新装 不重装软件如何修改密码 1.停止mysql服务 2.cmd中输入一个命令 mysqld --skip-grant-tables; -通过控制台,开启了一个mysql服务 3.开启一个新的cmd -mysql -u root -p 可以不使用密码进入数据库 show databases;----mysql 5. use mysql; 6. update user set password=password('新密码') where user="root"; 7. 关闭mysqld这个服务/进程 8. 重启mysql服务 作业 mysql02,一天的代码重新敲一遍,熟悉emp和dept列名 本篇文章为转载内容。原文链接:https://blog.csdn.net/sinat_41915844/article/details/79770973。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-02-16 12:44:07
544
转载
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_45525177/article/details/100654639。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 大容量文件上传早已不是什么新鲜问题,在.net 2.0时代,HTML5也还没有问世,要实现这样的功能,要么是改web.config,要么是用flash,要么是用一些第三方控件,然而这些解决问题的方法要么很麻烦,比如改配置,要么不稳定,比如文件上G以后,上传要么死掉,要么卡住,通过设置web.config并不能很好的解决这些问题。 这是一个Html5统治浏览器的时代,在这个新的时代,这种问题已被简化并解决,我们可以利用Html5分片上传的技术,那么Plupload则是一个对此技术进行封装的前端脚本库,这个库的好处是可以自动检测浏览器是否支持html5技术,不支持再检测是否支持flash技术,甚至是sliverlight技术,如果支持,就使用检测到的技术。 那么这个库到哪里下载,怎么搭建呢,比较懒的童鞋还是用Install-Package Plupload搞定吧,一个命令搞定所有事 Plupload支持的功能这里就不细说了,什么批量上传,这里我没有用到,主要是感觉它支持的事件非常丰富,文件选取后的事件,文件上传中的事件(可获得文件的上传进度),文件上传成功的事件,文件上传失败的事件,等等 我的例子主要是上传一个单个文件,并显示上传的进度条(使用jQuery的一个进度条插件) 下面的例子主要是为文件上传交给 UploadCoursePackage.ashx 来处理 /ProgressBar/ var progressBar = $("loading").progressbar({ width: '500px', color: 'B3240E', border: '1px solid 000000' }); /Plupload/ //实例化一个plupload上传对象 var uploader = new plupload.Uploader({ browse_button: 'browse', //触发文件选择对话框的按钮,为那个元素id runtimes: 'html5,flash,silverlight,html4',//兼容的上传方式 url: "Handlers/UploadCoursePackage.ashx", //后端交互处理地址 max_retries: 3, //允许重试次数 chunk_size: '10mb', //分块大小 rename: true, //重命名 dragdrop: false, //允许拖拽文件进行上传 unique_names: true, //文件名称唯一性 filters: { //过滤器 max_file_size: '999999999mb', //文件最大尺寸 mime_types: [ //允许上传的文件类型 { title: "Zip", extensions: "zip" }, { title: "PE", extensions: "pe" } ] }, //自定义参数 (键值对形式) 此处可以定义参数 multipart_params: { type: "misoft" }, // FLASH的配置 flash_swf_url: "../Scripts/plupload/Moxie.swf", // Silverligh的配置 silverlight_xap_url: "../Scripts/plupload/Moxie.xap", multi_selection: false //true:ctrl多文件上传, false 单文件上传 }); //在实例对象上调用init()方法进行初始化 uploader.init(); uploader.bind('FilesAdded', function (uploader, files) { $("<%=fileSource.ClientID %>").val(files[0].name); $.ajax( { type: 'post', url: 'HardDiskSpace.aspx/GetHardDiskFreeSpace', data: {}, dataType: 'json', contentType: 'application/json;charset=utf-8', success: function (result) { //选择文件以后检测服务器剩余磁盘空间是否够用 if (files.length > 0) { if (parseInt(files[0].size) > parseInt(result.d)) { $('error-msg').text("文件容量大于剩余磁盘空间,请联系管理员!"); } else { $('error-msg').text(""); } } }, error: function (xhr, err, obj) { $('error-msg').text("检测服务器剩余磁盘空间失败"); } }); }); uploader.bind('UploadProgress', function (uploader, file) { var percent = file.percent; progressBar.progress(percent); }); uploader.bind('FileUploaded', function (up, file, callBack) { var data = $.parseJSON(callBack.response); if (data.statusCode === "1") { $("<%=hfPackagePath.ClientID %>").val(data.filePath); var id = $("<%=hfCourseID.ClientID %>").val(); __doPostBack("save", id); } else { hideLoading(); $('error-msg').text(data.message); } }); uploader.bind('Error', function (up, err) { alert("文件上传失败,错误信息: " + err.message); }); /Plupload/ 后台 UploadCoursePackage.ashx 的代码也重要,主要是文件分片跟不分片的处理方式不一样 using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.IO; namespace WebUI.Handlers { /// <summary> /// UploadCoursePackage 的摘要说明 /// </summary> public class UploadCoursePackage : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; int statuscode = 1; string message = string.Empty; string filepath = string.Empty; if (context.Request.Files.Count > 0) { try { string resourceDirectoryName = System.Configuration.ConfigurationManager.AppSettings["resourceDirectory"]; string path = context.Server.MapPath("~/" + resourceDirectoryName); if (!Directory.Exists(path)) Directory.CreateDirectory(path); int chunk = context.Request.Params["chunk"] != null ? int.Parse(context.Request.Params["chunk"]) : 0; //获取当前的块ID,如果不是分块上传的。chunk则为0 string fileName = context.Request.Params["name"]; //这里写的比较潦草。判断文件名是否为空。 string type = context.Request.Params["type"]; //在前面JS中不是定义了自定义参数multipart_params的值么。其中有个值是type:"misoft",此处就可以获取到这个值了。获取到的type="misoft"; string ext = Path.GetExtension(fileName); //fileName = string.Format("{0}{1}", Guid.NewGuid().ToString(), ext); filepath = resourceDirectoryName + "/" + fileName; fileName = Path.Combine(path, fileName); //对文件流进行存储 需要注意的是 files目录必须存在(此处可以做个判断) 根据上面的chunk来判断是块上传还是普通上传 上传方式不一样 ,导致的保存方式也会不一样 FileStream fs = new FileStream(fileName, chunk == 0 ? FileMode.OpenOrCreate : FileMode.Append); //write our input stream to a buffer Byte[] buffer = null; if (context.Request.ContentType == "application/octet-stream" && context.Request.ContentLength > 0) { buffer = new Byte[context.Request.InputStream.Length]; context.Request.InputStream.Read(buffer, 0, buffer.Length); } else if (context.Request.ContentType.Contains("multipart/form-data") && context.Request.Files.Count > 0 && context.Request.Files[0].ContentLength > 0) { buffer = new Byte[context.Request.Files[0].InputStream.Length]; context.Request.Files[0].InputStream.Read(buffer, 0, buffer.Length); } //write the buffer to a file. if (buffer != null) fs.Write(buffer, 0, buffer.Length); fs.Close(); statuscode = 1; message = "上传成功"; } catch (Exception ex) { statuscode = -1001; message = "保存时发生错误,请确保文件有效且格式正确"; Util.LogHelper logger = new Util.LogHelper(); string path = context.Server.MapPath("~/Logs"); logger.WriteLog(ex.Message, path); } } else { statuscode = -404; message = "上传失败,未接收到资源文件"; } string msg = "{\"statusCode\":\"" + statuscode + "\",\"message\":\"" + message + "\",\"filePath\":\"" + filepath + "\"}"; context.Response.Write(msg); } public bool IsReusable { get { return false; } } } } 再附送一个检测服务器端硬盘剩余空间的功能吧 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Script.Services; using System.Web.Services; using System.Web.UI; using System.Web.UI.WebControls; namespace WebUI { public partial class CheckHardDiskFreeSpace : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } /// <summary> /// 获取磁盘剩余容量 /// </summary> /// <returns></returns> [WebMethod] public static string GetHardDiskFreeSpace() { const string strHardDiskName = @"F:\"; var freeSpace = string.Empty; var drives = DriveInfo.GetDrives(); var myDrive = (from drive in drives where drive.Name == strHardDiskName select drive).FirstOrDefault(); if (myDrive != null) { freeSpace = myDrive.TotalFreeSpace+""; } return freeSpace; } } } 效果展示: 详细配置信息可以参考这篇文章:http://blog.ncmem.com/wordpress/2019/08/12/plupload%e4%b8%8a%e4%bc%a0%e6%95%b4%e4%b8%aa%e6%96%87%e4%bb%b6%e5%a4%b9-2/ 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_45525177/article/details/100654639。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-19 09:43:46
127
转载
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/hong2511/article/details/80842704。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 原文地址为: 大数据——海量数据处理的基本方法总结 声明: 原文引用参考July大神的csdn博客文章 => 海量处理面试题 海量数据处理概述 所谓海量数据处理,就是数据量太大,无法在较短时间内迅速解决,无法一次性装入内存。本文在前人的基础上总结一下解决此类问题的办法。那么有什么解决办法呢? 时间复杂度方面,我们可以采用巧妙的算法搭配合适的数据结构,如Bloom filter/Hash/bit-map/堆/数据库或倒排索引/trie树。空间复杂度方面,分而治之/hash映射。 海量数据处理的基本方法总结起来分为以下几种: 分而治之/hash映射 + hash统计 + 堆/快速/归并排序; 双层桶划分; Bloom filter/Bitmap; Trie树/数据库/倒排索引; 外排序; 分布式处理之Hadoop/Mapreduce。 前提基础知识: 1 byte= 8 bit。 int整形一般为4 bytes 共32位bit。 2^32=4G。 1G=2^30=10.7亿。 1 分而治之+hash映射+快速/归并/堆排序 问题1 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url? 分析:50亿64=320G大小空间。 算法思想1:hash 分解+ 分而治之 + 归并 遍历文件a,对每个url根据某种hash规则求取hash(url)/1024,然后根据所取得的值将url分别存储到1024个小文件(a0~a1023)中。这样每个小文件的大约为300M。如果hash结果很集中使得某个文件ai过大,可以在对ai进行二级hash(ai0~ai1024)。 这样url就被hash到1024个不同级别的目录中。然后可以分别比较文件,a0VSb0……a1023VSb1023。求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_map中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_map中,如果是,那么就是共同的url,存到文件里面就可以了。 把1024个级别目录下相同的url合并起来。 问题2 有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。 解决思想1:hash分解+ 分而治之 +归并 顺序读取10个文件a0~a9,按照hash(query)%10的结果将query写入到另外10个文件(记为 b0~b9)中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。 找一台内存2G左右的机器,依次对用hash_map(query, query_count)来统计每个query出现的次数。利用快速/堆/归并排序按照出现次数进行排序。将排序好的query和对应的query_cout输出到文件中。这样得到了10个排好序的文件c0~c9。 对这10个文件c0~c9进行归并排序(内排序与外排序相结合)。每次取c0~c9文件的m个数据放到内存中,进行10m个数据的归并,即使把归并好的数据存到d结果文件中。如果ci对应的m个数据全归并完了,再从ci余下的数据中取m个数据重新加载到内存中。直到所有ci文件的所有数据全部归并完成。 解决思想2: Trie树 如果query的总量是有限的,只是重复的次数比较多而已,可能对于所有的query,一次性就可以加入到内存了。在这种假设前提下,我们就可以采用trie树/hash_map等直接来统计每个query出现的次数,然后按出现次数做快速/堆/归并排序就可以了。 问题3: 有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。 类似问题:怎么在海量数据中找出重复次数最多的一个? 解决思想: hash分解+ 分而治之+归并 顺序读文件中,对于每个词x,按照hash(x)/(10244)存到4096个小文件中。这样每个文件大概是250k左右。如果其中的有的文件超过了1M大小,还可以按照hash继续往下分,直到分解得到的小文件的大小都不超过1M。 对每个小文件,统计每个文件中出现的词以及相应的频率(可以采用trie树/hash_map等),并取出出现频率最大的100个词(可以用含100个结点的最小堆),并把100词及相应的频率存入文件。这样又得到了4096个文件。 下一步就是把这4096个文件进行归并的过程了。(类似与归并排序) 问题4 海量日志数据,提取出某日访问百度次数最多的那个IP 解决思想: hash分解+ 分而治之 + 归并 把这一天访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有2^32个IP。同样可以采用hash映射的方法,比如模1024,把整个大文件映射为1024个小文件。 再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。 然后再在这1024组最大的IP中,找出那个频率最大的IP,即为所求。 问题5 海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10。 解决思想: 分而治之 + 归并。 注意TOP10是取最大值或最小值。如果取频率TOP10,就应该先hash分解。 在每台电脑上求出TOP10,采用包含10个元素的堆完成(TOP10小,用最大堆,TOP10大,用最小堆)。比如求TOP10大,我们首先取前10个元素调整成最小堆,如果发现,然后扫描后面的数据,并与堆顶元素比较,如果比堆顶元素大,那么用该元素替换堆顶,然后再调整为最小堆。最后堆中的元素就是TOP10大。 求出每台电脑上的TOP10后,然后把这100台电脑上的TOP10组合起来,共1000个数据,再利用上面类似的方法求出TOP10就可以了。 问题6 在2.5亿个整数中找出不重复的整数,内存不足以容纳这2.5亿个整数。 解决思路1 : hash 分解+ 分而治之 + 归并 2.5亿个int数据hash到1024个小文件中a0~a1023,如果某个小文件大小还大于内存,进行多级hash。每个小文件读进内存,找出只出现一次的数据,输出到b0~b1023。最后数据合并即可。 解决思路2 : 2-Bitmap 如果内存够1GB的话,采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存2^322bit=1GB内存。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。 注意,如果是找出重复的数据,可以用1-bitmap。第一次bit位由0变1,第二次查询到相应bit位为1说明是重复数据,输出即可。 问题7 一共有N个机器,每个机器上有N个数。每个机器最多存O(N)个数并对它们操作。如何找到N^2个数中的中数? 解决思想1 : hash分解 + 排序 按照升序顺序把这些数字,hash划分为N个范围段。假设数据范围是2^32 的unsigned int 类型。理论上第一台机器应该存的范围为0~(2^32)/N,第i台机器存的范围是(2^32)(i-1)/N~(2^32)i/N。hash过程可以扫描每个机器上的N个数,把属于第一个区段的数放到第一个机器上,属于第二个区段的数放到第二个机器上,…,属于第N个区段的数放到第N个机器上。注意这个过程每个机器上存储的数应该是O(N)的。 然后我们依次统计每个机器上数的个数,一次累加,直到找到第k个机器,在该机器上累加的数大于或等于(N^2)/2,而在第k-1个机器上的累加数小于(N^2)/2,并把这个数记为x。那么我们要找的中位数在第k个机器中,排在第(N^2)/2-x位。然后我们对第k个机器的数排序,并找出第(N^2)/2-x个数,即为所求的中位数的复杂度是O(N^2)的。 解决思想2: 分而治之 + 归并 先对每台机器上的数进行排序。排好序后,我们采用归并排序的思想,将这N个机器上的数归并起来得到最终的排序。找到第(N^2)/2个便是所求。复杂度是O(N^2 lgN^2)的。 2 Trie树+红黑树+hash_map 这里Trie树木、红黑树或者hash_map可以认为是第一部分中分而治之算法的具体实现方法之一。 问题1 上千万或上亿数据(有重复),统计其中出现次数最多的钱N个数据。 解决思路: 红黑树 + 堆排序 如果是上千万或上亿的int数据,现在的机器4G内存可以能存下。所以考虑采用hash_map/搜索二叉树/红黑树等来进行统计重复次数。 然后取出前N个出现次数最多的数据,可以用包含N个元素的最小堆找出频率最大的N个数据。 问题2 1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。请怎么设计和实现? 解决思路:trie树。 这题用trie树比较合适,hash_map也应该能行。 问题3 一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。 解决思路: trie树 + 堆排序 这题是考虑时间效率。 1. 用trie树统计每个词出现的次数,时间复杂度是O(nlen)(len表示单词的平准长度)。 2. 然后找出出现最频繁的前10个词,可以用堆来实现,前面的题中已经讲到了,时间复杂度是O(nlg10)。 总的时间复杂度,是O(nle)与O(nlg10)中较大的哪一个。 问题4 搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复读比较高,虽然总数是1千万,但是如果去除重复和,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G。 解决思想 : trie树 + 堆排序 采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。 3 BitMap或者Bloom Filter 3.1 BitMap BitMap说白了很easy,就是通过bit位为1或0来标识某个状态存不存在。可进行数据的快速查找,判重,删除,一般来说适合的处理数据范围小于82^32。否则内存超过4G,内存资源消耗有点多。 问题1 已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。 解决思路: bitmap 8位最多99 999 999,需要100M个bit位,不到12M的内存空间。我们把0-99 999 999的每个数字映射到一个Bit位上,所以只需要99M个Bit==12MBytes,这样,就用了小小的12M左右的内存表示了所有的8位数的电话 问题2 2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。 解决思路:2bit map 或者两个bitmap。 将bit-map扩展一下,用2bit表示一个数即可,00表示未出现,01表示出现一次,10表示出现2次及以上,11可以暂时不用。 在遍历这些数的时候,如果对应位置的值是00,则将其置为01;如果是01,将其置为10;如果是10,则保持不变。需要内存大小是2^32/82=1G内存。 或者我们不用2bit来进行表示,我们用两个bit-map即可模拟实现这个2bit-map,都是一样的道理。 3.2 Bloom filter Bloom filter可以看做是对bit-map的扩展。 参考july大神csdn文章 Bloom Filter 详解 4 Hadoop+MapReduce 参考引用july大神 csdn文章 MapReduce的初步理解 Hadoop框架与MapReduce模式 转载请注明本文地址: 大数据——海量数据处理的基本方法总结 本篇文章为转载内容。原文链接:https://blog.csdn.net/hong2511/article/details/80842704。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-03-01 12:40:17
541
转载
Spark
...些频繁使用的数据放到内存里,供多个任务共享。听起来是不是很美好?但实际上,我在实际开发过程中遇到了不少麻烦。 比如有一次,我正在做一个数据分析项目,需要多次对同一份数据进行操作。我寻思着,这不就是常规操作嘛,直接用Spark的分布式缓存功能得了,这样岂不是能省掉好多重复加载的麻烦?嘿,事情是这样的——我辛辛苦苦搞完了任务,满怀期待地提交上去,结果发现这运行速度简直让人无语,不仅没达到预期的飞快效果,反而比啥缓存都不用的时候还慢!当时我就蒙圈了,心里直嘀咕:“卧槽,这是什么神仙操作?”没办法,只能硬着头皮一点点去查问题,最后才慢慢搞清楚了分布式缓存里到底藏着啥猫腻。 二、深入分析 为什么缓存反而变慢? 经过一番折腾,我发现问题出在以下几个方面: 2.1 数据量太大导致内存不足 首先,大家要明白一点,Spark的分布式缓存本质上是将数据存储在集群节点的内存中。要是数据量太大,超出了单个节点能装下的内存容量,那就会把多余的数据写到磁盘上,这个过程叫“磁盘溢写”。但这样一来,任务的速度就会被拖慢,变得特别磨叽。 举个例子吧,假设你有一份1GB大小的数据集,而你的集群节点只有512MB的可用内存。你要是想把这份数据缓存起来,Spark会自己挑个序列化的方式给数据“打包”,顺便还能压一压体积。不过呢,就算是这样,还是有可能会出现溢写这种烦人的情况,挡都挡不住。唉,真是没想到啊,本来想靠着缓存省事儿提速呢,结果这操作反倒因为磁盘老是读写(频繁I/O)变得更卡了,简直跟开反向加速器似的! 解决办法也很简单——要么增加节点的内存配置,要么减少需要缓存的数据规模。当然,这需要根据实际情况权衡利弊。 2.2 序列化方式的选择不当 另一个容易被忽视的问题是序列化方式的选择。Spark提供了多种序列化机制,包括JavaSerializer、KryoSerializer等。不同的序列化方式会影响数据的大小以及读取效率。 我曾经试过直接使用默认的JavaSerializer,结果发现性能非常差。后来改用了KryoSerializer之后,才明显感觉到速度有所提升。话说回来啊,用 KryoSerializer 的时候可别忘了先给所有要序列化的类都注册好,不然程序很可能就“翻车”报错啦! java import org.apache.spark.serializer.KryoRegistrator; import com.esotericsoftware.kryo.Kryo; public class MyRegistrator implements KryoRegistrator { @Override public void registerClasses(Kryo kryo) { kryo.register(MyClass.class); // 注册其他需要序列化的类... } } 然后在SparkConf中设置: java SparkConf conf = new SparkConf(); conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); conf.set("spark.kryo.registrator", "MyRegistrator"); 2.3 缓存时机的选择失误 还有一个关键点在于缓存的时机。有些人一启动任务就赶紧给数据加上.cache(),觉得这样数据就能一直乖乖待在内存里,不用再费劲去读了。但实际上,这种做法并不总是最优解。 比如,在某些情况下,数据可能只会在特定阶段被频繁访问,而在其他阶段则很少用到。要是你提前把这部分数据缓存了,不光白白占用了宝贵的内存空间,搞不好后面真要用缓存的地方还找不到足够的空位呢! 因此,合理规划缓存策略非常重要。比如说,在某个任务快开始了,你再随手调用一下.cache()这个方法,这样就能保证数据乖乖地待在内存里,别到时候卡壳啦! 三、实践案例 如何正确使用分布式缓存? 接下来,我想分享几个具体的案例,帮助大家更好地理解和运用分布式缓存。 案例1:简单的词频统计 假设我们有一个文本文件,里面包含了大量的英文单词。我们的目标是统计每个单词出现的次数。为了提高效率,我们可以先将文件内容缓存起来,然后再进行处理。 scala val textFile = sc.textFile("hdfs://path/to/input.txt") textFile.cache() val wordCounts = textFile.flatMap(_.split(" ")) .map(word => (word, 1)) .reduceByKey(_ + _) wordCounts.collect().foreach(println) 在这个例子中,.cache()方法确保了textFile RDD的内容只被加载一次,并且可以被后续的操作共享。其实嘛,要是没用缓存的话,每次你调用flatMap或者map的时候,都得重新去原始数据里翻一遍,这就跟每次出门都得把家里所有东西再检查一遍似的,纯属给自己找麻烦啊! 案例2:多步骤处理流程 有时候,一个任务可能会涉及到多个阶段的处理,比如过滤、映射、聚合等等。在这种情况下,合理安排缓存的位置尤为重要。 python from pyspark.sql import SparkSession spark = SparkSession.builder.appName("WordCount").getOrCreate() df = spark.read.text("hdfs://path/to/input.txt") 第一步:将文本拆分为单词 words = df.selectExpr("split(value, ' ') as words").select("words.") 第二步:缓存中间结果 words.cache() 第三步:统计每个单词的出现次数 word_counts = words.groupBy("value").count() word_counts.show() 这里,我们在第一步处理完之后立即调用了.cache()方法,目的是为了保留中间结果,方便后续步骤复用。要是不这么干啊,那每走一步都得把上一步的算一遍,想想就费劲,效率肯定低得让人抓狂。 四、总结与展望 通过今天的讨论,相信大家对Spark的分布式缓存有了更深刻的认识。虽然它能带来显著的性能提升,但也并非万能药。其实啊,要想把它用得溜、用得爽,就得先搞懂它是怎么工作的,再根据具体的情况去灵活调整。不然的话,它的那些本事可就都浪费啦! 未来,随着硬件条件的不断改善以及算法优化的持续推进,相信Spark会在更多领域展现出更加卓越的表现。嘿,咱们做开发的嘛,就得有颗永远好奇的心!就跟追剧似的,新技术一出就得赶紧瞅两眼,说不定哪天就用上了呢。别怕麻烦,多学点东西总没错,说不定哪天就能整出个大招儿来! 最后,感谢大家耐心阅读这篇文章。如果你有任何疑问或者想法,欢迎随时交流!让我们一起努力,共同进步吧!
2025-05-02 15:46:14
81
素颜如水
转载文章
本篇文章为转载内容。原文链接:https://blog.csdn.net/kangshihang1998/article/details/129407214。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 目录 效果 代码 SpringBoot Vue 效果 步骤 点击下载 在输入框输入下载的文件名称 点击暂停 再次点击开始 下载完成 代码 SpringBoot pom <!-- 做断点下载使用--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.8.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency> controller package com.kang.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.;import java.net.URLEncoder;import java.util.Optional;/ @Description 文件切片下载 @ClassName DownLoadController @Author 康世行 @Date 20:58 2023/2/22 @Version 1.0/@Controller@Slf4jpublic class DownLoadController {private final static String utf8 = "utf-8";@RequestMapping("/down")public void downLoadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {// 设置编码格式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
转载
转载文章
本篇文章为转载内容。原文链接:https://zhanghaiyang.blog.csdn.net/article/details/51397459。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 一、Quartz简介 Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。Quartz 是个开源的作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。Quartz 允许开发人员根据时间间隔(或天)来调度作业。它实现了作业和触发器的多对多关系,还能把多个作业与不同的触发器关联。整合了 Quartz 的应用程序可以重用来自不同事件的作业,还可以为一个事件组合多个作业。虽然可以通过属性文件(在属性文件中可以指定 JDBC 事务的数据源、全局作业和/或触发器侦听器、插件、线程池,以及更多)配置 Quartz,但它根本没有与应用程序服务器的上下文或引用集成在一起。结果就是作业不能访问 Web 服务器的内部函数;例如,在使用 WebSphere 应用服务器时,由 Quartz 调度的作业并不能影响服务器的动态缓存和数据源。 二、java中实现定时任务分类 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品): Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少,这篇文章将不做详细介绍。 使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂,稍后会详细介绍。 Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多,稍后会介绍。 从作业类的继承方式来讲,可以分为两类: 作业类需要继承自特定的作业类基类,如Quartz中需要继承自org.springframework.scheduling.quartz.QuartzJobBean;java.util.Timer中需要继承自java.util.TimerTask。 作业类即普通的java类,不需要继承自任何基类。 注:个人推荐使用第二种方式,因为这样所以的类都是普通类,不需要事先区别对待。 从任务调度的触发时机来分,这里主要是针对作业使用的触发器,主要有以下两种: 每隔指定时间则触发一次,在Quartz中对应的触发器为:org.springframework.scheduling.quartz.SimpleTriggerBean 每到指定时间则触发一次,在Quartz中对应的调度器为:org.springframework.scheduling.quartz.CronTriggerBean 注:并非每种任务都可以使用这两种触发器,如java.util.TimerTask任务就只能使用第一种。Quartz和spring task都可以支持这两种触发条件。 三、Quartz与Spring的集成 第一种,作业类继承自特定的基类:org.springframework.scheduling.quartz.QuartzJobBean。 第一步:定义作业类 Java代码 import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; public class Job1 extends QuartzJobBean { private int timeout; private static int i = 0; //调度工厂实例化后,经过timeout时间开始执行调度 public void setTimeout(int timeout) { this.timeout = timeout; } / 要调度的具体任务 / @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { System.out.println("定时任务执行中…"); } } 第二步:spring配置文件中配置作业类JobDetailBean Xml代码 <bean name="job1" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="com.gy.Job1" /> <property name="jobDataAsMap"> <map> <entry key="timeout" value="0" /> </map> </property> </bean> 说明:org.springframework.scheduling.quartz.JobDetailBean有两个属性,jobClass属性即我们在java代码中定义的任务类,jobDataAsMap属性即该任务类中需要注入的属性值。 第三步:配置作业调度的触发方式(触发器) Quartz的作业触发器有两种,分别是 org.springframework.scheduling.quartz.SimpleTriggerBean org.springframework.scheduling.quartz.CronTriggerBean 第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。 配置方式如下: Xml代码 <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job1" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。 配置方式如下: Xml代码 <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="job1" /> <!—每天12:00运行一次 --> <property name="cronExpression" value="0 0 12 ?" /> </bean> 关于cronExpression表达式的语法参见附录。 第四步:配置调度工厂 Xml代码 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> </bean> 说明:该参数指定的就是之前配置的触发器的名字。 第五步:启动你的应用即可,即将工程部署至tomcat或其他容器。 第二种,作业类不继承特定基类。 Spring能够支持这种方式,归功于两个类: org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean 这两个类分别对应spring支持的两种实现任务调度的方式,即前文提到到java自带的timer task方式和Quartz方式。这里我只写MethodInvokingJobDetailFactoryBean的用法,使用该类的好处是,我们的任务类不再需要继承自任何类,而是普通的pojo。 第一步:编写任务类 Java代码 public class Job2 { public void doJob2() { System.out.println("不继承QuartzJobBean方式-调度进行中..."); } } 可以看出,这就是一个普通的类,并且有一个方法。 第二步:配置作业类 Xml代码 <bean id="job2" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject"> <bean class="com.gy.Job2" /> </property> <property name="targetMethod" value="doJob2" /> <property name="concurrent" value="false" /><!-- 作业不并发调度 --> </bean> 说明:这一步是关键步骤,声明一个MethodInvokingJobDetailFactoryBean,有两个关键属性:targetObject指定任务类,targetMethod指定运行的方法。往下的步骤就与方法一相同了,为了完整,同样贴出。 第三步:配置作业调度的触发方式(触发器) Quartz的作业触发器有两种,分别是 org.springframework.scheduling.quartz.SimpleTriggerBean org.springframework.scheduling.quartz.CronTriggerBean 第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。 配置方式如下: Xml代码 <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job2" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。 配置方式如下: Xml代码 <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="job2" /> <!—每天12:00运行一次 --> <property name="cronExpression" value="0 0 12 ?" /> </bean> 以上两种调度方式根据实际情况,任选一种即可。 第四步:配置调度工厂 Xml代码 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> </bean> 说明:该参数指定的就是之前配置的触发器的名字。 第五步:启动你的应用即可,即将工程部署至tomcat或其他容器。 到此,spring中Quartz的基本配置就介绍完了,当然了,使用之前,要导入相应的spring的包与Quartz的包,这些就不消多说了。 其实可以看出Quartz的配置看上去还是挺复杂的,没有办法,因为Quartz其实是个重量级的工具,如果我们只是想简单的执行几个简单的定时任务,有没有更简单的工具,有! 四、Spring-Task 上节介绍了在Spring 中使用Quartz,本文介绍Spring3.0以后自主开发的定时任务工具,spring task,可以将它比作一个轻量级的Quartz,而且使用起来很简单,除spring相关的包外不需要额外的包,而且支持注解和配置文件两种 形式,下面将分别介绍这两种方式。 第一种:配置文件方式 第一步:编写作业类 即普通的pojo,如下: Java代码 import org.springframework.stereotype.Service; @Service public class TaskJob { public void job1() { System.out.println(“任务进行中。。。”); } } 第二步:在spring配置文件头中添加命名空间及描述 Xml代码 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:task="http://www.springframework.org/schema/task" 。。。。。。 xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> 第三步:spring配置文件中设置具体的任务 Xml代码 <task:scheduled-tasks> <task:scheduled ref="taskJob" method="job1" cron="0 ?"/> </task:scheduled-tasks> <context:component-scan base-package=" com.gy.mytask " /> 说明:ref参数指定的即任务类,method指定的即需要运行的方法,cron及cronExpression表达式,具体写法这里不介绍了,详情见上篇文章附录。 <context:component-scan base-package="com.gy.mytask" />这个配置不消多说了,spring扫描注解用的。 到这里配置就完成了,是不是很简单。 第二种:使用注解形式 也许我们不想每写一个任务类还要在xml文件中配置下,我们可以使用注解@Scheduled,我们看看源文件中该注解的定义: Java代码 @Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scheduled { public abstract String cron(); public abstract long fixedDelay(); public abstract long fixedRate(); } 可以看出该注解有三个方法或者叫参数,分别表示的意思是: cron:指定cron表达式 fixedDelay:官方文档解释:An interval-based trigger where the interval is measured from the completion time of the previous task. The time unit value is measured in milliseconds.即表示从上一个任务完成开始到下一个任务开始的间隔,单位是毫秒。 fixedRate:官方文档解释:An interval-based trigger where the interval is measured from the start time of the previous task. The time unit value is measured in milliseconds.即从上一个任务开始到下一个任务开始的间隔,单位是毫秒。 下面我来配置一下。 第一步:编写pojo Java代码 import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component(“taskJob”) public class TaskJob { @Scheduled(cron = "0 0 3 ?") public void job1() { System.out.println(“任务进行中。。。”); } } 第二步:添加task相关的配置: Xml代码 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd" default-lazy-init="false"> <context:annotation-config /> <!—spring扫描注解的配置 --> <context:component-scan base-package="com.gy.mytask" /> <!—开启这个配置,spring才能识别@Scheduled注解 --> <task:annotation-driven scheduler="qbScheduler" mode="proxy"/> <task:scheduler id="qbScheduler" pool-size="10"/> 说明:理论上只需要加上<task:annotation-driven />这句配置就可以了,这些参数都不是必须的。 Ok配置完毕,当然spring task还有很多参数,我就不一一解释了,具体参考xsd文档http://www.springframework.org/schema/task/spring-task-3.0.xsd。 附录: cronExpression的配置说明,具体使用以及参数请百度google 字段 允许值 允许的特殊字符 秒 0-59 , - / 分 0-59 , - / 小时 0-23 , - / 日期 1-31 , - ? / L W C 月份 1-12 或者 JAN-DEC , - / 星期 1-7 或者 SUN-SAT , - ? / L C 年(可选) 留空, 1970-2099 , - / - 区间 通配符 ? 你不想设置那个字段 下面只例出几个式子 CRON表达式 含义 "0 0 12 ?" 每天中午十二点触发 "0 15 10 ? " 每天早上10:15触发 "0 15 10 ?" 每天早上10:15触发 "0 15 10 ? " 每天早上10:15触发 "0 15 10 ? 2005" 2005年的每天早上10:15触发 "0 14 ?" 每天从下午2点开始到2点59分每分钟一次触发 "0 0/5 14 ?" 每天从下午2点开始到2:55分结束每5分钟一次触发 "0 0/5 14,18 ?" 每天的下午2点至2:55和6点至6点55分两个时间段内每5分钟一次触发 "0 0-5 14 ?" 每天14:00至14:05每分钟一次触发 "0 10,44 14 ? 3 WED" 三月的每周三的14:10和14:44触发 "0 15 10 ? MON-FRI" 每个周一、周二、周三、周四、周五的10:15触发 Cron 表达式包括以下 7 个字段: 秒 分 小时 月内日期 月 周内日期 年(可选字段) 特殊字符 Cron 触发器利用一系列特殊字符,如下所示: 反斜线(/)字符表示增量值。例如,在秒字段中“5/15”代表从第 5 秒开始,每 15 秒一次。 问号(?)字符和字母 L 字符只有在月内日期和周内日期字段中可用。问号表示这个字段不包含具体值。所以,如果指定月内日期,可以在周内日期字段中插入“?”,表示周内日期值无关紧要。字母 L 字符是 last 的缩写。放在月内日期字段中,表示安排在当月最后一天执行。在周内日期字段中,如果“L”单独存在,就等于“7”,否则代表当月内周内日期的最后一个实例。所以“0L”表示安排在当月的最后一个星期日执行。 在月内日期字段中的字母(W)字符把执行安排在最靠近指定值的工作日。把“1W”放在月内日期字段中,表示把执行安排在当月的第一个工作日内。 井号()字符为给定月份指定具体的工作日实例。把“MON2”放在周内日期字段中,表示把任务安排在当月的第二个星期一。 星号()字符是通配字符,表示该字段可以接受任何可能的值。 字段 允许值 允许的特殊字符 秒 0-59 , - / 分 0-59 , - / 小时 0-23 , - / 日期 1-31 , - ? / L W C 月份 1-12 或者 JAN-DEC , - / 星期 1-7 或者 SUN-SAT , - ? / L C 年(可选) 留空, 1970-2099 , - / 表达式意义 "0 0 12 ?" 每天中午12点触发 "0 15 10 ? " 每天上午10:15触发 "0 15 10 ?" 每天上午10:15触发 "0 15 10 ? " 每天上午10:15触发 "0 15 10 ? 2005" 2005年的每天上午10:15触发 "0 14 ?" 在每天下午2点到下午2:59期间的每1分钟触发 "0 0/5 14 ?" 在每天下午2点到下午2:55期间的每5分钟触发 "0 0/5 14,18 ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 "0 0-5 14 ?" 在每天下午2点到下午2:05期间的每1分钟触发 "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 "0 15 10 ? MON-FRI" 周一至周五的上午10:15触发 "0 15 10 15 ?" 每月15日上午10:15触发 "0 15 10 L ?" 每月最后一日的上午10:15触发 "0 15 10 ? 6L" 每月的最后一个星期五上午10:15触发 "0 15 10 ? 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 "0 15 10 ? 63" 每月的第三个星期五上午10:15触发 每天早上6点 0 6 每两个小时 0 /2 晚上11点到早上8点之间每两个小时,早上八点 0 23-7/2,8 每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点 0 11 4 1-3 1月1日早上4点 0 4 1 1 本篇文章为转载内容。原文链接:https://zhanghaiyang.blog.csdn.net/article/details/51397459。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-27 18:50:19
344
转载
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
ln -s source destination
- 创建软链接(符号链接)。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
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
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"