1.走进SWMM源代码——GIS转SWMM经验及工具分享
2.宏观看 Go 语言中的集集合 Map 内部
3.手把手带你学webpack(6)--source-map
4.å¦ä½è®¾è®¡å¹¶å®ç°ä¸ä¸ªçº¿ç¨å®å
¨ç Map
5.MapReduce源码解析之Mapper
6.记一次源码追踪分析,从Java到JNI,合源再到JVM的码分C++:fileChannel.map()为什么快;源码分析map方法,put方法
走进SWMM源代码——GIS转SWMM经验及工具分享
作者:赵也(深圳创环) GIS格式数据在城市管线数据储存、享视智慧水务平台等行业中广泛应用。原理PINS等工具提供简单处理方式,集集合名著转转源码但复杂转换需要二次开发。合源本文从Gis二次开发、码分UI使用、享视转换算法编写角度,原理介绍GIS转SWMM流程。集集合 欢迎关注“市政规划交流”公众号。合源 视频主要内容: Part1:Gis二次开发 基于ArcMap控件二次开发。码分环境配置:ArcGis 享视for Desktop.1、ArcObject SDK for Microsoft .Net Framework4.0、原理Microsoft Visual Studio (.4 + 4.5 +也行) Part2:UI简介 建立Gis和SWMM文件桥梁,简化数据转换流程。 Part3:核心算法模块介绍 Step1:背景数据需求。基础排水管网模型需空间数据包括: 点要素:检查井/排放口,包含编号、底高程、地表高程、标识字段等。 线要素:排水管渠,包含编号、起点编号、终点编号、断面类型、断面参数等。 面要素:汇水区,白菜视频源码包含编号、汇流编号、不透水率等。 Step2:解析GIS点、线、面要素生成Inp文件结构。 核心代码模块介绍: 定义输出字符串集合,Inp文件本质是ASCII文件,字符串集合表示文件内容。 遍历要素集,通过FeatureCursor光标遍历特定要素(管网、检查井、下垫面),提取具体字符串。 将前缀字符串和提取的字符串依次写入Inp文件。 Part4:案例实操/工具分享 视频分左右,展示使用工具生成可运行的Inp文件案例。 额外提及:软件包中ConvertSWMMTest.esriaddin文件,为无编程环境的用户提供了直接安装addin并使用ConvertSwmmTest工具的途径。 附录:INP文件结构介绍宏观看 Go 语言中的 Map 内部
在 Go 语言的世界里,关于 map 的深入理解相对较少,这与slice的热门探讨形成鲜明对比。为了揭开 map 的神秘面纱,我查阅了其源码,尽管代码复杂,但我们可以从宏观视角解析 map 的构建与增长机制,从而理解其为何高效快速且无序。
首先,创建和使用 map 的vue支付源码基本操作是通过指定 key 储存 value,通过 key 可直接访问 value,而且遍历 map 时,key 的顺序并非插入时的顺序,每次运行代码,key 的顺序都会有所变化。
深入来看,Go 语言中的 map 实质上是基于散列表(hash table)实现的,由一系列桶(bucket)组成,桶的数量是 2 的幂。插入键值对时,会根据 key 生成散列值并根据其低阶位决定放入哪个桶。桶内部包含一个数组和一个 byte 数组,数组用于存储键值对,而 byte 数组则是为了节省内存对齐而设计的。
当 map 需要增长时,会根据装载阈值决定,这涉及内存分配、旧桶数据迁移等操作,以保证迭代器在增长过程中仍能正常工作。虽然代码使用 C 编写,但理解这些细节有助于我们更有效地使用 map,尤其是对于预知 key 数量的场景。
最后,感谢 Stephen McQuay 和 Keith Randall 的审阅和指正。本文由 William Kennedy 编写,由 maxwell 译成中文,由 GCTT 原创编译,并由 Go语言中文网推出。
手把手带你学webpack(6)--source-map
本篇文章对应源码:JvcicpO1xuXG4gIHRocmIG5ldyBFcnJvcignctZXRoaW5nIHdybnLi4uJyk7XGXG5cbm1vZHVsZS5leHBvcnRzID0geyBlcnJvckZuIHXG4iXSwibmFtZXMiOltdLCJzb3VyY2VSbIjoiIn0=\n//#sourceURL=webpack-internal:///./src/utils.js\n");这种方式适用于在开发模式下需要精确的rstp的源码source-map时使用,相比直接的eval,会更加精确些
3.4inline-source-map顾名思义,就是以内联方式存放source-map文件,它会将source-map文件的内容编码成base后直接放在打包结果的最后
constHtmlWebpackPlugin=require('html-webpack-plugin');const{ CleanPlugin}=require('webpack');/***@type{ import('webpack').Configuration}*/module.exports={ mode:'development',devtool:'inline-source-map',plugins:[newHtmlWebpackPlugin(),newCleanPlugin()],};//#sourceMappingURL=data:application/json;charset=utf-8;base,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUEsbUJBQW1COzs7Ozs7O1VDTm5CO1VBQ0E7O1VBRUE7VUFDQTtVQUNBO1VBQ0E7VUFDQTtVQUNBO1VBQ0E7VUFDQTtVQUNBO1VBQ0E7VUFDQTtVQUNBO1VBQ0E7O1VBRUE7VUFDQTs7VUFFQTtVQUNBO1VBQ0E7Ozs7Ozs7OztBQ3RCQSxRQUFRLFVBQVUsRUFBRSxtQkFBTyxDQUFDLCtCQUFTOztBQUVyQyIsInNvdXJjZXMiOlsid2VicGFjazovLzA2X3dlYnBhY2tfccmNlXhcC8uL3NyYydGlscy5qcyIsIndlYnBhY2s6Ly8wNlZWJwYWNrX3NvdXJjZV9tYXAvd2VicGFjay9ibc3RyYXAiLCJ3ZWJwYWNrOi8vMDZfd2VicGFjazb3VyY2VfbWFwLy4vc3JjL2luZGV4LmpzIl0sInNvdXJjZXNDbZWIjpbImZ1bmN0aW9uIGVycm9yRm4oKSB7XG4gIGNvbnNvbGUubG9nKCdoZWxsbyBlcnJvcicpO1xuXG4gIHRocmIG5ldyBFcnJvcignctZXRoaW5nIHdybnLi4uJyk7XGXG5cbm1vZHVsZS5leHBvcnRzID0geyBlcnJvckZuIHXG4iLCIvLyBUaGUgbW9kdWxlIGNhY2hlXGYXIgXZWJwYWNrXvZHVsZV9jYWNoZV9fID0geXG5cbi8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG5mdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuXHR2YXIgY2FjaGVkTW9kdWxlID0gXZWJwYWNrXvZHVsZV9jYWNoZV9fWvZHVsZUlkXTtcblWYgKGNhY2hlZE1vZHVsZSAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0cmV0dXJuIGNhY2hlZE1vZHVsZS5leHBvcnRzO1xuXHR9XG5cdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaWbyB0aGUgY2FjaGUpXG5cdHZhciBtb2R1bGUgPSBfX3dlYnBhY2tfbW9kdWxlX2NhY2hlXbbW9kdWxlSWRdID0ge1xuXHRcdC8vIG5vIG1vZHVsZS5pZCBuZWVkZWRcblx0XHQvLyBubyBtb2R1bGUubG9hZGVkIG5lZWRlZFxuXHRcdGV4cG9ydHM6IHt9XG5cdHXG5cblx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG5cdF9fd2VicGFjatb2R1bGVzXbbW9kdWxlSWRdKG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFjayZXF1aXJlXpO1xuXG5cdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG5cdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbn1cblxuIiwiYuc3QgeyBlcnJvckZuIH0gPSByZXF1aXJlKCcuL3V0aWxzJyk7XG5cbmVycm9yRm4oKTtcbiJdLCJuYW1lcyI6WsInNvdXJjZVJvb3QiOiIifQ==从官方文档可以看到,这种方式的构建速度是最慢的,只适用于构建单个文件的时候使用
3.5cheap-source-map这种方式相比source-map而言,没有建立列映射,也就是说遇到报错的时候,只会告诉你哪一行代码出错了,而不会告诉你哪一列出错了,如果开发时对列映射没有太高要求的话可以使用这种方式,毕竟不用生成列映射,比起source-map来说会快一些
constHtmlWebpackPlugin=require('html-webpack-plugin');const{ CleanPlugin}=require('webpack');/***@type{ import('webpack').Configuration}*/module.exports={ mode:'development',devtool:'cheap-source-map',plugins:[newHtmlWebpackPlugin(),newCleanPlugin()],};3.6cheap-module-source-map官方文档对这种方式的devtool并没有进行任何详细介绍,事实上这种方式适用于js代码被loader转换过的场景,比如被babel进行了转换,又比如源码是用typescript写的,后来经过loader转成了js代码,而我们又希望在运行的时候出现报错信息时能够对应回typescript代码像这种有loader对js进行转换的场景下,想要保证正确的source-map就需要使用到带有module的devtool了,因为除了cheap-module-source-map,还有很多别的方式也是有module的,只要是在官方文档中看到带有module的devtool都是具有这种特性
下面就以babel为例,我们通过babel-loader对js进行转换,然后看看能否正确对应到转换前的代码首先安装如下依赖
pnpmi@babel/core@babel-preset-envbabel-loader@babel/core是babel的核心,所有功能都要在这个包的基础上运行
@babel-preset-env让我们可以不需要考虑转换成什么版本的js,它会根据要适配的浏览器自动转换成能兼容相应浏览器的版本,这里我们使用它主要是能够将我们写的es6代码转成es5,从而让我们的源码和打包后的结果有差异,方便观察source-map是解析fc源码否生效
babel-loader,用于和webpack搭配使用,转换js文件
接下来配置loader
constHtmlWebpackPlugin=require('html-webpack-plugin');const{ CleanPlugin}=require('webpack');/***@type{ import('webpack').Configuration}*/module.exports={ mode:'development',devtool:'eval',//默认就是eval,因此development模式下不写devtool配置项也可以plugins:[newHtmlWebpackPlugin(),newCleanPlugin()],};0然后我们写一个具有es6特性的语法的函数
constHtmlWebpackPlugin=require('html-webpack-plugin');const{ CleanPlugin}=require('webpack');/***@type{ import('webpack').Configuration}*/module.exports={ mode:'development',devtool:'eval',//默认就是eval,因此development模式下不写devtool配置项也可以plugins:[newHtmlWebpackPlugin(),newCleanPlugin()],};1使用到了const、箭头函数,经过babel转换成es5后,代码的位置会和源码中不一样,那么在浏览器中如果仍然能够找到转换前的源码,则说明cheap-module-source-map生效了可以看到,在浏览器中确实能够看到转换前的源码,这就是cheap-module-source-map中module的作用,事实上官方文档中这么多的配置项我们不需要害怕,只需要知道每个关键字是什么意思,那么它们组合起来无非就是各种特性的叠加而已
3.7hidden-source-map也是一个见名知意的配置项,相比于source-map,就是将最后的//#sourceMappingURL=main.js.map这句注释删除了,这也就意味着source-map不会生效了,但是仍然会生成source-map文件的官方文档中给我们的建议是在只需要知道有错误出现时给我们在控制台输出出来的话就可以使用这种方式
3.8nosources-source-map这种方式能够在出现错误的时候告诉我们是源码中哪个文件第几行出错了,但是不会在浏览器中给我们生成源码
总结了解完以上这几个devtool配置项,就足够了,官网的个配置项就是根据eval、hidden、inline、cheap、module、nosources这几个关键字组合出来的
但是组合也是有规则的,官方文档中给出的规则如下:
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
原文:/post/å¦ä½è®¾è®¡å¹¶å®ç°ä¸ä¸ªçº¿ç¨å®å ¨ç Map
Map æ¯ä¸ä¸ªé常常ç¨çæ°æ®ç»æï¼ä¸ä¸ªæ åºç key/value 对çéåï¼å ¶ä¸ Map ææç key é½æ¯ä¸åçï¼ç¶åéè¿ç»å®ç key å¯ä»¥å¨å¸¸æ°æ¶é´ O(1) å¤æ度å æ¥æ¾ãæ´æ°æå é¤å¯¹åºç valueã
è¦æ³å®ç°å¸¸æ°çº§çæ¥æ¾ï¼åºè¯¥ç¨ä»ä¹æ¥å®ç°å¢ï¼è¯»è åºè¯¥å¾å¿«ä¼æ³å°åå¸è¡¨ãç¡®å®ï¼Map åºå±ä¸è¬é½æ¯ä½¿ç¨æ°ç»æ¥å®ç°ï¼ä¼åç¨åå¸ç®æ³è¾ å©ã对äºç»å®ç keyï¼ä¸è¬å è¿è¡ hash æä½ï¼ç¶åç¸å¯¹åå¸è¡¨çé¿åº¦å模ï¼å° key æ å°å°æå®çå°æ¹ã
åå¸ç®æ³æå¾å¤ç§ï¼éåªä¸ç§æ´å é«æå¢ï¼
1. åå¸å½æ°
MD5 å SHA1 å¯ä»¥è¯´æ¯ç®ååºç¨æ广æ³ç Hash ç®æ³ï¼èå®ä»¬é½æ¯ä»¥ MD4 为åºç¡è®¾è®¡çã
MD4(RFC ) æ¯ MIT çRonald L. Rivest å¨ å¹´è®¾è®¡çï¼MD æ¯ Message Digestï¼æ¶æ¯æè¦ï¼ ç缩åãå®éç¨å¨ä½åé¿çå¤çå¨ä¸ç¨é«é软件å®ç°ââå®æ¯åºäº ä½æä½æ°çä½æä½æ¥å®ç°çã
MD5(RFC ) æ¯ Rivest äºå¹´å¯¹ MD4 çæ¹è¿çæ¬ãå®å¯¹è¾å ¥ä»ä»¥ä½åç»ï¼å ¶è¾åºæ¯4个ä½åç级èï¼ä¸ MD4 ç¸åãMD5 æ¯ MD4 æ¥å¾å¤æï¼å¹¶ä¸é度è¾ä¹è¦æ ¢ä¸ç¹ï¼ä½æ´å®å ¨ï¼å¨æåæåæå·®åæ¹é¢è¡¨ç°æ´å¥½ã
SHA1 æ¯ç± NIST NSA 设计为å DSA ä¸èµ·ä½¿ç¨çï¼å®å¯¹é¿åº¦å°äºçè¾å ¥ï¼äº§çé¿åº¦ä¸ºbit çæ£åå¼ï¼å æ¤æ穷举 (brute-force)
æ§æ´å¥½ãSHA-1 设计æ¶åºäºå MD4 ç¸ååç,并ä¸æ¨¡ä»¿äºè¯¥ç®æ³ã
常ç¨ç hash å½æ°æ SHA-1ï¼SHA-ï¼SHA-ï¼MD5 ãè¿äºé½æ¯ç»å ¸ç hash ç®æ³ãå¨ç°ä»£åç产ä¸ï¼è¿ä¼ç¨å°ç°ä»£ç hash ç®æ³ãä¸é¢å举å 个ï¼è¿è¡æ§è½å¯¹æ¯ï¼æååéå ¶ä¸ä¸ä¸ªæºç åæä¸ä¸å®ç°è¿ç¨ã
ï¼1ï¼ Jenkins Hash å SpookyHash
å¹´ Bob Jenkins å¨ã Dr. Dobbs Journalãæå¿ä¸å表äºä¸çå ³äºæ£åå½æ°çæç« ãA hash function for hash Table lookupããè¿ç¯æç« ä¸ï¼Bob 广æ³æ¶å½äºå¾å¤å·²æçæ£åå½æ°ï¼è¿å ¶ä¸ä¹å æ¬äºä»èªå·±æè°çâlookup2âãéåå¨å¹´ï¼Bob åå¸äº lookup3ãlookup3 å³ä¸º Jenkins Hashãæ´å¤æå ³ Bobâs æ£åå½æ°çå 容请åé ç»´åºç¾ç§ï¼Jenkins hash functionãmemcachedç hash ç®æ³ï¼æ¯æ两ç§ç®æ³ï¼jenkins, murmur3ï¼é»è®¤æ¯ jenkinsã
å¹´ Bob Jenkins åå¸äºä»èªå·±çä¸ä¸ªæ°æ£åå½æ°
SpookyHashï¼è¿æ ·å½åæ¯å 为å®æ¯å¨ä¸å£èåå¸çï¼ãå®ä»¬é½æ¥æ2åäº MurmurHash çé度ï¼ä½ä»ä»¬é½åªä½¿ç¨äºä½æ°å¦å½æ°è没æä½çæ¬ï¼SpookyHash ç»åºä½è¾åºã
ï¼2ï¼ MurmurHash
MurmurHash æ¯ä¸ç§éå å¯ååå¸å½æ°ï¼éç¨äºä¸è¬çåå¸æ£ç´¢æä½ã
Austin Appleby å¨å¹´åå¸äºä¸ä¸ªæ°çæ£åå½æ°ââMurmurHashãå ¶ææ°çæ¬å¤§çº¦æ¯ lookup3 é度ç2åï¼å¤§çº¦ä¸º1 byte/cycleï¼ï¼å®æä½åä½ä¸¤ä¸ªçæ¬ãä½çæ¬åªä½¿ç¨ä½æ°å¦å½æ°å¹¶ç»åºä¸ä¸ªä½çåå¸å¼ï¼èä½çæ¬ä½¿ç¨äºä½çæ°å¦å½æ°ï¼å¹¶ç»åºä½åå¸å¼ãæ ¹æ®Austinçåæï¼MurmurHashå ·æä¼å¼çæ§è½ï¼è½ç¶ Bob Jenkins å¨ãDr. Dobbs articleãæå¿ä¸å£°ç§°âæé¢æµ MurmurHash æ¯èµ·lookup3è¦å¼±ï¼ä½æ¯æä¸ç¥éå ·ä½å¼ï¼å 为æè¿æ²¡æµè¯è¿å®âãMurmurHashè½å¤è¿ é走红å¾çäºå ¶åºè²çé度åç»è®¡ç¹æ§ãå½åççæ¬æ¯MurmurHash3ï¼RedisãMemcachedãCassandraãHBaseãLuceneé½å¨ä½¿ç¨å®ã
ä½è ï¼ä¸ç¼æ®æµåéåè¾¹å°é
MapReduce源码解析之Mapper
MapReduce,大数据领域的标志性计算模型,由Google公司研发,其核心概念"Map"与"Reduce"简明易懂却威力巨大,打开了大数据时代的大门。对于许多大数据工作者来说,MapReduce是基础技能之一,而源码解析更是深入理解与实践的必要途径。 MapReduce由两部分组成:Map与Reduce。Map阶段通过映射函数将一组键值对转换成另一组键值对,而Reduce阶段则负责合并这些新的键值对。这种并行计算模型极大地提高了大数据处理的效率。 本文将聚焦于Map阶段的核心实现——Mapper。通过解析Mapper类及其子类的源码,我们可以更深入地理解MapReduce的工作机制,并在易观千帆等技术数据处理中发挥更大的效能。 Mapper类内部包含四个关键方法与一个抽象类: setup():主要为map()方法做准备,例如加载配置文件、传递参数。 cleanup():用于清理资源,如关闭文件、处理Key-Value。 map():程序的逻辑核心,对输入的文本进行处理(如分割、过滤),以键值对的形式写入context。 run():驱动Mapper执行的主方法,按照预设顺序执行setup()、map()、cleanup()。 Context抽象类扮演着重要角色,用于跟踪任务状态和数据存储,如在setup()中读取配置信息,并作为Key-Value载体。 下面是几个Mapper子类的详细解析: InverseMapper:将键值对反转,适用于不同需求的统计分析。 TokenCounterMapper:使用StringTokenizer对文本进行分割,计算特定token的数量,适用于词频统计等。 RegexMapper:对文本进行正则化处理,适用于特定格式文本的统计。 MultithreadedMapper:利用多线程执行Mapper任务,提高CPU利用率,适用于并发处理。 本文对MapReduce中Mapper及其子类的源码进行了详尽解析,旨在帮助开发者更深入地理解MapReduce的实现机制。后续将探讨更多关键类源码,以期为大数据处理提供更深入的洞察与实践指导。记一次源码追踪分析,从Java到JNI,再到JVM的C++:fileChannel.map()为什么快;源码分析map方法,put方法
前言
在系统IO相关的系统调用有read/write,mmap,sendfile等这些。
其中read/write是普通的读写,每次都需要将buffer从用户空间拷贝到内核空间;
而mmap使用的是内存映射,会将磁盘文件对应的页映射(拷贝)到内核空间的page cache,并记录到用户进程的页表中,使得用户空间也可以像操作用户空间一样操作该文件的映射,最后再由操作系统来讲该映射(脏页)回写到磁盘;
sendfile则使用的是零拷贝技术,在mmap的基础上,当发送数据的时候只拷贝fd和offset等元数据信息,而将数据主体直接拷贝至protocol buffer,实现了内核数据零冗余的零拷贝技术
本文地址:/post//
问题/目的问题1Java中哪些API使用到了mmap问题2怎么知道该API使用到了mmap,如何追踪程序的系统调用目的1源码中分析验证,从Java到JNI,再到C++:fileChannel.map()使用的是系统调用mmap目的2源码验证分析:调用mmapedByteBuffer.put(Byte[])时JVM在搞些什么?mmap比普通的read/write快在哪?揭晓答案1mmap在Java NIO中的体现/使用看一个例子
// 1GBpublic static final int _GB = 1**;File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer mmapedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB);for (int i = 0; i < _GB; i++) { count++;mmapedByteBuffer.put((byte)0);}其中fileChannel.map()底层使用的就是系统调用mmap,函数签名为: public abstract MappedByteBuffer map(MapMode mode,long position, long size)throws IOException
答案2程序执行的系统调用追踪/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}把上面这段代码编译后把“.class”文件拉到linux执行,并用linux上的strace工具记录其系统调用日志,拿到日志文件我们可以在日志中看到以下信息(关于怎么拿到日志可以参照我的博文:无(代写)):
注:日志有多行,这里只选取我们关注的
// ...// 看到了我们打的开始标志openat(AT_FDCWD, "start1.txt", O_RDONLY) = -1 ENOENT (No such file or directory)// ... // 打开文件,文件描述符fd为6openat(AT_FDCWD, "filename", O_RDWR|O_CREAT, ) = 6// 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// ... // 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// 进行内存映射mmap(NULL, , PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x7f2fd6cd// ...// 程序退出exit(0)// 看到了我们打的结束标志openat(AT_FDCWD, "end.txt", O_RDONLY) = -1 ENOENT (No such file or directory)在上面程序的系统调用日志中我们确实看到了我们打的开始标志,结束标志。在开始标志和结束标志之间我们看到了我们的文件"filename"确实被打开了,文件描述符fd = 6;在打开文件后紧接着又执行了系统调用mmap,这一点我们Java代码一致,这样,我们就验证了我们答案1中的结论,可以开始我们的下文了
源码追踪分析,从Java到JNI,再到JVM的C++目的1寻源之旅:fileChannel.map()我们知道我们执行Java代码fileChannel.map()确实会在底层调用系统调用,那怎么在源码中得到验证呢?怎么落脚于源码进行分析呢?下面开始我们的寻源之旅
FileChannelImpl.map() 注:由于代码较长,这里代码中略去了一些我们不关注的,比如异常捕获等
public MappedByteBuffer map(MapMode mode, long position, long size)throws IOException{ // ...try { // ...synchronized (positionLock) { // ...long mapPosition = position - pagePosition;mapSize = size + pagePosition;try { // !我们要找的语句就在这!addr = map0(imode, mapPosition, mapSize);} catch (OutOfMemoryError x) { // 如果内存不足,先尝试进行GCSystem.gc();try { Thread.sleep();} catch (InterruptedException y) { Thread.currentThread().interrupt();}try { // 再次试着mmapaddr = map0(imode, mapPosition, mapSize);} catch (OutOfMemoryError y) { // After a second OOME, failthrow new IOException("Map failed", y);}}} // ...} finally { // ...}}上面函数源码中真正执行mmap的语句是在addr = map0(imode, mapPosition, mapSize),于是我们寻着这里继续追踪
FileChannelImpl.map0()
// Creates a new mappingprivate native long map0(int prot, long position, long length)throws IOException;可以看到,该方法是一个native方法,所以后面的源码我们需要到这个FileChannelImpl.class对应的fileChannelImpl.c中去看,所以我们需要去找到JDK的源码
在JDK源码中我们找到fileChannelImpl.c文件
fileChannelImpl.c 根据JNI的对应规则,我们找到该文件内对应的Java_sun_nio_ch_FileChannelImpl_map0方法,其源码如下:
JNIEXPORT jlong JNICALLJava_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this, jint prot, jlong off, jlong len){ void *mapAddress = 0;jobject fdo = (*env)->GetObjectField(env, this, chan_fd);jint fd = fdval(env, fdo);int protections = 0;int flags = 0;if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) { protections = PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) { protections = PROT_WRITE | PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) { protections =PROT_WRITE | PROT_READ;flags = MAP_PRIVATE;}// !我们要找的语句就在这里!mapAddress = mmap(0,/* Let OS decide location */len,/* Number of bytes to map */protections,/* File permissions */flags,/* Changes are shared */fd, /* File descriptor of mapped file */off); /* Offset into file */if (mapAddress == MAP_FAILED) { if (errno == ENOMEM) { JNU_ThrowOutOfMemoryError(env, "Map failed");return IOS_THROWN;}return handle(env, -1, "Map failed");}return ((jlong) (unsigned long) mapAddress);}我们要找的语句就上面代码中的mapAddress = mmap(0,len,protections,flags,fd,off),至于为什么不是直接的mmap,而是mmap,是因为这里的mmap是一个宏,在文件上方有其定义,如下:
#define mmap mmap至此,我们就在源码中得到验证了我们问题2中的结论:fileChannelImpl.map()底层使用的是mmap系统调用
目的2寻源之旅:mmapedByteBuffer.put(Byte[ ])接着我们来看看当我们调用mmapedByteBuffer.put(Byte[])JVM底层在搞些什么动作
MappedByteBuffer ?首先我们得知道,当我们执行MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB)时,实际返回的对象是DirectByteBuffer类的实例,因为MappedByteBuffer为抽象类,且只有DirectByteBuffer继承了它,看下面两图就明白了
DirectByteBuffer 于是我们找到DirectByteBuffer内的put(Byte[ ])方法
public ByteBuffer put(byte x) { unsafe.putByte(ix(nextPutIndex()), ((x)));return this;}可以看到该方法内实际是调用Unsafe类内的putByte方法来实现功能的,所以我们还得去看Unsafe类
Unsafe.class
public native voidputByte(long address, byte x);该方法在Unsafe内是一个native方法,所以所以我们还得去看unsafe.cpp文件内对应的实现
unsafe.cpp
在JDK源码中,我们找到unsafe.cpp
在这份源码内,没有使用JNI内普通加前缀的方法来形成对应关系
不过我们还是能顺着源码的蛛丝轨迹找到我们要找的方法
注意到源码中有这样的注册机制,所以我们可以知道我们要找的代码就是上图中标注的代码
顺藤摸瓜,我们就找到了该方法的定义
UNSAFE_ENTRY(void, Unsafe_SetNative##Type(JNIEnv *env, jobject unsafe, jlong addr, java_type x)) \UnsafeWrapper("Unsafe_SetNative"#Type); \JavaThread* t = JavaThread::current(); \t->set_doing_unsafe_access(true); \void* p = addr_from_java(addr); \*(volatile native_type*)p = x; \t->set_doing_unsafe_access(false); \UNSAFE_END \该方法内主要的逻辑语句就是以下两句:
/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}0至此,我们就知道:其实我们调用mmapedByteBuffer.put(Byte[ ])时,JVM底层并不需要涉及到系统调用(这里也可以用strace工具追踪从而得到验证)。也就是说通过mmap映射的空间在内核空间和用户空间是共享的,我们在用户空间只需要像平时使用用户空间那样就行了————获取地址,设置值,而不涉及用户态,内核态的切换
总结fileChannelImpl.map()底层用调用系统函数mmap
fileChannelImpl.map()返回的其实不是MappedByteBuffer类对象,而是DirectByteBuffer类对象
在linux上可以通过strace来追踪系统调用
JNI中“.class”文件内方法与“.cpp”文件内函数的对应关系不止是前缀对应的方法,还可以是注册的方式,这一点的追寻代码的时候有很大帮助
directByteBuffer.put()方法底层并没有涉及系统调用,也就不需要涉及切态的性能开销(其底层知识执行获取地址,设置值的操作),所以mmap的性能就比普通读写read/write好
...
原文:/post/MapBox源码解读 - style 的加载逻辑
探讨MapBox源码中的style加载逻辑,从数据源到图层显示的变化过程。
style的加载涉及数据请求、解析、属性赋值、着色器执行等多个环节,演示通过示例代码进行定制化开发。
style加载和渲染流程包括请求、解析、数据赋值、渲染等步骤,详细内容见附件。
实现单侧线绘制效果,具体实现代码如下所示。
总结今日讲解内容,分享一个web端调试webgl的插件——Spector.js,Firefox和Chrome浏览器皆可使用,自行下载并探索其功能。