‘壹’ 求个蓝奏云直连解析接口,或者是源码。
接口和源码我都有。
源码我不知道有没有用,因为没php环境,不过以前测试过是可以用的!
1,接口地址:放个图片你们自己看。
2,源码地址 :就是上面这个接口的源码
<?php
functionMloocCurl($url,$method,$ifurl,$post_data){
$UserAgent='Mozilla/5.0(WindowsNT6.1;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/63.0.3239.132Safari/537.36';#设置ua
$curl=curl_init();
curl_setopt($curl,CURLOPT_URL,$url);
curl_setopt($curl,CURLOPT_USERAGENT,$UserAgent);
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,false);
curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
if($method=="post"){
curl_setopt($curl,CURLOPT_REFERER,$ifurl);
curl_setopt($curl,CURLOPT_POST,1);
curl_setopt($curl,CURLOPT_POSTFIELDS,$post_data);
}
$response=curl_exec($curl);
curl_close($curl);
return$response;
}
if(!empty($_GET['url'])){
$url=$_GET['url'];
#第一步
$ruleMatchDetailInList="~ifr2"sname="[sS]*?"ssrc="/(.*?)"~";
preg_match($ruleMatchDetailInList,MloocCurl($url,null,null,null),$link);
$ifurl="https://www.lanzous.com/".$link[1];
#第二步
$ruleMatchDetailInList="~=s'(.*?)';[Ss]*?=s'(.*?)'[Ss]*?=s'(.*?)'[Ss]*?=s'(.*?)'~";
preg_match($ruleMatchDetailInList,MloocCurl($ifurl,null,null,null),$segment);
#第三步
#post提交的数据
$post_data=array(
"action"=>$segment[1],
"file_id"=>$segment[2],
"t"=>$segment[3],
"k"=>$segment[4]
);
$obj=json_decode(MloocCurl("https://www.lanzous.com/ajaxm.php","post",$ifurl,$post_data));#json解析
if($obj->dom==""){#判断链接是否正确
echo"链接有误!";
}else{
$downUrl=$obj->dom."/file/".$obj->url."/&type=down/";
if(!empty($_GET['type'])){
$type=$_GET['type'];
if($type=="down"){
header('Location:'.$downUrl);#直接下载
}else{
header('Location:'.$downUrl);#直接下载
}
}else{
header('Location:'.$downUrl);#直接下载
}
}
}else{
$result_url=str_replace("index.php","","//".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']."");
echo"蓝奏云直连下载";
echo"<br/>";
/*echo"直接下载:"."<ahref='".$result_url."&type=down'target='_blank'>".$result_url."&type=down</a>";
echo"<br/>";
echo"输出直链:"."<ahref='".$result_url."'target='_blank'>".$result_url."</a>";*/
}
?>
‘贰’ 源代码怎么使用
问题一:网站源码怎么使用 一般下载的网站源码,都有后台管理系统的。没必要在本地修改。
你直接去不复制上传到你的空间上去。进入后台添加文章类的管理。你想做动态的话要看你的源码支持不支持。
问题二:源码怎么使用 下个VC++6.0 或者vs2008 以上的c/c++编译器
打开那个后缀为.cpp的文件
然后编译运行连接 vc6中按ctrl+F5 vs2008中 先按F7 再按F5
就会产生.exe的可执行程序了
问题三:得到C语言源代码后怎样使用? 如果是“.cpp”文件并且有VC++的环境,可直接双击文件打开或者先打开编译环境,在新建一个控制台下的源文件,然后,选择file菜单下的open找到你的文件导入,然后编译运行;如果是其他格式的,如txt文件,也可先打开编译环境,新建一个控制台下的源文件,然后直接复制粘贴进去,然后编译运行;
便已运行的操作如图:
问题四:css代码怎么用 CSS代码主要是用来修饰HTML的 现在网站前台开发时 HTML DOM CSS javaSCRIPT 页面的哪个部分应该怎么呈现 字体多大 什么颜色 等等 都是用CSS来调试的
问题五:网上下载的源代码怎么用啊 一般来说,网上下载源代码的时候,需要先弄清楚源代码的编译环境。最理想的情况就是下载你所需编译环境的代码。当然,如果没有适合你编译环境的代码,你可以先用源代码编写时候的环境来打开,然后再把需要的代码拷贝到自己的环境下。
问题六:知道了软件的源代码,有什么用,怎么用啊? 源代码(也称源程序),是指一系列人类可读的计算机语言指令。
在现代程序语言中,源代码可以是以书籍或者磁带的形式出现,但最为常用的格式是文本文件,这种典型格式的目的是为了编译出计算机程序。计算机源代码的最终目的是将人类可读的文本翻译成为计算机可以执行的二进制指令,这种过程叫做编译,通过编译器完成。
作用 :
源代码主要功用有如下2种作用:
生成目标代码,即计算机可以识别的代码。
对软件进行说明,即对软件的编写进行说明。为数不少的初学者,甚至少数有经验的程序员都忽视软件说明的编写,因为这部分虽然不会在生成的程序中直接显示,也不参与编译。但是说明对软件的学习、分享、维护和软件复用都有巨大的好处。因此,书写软件说明在业界被认为是能创造优秀程序的良好习惯,一些公司也硬性规定必须书写。
需要指出的是,源代码的修改不能改变已经生成的目标代码。如果需要目标代码做出相应的修改,必须重新编译。
问题七:这个代码怎么用。。。 这是网络发帖的脚本,使用成功的前提是你已经用网络账号登陆,并且在某个你想发帖的贴吧有发帖权限
使用方法时,直接赋值粘贴到浏览器的地址栏回车即可。有些浏览器可能稍有不同,例如chrome中你可能需要在javascript的控制台中键入这些代码
我需要提醒你的是:你贴的这个代码的作用是把我爱你重复发表999次,因为这个行为而被封号的风险你要自己想清楚,或者你可以在其中嵌入延迟代码,当然那又是另外的事情了。
问题八:这个代码属于什么代码,要如何使用??? Calibre is a famous open-source ebook software, we you pasted here is some codes of its python version.
If all you want is to use the software, download link is below:
calibre-ebook/
If you wanna contribute to it, you may 'git' it:
git clone git:github/kovidgoyal/calibre.git
Well, to modify the source, you need to learn python or C first, of course ;)
问题九:怎么用网页中的源代码下载视频? 看是哪个网站了,如果是下载链接直接右击另存为就OK了。要是网站的源代码可能需要下载播放器
问题十:VB程序源码如何使用 你需要安装vb6.0开发软件
如果安装了,就可以直接打开源码测试了,后缀名是vbp,(就是vb的工程文件)
如果没有安装vb6.0,还想打开源码看程序,那就右击 用记事本 打开后缀名为frm的文件,他是vb的窗口文件,源码都在他里面
一般得源码是没有生成exe可执行文件的,那样文件太多,太大,浪费空间
如果你自己想生成exe可执行文件,那就vb6.0得文件菜单中把文件保存成exe可执行文件就可以了,非常简单
好运!
‘叁’ MyBatisPlus快速入门源码笔记共享,拿走吧你
为什么要学习它呢?
答:MyBatisPlus可以节省我们大量工作时间,所有的CRUD代码它都可以自动化完成!
优点:
1. 易于上手和掌握。
2. sql写在xml里,便于统一管理和优化。
3. 解除sql与程序代码的耦合。
4. 提供映射标签,支持对象与数据库的orm字段关系映射
5. 提供对象关系映射标签,支持对象关系组建维护
6. 提供xml标签,支持编写动态sql。
MyBatis Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
特性:
无侵入 :只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
损耗小 :启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 强大的 CRUD 操作 :内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 支持 Lambda 形式调用 :通过 Lambda 表达式,方便地编写各类查询条件,无需再担心字段写错。
支持组件自动生成 :支持多达 4 种主键策略(内含分布式唯一 ID 生成器 Sequence),可自由配置,完美解决主键问题。
支持 ActiveRecord 模式 :支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作。
支持自定义全局通用操作 :支持全局通用方法注入( Write once, use anywhere )。
内置代码生成器 :采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用 内置分页插件 :基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List查询。
分页插件支持多种数据库 :支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库。
内置性能分析插件 :可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询。
内置全局拦截插件 :提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作。
1.1 、引入mybatis-plus相关maven依赖
引入mybatis-plus在spring boot中的场景启动器
ps:切记不可再在pom.xml文件中引入mybatis与mybatis-spring的maven依赖,这一点,mybatis-plus的官方文档中已经说明的很清楚了.
1.2、创建数据表
(1)SQL语句
(2) 数据表结构
1.3、 创建java bean
根据数据表新建相关实体类
1 package com.example.demo.pojo
1.4、 配置application.proprties
数据源使用druid
ps:在进行crud实验之前,简单对mybatis与mybatis-plus做一个简单的对比
2.1、mybatis与mybatis-plus实现方式对比
(1)提出问题: 假设我们已存在一张 tbl_employee 表,且已有对应的实体类 Employee,实现 tbl_employee 表的 CRUD操作我们需要做什么呢?
(2)实现方式: 基于 Mybatis 需要编写 EmployeeMapper 接口,并手动编写 CRUD 方法 提供 EmployeeMapper.xml 映射文件,并手动编写每个方法对应的 SQL 语句. 基于 Mybatis-plus 只需要创建 EmployeeMapper 接口, 并继承BaseMapper 接口.这就是使用 mybatis-plus 需要完成的所有操作,甚至不需要创建 SQL 映射文件。
2.2、BaseMapper接口介绍
(1)如何理解核心接口BaseMapper?
在使用Mybatis-Plus时,核心操作类是BaseMapper接口,其最终也是利用的Mybatis接口编程的实现机制,其默认提供了一系列的增删改查的基础方法,并且开发人员对于这些基础操作不需要写SQL进行处理操作(Mybatis提供的机制就是需要开发人员在mapper.xml中提供sql语句),那样我们可以猜测肯定是Mybatis-Plus完成了BaseMapper接口提供的方法的SQL语句的生成操作。
(2)BaseMapper接口为我们定义了哪些方法?
BaseMapper接口源码:
(3) mybatis-plus中常用的注解 1
由于我们的数据表名于实体类的类名不一致,并且实体类于数据表还存在字段名不对应的情况,因此我们需要引入mybatis-plus的注解.
编写EmployeeMapper接口继承BaseMapper接口
准备考试环境:
(1)插入
(2)修改
控制台打印出的sql语句
如果我们不设置实体类的email与gender属性,结果是怎样的呢?
控制台sql语句:
显然,mybatis-plus为我们做了非空判断,空值的话,默认不更新对应的字段.想一想,这是不是类似于mybatis中的动态sql呢?
这种处理效果又会带来什么好处呢?
(3)查询
selectById方法
selectBatchIds方法
ps:发现该方法底层使用的竟然是sql的in关键字
selectByMap方法
(4)删除
3、不得不提的条件构造器---Wrapper
3.1.wrapper及其子类介绍
(1)Wrapper :条件构造抽象类,最顶端父类,抽象类中提供3个方法以及其他方法.
‘肆’ 如何高效阅读源代码
下面是之前写的一篇文章:《如何快速阅读源码》
本文探讨在需要了解一个开源项目时,如何快速的理清开源项目的代码逻辑!
以下是个人认为行之有效的方法:
本文以Mybatis为例来进行演示!
先“跑起来”程序界有个老传统,学习新技术时都是从“Hello World”开始的!无论是学习新语言时,打印“Hello World”;还是学习新框架时编写个demo!那为什么这里的“跑起来”要打个引号呢?
实际上,当你想要阅读一个开源项目的源码时,绝大部分情况下,你已经能够使用这个开源项目了!所以这里的“跑起来”就不是写个“Hello World”,也不是能跑起来的程序了!而是能__在你的脑子里“跑起来”__!什么意思?
Mybatis你会用了吧?那么请问Mybatis是如何执行的呢?仔细想想,你能否用完整的语句把它描述出来?
这里是Mybatis的官方入门文章!你是如何看这篇文章的?读一遍就行了吗?还是跟着文章跑一遍就够了吗?从这篇文章里你能获得多少信息?
我们来理一下:
回答出了上面这些问题!你也就基本能在脑子里把Mybatis“跑起来”了!之后,你才能正真的开始阅读源码!
当你能把一个开源项目“跑起来”后,实际上你就有了对开源项目最初步的了解了!就像“ 书的索引 ”一样!基于这个索引,我们一步步的进行拆解,来细化出下一层的结构和流程,期间可能需要深入技术细节,考量实现,考虑是否有更好的实现方案!也就是说后面的三步并不是线性的,而是__不断交替执行__的一个过程!最终就形成一个完整的源码执行流程!
自顶向下拆解继续通过Mybatis来演示(限于篇幅,我只演示一个大概流程)!我们现在已经有了一个大概的流程了:
虽说每个点都可以往下细化,但是也分个轻重缓急!
很明显,SqlSession去执行 sql才是Mybatis的核心!我们先从这个点入手!
首先,你当然得先下载Mybatis的源码了(请自行下载)!
我们直接去看SqlSession!它是个接口,里面有一堆执行sql的方法!
这里只列出了一部分方法:
SqlSession就是通过这些方法来执行sql的!我们直接看我们常用的,也是Mybatis推荐的用法,就是基于Mapper的执行!也就是说“SqlSession通过Mapper来执行具体的sql”!上面的流程也就细化成了:
那SqlSession是如何获取Mapper的呢?Mapper又是如何执行sql的呢?
深入细节我们来看SqlSession的实现!SqlSession有两个实现类SqlSessionManager和DefaultSqlSession!通过IDE的引用功能可以查看两个类的使用情况。你会发现SqlSessionManager实际并没有使用!而DefaultSqlSession是通过DefaultSqlSessionFactory构建的!所以我们来看DefaultSqlSession是如何构建Mapper的!
它直接委托给了Configuration的getMapper方法!
Configuration又委托给了MapperRegistry类的getMapper方法!
在MapperRegistry类的getMapper中:
在这里knowMappers是什么?MapperProxyFactory又是什么?mapperProxyFactory.newInstance(sqlSession)具体做了什么?
其实很简单,knowMappers是个Map,里面包含了class与对应的MapperProxyFactory的对应关系!MapperProxyFactory通过newInstance来构建对应的Mapper(实际上是Mapper的代理)!
快接近真相了,看mapperProxyFactory.newInstance(sqlSession)里的代码:
这里干了什么?
最终实际还是委托给了sqlSession去执行具体的sql!后面具体怎么实现的就自行查看吧!
延伸改进现在我们的流程大概是这样的一个过程:
现在我们大概知道了:
那么,
这个问题列表可以很长,可以按个人需要去思考并尝试回答!可能最终这些问题已经和开源项目本身没有什么关系了!但是你思考后的收获要比看源码本身要多得多!
再循环一轮结束后,可以再次进行:
不断的拆解->深入->改进,最终你能__通过一个开源项目,学习到远比开源项目本身多得多的知识__!
最重要的是,你的流程是完整的。无论是最初的大致流程:
还是到最终深入的细枝末节,都是个完整的流程!
这样的好处是,你的时间能自由控制:
而不像debug那样的方式,需要一下子花费很长的时间去一步步的理流程,费时费力、收效很小,而且如果中断了就很难继续了!
总结本文通过梳理Mybatis源码的一个简单流程,来讲述一个个人认为比较好的阅读源码的方式,并阐述此方法与传统debug方式相比的优势。
阅读源码是每个优秀开发工程师的必经之路,那么这篇文章就来讲解下为什么要阅读源码以及如何阅读源码。
首先来说下为什么要读源码,有学习源码的必要吗?
为什么要阅读源码?
关于为什么阅读和学习源码,我个人认为可能有以下几点:
(一)吊打面试官,应对面试
为了找到更好的工作,应对面试,因为在面试中肯定会问到源码级别的问题,比如:为什么 HashMap 是线程不安全的?
如果你没有阅读过源码,面试官可能会对回答的结果不满意,进而导致面试结果不太理想,但如果你对源码有所研究,并能够很好地问答面试官的问题,这可能就是你的加分点,可以形成自己独特的竞争力,吊打面试官,升职加薪不是梦。
(二)解决问题(bug)
在开发过程中,我们或多或少会遇到 bug,比如:在 foreach 循环里进行元素的 remove/add 操作,为啥有可能会报 异常?
我们可以先在 Google、Stack Overflow 以及对应项目的 Issues 里看有没有类似问题以及解决办法,如果没有的话,我们只能通过阅读源码的方式去解决了。如果我们对相关源码有所涉猎,就可以快速定位到问题所在。
(三)提升编程能力
和阅读一本好书一样,阅读源码就是和编程大牛面对面交流的机会,在许多优秀的开源项目中,它们的编码规范和架构设计都是很棒的,另外在设计上也使用了大量的设计模式,通过阅读和学习源码,能够快速提升我们的编码水平,以及对设计模式有更深的理解。
同时,在我们阅读完一个源码后,可以触类旁通,能够快速地对其他框架的源码进行阅读和学习,减少时间成本。
除了上述提到的原因之外,可能还有许多,在这里就不一一赘述了,那么在确定了要阅读源码之后,就让我们看下如何阅读源码吧!
如何阅读源码?
如何阅读源码取决于你为什么要读源码,比如:
下面大概说下阅读源码的几点建议:
在阅读之前,可以先从开源项目的官网上看它的架构设计和功能文档,了解这个项目的 整体架构、模块组成以及各个模块之间的联系 。
如果没有对应的项目文档,可以根据代码的模块进行梳理,以形成对项目的初步了解,或者 查看已有的源码解析文章或者书籍 ,在阅读源码之前,了解项目的架构和思路会使阅读源码事半功倍。
在了解一个类的时候,可以使用 ctrl+F12 来查看类中的成员变量和方法。
可以通过 IDEA 的 Diagrams 功能去了解一个类的继承关系。
多打 断点调试 ,断点追踪源码是很好的阅读源码的方式,可以先通过 debug 了解下调用逻辑,都和哪些类有关联,有大致了解后再通过 debug 了解整体代码的功能实现,各个类都起到了什么作用,有没有涉及到设计模式等。
另外,优秀的开源项目中肯定会有许多地方应用到了 设计模式 ,建议在阅读源码之前,需要对常用的设计模式有大致的了解,不然阅读源码的效率会大大降低。
如果遇到读不懂某部分源码的时候,可以先跳过,之后再回来看,如果属于搞不懂这部分就茶不思饭不想的人,可以在网上找是否有该部分源码的解析或者文档,也可以自己通过 源码注释和测试用例 去阅读学习。
一般优秀的开源项目都会有 单元测试 ,可以通过对应类的单元测试去了解方法的含义和用法,加深对源码逻辑的理解。
在阅读源码的时候,可以在代码上加上 注释和总结 ,同时还可以画出 时序图和类图 ,这样对阅读源码有很大的帮助,可以很清楚地知道类之间的调用关系和依赖关系,也方便以后回顾,重新阅读。
在这里推荐大家一个 IDEA 插件 SequenceDiagram,可以根据源码生成调用时序图,便于阅读源码。
刚开始阅读源码,不建议直接看框架源码,可以先从 jdk 源码看起:
jdk 源码也是非常庞大的,可以分模块来阅读,下面是建议的阅读顺序:
其他包下的代码也可以做下了解,JDK源码阅读笔记:https://github.com/wupeixuan/JDKSourceCode1.8
再有了一定的源码阅读经验后,可以再去学习 Spring、Spring Boot、Dubbo、Spring Cloud 等框架的源码。
总结主要介绍了为什么读源码以及如何读源码,供大家参考,每个人都有适合自己的阅读源码的方式,希望可以在学习中去摸索出一套属于自己的方式。
阅读源码不是一蹴而就的,这是持久战,只要你能够坚持下来,肯定受益匪浅。阅读源码的过程比较枯燥,可以在社群里一起讨论学习,这样可能效率更高些。
没看过源代码,都不好意思出来说了,最近刚好在看一些,来说一个。
先看使用 https://element.eleme.cn/#/zh-CN/component/installation
先看一下这个库是做什么用的,然后提供了哪些功能。
看GitHub https://github.com/elemefe
一般会看下项目最新的情况,然后没有关闭的issue,看下wiki,大家在讨论什么。
再看代码
clone 一份到本地,然后先看下目录结构,然后根据文档看几个简单的组件的时候,一边看掘金上的分析,一边自己看下实现。
e le
饿了么这个框架代码结构还是很清楚的,基本上每个组件都是分开的,所以你只要看其他的一个文件夹就行。然后一些工具的都在src文件夹。
要学会看issue,一般开源的项目都有人会来提建议,有些是bug,有些是功能,你可以看看自己是否有能力去解决,如果可以的话,你可以去fork代码,然后自己修改,再提pr。
我最近恰好找摸索出一个梳理遗留系统架构的技巧:自底向上 找到一个典型的切面 沿着调用和回调的路径 在代码中添加结构化注释(比如eclipse中加//TAG 流程A1.1 甲->>乙),这样便得到了一个code地图,并且在tasks视图中看起来很直观(看起来跟书的目录一样)可快速跳转。将目录到有道云笔记的markdown序列图中 就自动生成了一个序列图。
我觉得这基本上就是可缩放的可视化架构地图了,对维护一个比较乱和庞大的遗留系统非常有帮助,定位代码 修改维护都方便多了。
1、需要过硬的基础知识,这个前提。不然基本语法、常用的模式都不晓得怎么读。
2、多参考 历史 版本和更新变化,好的源码都是反复迭代出来的精华,开始就读精华是很不明智的,所以看看版本更新说明,版本的 历史 演变。就想人一样是怎样进化过来的。
3、参考别人阅读注释,想必在你读源码之前也有人读过了源码,并且总结,注释。和分享原理,可供你参考,毕竟每个人读一篇文章,理解的东西是有差异化的。
4、直接买书,有些作品直接出书就是源码精解
5、找个大神给你慢慢分析,这个最快。娓娓道来,直接面授比啥都强。缺点是,你容易跟着他的思维走下去。
我觉得阅读代码就不应该高效,而应该像看小说一样,看的过程就像是在和作者交流,有趣才是看代码的动力。
画图,看数据走向,逻辑走向
先弄清楚这些代码实现了哪些功能,然后从主线开始往下看,好的代码光看变量和接口名称就能明白是什么意思?扒出源码实现的整体框架逻辑,然后再对自己感兴趣的模块进行剖析,还是从整体把握,细节深入,慢慢地整个框架就被丰满了。
接下来是思考为什么要如此设计,这样设计的好处是什么?如果是你来做应该怎么设计,把你觉得源码缺点的地方进行仔细研究,了解里面是否包含自己不清楚的细节,避免遗漏。
接下来就是根据代码改造或者是调试错误,对于源码中遇到的不理解的地方一定要弄明白,有的确实是画蛇添足,有的有独特的作用。
多多学习,对每一种主流框架铭记于心,对主流设计模式了如指掌,万变不离其宗,源码看多了,跟看一个电视机遥控器的操作说明一样。
1、一边阅读代码一边写注释。这是我用过的最好的方法,对代码理解得更深入,看一些重要代码或者特别难懂的代码时挺有用。更何况,注释也是一种文档嘛。
2、一边阅读代码一边绘制UML。这个方法适用于类之间的关系较复杂和调用层次较深的情况,我一般都是先绘制顺序图,然后为顺序图中的类绘制关系图。
3、通过Debug来跟踪程序的主要执行过程,这样就可以分清主次了,阅读的时候更有针对性。
4、类的快速阅读。先弄清楚它在继承链中的位置,看看它的内部状态,也就是成员变量,一般来说,类的对外接口都是对成员变量的访问、加工、代理等,然后看看它的对外接口,也就是公有成员函数,识别核心的一个或多个函数,这时候你应该可以大概了解这个类的职责或作用了。可能这个类是某个设计模式中的一个组成部分,所以,设计模式的掌握对代码的快速阅读也是很有帮助的。
5、带着问题去阅读。比如想了解android中的消息机制,那么看看Looper、Handler、MessegeQueue这几个类就可以了,其他的不要去看,要不然就跑题了。
下面列几个阅读源码时所处的情景,在特定场景下用哪些方法: 不太熟悉业务逻辑,还不是很清楚它是干啥的,可以用3、5。 代码量很大,有几十万行,甚至百万行,可以用2、3、5。 你无法看见程序的运行过程,比如没有用户界面,也有可能是无法运行的,可以用3、5。 设计复杂,用了大量的设计模式,调用链很深,可以用1、2、3、4、5。 时间有限,没有那么多时间让你看源码,可以用3、5。
画出逻辑流程图,先了解整体流程,再详解具体函数
‘伍’ “高并发”两种异步模型与深度解析Future接口-
大家好,我是冰河~~
本文有点长,但是满满的干货,以实际案例的形式分析了两种异步模型,并从源码角度深度解析Future接口和FutureTask类,希望大家踏下心来,打开你的IDE,跟着文章看源码,相信你一定收获不小!
在Java的并发编程中,大体上会分为两种异步编程模型,一类是直接以异步的形式来并行运行其他的任务,不需要返回任务的结果数据。一类是以异步的形式运行其他任务,需要返回结果。
1.无返回结果的异步模型
无返回结果的异步任务,可以直接将任务丢进线程或线程池中运行,此时,无法直接获得任务的执行结果数据,一种方式是可以使用回调方法来获取任务的运行结果。
具体的方案是:定义一个回调接口,并在接口中定义接收任务结果数据的方法,具体逻辑在回调接口的实现类中完成。将回调接口与任务参数一同放进线程或线程池中运行,任务运行后调用接口方法,执行回调接口实现类中的逻辑来处理结果数据。这里,给出一个简单的示例供参考。
便于接口的通用型,这里为回调接口定义了泛型。
回调接口的实现类主要用来对任务的返回结果进行相应的业务处理,这里,为了方便演示,只是将结果数据返回。大家需要根据具体的业务场景来做相应的分析和处理。
任务的执行类是具体执行任务的类,实现Runnable接口,在此类中定义一个回调接口类型的成员变量和一个String类型的任务参数(模拟任务的参数),并在构造方法中注入回调接口和任务参数。在run方法中执行任务,任务完成后将任务的结果数据封装成TaskResult对象,调用回调接口的方法将TaskResult对象传递到回调方法中。
到这里,整个大的框架算是完成了,接下来,就是测试看能否获取到异步任务的结果了。
在测试类中,使用Thread类创建一个新的线程,并启动线程运行任务。运行程序最终的接口数据如下所示。
大家可以细细品味下这种获取异步结果的方式。这里,只是简单的使用了Thread类来创建并启动线程,也可以使用线程池的方式实现。大家可自行实现以线程池的方式通过回调接口获取异步结果。
2.有返回结果的异步模型
尽管使用回调接口能够获取异步任务的结果,但是这种方式使用起来略显复杂。在JDK中提供了可以直接返回异步结果的处理方案。最常用的就是使用Future接口或者其实现类FutureTask来接收任务的返回结果。
使用Future接口往往配合线程池来获取异步执行结果,如下所示。
运行结果如下所示。
FutureTask类既可以结合Thread类使用也可以结合线程池使用,接下来,就看下这两种使用方式。
结合Thread类的使用示例如下所示。
运行结果如下所示。
结合线程池的使用示例如下。
运行结果如下所示。
可以看到使用Future接口或者FutureTask类来获取异步结果比使用回调接口获取异步结果简单多了。注意:实现异步的方式很多,这里只是用多线程举例。
接下来,就深入分析下Future接口。
1.Future接口
Future是JDK1.5新增的异步编程接口,其源代码如下所示。
可以看到,在Future接口中,总共定义了5个抽象方法。接下来,就分别介绍下这5个方法的含义。
取消任务的执行,接收一个boolean类型的参数,成功取消任务,则返回true,否则返回false。当任务已经完成,已经结束或者因其他原因不能取消时,方法会返回false,表示任务取消失败。当任务未启动调用了此方法,并且结果返回true(取消成功),则当前任务不再运行。如果任务已经启动,会根据当前传递的boolean类型的参数来决定是否中断当前运行的线程来取消当前运行的任务。
判断任务在完成之前是否被取消,如果在任务完成之前被取消,则返回true;否则,返回false。
这里需要注意一个细节:只有任务未启动,或者在完成之前被取消,才会返回true,表示任务已经被成功取消。其他情况都会返回false。
判断任务是否已经完成,如果任务正常结束、抛出异常退出、被取消,都会返回true,表示任务已经完成。
当任务完成时,直接返回任务的结果数据;当任务未完成时,等待任务完成并返回任务的结果数据。
当任务完成时,直接返回任务的结果数据;当任务未完成时,等待任务完成,并设置了超时等待时间。在超时时间内任务完成,则返回结果;否则,抛出TimeoutException异常。
2.RunnableFuture接口
Future接口有一个重要的子接口,那就是RunnableFuture接口,RunnableFuture接口不但继承了Future接口,而且继承了java.lang.Runnable接口,其源代码如下所示。
这里,问一下,RunnableFuture接口中有几个抽象方法?想好了再说!哈哈哈。。。
这个接口比较简单run()方法就是运行任务时调用的方法。
3.FutureTask类
FutureTask类是RunnableFuture接口的一个非常重要的实现类,它实现了RunnableFuture接口、Future接口和Runnable接口的所有方法。FutureTask类的源代码比较多,这个就不粘贴了,大家自行到java.util.concurrent下查看。
(1)FutureTask类中的变量与常量
在FutureTask类中首先定义了一个状态变量state,这个变量使用了volatile关键字修饰,这里,大家只需要知道volatile关键字通过内存屏障和禁止重排序优化来实现线程安全,后续会单独深度分析volatile关键字是如何保证线程安全的。紧接着,定义了几个任务运行时的状态常量,如下所示。
其中,代码注释中给出了几个可能的状态变更流程,如下所示。
接下来,定义了其他几个成员变量,如下所示。
又看到我们所熟悉的Callable接口了,Callable接口那肯定就是用来调用call()方法执行具体任务了。
看一下WaitNode类的定义,如下所示。
可以看到,WaitNode类是FutureTask类的静态内部类,类中定义了一个Thread成员变量和指向下一个WaitNode节点的引用。其中通过构造方法将thread变量设置为当前线程。
(2)构造方法
接下来,是FutureTask的两个构造方法,比较简单,如下所示。
(3)是否取消与完成方法
继续向下看源码,看到一个任务是否取消的方法,和一个任务是否完成的方法,如下所示。
这两方法中,都是通过判断任务的状态来判定任务是否已取消和已完成的。为啥会这样判断呢?再次查看FutureTask类中定义的状态常量发现,其常量的定义是有规律的,并不是随意定义的。其中,大于或者等于CANCELLED的常量为CANCELLED、INTERRUPTING和INTERRUPTED,这三个状态均可以表示线程已经被取消。当状态不等于NEW时,可以表示任务已经完成。
通过这里,大家可以学到一点:以后在编码过程中,要按照规律来定义自己使用的状态,尤其是涉及到业务中有频繁的状态变更的操作,有规律的状态可使业务处理变得事半功倍,这也是通过看别人的源码设计能够学到的,这里,建议大家还是多看别人写的优秀的开源框架的源码。
(4)取消方法
我们继续向下看源码,接下来,看到的是cancel(boolean)方法,如下所示。
接下来,拆解cancel(boolean)方法。在cancel(boolean)方法中,首先判断任务的状态和CAS的操作结果,如果任务的状态不等于NEW或者CAS的操作返回false,则直接返回false,表示任务取消失败。如下所示。
接下来,在try代码块中,首先判断是否可以中断当前任务所在的线程来取消任务的运行。如果可以中断当前任务所在的线程,则以一个Thread临时变量来指向运行任务的线程,当指向的变量不为空时,调用线程对象的interrupt()方法来中断线程的运行,最后将线程标记为被中断的状态。如下所示。
这里,发现变更任务状态使用的是UNSAFE.putOrderedInt()方法,这个方法是个什么鬼呢?点进去看一下,如下所示。
可以看到,又是一个本地方法,嘿嘿,这里先不管它,后续文章会详解这些方法的作用。
接下来,cancel(boolean)方法会进入finally代码块,如下所示。
可以看到在finallly代码块中调用了finishCompletion()方法,顾名思义,finishCompletion()方法表示结束任务的运行,接下来看看它是如何实现的。点到finishCompletion()方法中看一下,如下所示。
在finishCompletion()方法中,首先定义一个for循环,循环终止因子为waiters为null,在循环中,判断CAS操作是否成功,如果成功进行if条件中的逻辑。首先,定义一个for自旋循环,在自旋循环体中,唤醒WaitNode堆栈中的线程,使其运行完成。当WaitNode堆栈中的线程运行完成后,通过break退出外层for循环。接下来调用done()方法。done()方法又是个什么鬼呢?点进去看一下,如下所示。
可以看到,done()方法是一个空的方法体,交由子类来实现具体的业务逻辑。
当我们的具体业务中,需要在取消任务时,执行一些额外的业务逻辑,可以在子类中覆写done()方法的实现。
(5)get()方法
继续向下看FutureTask类的代码,FutureTask类中实现了两个get()方法,如下所示。
没参数的get()方法为当任务未运行完成时,会阻塞,直到返回任务结果。有参数的get()方法为当任务未运行完成,并且等待时间超出了超时时间,会TimeoutException异常。
两个get()方法的主要逻辑差不多,一个没有超时设置,一个有超时设置,这里说一下主要逻辑。判断任务的当前状态是否小于或者等于COMPLETING,也就是说,任务是NEW状态或者COMPLETING,调用awaitDone()方法,看下awaitDone()方法的实现,如下所示。
接下来,拆解awaitDone()方法。在awaitDone()方法中,最重要的就是for自旋循环,在循环中首先判断当前线程是否被中断,如果已经被中断,则调用removeWaiter()将当前线程从堆栈中移除,并且抛出InterruptedException异常,如下所示。
接下来,判断任务的当前状态是否完成,如果完成,并且堆栈句柄不为空,则将堆栈中的当前线程设置为空,返回当前任务的状态,如下所示。
当任务的状态为COMPLETING时,使当前线程让出CPU资源,如下所示。
如果堆栈为空,则创建堆栈对象,如下所示。
如果queued变量为false,通过CAS操作为queued赋值,如果awaitDone()方法传递的timed参数为true,则计算超时时间,当时间已超时,则在堆栈中移除当前线程并返回任务状态,如下所示。如果未超时,则重置超时时间,如下所示。
如果不满足上述的所有条件,则将当前线程设置为等待状态,如下所示。
接下来,回到get()方法中,当awaitDone()方法返回结果,或者任务的状态不满足条件时,都会调用report()方法,并将当前任务的状态传递到report()方法中,并返回结果,如下所示。
看来,这里还要看下report()方法啊,点进去看下report()方法的实现,如下所示。
可以看到,report()方法的实现比较简单,首先,将outcome数据赋值给x变量,接下来,主要是判断接收到的任务状态,如果状态为NORMAL,则将x强转为泛型类型返回;当任务的状态大于或者等于CANCELLED,也就是任务已经取消,则抛出CancellationException异常,其他情况则抛出ExecutionException异常。
至此,get()方法分析完成。注意:一定要理解get()方法的实现,因为get()方法是我们使用Future接口和FutureTask类时,使用的比较频繁的一个方法。
(6)set()方法与setException()方法
继续看FutureTask类的代码,接下来看到的是set()方法与setException()方法,如下所示。
通过源码可以看出,set()方法与setException()方法整体逻辑几乎一样,只是在设置任务状态时一个将状态设置为NORMAL,一个将状态设置为EXCEPTIONAL。
至于finishCompletion()方法,前面已经分析过。
(7)run()方法与runAndReset()方法
接下来,就是run()方法了,run()方法的源代码如下所示。
可以这么说,只要使用了Future和FutureTask,就必然会调用run()方法来运行任务,掌握run()方法的流程是非常有必要的。在run()方法中,如果当前状态不是NEW,或者CAS操作返回的结果为false,则直接返回,不再执行后续逻辑,如下所示。
接下来,在try代码块中,将成员变量callable赋值给一个临时变量c,判断临时变量不等于null,并且任务状态为NEW,则调用Callable接口的call()方法,并接收结果数据。并将ran变量设置为true。当程序抛出异常时,将接收结果的变量设置为null,ran变量设置为false,并且调用setException()方法将任务的状态设置为EXCEPTIONA。接下来,如果ran变量为true,则调用set()方法,如下所示。
接下来,程序会进入finally代码块中,如下所示。
这里,将runner设置为null,如果任务的当前状态大于或者等于INTERRUPTING,也就是线程被中断了。则调用()方法,接下来,看下()方法的实现。
可以看到,()方法的实现比较简单,当任务的状态为INTERRUPTING时,使用while()循环,条件为当前任务状态为INTERRUPTING,将当前线程占用的CPU资源释放,也就是说,当任务运行完成后,释放线程所占用的资源。
runAndReset()方法的逻辑与run()差不多,只是runAndReset()方法会在finally代码块中将任务状态重置为NEW。runAndReset()方法的源代码如下所示,就不重复说明了。
(8)removeWaiter()方法
removeWaiter()方法中主要是使用自旋循环的方式来移除WaitNode中的线程,比较简单,如下所示。
最后,在FutureTask类的最后,有如下代码。
关于这些代码的作用,会在后续深度解析CAS文章中详细说明,这里就不再探讨。
至此,关于Future接口和FutureTask类的源码就分析完了。
好了,今天就到这儿吧,我是冰河,我们下期见~~