不想做程序员了太累了一般把收货地址 放在哪个文件里面

&nbsp&nbsp &
&nbsp&nbsp
&nbsp&nbsp &
&nbsp&nbsp
前言或许你知道了jni的简单调用,其实不算什么百度谷歌一大把,虽然这些jni绝大多数情况下都不会让我们安卓工程师来弄,毕竟还是有点难,但是我们还是得打破砂锅知道为什么这样干吧,至少也让我们知道调用流程和数据类型以及处理方法,或许你会有不一样的发现。其实总的来说从java的角度来看.h文件就是java中的interface(插座),然后.c/.cpp文件呢就是实现类罢了,然后数据类型和java还是有点出入我们还是得了解下(妈蛋,天气真热不适合生存了)。今天也给出一个JNI动态注
在文章《Android程序员从小白到大神必读资料汇总(一)和(三)》里面介绍了基础学习资料和一点点的进阶资料,今天小编收集了5篇带有实例干货的资料,赶紧来看看吧!另外,喜欢写博客的博主可以申请加工程师博主交流群:,分享你的博文,和大牛们一起交流技术~一、Android内存泄漏总结内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题,本篇详细总结了如何防止内存泄露,满满的干货二、理解Android安全机制从Android系统架构着手,分析
在文章《Android程序员从小白到大神必读资料汇总(一)和(二)》里面介绍了基础学习资料和一点点的进阶资料,今天小编收集了5篇带有实例干货的资料,赶紧来看看吧!另外,喜欢写博客的博主可以申请加工程师博主交流群:,分享你的博文,和大牛们一起交流技术~一、GitHub优秀的Android开源项目网上最火的Android开源项目介绍文章,感谢博主的分享二、Android瀑布流实例Github上一个实例,实现了类似于迷尚android和蘑菇街android的瀑布流
原文前言骚年,你听说过组件化吗?没有?但你一定玩过乐高玩具,乐高玩具本身一个庞大的模型却是依靠无数个各自分工的基础模块构建而成!同理,随着项业务的增长,项目也必然越来越庞大。此时,为了更好地管理我们的项目,我们可以适当地拆分部分模块与其相关业务逻辑。这样不仅能提高相关模块的复用性也使其单元测试变得可行等。如何进行组件化管理CocoaPods是一个很好的三方开源库管理工具,在配置和更新上为我们节省了大量的时间与降低了工作的繁琐度。CocoaPods不仅可以对公有的Git仓库进行
iOS中让cell分割线顶头在可认知的范围内,一共有四种办法。但是度娘一抄一大把,往往给出的方法都是舍近求远。很多小白也是两眼一闭,拿来就复制粘贴。以后不要再求人了,也不用在度娘了。记住下面的方法,足够应对各种场景了。1iOS8以前iOS8以前需要在controller中以下两个方法中进行约束。-(void)viewDidLoad{[superviewDidLoad];self.tableView.separatorInset=UIEdgeInsetsZself.ta
注意:后台抓取信息有误,这款软件只能在iPad上运行,抱歉给大家带来的困扰~还记得WWDC上一个美女程序员介绍一款叫「SwiftPlaygrounds」的官方软件么?这是苹果向校园的孩子们传授“Swift”语言的一个寓教于乐App。通过简单又神奇的代码,咱们也能短暂的变身“码农”啦!「SwiftPlaygrounds」只适用于iPad,不需要之前有任何编程经验,认识基本的英文单词即可。它就像以前的课本一样,我们可以通过变成来解决一些小问题,当然,如果还学有余力,软件中也有不少
假期前最后一弹,iOS开发进阶系列第五篇,希望对你能有所启发!另外,喜欢写博客的博主可以申请加工程师博主交流群:,分享你的博文,和大牛们一起交流技术~一、iOS开发中的Self-Manager模式创业公司的iOS程序员不容易啊,时常面对单挑一个项目的状况,不过这也是能快速自我提升的一种方式,做自己的self-manager二、iOS热加载之JSPatch纯技术干货,iOS热加载之JSPatch三、如何使用OAuth2.0将LinkedIn集成入iOS应用借此
----------------Author:[shikanon](https://github.com/shikanon)CreateTime:0:33:34----------------&Tensorflow1.0正式发布,谷歌首届Tensorflow开发者大会在山景召开,深度学习迎来新的高潮和狂欢。随着深度学习框架的普及和推广,会有越来越多人加入到这场盛宴中来,就像Android技术的普及使得开发人员迅速扩大。在这里給大家带来一套小白入门深
Python(英语发音:/'pa?θ?n/),是一种面向对象、解释型计算机程序设计语言,由GuidovanRossum于1989年发明,第一个公开发行版发行于1991年。Python具有丰富和强大的库。它常被昵称为胶水语言,能够把用其他语言制作的各种模块(尤其是C/C++)很轻松地联结在一起。今天小编收集了5篇关于Python的干货文章,赶紧来看看吧!一、GitHub上最受欢迎的PythonCookBooks汇总Python大法必学技术哦,挑战你的英文功底二、十分钟学会Pyt
欢迎阅读往期内容,点击下列蓝色字链接可跳转:跟我学Python从小白到入门——系列文章(更新至第五章if语句)手绘题图——心流线描在前一次课程里,我们学习了Part4操作列表内容,今天我们开始第五章的学习。请点击以下蓝色字链接,进入代码编程页面,输入文章中的代码进行练习,点击运行就可以查看运行结果。Python在线编程页面首先我们来看看,第五章我们会进入到Python的哪些新领域:下面进入今天新课的学习5.1简单示例本章学习的if语句是用来进行条件判断的,满足条件,执行某些语
你自己还是老样子,又怎能希望未来的世界有翻天覆地的变化呢?——《摩西脑图》手绘题图——禅绕画《奇艺空间》往期课程:跟我学Python从小白到入门之Part4操作列表002在前一次课程里,我们学习了以下四个内容:图片发自简书App我们继续今天的学习,请点击以下蓝色字链接,进入代码编程页面,输入代码,点击运行就可以查看运行结果。Python在线编程页面首先来回顾一下概念:列表,由一系列按特定顺序排列的元素组成,你可以创建包含字母表中所有的字母、数字0到9,或者是姓名列表,也可
packageorg.crazyit.app.importjava.util.HashMimportjava.util.MpublicclassTest{publicstaticvoidmain(String[]args){&&&&&&&&&&//&&&0&1&2&3&4Intege
文章来源:小青年原创发布时间:关键词:mui,html5+,webview转载需标注本文原始地址:http://zhaomenghuan.github.io/#!/blog/写在前面写这篇文章之前先吐吐槽,因为是学生,不想用父母辛辛苦苦挣的钱买什么苹果手机,因为确实贵,本人至今用的还是去年买的魅蓝note1(虽然已经很久了,但是没办法啊,舍不得花钱换。),前段时间项目钱收到了本来想换手机的,但是想了想还是先省一省,过几个月开始实习了再看吧!
在文章《前端开发的技术资料和鸡汤美文汇总(一)》里,向大家介绍了前端入门的基础资料,今天小编汇总了5篇前端技术进阶实操的干货,赶紧来看看吧!另外,喜欢写博客的工程师博主可以加工程师博主交流群:,分享你的博文,和大牛们一起交流技术~一、助力Web开发20个超实用CSS库在这篇文件章中作者找到了一系列对开发者有用的CSS库,它们能帮助开发者在一定的期限内取得有创造性和创新性的成果。希望这个列表能有助于您的开发并为您提供方便二、HTML5摇一摇—如何判断设备摇动刚
本章主要为大家介绍Java的一些基本语法,其中包括标识符、关键字、保留字、常量、变量、表达式等内容。4.1标识符、关键字和保留字任何一种计算机语言都离不开标识符和关键字,因此下面将详细介绍Java标识符、关键字和保留字。4.1.1标识符标识符就是变量、常量、方法、枚举、类、接口等由程序员指定的名字。构成标识符的字母均有一定的规范,Java语言中标识符的命名规则如下:区分大小写:Myname与myname是两个不同的标识符。首字符,可以是下划线(_)或美元符或字母,但不能是数字
本系列是我在学习Shiro的路上的笔记,第一篇是属于非常入门级别的。首先是介绍了下shiro,然后进行了一个小例子进行实际的操作本节操作不涉及数据库,只是文本字符操作认证Shiro简介:百度百科上的介绍:ApacheShiro(日语“堡垒(Castle)”的意思)是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障-从命令行应用、移动应用到大型网络及企业应用。Shiro为解决下列问题(我喜欢称它们为应用安全的四要素)提供了保护应用的
Java诞生到现在已经有20多年了,但是Java仍然是非常热门的编程语言之一,很多平台中使用Java开发。表1-1所示的是TIOBE社区发布的2016年5月和2017年5月的编程语言排行榜,可见Java语言的热度,或许这也是很多人选择学习Java的主要原因。表1-1TIOBE编程语言排行榜16年5月变化编程语言评级评级变化11Java14.639%-6.320%22C7.002%-6.220%33C++4.751%-1.950%45Python3.548%-
内容简介本书是一本Java语言学习教程,读者群是零基础小白,通过本书的学习能够成为Java大牛。主要内容包括:Java语法基础、数据类型、运算符、控制语句、数组、字符串、面向对象基础、继承与多态、抽象类与接口、集合框架、异常处理、输入输出和网络编程等技术。版权声明《Java从小白到大牛精简版》免费电子图书是作者关东升原创作品,作者已将该书(包括:文字、图片和源代码)进行了版权注册,版权归作者关东升所有,仅供个人研究和学习之用。任何单位或个人不得以任何方式进行出版、篡改、编辑,
0基础学java?我用4个月从小白到月薪9000元0基础学java?时间匆匆而过,在尚学堂的培训时间已经结束,难忘和老师们在一起的日子,他们都具有丰富的软件开发经验。在此次学习中,让我感受到了很多不同于以往的学习方法和教学方法。课程——不再是各科独立的,而是通过具体的研发过程把知识融会贯通,让我们既体会到课程之间的联系,也了解了各科课程在软件开发中的作用。同时也让我体会到拥有渊博的知识,不是年龄的积累而是不断的学习。尚学堂的课程模式是前期一个月的基础学习,如:IO、JDBC、
什么是原型原型是js中非常特殊一个对象,当一个函数创建之后,会随之就产生一个原型对象,当通过这个函数的构造函数创建了一个具体的对象之后,在这个具体的对象中就会有一个属性指向原型。原型的详细分析过程第一步functionPerson(){}图形分析:第一种状态以上是第一种状态:functionPerson(){},Person函数中有一个prototype的属性指向Person的原型对象,在原型对象中有一个constructor的属性指向了Person函数,所以可以通过newP
你可能还喜欢
你可能感兴趣
阿里云教程中心为您免费提供信息,所有相关内容均不代表阿里云的意见!投稿删除文章请联系邮箱:zixun-group@service.aliyun.com,工作人员会在五个工作日内答复
售前咨询热线
支持与服务
资源和社区
关注阿里云
International获取最近运行容器的id 这是我们经常会用到的一个操作,按照官方示例,你可以这样做(环境ubuntu):
这种方式在编写脚本的时候很有用,比如你想在脚本中批量获取id,然后进一步操作。但是这种方式要求你必须给ID赋值,如果是直接敲命令,这样做就不太方便了。 这时,你可以换一种方式:
docker ps -l -q命令将返回最近运行的容器的id,通过设置别名(alias),dl命令就是获取最近容器的id。这样,就无需再输入冗长的docker ps -l -q命令了。通过两个斜引号“,可以获取dl命令的值,也就是最近运行的容器的id。
尽量在Dockerfile中指定要安装的软件,而不用Docker容器的shell直接安装软件。 说实话,我有时候也喜欢在shell中安装软件,也许你也一样,喜欢在shell中把所有软件安装都搞定。但是,搞来搞去,最后还是发现,你还是需要在Doockerfile中指定安装文件。在shell中安装软件,你要这样做:
然后输入下面的命令来安装文件:
然后再调用exit:
退出docker容器,再给docker commit命令传递一个复杂的JSON字符串来提交新的镜像:
太麻烦了,不是吗?还是在Dockerfile中指定安装文件吧,只要两个步骤:
1.在一个小巧的Dockerfile中,指定当前操作的镜像为FROM命令的参数
2.然后在Dockerfile中指定一些docker的命令,如CMD, ENTERPOINT, VOLUME等等来指定安装的软件
超-超-超级用户
你可能需要一直用超级用户来操作docker,就像早期示例里一直提示的:
Wow!连续三个sudo!三次化身“超级用户”,真可谓是“超-超-超级用户”啊!别担心,设置完毕,以后你就再也不用打那么多sudo了!
如果你想删除所有停止运行的容器,用这个命令:
顺便说一句,docker ps命令很慢,不知道为啥这么慢,按理说Go语言是很快的啊。docker ps -a -q命令列出所有容器的id,然后根据id删除容器。docker rm命令遇到正在运行的容器就会失效,所以这个命令完美的删除了所有没在运行的容器。
docker inspect输出结果的解析利器:jq 要对docker inspect的输出结果进行过滤,一般情况下,用grep命令,你需要这样操作:
哦!看上去很复杂,用jq吧,专业解析docker inspect输出结果,具有更强的可读性,方便易用:
其中第一个’.’代表所有的结果。’[0]’代表数组的第一个元素。就像JavaScript访问一个JSON对象一样,简单方便。
镜像有哪些环境变量? 有时候,你需要知道自己创建的镜像有哪些环境变量。简单!只要这样:
输出结果如下:
调用env查看环境变量,对于后面要讲到的“链接”(-link)很有用,在连接两个容器时候需要用到这些环境变量,具体请看最后一个要点“链接”。
RUN命令 vs CMD命令
Docker的新手用户比较容易混淆RUN和CMD这两个命令。 RUN命令在构建(Build)Docker时执行,这时CMD命令不执行。CMD命令在RUN命令执行时才执行。我们来理清关系,假设Dockerfile内容如下:
我们要向系统中安装一些软件,那么:
Build时执行RUN,RUN时执行CMD,也就是说,CMD才是镜像最终执行的命令。
CMD命令 vs ENTRYPOINT命令
又是两条容易混淆的命令!具体细节我们就不说了,举个例子,假设一个容器的Dockerfile指定CMD命令,如下:
另一个容器的Dockerfile指定ENTRYPOINT命令,如下:
运行第一个容器:
得到的结果:
运行第二个容器:
得到的结果:
看到不同了吧?实际上,CMD命令是可覆盖的,docker run后面输入的命令与CMD指定的命令匹配时,会把CMD指定的命令替换成docker run中带的命令。而ENTRYPOINT指定的命令只是一个“入口”,docker run后面的内容会全部传给这个“入口”,而不是进行命令的替换,所以得到的结果就是“echo hello”。
Docker容器有自己的IP地址吗?
刚接触Docker的人或许会有这样的疑问:Docker容器有自己的IP地址吗?Docker容器是一个进程?还是一个虚拟机?嗯…也许两者兼具?哈哈,其实,Docker容器确实有自己的IP,就像一个具有IP的进程。只要分别在主机和Docker容器中执行查看ip的命令就知道了。
查看主机的ip:
得到结果:
查看Docker容器的ip:
得到结果:
两者并不相同,说明Docker容器有自己的ip。
基于命令行的瘦客户端,使用UNIX Socket和Docker后台服务的REST接口进行通信。Docker默认是用UNIX socket通信的,一直到大概0.5、0.6的版本还是用端口来通信,但现在则改成UNIX socket,所以从外部无法控制Docker容器的内部细节。下面我们来搞点有趣的事情,从主机链接到docker的UNIX socket:
连接成功后,输入:
输入后连敲两个回车,第二个回车表示输入结束。然后,得到的结果应该是:
有一天,我不小心把提交的名称打错了,名字开头打成”-xxx”(我把命令和选项的顺序搞混了),所以当我删除的时候出了问题,docker rm -xxx,会把-xxx当成参数而不是镜像的名称。所以我只得通过socket直接连到容器来调用REST Server把错误的东西删掉。
把镜像的依赖关系绘制成图
docker images命令有一个很拉风的选项:-viz,可以把镜像的依赖关系绘制成图并通过管道符号保存到图片文件:
这样,主机的当前路径下就生成了一张png图,然后,用python开启一个微型的HTTP服务器:
然后在别的机器上用浏览器打开:
OK,依赖关系一目了然!
(译者注:要使用dot命令,主机要安装graphviz包。另外,如果主机ip没有绑定域名,machinename换成主机的ip即可。)
Docker把东西都存到哪里去了? Docker实际上把所有东西都放到/var/lib/docker路径下了。切换成super用户,到/var/lib/docker下看看,你能学到很多有趣的东西。执行下面的命令:
可以看到不少目录,containers目录当然就是存放容器(container)了,graph目录存放镜像,文件层(file system layer)存放在graph/imageid/layer路径下,这样你就可以看看文件层里到底有哪些东西,利用这种层级结构可以清楚的看到文件层是如 何一层一层叠加起来的。
Docker源代码:Go, Go, Go, Golang! Docker的源代码全部是用Go语言写的。Go是一门非常酷的语言。其实,不只是Docker,很多优秀的软件都是用Go写的。对我来说,Docker源文件中,有4个是我非常喜欢阅读的:
commands.go docker的命令行接口,是对REST API的一个轻量级封装。Docker团队不希望在命令中出现逻辑,因此commands.go只是向REST API发送指令,确保其较小的颗粒性。
api.go REST API的路由(接受commands.go中的请求,转发到server.go)
server.go 大部分REST API的实现
buildfile.go Dockerfile的解析器
有的伙计惊叹”Wow!Docker是怎么实现的?!我无法理解!”没关系,Docker是开源软件,去看它的源代码就可以了。如果你不太清楚Dockerfile中的命令是怎么回事,直接去看buildfile.go就明白了。
运行几个Docker后台程序,再退出容器,会发生什么? OK,倒数第二个要点。如果在Docker中运行几个后台程序,再退出Docker容器,会发生什么?答案是:不要这么做!因为这样做后台程序就全丢了。
Dockerfile中用RUN命令去开启一个后台程序,如:
这样的话,RUN命令开启的后台程序就会丢失。调用容器的bash连到容器的shell:
然后调用 ps aux查看进程,你会发现postgres的进程并没有跑起来。 RUN命令会影响文件系统。因此,不要再Dockerfile中用启动后台程序,要把后台程序启动成前台进程。或者,像一些高手提议的那样,写一个启动脚 本,在脚本中启动这些后台程序或进程。
容器之间进行友好沟通:链接
这是最拉风的功能!我把它留到最后压轴!这是0.6.5中最重要的新功能,我们前面已经提过两次了。运行一个容器,给它一个名称,在下面的例子中,我们通过-name参数给容器指定名称”loldb”:
再运行另一个容器,加上-link参数来连接到第一个容器(别名为loldb),并给第二个容器也指定一个别名(这里用的是cheez):
顺便得到cheez的环境变量:
这样,我们就在两个容器间建立起一个网络通道(bridge),基于此,我们可以建立一个类似rails的程序:一个容器可以访问数据库容器而不对外暴露其他接口。非常酷!数据库容器只需要知道第一个容器的别名(在本例中为cheez)和要打开的端口号。所以数据库容器也可以env命令来查看这个端口是否打开。
欢迎工作一到五年的Java工程师朋友们加入Java架构开发:
本群提供免费的学习指导 架构资料 以及免费的解答
不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导
整理了一些MacBook使用过程中发现的吊炸天的小技巧,我不太相信你都知道。1.command+delete快速删除文件。妈妈再也不用担心我用右键-&删除这么low的技能了!2.shift+optio...
文章来源:http://www.jb51.net/article/96617.htm最近公司在试验如何将项目部署到docker容器中去,这其中涉及到一个技术环节,那就是如何让docker容器优雅的终止...
docker容器如何优雅的终止详解文章来源:http://www.jb51.net/article/96617.htm最近公司在试验如何将项目部署到docker容器中去,这其中涉及到一个技术环节,那就...
本周,我们为您准备了一份数据科学家Ben Gorman撰写的神经网络指导。这份指导包含了他具体的学习思路,包括所遇到的难点以及多种详细的解决方法。
文章不短,但是值得深读,请收藏!
...
IDE是我们最常用的工具之一。熟练运用好工具,能很大程度提到生产率。为此,本博主特意将一些遇到的实用的能极大提高生产率的小技巧记录如下。后续会不断补充更新。1.高亮匹配大括号对于c++代码或者java...
Java优雅停机实现
pdf文件是电脑办公经常要用到的,大家一定不陌生,闲话就不多说了,马上分享几个可以有效提升工作效率的pdf小技巧,一起来看看吧!
  1.PDF文件在PDF中打开时,轻松导入PS修改编辑内容
没有更多推荐了,
(window.slotbydup=window.slotbydup || []).push({
id: "5865575",
container: s,
size: "300,250",
display: "inlay-fix"&figure&&img src=&https://pic3.zhimg.com/v2-ec0e60364deeeb977679_b.jpg& data-rawwidth=&1010& data-rawheight=&308& class=&origin_image zh-lightbox-thumb& width=&1010& data-original=&https://pic3.zhimg.com/v2-ec0e60364deeeb977679_r.jpg&&&/figure&&p&
在移动客户端的测试环节中,性能测试是比较重要的一部分,相对于普通的常规测试,性能测试实施复杂、环境配置困难、测试问题场景很难复现、报告编写和结果分析对测试人员要求较高等等因素导致性能测试很难在实际的测试过程中推行。&/p&&p&
实际的测试过程中我们经常会遇见通讯异常、客户端停止运行,还有各种崩溃、卡死、页面加载一直转圈、手机运行过热、耗电严重等等问题,如下是几款典型的性能测试工具对比:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-8eb845b5bdc4f1dbe560e7_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2364& data-rawheight=&288& class=&origin_image zh-lightbox-thumb& width=&2364& data-original=&https://pic2.zhimg.com/v2-8eb845b5bdc4f1dbe560e7_r.jpg&&&/figure&&p&
可以看出上述工具的实施难度都比较高,并且在一定程度上都会依赖本地开发环境配置,在跨平台方面常规第三方工具必须添加低侵入式 SDK,生成报告又需要手动获取文件,整理文件数据手动生成各类报表等。所以考虑到性能测试的便捷性、低成本等方面的要求,尽可能在测试阶段解决上述这一类问题,于是设计了一个便捷、无门槛的性能测试工具,我会从工具的使用步骤、设计目标、问题发现、工具实现细节、自动化接入、工具扩展等方面来进行介绍。&/p&&p&&br&&/p&&h2&&b&使用步骤&/b&&br&&/h2&&p&首先简单介绍一下工具的主要步骤如下:&/p&&p&1.打开工具页面&/p&&figure&&img src=&https://pic3.zhimg.com/v2-f36dafdaba6acec7fc4ca_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&600& data-rawheight=&363& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic3.zhimg.com/v2-f36dafdaba6acec7fc4ca_r.jpg&&&/figure&&p&2.APP 扫码&br&使用 APP 内置的扫码工具扫描二维码,使 APP 内置的地址修改为测试服务器的地址,这时工具页面会自动跳转至性能数据实时查看页面。&/p&&p&3.APP 实时性能表现查看&/p&&figure&&img src=&https://pic4.zhimg.com/v2-a363abfa7d22bd3891471cbe7d580409_b.jpg& data-size=&normal& data-rawwidth=&600& data-rawheight=&116& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic4.zhimg.com/v2-a363abfa7d22bd3891471cbe7d580409_r.jpg&&&figcaption&CPU/MEM&/figcaption&&/figure&&figure&&img src=&https://pic4.zhimg.com/v2-00a0b269bdddc86_b.jpg& data-size=&normal& data-rawwidth=&600& data-rawheight=&95& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic4.zhimg.com/v2-00a0b269bdddc86_r.jpg&&&figcaption&流量上传下载&/figcaption&&/figure&&figure&&img src=&https://pic4.zhimg.com/v2-a65bbdc7ff72b91d8fabe_b.jpg& data-size=&normal& data-rawwidth=&600& data-rawheight=&93& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic4.zhimg.com/v2-a65bbdc7ff72b91d8fabe_r.jpg&&&figcaption&Java GC&/figcaption&&/figure&&figure&&img src=&https://pic2.zhimg.com/v2-fac8fd09bc83fae_b.jpg& data-size=&normal& data-rawwidth=&600& data-rawheight=&190& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic2.zhimg.com/v2-fac8fd09bc83fae_r.jpg&&&figcaption&帧率&/figcaption&&/figure&&figure&&img src=&https://pic2.zhimg.com/v2-3cd7f029ddea5e1dd109df1_b.jpg& data-size=&normal& data-rawwidth=&600& data-rawheight=&191& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic2.zhimg.com/v2-3cd7f029ddea5e1dd109df1_r.jpg&&&figcaption&内存占用&/figcaption&&/figure&&p&4.测试报告生成和留存&/p&&figure&&img src=&https://pic4.zhimg.com/v2-e35aaea1baed_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&600& data-rawheight=&282& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic4.zhimg.com/v2-e35aaea1baed_r.jpg&&&/figure&&p&完成测试之后系统会直接生成测试报告,开发、产品、测试可以用来共同分析测试过程中遇到的问题。&/p&&p&&br&&/p&&h2&&b&系统设计目标&/b&&/h2&&ol&&li&环境配置、易用性&br&
在环境配置方面、基本上不需要再依赖本地环境,由客户端内置代码和服务端通讯完成数据上传,完整的测试过程一共只有 4 个步骤即可,测试门槛几乎基本上全部消失、通过扫码 → 报告的方式,最大程度简化了使用流程,带来了极大的便捷性和易用性。&/li&&li&数据展示&br&1) 基础数据展示&br&
客户端第一次上传数据时,系统会自动识别客户端的基础信息、包括平台、客户端版本、设备型号、测试人员、测试时间等相关信息。&br&2) 性能数据展示&br&
数据展示方面使用了 bizcharts 对数据进行可视化、分别采用区域图、线图、点阵图展示最近 5 - 10 分钟的性能数据,在测试期间、系统会动态定时刷新性能数据。&/li&&li&结果分析&br&
在上面的图表中,可以看到‘流量下载’部分的图表中存在某一区域明显上升,在通常的操作过程中这种流量下载的突然增高可能会是用户使用时进行了下载、语言、视频、音乐等操作,在测试过程中实际上并没有上述操作,可以判断是由一些 API 的数据请求造成,复现几次后可以得到实际的请求 API,然后由开发人员判断是否需要针对这个 API 进行瘦身优化,减少请求流量的消耗。&/li&&li&质量报告&br&
质量报告的生成没有时间限制,也不需要进行特殊处理,在测试人员完成性能测试、或者手动结束性能测试之后,系统会自动生成性能测试结果报告的页面,并生成唯一的测试报告链接。&/li&&li&实施难度和扩展&br&
在设计开发和测试实践过程中,遇到的主要难度是:&br&
客户端数据收集上传的过程中、需要保证数据的完整,保证数据的连贯性。所以客户端数据上传时采用队列的方式存储数据,当系统出现问题、网络异常、服务器故障时把数据存入队列,在问题解决之后再将队列中的数据发送到服务端。&/li&&/ol&&p&&br&&/p&&h2&&b&实现过程和细节&/b&&/h2&&p&
性能数据获取的前提是质量团队和客户端团队对指标的获取达成共识,主要方式由客户端实现后质量团队 review 代码确认后确定数据获取方式,在数据获取方面没有做更加深入的自定义,包括一些指标如下:&/p&&ol&&li&CPU、内存、网络流量、帧率&/li&&li&Android GC 数据&/li&&li&电池、电量相关数据&/li&&li&异常、卡顿、自定义代码运行问题&/li&&/ol&&p&客户端架构设计:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-07abaf6ffb5daf2c307d9_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&681& data-rawheight=&691& class=&origin_image zh-lightbox-thumb& width=&681& data-original=&https://pic4.zhimg.com/v2-07abaf6ffb5daf2c307d9_r.jpg&&&/figure&&p&客户端采取队列方式上传数据,提供可配置的间隔控制和开关。&/p&&p&服务端数据交互&br&数据采用 base64 加密后,传输 gzip 压缩,服务接收数据解析&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&nd&&@Throws&/span&&span class=&o&&(&/span&&span class=&n&&IOException&/span&&span class=&o&&::&/span&&span class=&n&&class&/span&&span class=&o&&)&/span&
&span class=&kd&&private&/span& &span class=&n&&fun&/span& &span class=&nf&&preProcess&/span&&span class=&o&&(&/span&&span class=&n&&request&/span&&span class=&o&&:&/span& &span class=&n&&HttpServletRequest&/span&&span class=&o&&):&/span& &span class=&n&&ByteArray&/span& &span class=&o&&{&/span&
&span class=&c1&&// 获取输入流&/span&
&span class=&c1&&// 转 GZIP 输入流&/span&
&span class=&c1&&// 获取解密方式 base64 解密&/span&
&span class=&c1&&// 捕获异常并返回字节流数据&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&&br&&/p&&h2&&b&自动化性能测试接入&/b&&/h2&&p&&br&1. 通过自动化接口调用服务端生成性能测试实例&/p&&figure&&img src=&https://pic2.zhimg.com/v2-fb5dd08a821a250d09b16_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2092& data-rawheight=&710& class=&origin_image zh-lightbox-thumb& width=&2092& data-original=&https://pic2.zhimg.com/v2-fb5dd08a821a250d09b16_r.jpg&&&/figure&&p&2. 客户端自动化打开开关进行自动化性能测试&/p&&p&
客户端通过自动化脚本打开性能测试开关、同时调整性能测试服务器 url 的指向,然后执行编写完成的自动化性能测试脚本,同时上传数据并记录时间戳,当发现问题时用于核对性能数据和操作步骤。&br&3. 调用性能测试结束接口&/p&&p&
当自动化性能脚本反复执行完毕之后,达到 2 小时的标准建议或者手动调用测试结束接口,系统会结束测试不再接收后面的数据,生成测试报告同时按照规则生成报告的唯一 url 地址。&br&4. 自动获取生成的性能测试报告&/p&&p&
自动化性能测试完成之后,会拿到性能的报告地址,可以通过邮件、IM 等方式将报告发送给相关人员或进行下一步的其他测试。&/p&&p&&br&&/p&&h2&&b&扩展&/b&&/h2&&p&系统还在建设过程中,后续的扩展主要基于下图:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-059d2bfe57b0b31cf6dd1eee4b385023_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&450& data-rawheight=&410& class=&origin_image zh-lightbox-thumb& width=&450& data-original=&https://pic4.zhimg.com/v2-059d2bfe57b0b31cf6dd1eee4b385023_r.jpg&&&/figure&&figure&&img src=&https://pic1.zhimg.com/v2-db6eabd7faa4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&783& data-rawheight=&403& class=&origin_image zh-lightbox-thumb& width=&783& data-original=&https://pic1.zhimg.com/v2-db6eabd7faa4_r.jpg&&&/figure&&p&在客户端、浏览器、测试服务器之间建设一个性能测试闭环,同时在后面的性能测试过程中还会扩展关注如下一些指标:&/p&&p&1.卡顿异常(卡顿堆栈信息、异常定位数据)&/p&&p&2.过程耗时(冷启、热启、重要业务耗时)&/p&&p&3.页面切换、加载时间&/p&&p&4.内存泄漏、抖动、页面渲染(FPS)、图片/视频/语音等专项指标&/p&&p&&br&&/p&&h2&&b&团队介绍&/b&&/h2&&p&知乎的质量保障团队,属于知乎技术中台事业部, 承担了公司各产品线的质量保障工作及以下平台的设计开发工作:&/p&&ul&&li&移动端云测平台&/li&&li&性能数据分析监控报警平台&/li&&li&移动端持续集成平台&/li&&li&移动端内测及发布平台&/li&&li&知乎质量平台&/li&&li&接口自动化平台&/li&&li&...&/li&&/ul&&p&国际惯例,欢迎有志于质量保障工作的知友们加入知乎 QA 团队,与知乎一起发现更大的世界,详细职位可以参见 &a href=&https://link.zhihu.com/?target=https%3A//app.mokahr.com/apply/zhihu%23/jobs/%3Fkeyword%3D%25E6%25B5%258B%25E8%25AF%2595%26_k%3Do1ycms& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&/a&,期待知友们的加入 ~&/p&
在移动客户端的测试环节中,性能测试是比较重要的一部分,相对于普通的常规测试,性能测试实施复杂、环境配置困难、测试问题场景很难复现、报告编写和结果分析对测试人员要求较高等等因素导致性能测试很难在实际的测试过程中推行。 实际的测试过程中我们经…
&figure&&img src=&https://pic3.zhimg.com/v2-d6e1accb870f5bef0a5da93d245abd12_b.jpg& data-rawwidth=&1666& data-rawheight=&1250& class=&origin_image zh-lightbox-thumb& width=&1666& data-original=&https://pic3.zhimg.com/v2-d6e1accb870f5bef0a5da93d245abd12_r.jpg&&&/figure&&h2&作者:闲鱼技术-凯航&/h2&&p&Flutter是一个使用Dart语言开发的跨平台移动UI框架,通过自建绘制引擎,能高性能、高保真地进行Android和IOS开发。在业界还未出现过Base Flutter的大型商业应用实战验证的情况下,闲鱼技术团队在最复杂且重要的商品详情页作了相关的技术实践并取得良好的结果。现尝试通过本文向有兴趣进行类似实践的开发者或团队分享过程中的思考/实践过程。&/p&&h2&Flutter特色&/h2&&p&面对一系列移动开发技术:IOS、Android、Weex,RN, Kotlin,H5... Flutter究竟特色何在?&/p&&figure&&img src=&https://pic2.zhimg.com/v2-fe499b71bcf73cdafb799_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1058& data-rawheight=&625& class=&origin_image zh-lightbox-thumb& width=&1058& data-original=&https://pic2.zhimg.com/v2-fe499b71bcf73cdafb799_r.jpg&&&/figure&&p&&br&&/p&&h2&开发语言选择&/h2&&p&了解过Flutter的都知道,它采用Dart语言进行开发,而并非Java,Javascript这类热门语言,这是Flutter团队对当前热门的10多种语言慎重评估后的选择。因为Dart囊括了多数编程语言的优点,它更符合Flutter构建界面的方式。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-90efb1a96b2fcca40b242d1_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1460& data-rawheight=&662& class=&origin_image zh-lightbox-thumb& width=&1460& data-original=&https://pic2.zhimg.com/v2-90efb1a96b2fcca40b242d1_r.jpg&&&/figure&&p&&br&Dart更多优势可查看&a href=&http://link.zhihu.com/?target=http%3A//www.infoq.com/cn/articles/why-flutter-uses-dart& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&为什么Flutter会选择Dart&/a&&/p&&h2&Flutter vs ReactNative框架对比&/h2&&p&|
ReactNative
| Flutter|&br&| ---------- | --- |&br&| &/p&&figure&&img src=&https://pic2.zhimg.com/v2-ec0a576f787ff3fc0abb8cd_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&635& data-rawheight=&307& class=&origin_image zh-lightbox-thumb& width=&635& data-original=&https://pic2.zhimg.com/v2-ec0a576f787ff3fc0abb8cd_r.jpg&&&/figure&&p& |
&/p&&figure&&img src=&https://pic3.zhimg.com/v2-7ba0b322dc9300cbd480382_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&639& data-rawheight=&308& class=&origin_image zh-lightbox-thumb& width=&639& data-original=&https://pic3.zhimg.com/v2-7ba0b322dc9300cbd480382_r.jpg&&&/figure&&p& |&/p&&p&&b&ReactNative&/b&&/p&&ul&&li&采用Javascript开发,需学React,成本高&/li&&li&需要JavaScript桥接器,实现JS到Native转化,性能耗损&/li&&li&访问原生UI,频繁操作易出性能问题&/li&&li&支持线上动态性,可有效避免频繁更新版本&/li&&/ul&&p&&b&Flutter&/b&&/p&&ul&&li&采用Dart开发,可直接编译成Native代码(易学)&/li&&li&自带UI组件和渲染器,仅依赖系统提供的Canvas(无桥接耗损)&/li&&li&暂不支持线上动态性&/li&&/ul&&p&Flutter更多特色可以链接&a href=&http://link.zhihu.com/?target=http%3A//www.infoq.com/cn/articles/why-is-flutter-revolutionary& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&为什么说Flutter是革命性的?&/a&&/p&&p&每个框架都是为解决特定问题而产生的,不存在最好的框架,只有最适合你团队的框架。闲鱼是个业务快速发展的App,为更多业务尝试和探索,它采用现有流行的框架,能支持线上动态化需求。但出于个性化交互以及流畅性体验(首页、商品详情、发布闲置等),主链路依旧只采用原生开发。为兼顾跨端开发及高性能需求,闲鱼经过充分调用,最终选择了Flutter。为验证Flutter的性能,闲鱼挑选重要且复杂的主链路业务(商品详情)作为首个Flutter页面实践点,以这种方式来快速暴露并解决Flutter相关问题。&/p&&h2&闲鱼Flutter突破点&/h2&&h2&Flutter与Native混合编程方案&/h2&&p&随着Flutter版本的不断迭代,稳定性和质量逐渐完善,市场上纯Flutter开发的App也不断涌现。闲鱼对Flutter采取“由点到面,逐一替换” 的策略,先将商品详情迁移到Flutter页面,后续逐步扩展到其他功能模块,但这样就不可避免涉及到Flutter与Native页面混合调用的场景(如下图):&/p&&figure&&img src=&https://pic1.zhimg.com/v2-7c1cbdfae1552d0bff41f4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2502& data-rawheight=&1130& class=&origin_image zh-lightbox-thumb& width=&2502& data-original=&https://pic1.zhimg.com/v2-7c1cbdfae1552d0bff41f4_r.jpg&&&/figure&&p&&br&对纯Flutter工程而言,它主要通过FlutterView中Navigator来管理页面间的跳转;对纯Native工程而言(如:android), 它主要通过系统中ActivityStackSupervisor类对页面切换进行管理,这样当Flutter与Native混合时,就面临浏览一组页面,两套页面管理方式(Flutter管理Flutter页面,Native管理Native页面), 若执行回退操作时,很难保证能回退到期望页面。另外,Flutter工程中界面都是一个继承自SurfaceView的FlutterView(说白了Flutter界面就一个View,不是Activity也不是Fragment),Flutter和Native组件间相互调用也不可避免。&/p&&p&|
| Native(Android) |&br&| ---------- | --- |&br&|&/p&&figure&&img src=&https://pic1.zhimg.com/v2-7c05a4e3bbf8ba3006b8_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&668& data-rawheight=&304& class=&origin_image zh-lightbox-thumb& width=&668& data-original=&https://pic1.zhimg.com/v2-7c05a4e3bbf8ba3006b8_r.jpg&&&/figure&&p&
&/p&&figure&&img src=&https://pic1.zhimg.com/v2-789ff433b0a0dfc954f4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&671& data-rawheight=&247& class=&origin_image zh-lightbox-thumb& width=&671& data-original=&https://pic1.zhimg.com/v2-789ff433b0a0dfc954f4_r.jpg&&&/figure&&p&|&/p&&p&因此要混合调用就会涉及两个问题:&/p&&ul&&li&混合栈管理&/li&&li&组件间调用&/li&&/ul&&h2&混合栈管理&/h2&&p&Flutter出现的目的旨在统一Android/IOS两端编程,因此完全基于Flutter开发的App,只需提供一个包含FlutterView的页面,后续页面增加/删除/跳转均在FlutterView的Navigator中进行管理。但现在闲鱼只是将部分模块修改成Flutter开发,我们不可能为统一页面栈管理而将其他所有页面用Flutter重做一次,权衡成本与风险,亟需统一管理Native页面和Flutter页面跳转交互的混合栈。为此,闲鱼提出了4种解决方案(如下图):&/p&&figure&&img src=&https://pic3.zhimg.com/v2-d36f9ff4b3d54a23cf80a6a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&985& data-rawheight=&605& class=&origin_image zh-lightbox-thumb& width=&985& data-original=&https://pic3.zhimg.com/v2-d36f9ff4b3d54a23cf80a6a_r.jpg&&&/figure&&p&&br&由于IOS有对外系统接口可以方便管理页面栈,因此主动记录页面栈信息就可以解决混合栈管理(方案1),但Android任务栈由系统管理,且融合复杂的Activity回收机制,为降低android学习成本,google并没有对外提供页面栈管理API,方案1方式行不通。为统一android/IOS混合栈管理方式,从FlutterView上着手更为可靠,以此为引,闲鱼提出两种方式:&/p&&ol&&li&每启动一个Activity就启动一个新的FlutterView(方案4);&/li&&li&抽取单一FlutterView或FlutterNativeView,后续每启动一个Activity都对FlutterView或FlutterNativeView进行复用(方案2或方案3);&/li&&/ol&&p&考虑到每启动一个页面都新创建一套新的Flutter渲染机制,开销过重,目前闲鱼Flutter实践采用方案2,相比而言,该方案性能相对稳定且易操作,下面就是否复用FlutterView进行对比,主要观测Java内存和Native内存增加情况:&/p&&p&|
未复用FlutterView
| 复用FlutterView |&br&| ---------- | --- |&br&|&/p&&figure&&img src=&https://pic4.zhimg.com/v2-aac0ece61341cb_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&822& data-rawheight=&481& class=&origin_image zh-lightbox-thumb& width=&822& data-original=&https://pic4.zhimg.com/v2-aac0ece61341cb_r.jpg&&&/figure&&p&
| &/p&&figure&&img src=&https://pic4.zhimg.com/v2-6c4dc4ef3eaa72fb66bfe3_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&830& data-rawheight=&494& class=&origin_image zh-lightbox-thumb& width=&830& data-original=&https://pic4.zhimg.com/v2-6c4dc4ef3eaa72fb66bfe3_r.jpg&&&/figure&&p&|&br&数据表明:不复用FlutterView时平均打开一个页面(空页面),Java内存增长0.02M,Native内存增长0.73M;复用FlutterView时平均打开一个页面(空页面),Java内存增长0.019M,Native内存增长0.65M,因此,复用FlutterView在内存使用上是有优势的,如需更深了解可查看&a href=&http://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s/efKCpCtvvHDHUiAsizobBQ& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android Flutter内存初探&/a&。此外,相关方案的详细表述在&a href=&http://link.zhihu.com/?target=https%3A//github.com/flutter/flutter/issues/15559& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&& How to manage page stack in flutter/native hybrid App &/a&以及&a href=&http://link.zhihu.com/?target=https%3A//github.com/flutter/engine/pull/4932& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Support multiple shells in a single process&/a&均有阐述。&/p&&h2&组件间调用&/h2&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-55d32c1c510ea2c630b5cb8ff263c8a5_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1480& data-rawheight=&900& class=&origin_image zh-lightbox-thumb& width=&1480& data-original=&https://pic2.zhimg.com/v2-55d32c1c510ea2c630b5cb8ff263c8a5_r.jpg&&&/figure&&p&&br&组件间采用比较常见场景就是黑屏问题,出现该问题多数为Layer冲突。从上图(右)可知UI渲染原理:GPU的VSync信号同步到UI线程,UI线程使用Dart构建抽象的视图结构(Layer Tree),接着在GPU线程进行图层合成,且视图数据提供给Skia引擎进行渲染生成GPU数据,最终通过OpenGL或Vulkan提供给GPU,由此可以看出Flutter并不关心显示器、视频控制器以及GPU具体工作细节,它只关心发出的VSync信号,以求尽可能快地在两个VSync信号之间计算并合成视图数据并提供给GPU。Flutter开发者都知道Flutter界面渲染时,使用的是FlutterViewController.view的Layer,倘若Flutter页面跳转到Native做界面渲染相关逻辑时, Native也使用同一个Layer,这将会导致Flutter在release模式无法渲染,LayerTree合成失败即Layer冲突。不过这问题解决也很简单,只需要采用Window或独立View方式唤起Native即可。&/p&&p&解决了Flutter与Native混合编程所面临的问题后,接下来要处理的就是混编工程问题,出现该问题的原因还是我们的项目不是完全的Flutter工程(即:android /ios + Flutter)所致。混合工程项目结构以及Flutter产物如下图:&/p&&p&&b&项目结构&/b&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-146cbe3f4c33ab4c6e4c4ff4938fa74a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2568& data-rawheight=&922& class=&origin_image zh-lightbox-thumb& width=&2568& data-original=&https://pic3.zhimg.com/v2-146cbe3f4c33ab4c6e4c4ff4938fa74a_r.jpg&&&/figure&&p&&br&&/p&&p&&b&Flutter产物&/b&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-72be9ce04abe5c989132c_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2190& data-rawheight=&978& class=&origin_image zh-lightbox-thumb& width=&2190& data-original=&https://pic1.zhimg.com/v2-72be9ce04abe5c989132c_r.jpg&&&/figure&&p&&br&&/p&&p&其实对一般Flutter工程而言,采用AndroidStudio编译Flutter与编译Native工程方式一样,当将其部署到server端采用mtl编译时,server缺少Flutter编译环境,因而导致Flutter工程无法编译。解决此问题可以采取两种方式:&/p&&ol&&li&在每个server端部署Flutter编译环境&/li&&li&Native工程远程依赖Flutter编译产物&/li&&/ol&&p&对1,对各server端都去部署Flutter环境有点不切实际(若server就那么几台也可以);对2,闲鱼的做法是将Flutter工程编译出的中间产物以AAR形式导出并上传至maven库,最后Native工程以依赖包形式将AAR打入最终apk中,这样处理后解耦了Native团队对Flutter团队的依赖。当然,具体实践过程中肯定没有这么简单,我们在编译过程中对Pod/Gradle编译脚本、engine以及flutter_tools等均有所优化,对后续集团推广Flutter奠定了基础。&/p&&h2&阿里Flutter生态适配&/h2&&p&将Flutter应用于闲鱼,不可避免需要使用集团提供的基础组件库,但这些组件库都是Native,考虑到为后续Flutter在集团推广,打造阿里Flutter生态圈,闲鱼团队对集团内部基础组件库做了适配支撑,后续可建立私有仓库,直接Git引用。&/p&&h2&生态适配原理及性能&/h2&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-174d04bedecf7b48e7ee593b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2250& data-rawheight=&1048& class=&origin_image zh-lightbox-thumb& width=&2250& data-original=&https://pic4.zhimg.com/v2-174d04bedecf7b48e7ee593b_r.jpg&&&/figure&&p&&br&上图(左)概述了Flutter平台通道,使用MethodChannel在Client(UI)和主机(平台)之间传递消息,消息和响应异步传递以确保用户界面保持正常响应。对UI,Flutter的MethodChannel类可以发送与方法调用相对应的消息;对平台,Android端MethodChannel类和IOS端FlutterMethodChannel类可以接收方法调用并发送结果,同时方法调用还可以逆向发送,即以平台作为实现Dart方法的Client。值得注意的是Flutter Plugin开发相关原理也是如此。上图(右)还对MethodChannel吞吐量性能进行了简略表述。&/p&&h2&多媒体解决方案&/h2&&p&在以内容为王的时代,多媒体技术备受关注,性能的好坏直接影响用户体验,但Flutter多媒体默认功能存在以下缺陷:&/p&&ul&&li&功能单一,如:播放器缺少滤镜,与Native淘宝播放器存在一定差距&/li&&li&兼容性欠佳&/li&&/ul&&p&为改善体验,优化性能,闲鱼对Flutter播放器以及图片性能作出如下改进:&/p&&h2&Texture对接自定义视频播放器&/h2&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-d6d87bf1da2195_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2432& data-rawheight=&1050& class=&origin_image zh-lightbox-thumb& width=&2432& data-original=&https://pic2.zhimg.com/v2-d6d87bf1da2195_r.jpg&&&/figure&&p&&br&&/p&&p&具体方案:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-25f104bf845c4b03b9fe9_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2228& data-rawheight=&1062& class=&origin_image zh-lightbox-thumb& width=&2228& data-original=&https://pic2.zhimg.com/v2-25f104bf845c4b03b9fe9_r.jpg&&&/figure&&p&&br&&/p&&h2&图片性能优化&/h2&&p&有过移动开发经验的都知道,图片展示是OOM出现的高频场景,而Flutter默认采用基于LRU算法的图片缓存策略,且图片缓存MaxSize=1000,占用内存较高,为此闲鱼采用以下两种策略对图片性能进行优化&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-b205eaef2b1ee49eb9d144_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2200& data-rawheight=&1012& class=&origin_image zh-lightbox-thumb& width=&2200& data-original=&https://pic1.zhimg.com/v2-b205eaef2b1ee49eb9d144_r.jpg&&&/figure&&p&&br&&/p&&h2&Flutter 上线效果 & 性能对比 & 成熟度&/h2&&p&解决了Flutter存在的问题,要的就是产品能够以一种性能稳定、交互流畅、界面美观的姿态呈现在用户面前,下面就以闲鱼宝贝详情线上Flutter版本为引,对Flutter应用页面展示、性能以及成熟度进行阐述。&/p&&h2&闲鱼宝贝详情Flutter应用线上效果&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-80de96add08b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2180& data-rawheight=&1012& class=&origin_image zh-lightbox-thumb& width=&2180& data-original=&https://pic1.zhimg.com/v2-80de96add08b_r.jpg&&&/figure&&p&&br&&/p&&h2&Native性能对比&/h2&&p&&b&测试环境&/b&&/p&&ul&&li&测试机型:iphone6&/li&&li&IOS版本:11.3&/li&&li&闲鱼版本: 6.1.3&/li&&li&测试方法: 在Flutter版本和Native版本各自宝贝详情页面执行相同操作:即从我发布的页面进行10个不同详情页面&/li&&/ul&&p&&b&注:&/b& &i&下图仅对Flutter和Native性能进行了粗略对比&/i&&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-8f5fad421cec90cd58d50da_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2678& data-rawheight=&1160& class=&origin_image zh-lightbox-thumb& width=&2678& data-original=&https://pic3.zhimg.com/v2-8f5fad421cec90cd58d50da_r.jpg&&&/figure&&p&&br&&/p&&h2&Flutter成熟度&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-ddce4a1085374_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&960& data-rawheight=&640& class=&origin_image zh-lightbox-thumb& width=&960& data-original=&https://pic1.zhimg.com/v2-ddce4a1085374_r.jpg&&&/figure&&p&&br&上图为crash收敛曲线图,可容易看出经过几个版本迭代,crash率基本趋于稳定,体感与Native可以媲美。&/p&&h2&延展讨论&/h2&&p&目前Flutter尚处于Preview阶段,没有经过大规模实践验证,框架成熟度及稳定性仍有待完善。上文仅仅是将闲鱼团队在实践Flutter开发时碰到部分问题及解决方案进行了简略阐述,一个产品从开发到上线所面临的问题,肯定远不及这些。随着Flutter覆盖场景的增加,难题也会不断涌现,健全有效的性能及稳定性监控体系不可或缺。为建设基于Flutter全新的一体化研发体系,提高开发效率,对动态化需求、规范Dart编码、统一中间件桥接机制、快速发版能力及完备的自动化测试建设等一系列问题亟需解决,倘若您对此感兴趣,欢迎一起交流学习~&/p&&p&简历投递:guicai.gxy艾特&a href=&http://link.zhihu.com/?target=http%3A//alibaba-inc.com& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&alibaba-inc.com&/span&&span class=&invisible&&&/span&&/a&&/p&
作者:闲鱼技术-凯航Flutter是一个使用Dart语言开发的跨平台移动UI框架,通过自建绘制引擎,能高性能、高保真地进行Android和IOS开发。在业界还未出现过Base Flutter的大型商业应用实战验证的情况下,闲鱼技术团队在最复杂且重要的商品详情页作了相关的技术…
&figure&&img src=&https://pic1.zhimg.com/v2-ac1ec52c11ccb64a2aa6_b.jpg& data-rawwidth=&547& data-rawheight=&300& class=&origin_image zh-lightbox-thumb& width=&547& data-original=&https://pic1.zhimg.com/v2-ac1ec52c11ccb64a2aa6_r.jpg&&&/figure&&h2&&b&插入排序&/b&&/h2&&ul&&li&基本思想:每步将一个待排序的纪录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。&/li&&li&算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。&/li&&li&代码:&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&insertionSort&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&array&/span&&span class=&o&&){&/span&
&span class=&kt&&int&/span& &span class=&n&&tmp&/span&&span class=&o&&;&/span&
&span class=&k&&for&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span& &span class=&n&&i&/span&&span class=&o&&=&/span&&span class=&mi&&1&/span&&span class=&o&&;&/span&&span class=&n&&i&/span&&span class=&o&&&&/span&&span class=&n&&array&/span&&span class=&o&&.&/span&&span class=&na&&length&/span&&span class=&o&&;&/span&&span class=&n&&i&/span&&span class=&o&&++){&/span&
&span class=&n&&tmp&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&i&/span&&span class=&o&&];&/span&
&span class=&c1&&//将当前位置的数给tmp&/span&
&span class=&kt&&int&/span& &span class=&n&&j&/span& &span class=&o&&=&/span& &span class=&n&&i&/span&&span class=&o&&;&/span&
&span class=&k&&for&/span&&span class=&o&&(;&/span&&span class=&n&&j&/span&&span class=&o&&&&/span&&span class=&mi&&0&/span&&span class=&o&&&&&/span&&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&o&&]&&/span&&span class=&n&&tmp&/span&&span class=&o&&;&/span&&span class=&n&&j&/span&&span class=&o&&--){&/span&
&span class=&cm&&/*&/span&
&span class=&cm&&
* 往右移,腾出左边的位置,&/span&
&span class=&cm&&
* array[j-1]&tmp:大于号是升序排列,小于号是降序排列&/span&
&span class=&cm&&
&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&o&&];&/span&
&span class=&o&&}&/span&
&span class=&c1&&//将当前位置的数插入到合适的位置&/span&
&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&tmp&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&h2&&b&冒泡排序&/b&&/h2&&ul&&li&基本思想:持续比较相邻的元素。如果第一个比第二个大,就交换他们两个。直到没有任何一对数字需要比较。&/li&&li&冒泡排序最好的时间复杂度为O(n)。冒泡排序的最坏时间复杂度为O(n^2)。因此冒泡排序总的平均时间复杂度为O(n^2)。&/li&&li&算法适用于少量数据的排序,是稳定的排序方法。&/li&&li&代码:&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&bubbleSort&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&array&/span&&span class=&o&&){&/span&
&span class=&kt&&int&/span& &span class=&n&&tmp&/span&&span class=&o&&;&/span&
&span class=&kt&&boolean&/span& &span class=&n&&flag&/span& &span class=&o&&=&/span& &span class=&kc&&false&/span&&span class=&o&&;&/span&
&span class=&c1&&//设置是否发生交换的标志&/span&
&span class=&k&&for&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span& &span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&.&/span&&span class=&na&&length&/span&&span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&o&&;&/span&&span class=&n&&i&/span& &span class=&o&&&=&/span& &span class=&mi&&0&/span&&span class=&o&&;&/span&&span class=&n&&i&/span&&span class=&o&&--){&/span&
&span class=&k&&for&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span& &span class=&n&&j&/span&&span class=&o&&=&/span&&span class=&mi&&0&/span&&span class=&o&&;&/span&&span class=&n&&j&/span&&span class=&o&&&&/span&&span class=&n&&i&/span&&span class=&o&&;&/span&&span class=&n&&j&/span&&span class=&o&&++){&/span&
&span class=&c1&&//每一轮都找到一个最大的数放在右边&/span&
&span class=&k&&if&/span&&span class=&o&&(&/span&&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&]&&/span&&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&+&/span&&span class=&mi&&1&/span&&span class=&o&&]){&/span&
&span class=&n&&tmp&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&];&/span&
&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&+&/span&&span class=&mi&&1&/span&&span class=&o&&];&/span&
&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&+&/span&&span class=&mi&&1&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&tmp&/span&&span class=&o&&;&/span&
&span class=&n&&flag&/span& &span class=&o&&=&/span& &span class=&kc&&true&/span&&span class=&o&&;&/span&
&span class=&c1&&//发生了交换&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&k&&if&/span&&span class=&o&&(!&/span&&span class=&n&&flag&/span&&span class=&o&&)&/span&
&span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&c1&&//这一轮循环没有发生交换,说明排序已经完成,退出循环&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&h2&&b&选择排序&/b&&/h2&&ul&&li&基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。&/li&&li&选择排序是不稳定的排序方法。时间复杂度 O(n^2)。&/li&&li&代码:&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&selectSort&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&array&/span&&span class=&o&&){&/span&
&span class=&k&&for&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span& &span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&o&&;&/span&&span class=&n&&i&/span&&span class=&o&&&&/span&&span class=&n&&array&/span&&span class=&o&&.&/span&&span class=&na&&length&/span&&span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&o&&;&/span&&span class=&n&&i&/span&&span class=&o&&++){&/span&
&span class=&kt&&int&/span& &span class=&n&&min&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&i&/span&&span class=&o&&];&/span&
&span class=&kt&&int&/span& &span class=&n&&minindex&/span& &span class=&o&&=&/span& &span class=&n&&i&/span&&span class=&o&&;&/span&
&span class=&k&&for&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span& &span class=&n&&j&/span& &span class=&o&&=&/span& &span class=&n&&i&/span&&span class=&o&&;&/span&&span class=&n&&j&/span&&span class=&o&&&&/span&&span class=&n&&array&/span&&span class=&o&&.&/span&&span class=&na&&length&/span&&span class=&o&&;&/span&&span class=&n&&j&/span&&span class=&o&&++){&/span&
&span class=&k&&if&/span&&span class=&o&&(&/span&&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&]&&/span&&span class=&n&&min&/span&&span class=&o&&){&/span&
&span class=&c1&&//选择当前最小的数&/span&
&span class=&n&&min&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&];&/span&
&span class=&n&&minindex&/span& &span class=&o&&=&/span& &span class=&n&&j&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&k&&if&/span&&span class=&o&&(&/span&&span class=&n&&i&/span& &span class=&o&&!=&/span& &span class=&n&&minindex&/span&&span class=&o&&){&/span& &span class=&c1&&//若i不是当前元素最小的,则和找到的那个元素交换&/span&
&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&minindex&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&i&/span&&span class=&o&&];&/span&
&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&i&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&min&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&h2&&b&希尔排序&/b&&/h2&&ul&&li&基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2&d1重复上述的分组和排序,直至所取的增量dt=1(dt&dt-1…&d2&d1),即所有记录放在同一组中进行直接插入排序为止。&/li&&li&在使用增量dk的一趟排序之后,对于每一个i,我们都有a[i]&=a[i+dk],即所有相隔dk的元素都被排序。&/li&&li&如图:增量序列为5,3,1,每一趟排序之后,相隔对应增量的元素都被排序了。当增量为1时,数组元素全部被排序。&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-1e0d0622cda15fcc7ab1ceb_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1175& data-rawheight=&312& class=&origin_image zh-lightbox-thumb& width=&1175& data-original=&https://pic3.zhimg.com/v2-1e0d0622cda15fcc7ab1ceb_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&希尔排序不稳定,时间复杂度 平均时间 O(nlogn) 最差时间O(n^2)&/li&&li&代码:&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&shellSort&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&array&/span&&span class=&o&&){&/span&
&span class=&kt&&int&/span& &span class=&n&&j&/span&&span class=&o&&;&/span&
&span class=&k&&for&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span& &span class=&n&&gap&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&.&/span&&span class=&na&&length&/span&&span class=&o&&/&/span&&span class=&mi&&2&/span&&span class=&o&&;&/span& &span class=&n&&gap&/span&&span class=&o&&&&/span&&span class=&mi&&0&/span&&span class=&o&&;&/span& &span class=&n&&gap&/span& &span class=&o&&/=&/span& &span class=&mi&&2&/span&&span class=&o&&){&/span&
&span class=&c1&&//定义一个增长序列,即分割数组的增量,d1=N/2
dk=(d(k-1))/2&/span&
&span class=&k&&for&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span& &span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&n&&gap&/span&&span class=&o&&;&/span& &span class=&n&&i&/span&&span class=&o&&&&/span&&span class=&n&&array&/span&&span class=&o&&.&/span&&span class=&na&&length&/span&&span class=&o&&;&/span&&span class=&n&&i&/span&&span class=&o&&++){&/span&
&span class=&kt&&int&/span& &span class=&n&&tmp&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&i&/span&&span class=&o&&];&/span&
&span class=&k&&for&/span&&span class=&o&&(&/span& &span class=&n&&j&/span& &span class=&o&&=&/span&&span class=&n&&i&/span&&span class=&o&&;&/span& &span class=&n&&j&/span&&span class=&o&&&=&/span&&span class=&n&&gap&/span&&span class=&o&&&&&/span&&span class=&n&&tmp&/span&&span class=&o&&&&/span&&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&-&/span&&span class=&n&&gap&/span&&span class=&o&&];&/span& &span class=&n&&j&/span& &span class=&o&&-=&/span& &span class=&n&&gap&/span&&span class=&o&&){&/span&
&span class=&c1&&//将相距为Dk的元素进行排序&/span&
&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&-&/span&&span class=&n&&gap&/span&&span class=&o&&];&/span&
&span class=&o&&}&/span&
&span class=&n&&array&/span&&span class=&o&&[&/span&&span class=&n&&j&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&tmp&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&h2&&b&堆排序&/b&&/h2&&ul&&li&预备知识:&/li&&ul&&li&二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树)。 二叉堆有两种:最大堆和最小堆。 大根堆:父结点的键值总是大于或等于任何一个子节点的键值; 小根堆:父结点的键值总是小于或等于任何一个子节点的键值。 二叉堆一般用数组来表示。例如,根节点在数组中的位置是0,第n个位置的子节点分别在2n+1和 2n+2。因此,第0个位置的子节点在1和2,1的子节点在3和4。以此类推。这种存储方式便於寻找父节点和子节点。 例如初始要排序的数组为:49, 38, 65, 97, 76, 13, 27, 49 构造成大根堆之后的数组为:97 76 65 49 49 13 27 38 实际树形结构如图(最大堆):&/li&&/ul&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-e360b288e777cdfce3cf_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&432& data-rawheight=&393& class=&origin_image zh-lightbox-thumb& width=&432& data-original=&https://pic3.zhimg.com/v2-e360b288e777cdfce3cf_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&堆排序基本思想:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系【参见二叉树的顺序存储结构】,在当前无序区中选择关键字最大(或最小)的记录。堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。&/li&&li&堆排序是一种选择排序,其时间复杂度为O(nlogn)。堆排序是不稳定的&/li&&li&代码:&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&cm&&/*&/span&
&span class=&cm&&
* 堆排序&/span&
&span class=&cm&&
* 调整最大堆,交换根元素和最后一个元素。&/span&
&span class=&cm&&
* 参数说明:&/span&
&span class=&cm&&
a -- 待排序的数组&/span&
&span class=&cm&&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&heapSort&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&a&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&n&/span& &span class=&o&&=&/span& &span class=&n&&a&/span&&span class=&o&&.&/span&&span class=&na&&length&/span&&span class=&o&&;&/span&
&span class=&kt&&int&/span& &span class=&n&&i&/span&&span class=&o&&,&/span&&span class=&n&&tmp&/span&&span class=&o&&;&/span&
&span class=&c1&&// 从(n/2-1) --& 0逐次遍历。遍历之后,得到的数组实际上是一个(最大)二叉堆。&/span&
&span class=&k&&for&/span& &span class=&o&&(&/span&&span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&n&&n&/span& &span class=&o&&/&/span& &span class=&mi&&2&/span& &span class=&o&&-&/span& &span class=&mi&&1&/span&&span class=&o&&;&/span& &span class=&n&&i&/span& &span class=&o&&&=&/span& &span class=&mi&&0&/span&&span class=&o&&;&/span& &span class=&n&&i&/span&&span class=&o&&--)&/span&
&span class=&n&&maxHeapDown&/span&&span class=&o&&(&/span&&span class=&n&&a&/span&&span class=&o&&,&/span& &span class=&n&&i&/span&&span class=&o&&,&/span& &span class=&n&&n&/span&&span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&o&&);&/span&
&span class=&c1&&// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素&/span&
&span class=&k&&for&/span& &span class=&o&&(&/span&&span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&n&&n&/span& &span class=&o&&-&/span& &span class=&mi&&1&/span&&span class=&o&&;&/span& &span class=&n&&i&/span& &span class=&o&&&&/span& &span class=&mi&&0&/span&&span class=&o&&;&/span& &span class=&n&&i&/span&&span class=&o&&--)&/span& &span class=&o&&{&/span&
&span class=&c1&&// 交换a[0]和a[i]。交换后,a[i]是a[0...i]中最大的。&/span&
&span class=&n&&tmp&/span& &span class=&o&&=&/span& &span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&mi&&0&/span&&span class=&o&&];&/span&
&span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&mi&&0&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&i&/span&&span class=&o&&];&/span&
&span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&i&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&tmp&/span&&span class=&o&&;&/span&
&span class=&c1&&// 调整a[0...i-1],使得a[0...i-1]仍然是一个最大堆。&/span&
&span class=&c1&&// 即,保证a[i-1]是a[0...i-1]中的最大值。&/span&
&span class=&n&&maxHeapDown&/span&&span class=&o&&(&/span&&span class=&n&&a&/span&&span class=&o&&,&/span& &span class=&mi&&0&/span&&span class=&o&&,&/span& &span class=&n&&i&/span&&span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&cm&&/*&/span&
&span class=&cm&&
* 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。&/span&
&span class=&cm&&
其中,N为数组下标索引值,如数组中第1个数对应的N为0。&/span&
&span class=&cm&&
&span class=&cm&&
* 参数说明:&/span&
&span class=&cm&&
a -- 待排序的数组&/span&
&span class=&cm&&
start -- 被下调节点的起始位置(一般为0,表示从第1个开始)&/span&
&span class=&cm&&
-- 截至范围(一般为数组中最后一个元素的索引)&/span&
&span class=&cm&&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&maxHeapDown&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&a&/span&&span class=&o&&,&/span& &span class=&kt&&int&/span& &span class=&n&&start&/span&&span class=&o&&,&/span& &span class=&kt&&int&/span& &span class=&n&&end&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&c&/span& &span class=&o&&=&/span& &span class=&n&&start&/span&&span class=&o&&;&/span&
&span class=&c1&&// 当前(current)节点的位置&/span&
&span class=&kt&&int&/span& &span class=&n&&l&/span& &span class=&o&&=&/span& &span class=&mi&&2&/span&&span class=&o&&*&/span&&span class=&n&&c&/span& &span class=&o&&+&/span& &span class=&mi&&1&/span&&span class=&o&&;&/span&
&span class=&c1&&// 左(left)孩子的位置&/span&
&span class=&kt&&int&/span& &span class=&n&&tmp&/span& &span class=&o&&=&/span& &span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&c&/span&&span class=&o&&];&/span&
&span class=&c1&&// 当前(current)节点的大小&/span&
&span class=&k&&for&/span& &span class=&o&&(;&/span& &span class=&n&&l&/span& &span class=&o&&&=&/span& &span class=&n&&end&/span&&span class=&o&&;&/span& &span class=&n&&c&/span&&span class=&o&&=&/span&&span class=&n&&l&/span&&span class=&o&&,&/span&&span class=&n&&l&/span&&span class=&o&&=&/span&&span class=&mi&&2&/span&&span class=&o&&*&/span&&span class=&n&&l&/span&&span class=&o&&+&/span&&span class=&mi&&1&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&c1&&// &l&是左孩子,&l+1&是右孩子&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span& &span class=&n&&l&/span& &span class=&o&&&&/span& &span class=&n&&end&/span& &span class=&o&&&&&/span& &span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&l&/span&&span class=&o&&]&/span& &span class=&o&&&&/span& &span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&l&/span&&span class=&o&&+&/span&&span class=&mi&&1&/span&&span class=&o&&])&/span&
&span class=&n&&l&/span&&span class=&o&&++;&/span&
&span class=&c1&&// 左右两孩子中选择较大者,即m_heap[l+1] &/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&tmp&/span& &span class=&o&&&=&/span& &span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&l&/span&&span class=&o&&])&/span&
&span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&c1&&// 调整结束&/span&
&span class=&k&&else&/span& &span class=&o&&{&/span&
&span class=&c1&&// 交换值&/span&
&span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&c&/span&&span class=&o&&]&/span& &span class=&o&&=&/span& &span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&l&/span&&span class=&o&&];&/span&
&span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&l&/span&&span class=&o&&]=&/span& &span class=&n&&tmp&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&h2&&b&归并排序&/b&&/h2&&ul&&li&归并排序的原理:&/li&&ul&&li&将待排序的数组分成前后两个部分,再递归的将前半部分数据和后半部分的数据各自归并排序,得到的两部分数据,然后使用merge合并算法(算法见代码)将两部分算法合并到一起。 例如:如果N=1;那么只有一个数据要排序,N=2,只需要调用merge函数将前后合并,N=4,........... 也就是将一个很多数据的数组分成前后两部分,然后不断递归归并排序,再合并,最后返回有序的数组。&/li&&/ul&&li&归并排序的时间复杂度:&/li&&ul&&li&归并排序的最好、最坏和平均时间复杂度都是O(nlogn),而空间复杂度是O(n),比较次数介于(nlogn)/2和(nlogn)-n+1,赋值操作的次数是(2nlogn)。因此可以看出,归并排序算法比较占用内存,但却是效率高且稳定的排序算法。&/li&&/ul&&li&代码:&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&kd&&public&/span& &span class=&kd&&class&/span& &span class=&nc&&MergeSort&/span& &span class=&o&&{&/span&
&span class=&kd&&private&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&mergeSort&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&array&/span&&span class=&o&&,&/span&&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&tmp&/span&&span class=&o&&,&/span&&span class=&kt&&int&/span& &span class=&n&&left&/span&&span class=&o&&,&/span&&span class=&kt&&int&/span& &span class=&n&&right&/span&&span class=&o&&){&/span&
&span class=&k&&if&/span&&span class=&o&&(&/span&&span class=&n&&left&/span&&span class=&o&&&&/span&&span class=&n&&right&/span&&span class=&o&&){&/span&
&span class=&kt&&int&/span& &span class=&n&&center&/span& &span class=&o&&=&/span& &span class=&o&&(&/span& &span class=&n&&left&/span& &span class=&o&&+&/span& &span class=&n&&right&/span& &span class=&o&&)&/span& &span class=&o&&/&/span& &span class=&mi&&2&/span&&span class=&o&&;&/span&&span class=&c1&&//取数组的中点&/span&
&span class=&n&&mergeSort&/span&&span class=&o&&(&/span&&span class=&n&&array&/span&&span class=&o&&,&/span&&span class=&n&&tmp&/span&&span class=&o&&,&/span&&span class=&n&&left&/span&&span class=&o&&,&/span&&span class=&n&&center&/span&&span class=&o&&);&/span&&span class=&c1&&//归并排序数组的前半部分&/span&
&span class=&n&&mergeSort&/span&&span class=&o&&(&/span&&span class=&n&&array&/span&&span class=&o&&,&/span&&span class=&n&&tmp&/span&&span class=&o&&,&/span&&span class=&n&&center&/span&&span class=&o&&+&/span&&span class=&mi&&1&/span&&span class=&o&&,&/span&&span class=&n&&right&/span&&span class=&o&&);&/span&&span class=&c1&&//归并排序数组的后半部分&/span&
&span class=&n&&merge&/span&&span class=&o&&(&/span&&span class=&n&&array&/span&&span class=&o&&,&/span&&span class=&n&&tmp&/span&&span class=&o&&,&/span&&span class=&n&&left&/span&&span class=&o&&,&/span&&span class=&n&&center&/span&&span class=&o&&+&/span&&span class=&mi&&1&/span&&span class=&o&&,&/span&&span class=&n&&right&/span&&span class=&o&&);&/span&&span class=&c1&&//将数组的前后半部分合并&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&cm&&/*&/span&
&span class=&cm&&
* 超简单的合并函数&/span&
&span class=&cm&&
&span class=&kd&&private&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&merge&/span&&span class=&o&&(&/span&&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&array&/span&&span class=&o&&,&/span& &span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&tmp&/span&&span class=&o&&,&/

我要回帖

更多关于 一般程序员真实工资 的文章

 

随机推荐