【共享雨伞系统源码错误】【斗地主游戏源码下载】【java开发crm系统源码】TP5小说站源码
1.易语言刷课源码?
2.超详细通达信主图指标安装教程,小说意简言赅过程简单!站源有惊喜源码
3.对TP5数据库缓存cache的小说一些思考
4.IM即时通讯源码搭建教程全开源
5.记一次源码追踪分析,从Java到JNI,站源再到JVM的小说C++:fileChannel.map()为什么快;源码分析map方法,put方法
易语言刷课源码?
易语言源码是站源共享雨伞系统源码错误什么?
源码就是一个程序在编写时候的代码文件,易语言的源码是.e拓展名的文件,通过易语言可以打开源码文件来修改和重新编译可执行文件
易语言yy协议刷花源码这么写,求大神
复制别人的小说源码要连组件也复制,你只复制源码肯定不行的站源
你那个的提示就是按钮改名,但你的小说按钮没有复制,自然会出现这个提示
易语言编程源码在哪里?站源易语言\易语言v5.\samples
这个是易语言安装路径的自带源码,也可以去百度搜索源码!小说
易语言视频播放器源码易语言的站源支持库例程里面有,具体打开易语言
如下:
.版本2
.支持库eMMedia
.支持库iext2
.程序集窗口程序集1
.子程序_播放按钮_被单击
媒体播放1.播放(-1)
媒体播放1.取长度()
.子程序_暂停按钮_被单击
.如果(媒体播放1.取状态()=1)
媒体播放1.暂停()
.否则
媒体播放1.播放(媒体播放1.取位置())
.如果结束
.子程序_停止按钮_被单击
媒体播放1.停止()
.子程序_打开_被选择
通用对话框1.过滤器=“媒体文件|*.wav;*.mid;*.avi;*.mpg;*.mp3;*.wmv;*.rm”
.如果真(通用对话框1.打开()=真)
媒体播放1.打开(通用对话框1.文件名)
.如果(媒体播放1.是小说否视频()=真)
_启动窗口.标题=“视频文件”
.否则
_启动窗口.标题=“声音文件”
.如果结束
.如果真结束
.子程序__启动窗口_创建完毕
播放按钮.=取组(#组,0,取默认底色())
暂停按钮.=取组(#组,1,取默认底色())
停止按钮.=取组(#组,2,取默认底色())
.子程序_选择框1_被单击
.如果(选择框1.选中=真)
媒体播放1.置句柄(分组框1.取窗口句柄())
.否则
媒体播放1.置句柄(0)
.如果结束
.子程序_媒体信息_被选择
.局部变量信息文本,文本型
信息文本=“比率:”+到文本(媒体播放1.取比率())+#换行符+“总时间:”+到文本(媒体播放1.取总时间())+#换行符+“长度:”+到文本(媒体播放1.取长度())+#换行符+“播放位置:”+到文本(媒体播放1.取位置())+#换行符+“MCI别名:”+媒体播放1.取别名()
信息框(信息文本,0,)
易语言怎么取网页源码?问题一:易语言如何获取网页源码的并展示出来?.版本2
.支持库RegEx
.支持库internet
.支持库iext
.子程序_按钮1_被单击
.局部变量网页文本,文本型
.局部变量表达式,正则表达式
.局部变量搜索结果,搜索结果,,0
.局部变量计次变量,整数型
.局部变量文本,文本型
网页文本=到文本(HTTP读文件(编辑框1.内容))
网页文本=子文本替换(网页文本,#引号,“'”,0,,真)
网页文本=到小写(网页文本)
透明标签1.标题=取中间文(网页文本,“”,“”)
表达式.创建(“meta(.*?)”,假)
搜索结果=表达式.搜索全部(网页文本)
.计次循环首(取数组成员数(搜索结果),计次变量)
文本=搜索结果[计次变量].取子匹配文本(网页文本,1,)
.判断开始(寻找文本(文本,“name='keywords'”,1,假)>0)
透明标签2.标题=取中间文(文本,“content='”,“'”)
.判断(寻找文本(文本,“name='description'”,1,假)>0)
透明标签3.标题=取中间文(文本,“content='”,“'”)
.默认
.判断结束
.计次循环尾()
.子程序取中间文,文本型
.参数全文,文本型
.参数左文,文本型
.参数右文,文本型
.局部变量位置,整数型
.局部变量总长度,整数型
.局部变量文本,文本型
总长度=取文本长度(全文)
位置=寻找文本(全文,左文,,假)
.如果真(位置<0)
返回(“”)
.如果真结束
位置=位置+取文本长度(左文)
文本=取文本中间(全文,位置,总长度)
总长度=取文本长度(文本)
位置=寻找文本(文本,右文,,假)-1
.如果真(位置<0)
返回(“”)
.如果真结束
文本=取文本中间(文本,1,位置)
返回(文本)
问题二:易语言怎么获取网页源代码我记得我给你回答过了,其实说老实话,站源想学好一门编程语言,小说不下大功夫是不行的,网上有很多开源的,我不能帮你太多,因为我对这方面不是很懂,呵呵,不好意思。
问题三:易语言取网页源码的问题,求解答!思路:
超文本浏览框.取文档对象().对象型方法(“getElementById”,“xxx”).方法(“focus”,)
超文本浏览框.取文档对象().对象型方法(“getElementById”,“xxx”).方法(“click”,)
取这个选择框的对象(XXX是对象名称或者ID),然后让其获得焦点并单击它
就可以再取
临时文本=超文本浏览框.取文档对象().读对象型属性(“body”,).读文本属性(“outerText”,)
此时的临时文本就是网页的源码如果你要HTML格式的就用“读文本属性(“outerhtml”,)”
问题四:易语言怎么取网页代码中的一段代码!分高级答案:
首先创建子程序---取中间文件内容---------------------------------------------------------------------
.版本2
.子程序取文本中间内容,文本型,公开
.参数需取文本,文本型
.参数左边内容,文本型
.参数右边内容,文本型
.参数成功与否,逻辑型,参考可空
.局部变量长度,整数型
.局部变量左边位置,整数型
.局部变量左边长度,整数型
.局部变量右边位置,整数型
.局部变量右边长度,整数型
长度=取文本长度(需取文本)
左边位置=寻找文本(需取文本,左边内容,0,假)
左边长度=取文本长度(左边内容)
.如果真(左边位置=-1)
成功与否=假
返回(“未找到左边内容”)
.如果真结束
右边位置=寻找文本(需取文本,右边内容,0,假)
右边长度=取文本长度(右边内容)
.如果真(右边位置=-1)
成功与否=假
返回(“未找到右边内容”)
.如果真结束
成功与否=真
返回(取文本中间(需取文本,左边位置+左边长度,长度-(左边位置+左边长度)-(长度-右边位置)))
--------------------创建完毕-------------------------厂------------------------------------------------------------
新建个编辑框1.然后在建个按钮,在按钮里输入代码
编辑框1.内容=取文本中间内容(zxcvbnmasfhyf,zxcvbnm,asfhyf)
点击这个按钮,编辑框1的内容就是
概要:
问题五:易语言取网页源码可以实时获取超文本浏览框里的源码,和查看源代码一样。
问题六:易语言这个取网页源码如何取文本中间?有图到整数删除了就可以了
问题七:易语言如何读取网页源文件的代码!.版本2
.支持库internet
.子程序__启动窗口_创建完毕
_启动窗口.标题=到文本(HTTP读文件(“你要打开的斗地主游戏源码下载t攻t地址。txt”))
问题八:求易语言多线程提取网页源码的例子这是一个最简单的多线程网页访问保存网页的程序,如果满意采纳哦
问题九:易语言取网页源码中的元素内容,应该关于精易模块分你要的只是取这个span里的内容吗?
用了精易模块的话
你可以用文本_取出中间文本()命令来获取,命令格式如下:
文本_取出中间文本(原文本,目标文本左边,目标文本右边)
原文本这里就可以是你读取出来的网页源码
目标文本左边,在你的里,就可以是
目标文本右边,在你的里,就是
问题十:易语言取网页指定内容源码要方便快捷的话,就用精易模块里面的取出中间文本(),不想用模块的话就用分割文本。建议你用精易模块的文本_取中间文本()
易语言源码怎么写问题一:易语言这个源码要怎么写帮忙写下分逐个判断
也就是
判断(编辑框1.内容=“0”)
判断(编辑框1.内容=“1”)
这样逐个判断
问题二:易语言的源码怎么写?易语言编程系统《全书PDF》gz/viewthread.php?tid=
问题三:易语言压缩文件源码怎么写此例子中用到的控件是(按钮1)(按钮2)(zip压缩1)
例子是将运行目录下的“1.ini”压缩成.ZIP文件,再讲ZIP文件解压到文件夹
代码如下:(效果如例子图)
.版本2.支持库epress.子程序_按钮1_被单击ZIP压缩1.压缩(取运行目录()+“\1.ini”,“压缩文件.zip”).子程序_按钮2_被单击ZIP压缩1.解压(取运行目录()+“\压缩文件.zip”,“解压开的文件夹”)
问题四:易语言怎么写运行某个程序的代码?这个很基础哦。。
添加一个按钮标题就叫腾迅QQ把源码复制进去就行啦,当然了,路径要看你自己QQ的安装目录啦。
我这样写最简单,也可以说是简陋啦。不过是可行的
.版本2
.支持库eAPI
.程序集窗口程序集1
.子程序_按钮QQ_被单击
.如果(按钮QQ.标题=“腾迅QQ”)
运行(“D:\ProgramFiles\Tencent\QQ\Bin\QQ.exe”,假,)
按钮QQ.标题=“QQ运行中”
.否则
终止进程(“QQ.exe”)
.如果结束
问题五:易语言发送邮件源码怎么写啊已经加了,顺便告诉你,QQ邮箱没办法收到邮件
写一组代码给你吧,新浪邮箱比较稳定,开启SMTP服务
.版本2
.支持库internet
连接发信服务器(“***tp.sina”,,“[emailprotected]”,“密码我就不填了”,)
发送邮件(“SB送号来了”,“QQ帐号:”+编辑框1.内容+#换行符+“QQ密码:”+编辑框2.内容,“[emailprotected]”,,,“[emailprotected]”,)
新浪邮箱开启SMTP服务在设置――账户倒数第一二行里面开启
问题六:易语言怎么写载入窗口的源码?先插入一个新的窗口,在启动窗口上弄个按钮,双击按就会跳转到窗口程序集。在窗口程序集里输入命令。java开发crm系统源码
.版本2
.子程序_按钮1_被单击
载入(窗口1,,真)注:窗口1是你刚刚新插入的窗口。
问题七:易语言发邮件代码怎么写给你个QQ发信的例子.版本2
.支持库internet连接发信服务器(“***tp.qq”,,“QQ号”,“密码”,)
发送邮件(“邮件主题”,“邮件正文”,“收件人邮件地址”,“抄送邮件地址”,“暗送邮件地址”,“发信人邮件地址”,“回复邮件地址”)
注意:收信人要在邮箱――设置――帐户――POP3/IMAP/SMTP服务----开启POP3/SMTP服务
问题八:易语言,保存功能的源码怎么写?_选择框1_被单击
.如果真(选择框1.选中=真)
写配置项(“.\保存.ini”,“保存的数据”,“名字”,编辑框1.内容)
.如果真结束
__启动窗口_创建完毕
编辑框1.内容=读配置项(“.\保存.ini”,“保存的数据”,名字)
纯手打,代码格式有误,不要复制~
问题九:求助!易语言自动整理编辑框内容源码怎么写用分割文本命令
.版本2
.子程序_按钮1_被单击
.局部变量临时数组,文本型,,0
临时数组=分割文本(编辑框1.内容,“p:”,)
.如果真(取数组成员数(临时数组)=2)
编辑框3.内容=临时数组[2]
临时数组=分割文本(临时数组[1],“n:”,)
.如果真(取数组成员数(临时数组)=2)
编辑框2.内容=临时数组[2]
.如果真结束
.如果真结束
问题十:易语言有源码怎么写CF辅助,需要基址吗很抱歉。。你这种做法,我不会做这种。我发一下:你看看.......
.版本2.支持库eAPI
.程序集窗口程序集1
.子程序_靶子喷涂_被单击
VMP保护标记开始()
.如果(靶子喷涂.选中=真)
时钟1.时钟周期=1
.否则
时钟1.时钟周期=0
.如果结束
.子程序_时钟1_周期事件
VMP保护标记开始()内存_驱动读写1.写整数型(取进程ID(“crossfire.exe”),到十进制(“F4”),)
.子程序_撤退喷涂_被单击
VMP保护标记开始()
.如果(撤退喷涂.选中=真)
时钟2.时钟周期=1
.否则
时钟2.时钟周期=0
.如果结束
.子程序_时钟2_周期事件
VMP保护标记开始()内存_驱动读写1.写整数型(取进程ID(“crossfire.exe”),到十进制(“F4”),)
.子程序_连杀喷涂_被单击
VMP保护标记开始()
.如果(连杀喷涂.选中=真)
时钟3.时钟周期=1
.否则
时钟3.时钟周期=0
.如果结束
.子程序_时钟3_周期事件
VMP保护标记开始()内存_驱动读写1.写整数型(取进程ID(“crossfire.exe”),到十进制(“F4”),)
.子程序_奥摩初级头_被单击
VMP保护标记开始()
.如果(奥摩初级头.选中=真)
时钟4.时钟周期=1
.否则
时钟4.时钟周期=0
.如果结束
.子程序_时钟4_周期事件
VMP保护标记开始()内存_驱动读写1.写整数型(取进程ID(“crossfire.exe”),到十进制(“F4”),)
.子程序_奥摩迷彩包_被单击
VMP保护标记开始()
.如果(奥摩迷彩包.选中=真)
时钟5.时钟周期=1
.否则
时钟5.时钟周期=0
.如果结束
超详细通达信主图指标安装教程,意简言赅过程简单!有惊喜源码
安装通达信主图指标的步骤简洁明了,只需遵循以下步骤即可。
首先,找寻你心仪的主图指标公式。进入通达信软件,找到“功能”菜单,点击“公示系统”,然后选择“公式管理器”。打开公式管理器后,转至“技术指标公式”部分,选择“其他类型”,php7 源码加密点击右侧的“新建”按钮,此时将打开指标公式编辑器。
在编辑器中,输入公式名称,选择画线方法,并粘贴已选中的公式。确认无误后,点击确定按钮完成公式创建。
接下来,预览新建的指标。返回至公式管理器界面,找到新增的指标,点击右侧的“预览”按钮,预览图将随即呈现。
源码如下,供参考学习与应用。代码包含复杂的指标逻辑,用于辅助交易决策。
VAR1:=CLOSE-LOW;VAR2:=HIGH-LOW;VAR3:=CLOSE-HIGH;VAR4:=IF(HIGH>LOW,(VAR1/VAR2+VAR3/VAR2)*VOL,0);HPTP:=SUM(VAR4,)/,COLORSTICK;TKXL:=-1;XVYO:=UPNDAY(TKXL,1),NODRAW;G:=MA(C,5);D:=MA(C,);HH:=REF(H,5)=HHV(H,);LL:=REF(L,5)=LLV(L,);FG:=BACKSET(HH,6)>BACKSET(HH,5);FD:=BACKSET(LL,6)>BACKSET(LL,5);FG:=IF(BARSLAST(FG)=BARSLAST(FD) AND G>D,FG,IF(BARSLAST(FD)>BARSLAST(FG),FG,0));FD:=IF(BARSLAST(FG)=BARSLAST(FD) AND D>G,FD,IF(BARSLAST(FG)>BARSLAST(FD),FD,0));FG0:=FG AND H=HHV(H,BARSLAST(FD));FD0:=FD AND L=LLV(L,BARSLAST(FG));GQ:=L>REF(H,1) AND DAY!=REF(DAY,1);DQ:=H>=REF(H,1) AND L<=REF(L,1);BHG:=COUNT(BH0,BARSLAST(FD0));BHD:=COUNT(BH0,BARSLAST(FG0));BGQ:=COUNT(GQ,BARSLAST(FD0));BDQ:=COUNT(DQ,BARSLAST(FG0));BK0:=IF(BHG>0,BHG+2,IF(BHD,BHD+2,3));BK:=IF(BGQ,BK0-BGQ,IF(BDQ,BK0-BDQ,BK0));G1X:=(FG AND BARSLAST(FD)>BK),NODRAW;D1X:=(FD AND BARSLAST(FG)>BK),NODRAW;G1:=(FG0 AND REF(H,BARSLAST(FG0))>=REF(H,BARSLAST(G1X)) AND BARSLAST(D1X)>BARSLAST(G1X))OR (FG1 AND COUNT(GQ,BARSLAST(FD1))>0 AND REF(H,BARSLAST(FG1))>REF(H,BARSLAST(G1X)));{ W:=IF(DATE<,C,DRAWNULL);}D1:=(FD0 AND REF(L,BARSLAST(FD0))<=REF(L,BARSLAST(D1X)) AND BARSLAST(G1X)>BARSLAST(D1X))OR (FD1 AND COUNT(DQ,BARSLAST(FG1))>0 AND REF(L,BARSLAST(FD1))>REF(L,BARSLAST(G1X)));G2:=G1 AND H=HHV(G1H,BARSLAST(D1)+1) AND H>REF(H,1) AND BARSLAST(D1)>BARSLAST(G1);D2:=D1 AND L=LLV(D1L,BARSLAST(G1)+1)
为了感谢各位粉丝的支持,我承诺提供此技术与指标的免费分享。如需使用或获取全套源码,欢迎私信联系我。
对TP5数据库缓存cache的一些思考
在优化代码过程中,我偶然想起TP5中的数据库操作cache,发现其在缓存时间内能够显著提高请求速度,但修改数据后可能不能及时更新。本文旨在深入理解cache的工作原理。
然而,jsp会员管理系统源码官方文档和网络搜索结果大多仅介绍了如何使用cache,对于其原理并未详细阐述,因此我决定阅读源码以获取更多信息。
首先,我疑惑cache与常规缓存的区别。通过实验,我发现其功能与常规缓存类似,均支持设置key值、有效期及标签。cache方法在设置属性后,真正的操作在select、find、value、column等方法中。
结论显而易见:不论是写入还是读取cache,其过程与常规缓存相同。不同之处在于,若未指定key名,系统会根据操作的数据库名、表名及主键ID自动生成密文key,避免了key重复可能导致的缓存覆盖问题。
其次,我关注数据更新时cache的处理。文档提到两种方法:手动在update等更新操作中添加cache以实现缓存更新;或使用find方法结合主键查询自动清理缓存。新增操作不会触及缓存,而更新操作后缓存将被清除,随后在查询时重新写入。增删改查中,新增操作不涉及缓存。
接着,我探讨了数据更新自动清除缓存的条件。文档提及两种操作均使用主键ID作为条件时,可以实现自动清除缓存,涉及缓存操作时是否使用主键查询条件的差异,共八种组合情况。
尽管还有些未测试的情况,如更新操作的数据是否为缓存数据、查询与更新操作条件是否一致等,我更倾向于深入源码探索答案。以update操作为例,结论是只有当查询与修改操作均使用主键ID作为条件时,才能实现自动清除缓存。
因此,数据库缓存并非随意使用,不当使用可能影响数据时效性和用户体验。若确需使用,建议手动设置缓存名称,并在更新操作时明确清除指定缓存。
本文总结了cache的基本原理、使用方法及注意事项。希望对您有所帮助。如有问题或讨论,欢迎访问我的博客:/blog
IM即时通讯源码搭建教程全开源
1. 选择VUE或UNIAPP技术栈进行前端开发,实现一套代码多端覆盖,包括Android、iOS和H5平台。
2. 服务器端采用PHP配合WebSocket实现实时通信功能,确保消息传输的实时性。
3. 数据库选择MySql和mongodb的组合,分别用于结构化数据和非结构化数据的存储。
4. 使用Hbuilder作为前端打包工具,简化开发流程和部署操作。
5. 在服务器部署上,推荐使用宝塔和Xshell,宝塔提供一站式管理,Xshell用于远程管理。
6. 短信接口可选择阿里云,而支付接口则可以集成支付宝。
7. 服务器配置建议:4核8GB内存,兆以上带宽,操作系统选用Linux Centos 7.6 位。
8. 开放特定端口如、和,并修改mongodb默认端口为,以确保安全和性能。
9. 安装PHP7.0时需添加fileinfo、redis、Swoole4和mongodb扩展。
. 创建站点时,设置TP伪静态,关闭跨域,删除默认文档index.php,为后续配置做准备。
. 安装mongodb时,在/www/server/mongodb/bin目录下操作,并安装ffmpeg。
. 后台管理界面可通过域名访问,初始用户名和密码分别为admin和。
. 使用Hbuilder修改接口域名,打包前端应用并上传至服务器根目录,完成基础部署和测试。
记一次源码追踪分析,从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/