首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

《阿里巴巴编码规范(JAVA)》学习认证考后感

image.png 《阿里巴巴 Java 开发手册》是阿里巴巴集团技术团队的集体智慧结晶和经验总结,经历了多次大规模一线实战的检验及不断完善,系统化地整理成册,回馈给广大开发者。 本手册的旨在码出高效,码出质量。现代软件架构的复杂性需要协同开发完成,如何高效地协同呢?无规矩不成方圆,无规范难以协同代码的字里行间流淌的是软件系统的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。 阿里巴巴编码规范基础认证的考试要点90%来源于这本开发手册中的内容,目前此手册的最新版本为1.4.0的PDF(详尽版),此版本已为非常详尽的版本,在上一个版本的基础上新增了设计规约大章节,并增加了若干条目。根据手册规约中的内容阿里在2017杭州云栖大会上发布了Java开发规约插件,插件同时支持Intelli IDEA和Eclipse,文后我会简单介绍下这个插件的安装及使用。 我在2018-08-21的晚上通过阿里云大学在线考试通过了这个认证,考试包含50道选择题,分为多选和单选,每次考试题目内容是随机生成的,主要是多选题居多,每次考试单选题目数量也是随机的,但不会超过10道单选,考试时间90分钟,说来惭愧,我第一次考试的时候没有通过,做的比较快,45分钟我就做完了,也没检查直接交卷了,结果考了78分,差2分。。。 如果你关注阿里云在招的Java工程师职位,你会发现岗位说明中有这样的字样“通过阿里巴巴编码规范认证的同学优先录取”,足以看出这份认证考试还是有些许价值的。 image.png 那么接下来我将聊聊《阿里巴巴Java开发手册》(文后简称“手册”)读后感以及认证考试的主要考试要点,最后再简单写下插件的安装和使用。 手册读后感 正如阿里官方说明的一样,这是一本由阿里近万名Java开发精英工程师通过大规模一线实战的经验总结而来的,读后确实觉得写的很实用,有些地方可以说是闭坑指南,从书的首屏图样来说这本书是要做领头雁的意思。 阿里巴巴Java开发手册首页.png 本手册以 Java 开发者为中心视角,划分为编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约七个维度,再根据内容特征,细分成若干二级子目录。根据约束力强弱及故障 敏感性,规约依次分为强制、推荐、参考三大类。对于规约条目的延伸信息中,“说明”对规约做了适当扩展和解释;“正例”提倡什么样的编码和实现方式;“反例” 说明需要提防的雷区,以及真实的错误案例。 这是一本关于Java开发规范方面的中文原版说明书,当然有些内容它也遵循了Oracle Sun规范,如编程规约中类名使用UpperCamelCase风格。方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式。 讲编码规范的说明书并不是要求我们程序员去死记硬背这些条条框框,我们应该去发掘在这些条条框框之后隐藏的真理是什么?如“为什么我们要去遵循这些规范,为什么要制定这样的规则,使用这样的规则有什么好处,它会限制我们代码内容的创造性吗?”。我觉得带着这些问题去阅读本书,理解这些规范和标准才是正确的打开方式。为了不让本章节拖的太长,我将挑选手册中重要的几个规范点来说下我个人的见解和补充。 (1)编程规约中POJO概念 POJO(Plain Ordinary Java Object): 在本手册中,POJO 专指只有 setter / getter / toString的简单类,包括DO/DTO/BO/VO等。 DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。 DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。 BO(Business Object):业务对象,由 Service 层输出的封装业务逻辑的对象。 AO(ApplicationObject):应用对象,在Web层与Service层之间抽象的复用对象模型, 极为贴近展示层,复用度不高。 VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。 我们在实际简单的项目中,可能不会将实体像上面分的那么细,多数命名会直接末尾加POJO或Entity或Model,这对于软件架构不是很复杂,对性能要求也不高的小型后台管理类系统来说,确实已经够了。但对于复杂的,且对性能要求高的项目,就有必要分这么细了,举个例子,一般复杂的项目,开发团队都是10人以上,团队按组划分,各组负责软件架构中不同的应用层级,各层级之间肯定要定义接口,那么数据传输的实体就不能一概而就了,必须分层规范定义;再举个例子,很多时候一个DO实体对应在数据库中表的字段有10多个,但是在页面中需要显示的只需要3-5个,多余的数据会导致传输速度下降,所以需要新建VO对象去进行页面显示,只取需要展示的数据;还有诸如一个用户实体,密码不应该在查询的时候返回给用户,用户编号也不是用户想看到的,用户想看到的只是用户昵称,这时我们必然需要通过BO或者DTO层进行过滤和转义,最终新建Vo对象用于界面展示。 【强制】POJO类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误 这一条我也是感触挺深的,这个是真细节,在我还是个菜鸟的时候,我也遇到过 boolean isSuccess的错误,debug调试的时候明明这个值是有值的且为true,怎么传递到前台来了之后就变成了无值默认false呢,千万千万不要这样命名。 (2)格式规范 这里我主要讲一点,代码缩进的时候到底是敲空格键还是tab键,我习惯敲tab,所以我在IDE中设置了一个tab使用4个空格来代替,因为敲空格容易敲错。 (3)集合处理 foreach中不要进行remove和add操作,remove元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。 下面这张表格是重点(考试必考,敲黑板,可以记一下) image.png (4)并发处理 【强制】线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 对于FixedThreadPool这个线程池,我也是掉过坑里,由于系统在一个特殊时期内业务请求量翻了好几番,当前线程池设定的最大线程数不足,工作线程处理任务的速度大大低于请求任务的新增速度,这会导致请求队列深度无限扩大,内存使用量直线攀升,最终造成频繁FullGC,这样又导致工作线程处理任务的速度进一步变慢,形成死链,最后OOM服务端进程崩溃了。。。 (5)索引规约 【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。 说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明 显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。 这一点我也是非常赞同,在生产项目实际运行过程中见到太多这样的脏数据导致被业务或者客户投诉吐槽(怎么我只操作了一次,你们系统记录了2遍,系这也太坑了吧。。。)所以这条规范排在了NO.1。 还有很多痛点和闭坑指南,这里就不一一举例了,我相信每个有经验的Java开发人员看完这本手册都会在其中找到自己曾经的痛点影子,这是一本可以给自己查漏补缺的好书。 认证考试要点 绝大部分的考题涉及到的知识点都能在《阿里巴巴JAVA开发手册》上找到,这些都是比较基础的考点,这里就不说明了,我主要讲一些只在书上稍微提及却又在考试中扩展延伸的考点。 JAVA: 1.考点:集合处理这个章节,特别是数组与集合相互转换,涉及考题较多。 subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList 的一个视图,对于SubList子列表的所有操作最终会反映到原列表上(在sublist中添加/删除元素,不只影响sublist本身,同时会影响原ArrayList)。 在subList场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均会产生ConcurrentModificationException 异常(切记,这个场景是不被允许的,不要和上一点记混淆) sublist没有实现序列化,查看源码就可以发现其没有实现Serializable接口,这点必须注意,在大多数写入缓存容器、RPC调用等场景需要做一些调整。 特别是最后一条,手册中并未提及,但是我遇到的多选题,其中有一个选项就是:subList不能作为RPC接口方法的返回结果,因为其没有实现序列化,第一次考试的时候我没有选这个选项,要是选了就一次过了(苦笑) 2.考点:所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。 对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断 这点在很多书上都有提及,特别重要,这里说下我遇到的题目代码: Integer var1 = 20; Integer var2 = 20; Integer var3 = 259; Integer var4 = 259; 题目选项是让你选择 var1 == var2 的值为true还是false,var3 == var4的值为true还是false。 数据库考点: 数据库题目考试的比重还是比较大的,最主要的是sql索引规范,然后就是SQL语句,ORM规范。 1.考点:NULL与任何值比较的结果都为NULL。~记住这点很重要。 2.考点:Mysql涉及的索引从数据结构角度来看涉及到 B-Tree、Hash、R-Tree、Full-text,从物理存储角度,聚集索引(clustered)、非聚集索引(non-clustered)。 在MySQL中,InnoDB引擎表是(聚集)索引组织表(clustered index organize table),而MyISAM引擎表则是堆组织表(heap organize table)。 InnoDB的数据文件本身就是索引文件,B+Tree的叶子节点上的data就是数据本身,key为主键,这是聚簇索引。非聚簇索引,叶子节点上的data是主键(所以聚簇索引的key,不能过长)。为什么存放的主键,而不是记录所在地址呢,理由相当简单,因为记录所在地址并不能保证一定不会变,但主键可以保证。 3.考点:索引规约中相关的点,考到的题比较多,比如索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引a_b无法排序。又比如页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。 4.考点:iBATIS自带的queryForList(String statementName,int start,int size)不推荐使用,存在性能风险。 说明:其实现方式是在数据库取到 statementName 对应的 SQL 语句的所有记录,再通过 subList 取 start,size 的子集合 5.考点:利用延迟关联或者子查询优化超多分页场景 说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过 特定阈值的页数进行 SQL 改写。 6.考点:在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句 日志: 1.考点:应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。 平时常用的日志适配框架是SLF4J,这里重点提一下JCL(Jakarta Commons Logging),题目中出现的,后来查询了资源它也是一种日志适配框架,考试的时候猜测应该是类似于SLF4J的东西,也选了这个,蒙对了。。。用这些日志框架的好处是降低与具体日志框架的耦合,可以灵活改变使用的具体日志框架。 单元测试: 1.考点:编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。 B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。 C:Correct,正确的输入,并得到预期的结果。 D:Design,与设计文档相结合,来编写单元测试。 E:Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得 到预期的结果。 这里考试的时候我遇到的是BCDE解释故意混淆,如E:Equal,保证单元测试的运行生产环境和测试环境完全一样(这怎么可能呢,完全一样了还有测试环境这个称呼吗,呵呵。。。) 2.考点:白盒测试中的代码覆盖率相关概念,语句覆盖、分支覆盖(判定覆盖)、条件覆盖、路径覆盖。语句覆盖是最弱的一种度量方式,即使是路径覆盖也不能保证程序完全没问题。这些相关概念还是看下专业的书籍或者博客等资料,在这里就不多描述了。 网络: 1.考点:高并发服务器建议调小TCP协议的time_wait超时时间。 重点需要理解tcp协议,三次握手、四次挥手相关概念及状态转换。 阿里巴巴Java开发规约插件安装及使用 image.png 因为我目前使用IDEA开发,这里只简单介绍下在IntelliJ IDEA下的安装及使用,Eclipse的同学可以通过GitHub链接传送门了解: ailibaba/p3c/Eclipse-plugin IDEA插件安装 从插件资源库中安装 1.Settings >> Plugins >> Browse repositories... image.png 通过关键字'alibaba'搜索插件,然后安装'Alibaba Java Coding Guidelines' plugin image.png 重启IDEA即可生效 当然也可以下载插件的zip file 通过本地导入的方式安装,这里就不说了。 使用 1.语言切换,支持中文和英文两种语言可选 image.png 2.自定义检查设定,可以根据自己公司,自身项目团队的实际情况自定义勾选插件需要检查的规约内容 image.png image.png 3.项目代码分析 Code Analyze image.png 我的一个项目使用标准的检查定义分析之后: Code Analyze.png 这个插件做的非常简单,也很轻量,设计者秉承的是开箱即用的设计理念。 写在最后 最后我给大家总结一下以上你将会用到的资源吧,方便大家今后进入。 点击下载《阿里巴巴Java开发手册》v1.4.0(最新详尽版):https://yq.aliyun.com/attachment/download/?id=5585 编码规范考试认证:https://edu.aliyun.com/certification/cldt02 IDE插件下载:https://github.com/alibaba/p3c 完

优秀的个人博客,低调大师

docker学习系列10 开源图形化管理系统

为什么需要docker图形化管理平台? 答:命令行虽然效率高,但不够直观,而且多主机管理不方便。 图形化管理系统还可以和用户角色管理等关联起来。 都有哪些开源免费的docker图形化管理平台? 截至当前(2018年) Rancher 和 portainer 比较火,star数量都将近1w。还有个shipyard,但是作者已经停止维护,并推荐使用前面两款。 portainer 比 Rancher 要轻量,如果刚接触 Docker,建议先使用这个。如果要图形化管理 Kubernetes 就用 Rancher。 portainer - 轻量的 Docker UI管理系统 image.png image.png 先看下 portainer ,以 Windows 为例,portainer 可以运行在容器中,也可以下载编译后的包。比如这里我下载的是 portainer-1.19.2-windows-amd64.tar.gz 下载最新的发行版本 https://github.com/portainer/portainer/releases 解压到新建的portainer目录中,这个目录底下再新建保存数据的目录 portainer_data 打开命令行执行下面的命令,然后浏览器就可以访问了./portainer.exe -p :9000 --template-file templates.json --data ./portainer_data/ 具体细节参考:https://portainer.readthedocs.io/en/latest/deployment.html#quick-start 关于在Windows运行的教程http://blog.airdesk.com/2017/10/windows-containers-portainer-gui.html Rancher - 针对 Kubernetes 企业级管理系统 文档: Rancher 。 下面放几张图: 装好后,打开先让设置管理员密码: image.png 然后让添加一个集群,先修改语言为中文。 image.png 填写信息,呃,好像是配置Kubernates。还没有研究到这里 先到这里吧。有空再研究。 image.png image.png 容器监控工具 cadvisor 有时候需要监控每个容器的运行情况。 google出品了cAdvisor 运行后,可打开web界面查看所有的容器, 镜像。 image.png 点击某容器,可查看具体的CPU,内存,网络,文件系统的运行情况 image.png image.png cAdvisor提供的页面非常简洁。 页面上的数据可以通过他暴露的API直接获取,可以把 cAdvisor 定位为一个监控数据收集器,收集和导出数据是它的强项,而非展示数据。所以可以结合其他工具一块使用。

优秀的个人博客,低调大师

Java 学习(18)--列表(List)/ 集合 (Set)/ 泛型 / Map

List 列表 (1)List 是Collection 的子接口 特点:有序(存储顺序和取出顺序一致 ),可重复。 (2)List 的特有功能: A:添加功能 void add(intindex,Objectelement) :在指定位置添加元素B:删除功能 Objectget(intindex): 获取指定位置的元素 C:获取功能 ListIteratorlistIterator() : List集合特有的迭代器 D:迭代器功能 Objectremove(intindex) :根据索引删除元素 ,返回被删除的元素E:修改功能 Object set(intindex,Objectelement) :根据索引修改元素,返回被修 饰的元素 (3)List 集合的特有遍历功能 A:由 size()和 get()结合。 B:代码演示 List list =newArrayList(); // 创建集合对象 list.add('hello'); list.add('world'); list.add('Java'); // 创建并添加元素 Iterator it = list.iterator(); while (it.hasNext()) { String s =(String) it.next(); System. out .println(s); } System. out .println("----------"); for( int x=0; x<list.size(); x++) { String s =(String) list.get(x); System. out .println(s); } 1.列表迭代器的特有功能 可以逆向遍历,但是要先正向遍历,所以无意义,基本不使用。 2.并发修改异常 A:出现的现象 迭代器遍历集合,集合修改集合元素 B:原因 迭代器是依赖于集合的,而集合的改变迭代器并不知道。 C:解决方案 a:迭代器遍历,迭代器修改 (ListIterator) 元素添加在刚才迭代的位置 b:集合遍历,集合修改 (size() 和get()) 元素添加在集合的末尾 3.常见数据结构 A:栈先进后出 B:队列先进先出 C:数组查询快,增删慢 D:链表查询慢,增删快 4. List 的子类特点 ( 面试题 ) ArrayList 底层数据结构是数组,查询快,增删慢。线程不安全,效率高。 Vector 底层数据结构是数组,查询快,增删慢。线程安全,效率低。 LinkedList 底层数据结构是链表,查询慢,增删快。线程不安全,效率高。 如何使用 分析: 要安全吗 ? 要:Vector(即使要,也不使用这个 ) List<String> list2 = Collections.synchronizedList(new ArrayList<String>()) 不要: ArrayList 或者 LinkedList 查询多; ArrayList增删多: LinkedList (什么都不知道,就用 ArrayList) (5)LinkedList 的特有功能: A:添加功能 public void addFirst(Object e) public void addLast(Object e) B:获取功能 public Object getFirst() public Obejct getLast() C:删除功能 public Object removeFirst() public Object removeLast() Set 集合 1.Set 集合的特点 无序 ,唯一 2.HashSet 集合 A:底层数据结构是哈希表 (是一个元素为链表的数组 ) B:哈希表底层依赖两个方法: hashCode() 和 equals() 执行顺序: 首先比较哈希值是否相同 相同:继续执行 equals()方法 返回 true :元素重复了,不添加返回 false:直接把元素添加到集合 不同:就直接把元素添加到集合 C:如何保证元素 唯一性? 由 hashCode()和 equals() 保证的 D:开发的时候,代码非常的简单,自动生成即可。 E:HashSet存储字符串并遍历 F:HashSet存储自定义对象并遍历 (对象的成员变量值相同即为同一个元素 ) 3.TreeSet 集 合 能够对元素按照某种规则进行排序。 TreeSet 集合的特点:排序和唯一排序有两种方式 A:自然排序 B:比较器排序 A:底层数据结构是红黑树 (是一个自平衡的二叉树 ) B:保证元素的排序方式 a:自然排序(元素具备比较性 ) 让元素所属的类实现 Comparable接口 @Override publicint compareTo(Student s) { // return 0; // return 1; // return -1; 这里返回什么,其实应该根据我的排序规则来做 按照年龄排序 int num = this .age-s.age; //主要条件 //年龄相同的时候,还得去看姓名是否也相同 //如果年龄和姓名都相同,才是同一个元素 int num2 = num == 0; //次要条件 this.name.compareTo(s.name) : num; return num2; }次要条件 //年龄相同的时候,还得去看姓名是否也相同 //如果年龄和姓名都相同,才是同一个元素 int num2 = num == 0; //次要条件 this.name.compareTo(s.name) : num; return num2; } b:比较器排序(集合具备比较性 ) 让集合构造方法接收 Comparator的实现类对象 代码: TreeSet<Student> ts = newTreeSet <Student>(newComparator<Student>(){ @Override publicint compare(Student s1, Student s2) { // 姓名长度 int num = s1.getName().length() - s2.getName().length(); // 姓名内容 int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num; // 年龄 int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2; return num3; } 4.LinkedHashSet 底层数据结构由哈希表和链表组成。 哈希表保证元素的唯一性。 链表保证元素有序。 (存储和取出是一致 ) 泛型(一般是在集合中使用) (1)泛型概述 是一种把明确类型的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。 (2)格式: <数据类型 > 注意:该数据类型只能是引用类型。 (3)好处: A:把运行时期的问题提前到了编译期间 B:避免了强制类型转换 C:优化了程序设计,解决了黄色警告线问题,让程序更安全 (4)泛型的前世今生 A:泛型的由来 Object 类型作为任意类型的时候,在向下转型的时候,会隐含一个转型问题 B:泛型类 C:泛型方法 D:泛型接口 E:泛型高级通配符 ? extends E ? super E //泛型如果明确的写的时候,前后必须一致 Collection<Object> c1 =new ArrayList<Object>(); // Collection<Object> c2 = new ArrayList<Animal>(); // Collection<Object> c3 = new ArrayList<Dog>(); // Collection<Object> c4 = new ArrayList<Cat>(); // ? 表示任意的类型都是可以的 Collection<?> c5 =new ArrayList<Object>(); Collection<?> c6 = new ArrayList<Animal>(); Collection<?> c7 =new ArrayList<Dog>(); Collection<?> c8 =new ArrayList<Cat>(); Map (1)Map:是一种集合、一个接口;可以一对一对往里放数据,map中的数据是有关联关系的,以键值对的形式组织映射关系 Map 特点 * 1:存放的是键值对(夫妻对) * 2:键是无序的不能重复(Set) * 3:值是可以重复的(List) Map和 Collection的区别 ? A:Map 存储的是键值对形式的元素,键唯一,值可以重复。夫妻对 B:Collection 存储的是单独出现的元素, 子接口Set元素唯一,子接口List元素可重复。光棍 Map接口功能概述 A:添加功能 Vput(K key,Vvalue):添加元素。 如果键是第一次存储,就直接存储元素,返回 null 如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值 B:删除功能 void clear() :移除所有的键值对元素 Vremove(Objectkey) :根据键删除键值对元素,并把值返回 C:判断功能 booleancontainsKey(Objectkey) :判断集合是否包含指定的键 booleancontainsValue(Objectvalue): 判断集合是否包含指定的值 booleanisEmpty() :判断集合是否为空 D:获取功能 Set<Map.Entry<K,V>>entrySet(): 获取所有键值对集合 Vget(Objectkey) :根据键获取值 Set<K> keySet(): 获取集合中所有键的集合 Collection<V> values() :获取集合中所有值的集合E:长度功能 intsize() :返回集合中的键值对的对数(map 的长度) 示例: public class MapDemo { public static void main(String[] args) { Map<String,String> map = new HashMap<String,String>(); String value = map.put("杨过", "小龙女"); System.out.println(value); /*value = map.put("杨过", "黄蓉"); System.out.println(value);*/ map.put("郭靖", "黄蓉"); // map.put("杨过", "黄蓉"); map.put("段誉", "王语嫣"); Set<Entry<String, String>> entrySet = map.entrySet(); for(Entry<String, String> entry : entrySet){ String key = entry.getKey(); String value2 = entry.getValue(); System.out.println("key:" + key + ",value:" + value2); } Set<String> keySet = map.keySet(); for(String key : keySet){ String value3 = map.get(key); System.out.println("key:" + key + ",value:" + value3); } 输出: 2.Map集合的遍历 A:键找值 a:获取所有键的集合 b: 遍历键的集合 ,得到每一个键c:根据键到集合中去找值 B:键值对对象找键和值 a:获取所有的键值对对象的集合 b: 遍历键值对对象的集合,获取每一个键值对对象c:根据键值对对象去获取键和值 代码体现: 问题: 1:Hashtable 和 HashMap 的区别 ? Hashtable:线程安全,效率低。不允许 null 键 和 null值 HashMap:线程不安全,效率高。允许 null 键 和 null值 LinkedHashMap可以保证存储顺序与取出顺序一致 2.TreeMap:可排序 A:TreeMap<String,String> B:TreeMap<Student,String> 示例: package com.hd.map; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class MapDemo2 { public static void main(String[] args) { MyMap<String,Integer> myMap = new MyMap<>(); myMap.put("数学", 150); myMap.put("语文", 149); myMap.put("英语", 151); myMap.put("理综", 300); Set<MyMap.Entry<String,Integer>> entrySet = myMap.entrySet(); for(MyMap.Entry<String,Integer> entry : entrySet){ String key = entry.getKey(); Integer value = entry.getValue(); System.out.println("key:" + key + ",value:" + value); } } } class MyMap<K,V>{ private Map<K,V> map = new HashMap<K,V>(); //泛型 public void put(K key,V value){ map.put(key, value); } public Set<Entry<K,V>> entrySet(){ Set<Entry<K,V>> set = new HashSet<Entry<K,V>>(); Set<K> keySet = map.keySet(); for(K key : keySet){ V value = map.get(key); Entry<K,V> entry = new Entry<K,V>(); entry.setKey(key); entry.setValue(value); set.add(entry); } return set; } public static class Entry<K,V>{ private K key; private V value; public K getKey() { return key; } public V getValue() { return value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } } } 输出:

优秀的个人博客,低调大师

Android学习——手把手教你实现Android热修复

版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/80954639 前言 最近一段时间看了一些关于Android热修复的知识,比如Andfix,Tinker,Sophix等,看了这些框架的原理,就想着自己能不能手撸一个简单的demo。下面我们就来自己动手实现Android热修复吧。 热修复实现原理 所谓热修复就是,在我们应用上线后出现小bug需要及时修复时,不用再发新的安装包,只需要发布补丁包,在客户不知不觉之间修复掉bug,JAVA虚拟机JVM在运行时,加载的是.classes的字节码文件。而Android也有自己的虚拟机Dalvik/ART虚拟机,不过他们加载的是dex文件,但是他们的工作原理都一样,都是经过ClassLoader类加载器。Android在ClassLoader的基础上又定义类PathClassLoader和DexClassLoader,两者都继承自BaseDexClassLoader,下面我们看下他们间的区别: * BaseDexClassLoader源代码位于libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java。 * PathClassLoader源代码位于libcore\dalvik\src\main\Java\dalvik\system\PathClassLoader.java。他主要用来加载系统类和应用类。 * DexClassLoader源代码位于libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java。用来加载jar、apk、dex文件.加载jar、apk也是最终抽取里面的Dex文件进行加载. 手写Android热修复框架 下面我们一步一步来实现Android热修复。 写一个专门带有bug的类 既然要测试热修复,我们肯定要写一个带有bug的类。 package com.example.bthvi.mycloassloaderapplication.xxx; import android.content.Context; import android.view.View; import android.widget.Toast; /** * bug测试类 */ public class BugClass { public BugClass(Context context){ Toast.makeText(context,"这是一个优美的bug!",Toast.LENGTH_SHORT).show(); } } 下面我们要写一个热修复的核心工具类。 热修复核心类 package com.example.bthvi.mycloassloaderapplication; import android.content.Context; import android.os.Environment; import android.support.annotation.NonNull; import android.widget.Toast; import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.HashSet; import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader; public class FixDexUtil { private static final String DEX_SUFFIX = ".dex"; private static final String APK_SUFFIX = ".apk"; private static final String JAR_SUFFIX = ".jar"; private static final String ZIP_SUFFIX = ".zip"; public static final String DEX_DIR = "odex"; private static final String OPTIMIZE_DEX_DIR = "optimize_dex"; private static HashSet<File> loadedDex = new HashSet<>(); static { loadedDex.clear(); } /** * 加载补丁,使用默认目录:data/data/包名/files/odex * * @param context */ public static void loadFixedDex(Context context) { loadFixedDex(context, null); } /** * 加载补丁 * * @param context 上下文 * @param patchFilesDir 补丁所在目录 */ public static void loadFixedDex(Context context, File patchFilesDir) { // dex合并之前的dex doDexInject(context, loadedDex); } /** *@author bthvi *@time 2018/6/25 0025 15:51 *@desc 验证是否需要热修复 */ public static boolean isGoingToFix(@NonNull Context context) { boolean canFix = false; File externalStorageDirectory = Environment.getExternalStorageDirectory(); // 遍历所有的修复dex , 因为可能是多个dex修复包 File fileDir = externalStorageDirectory != null ? new File(externalStorageDirectory,"007"): new File(context.getFilesDir(), DEX_DIR);// data/data/包名/files/odex(这个可以任意位置) File[] listFiles = fileDir.listFiles(); if (listFiles != null){ for (File file : listFiles) { if (file.getName().startsWith("classes") && (file.getName().endsWith(DEX_SUFFIX) || file.getName().endsWith(APK_SUFFIX) || file.getName().endsWith(JAR_SUFFIX) || file.getName().endsWith(ZIP_SUFFIX))) { loadedDex.add(file);// 存入集合 //有目标dex文件, 需要修复 canFix = true; } } } return canFix; } private static void doDexInject(Context appContext, HashSet<File> loadedDex) { String optimizeDir = appContext.getFilesDir().getAbsolutePath() + File.separator + OPTIMIZE_DEX_DIR; // data/data/包名/files/optimize_dex(这个必须是自己程序下的目录) File fopt = new File(optimizeDir); if (!fopt.exists()) { fopt.mkdirs(); } try { // 1.加载应用程序dex的Loader PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader(); for (File dex : loadedDex) { // 2.加载指定的修复的dex文件的Loader DexClassLoader dexLoader = new DexClassLoader( dex.getAbsolutePath(),// 修复好的dex(补丁)所在目录 fopt.getAbsolutePath(),// 存放dex的解压目录(用于jar、zip、apk格式的补丁) null,// 加载dex时需要的库 pathLoader// 父类加载器 ); // 3.开始合并 // 合并的目标是Element[],重新赋值它的值即可 /** * BaseDexClassLoader中有 变量: DexPathList pathList * DexPathList中有 变量 Element[] dexElements * 依次反射即可 */ //3.1 准备好pathList的引用 Object dexPathList = getPathList(dexLoader); Object pathPathList = getPathList(pathLoader); //3.2 从pathList中反射出element集合 Object leftDexElements = getDexElements(dexPathList); Object rightDexElements = getDexElements(pathPathList); //3.3 合并两个dex数组 Object dexElements = combineArray(leftDexElements, rightDexElements); // 重写给PathList里面的Element[] dexElements;赋值 Object pathList = getPathList(pathLoader);// 一定要重新获取,不要用pathPathList,会报错 setField(pathList, pathList.getClass(), "dexElements", dexElements); } Toast.makeText(appContext, "修复完成", Toast.LENGTH_SHORT).show(); } catch (Exception e) { e.printStackTrace(); } } /** * 反射给对象中的属性重新赋值 */ private static void setField(Object obj, Class<?> cl, String field, Object value) throws NoSuchFieldException, IllegalAccessException { Field declaredField = cl.getDeclaredField(field); declaredField.setAccessible(true); declaredField.set(obj, value); } /** * 反射得到对象中的属性值 */ private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true); return localField.get(obj); } /** * 反射得到类加载器中的pathList对象 */ private static Object getPathList(Object baseDexClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList"); } /** * 反射得到pathList中的dexElements */ private static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException { return getField(pathList, pathList.getClass(), "dexElements"); } /** * 数组合并 */ private static Object combineArray(Object arrayLhs, Object arrayRhs) { Class<?> clazz = arrayLhs.getClass().getComponentType(); int i = Array.getLength(arrayLhs);// 得到左数组长度(补丁数组) int j = Array.getLength(arrayRhs);// 得到原dex数组长度 int k = i + j;// 得到总数组长度(补丁数组+原dex数组) Object result = Array.newInstance(clazz, k);// 创建一个类型为clazz,长度为k的新数组 System.arraycopy(arrayLhs, 0, result, 0, i); System.arraycopy(arrayRhs, 0, result, i, j); return result; } } 我们这里暂且指定热修复目录007,下面我们看一下如何调用。 Splash页面调用检查热修复 private void init() { File externalStorageDirectory = Environment.getExternalStorageDirectory(); // 遍历所有的修复dex , 因为可能是多个dex修复包 File fileDir = externalStorageDirectory != null ? new File(externalStorageDirectory,"007"): new File(getFilesDir(), FixDexUtil.DEX_DIR);// data/user/0/包名/files/odex(这个可以任意位置) if (!fileDir.exists()){ fileDir.mkdirs(); } if (FixDexUtil.isGoingToFix(this)) { FixDexUtil.loadFixedDex(this, Environment.getExternalStorageDirectory()); textView.setText("正在修复。。。。"); } new Handler().postDelayed(new Runnable() { @Override public void run() { startActivity(new Intent(SplashActivity.this,MainActivity.class)); finish(); } },3000); } 下面我们先来看下有bug时的APP。 在出bug的对应类修复bug package com.example.bthvi.mycloassloaderapplication.xxx; import android.content.Context; import android.view.View; import android.widget.Toast; /** * bug测试类 */ public class BugClass { public BugClass(Context context){ Toast.makeText(context,"你很优秀!bug已修复��",Toast.LENGTH_SHORT).show(); } } 修改好bug之后我们需要打出补丁包。 打出热修复补丁包 在AndroidStudio里面关闭掉Instant_Run 由于Android Studio的instan run的原理也是热修复,所以安装的时候不会安装完整的安装包,只会安装新改变的代码。 重新编译并拷贝出新修改的类 首先点击Build->RebuildProject来重新构建,构建完成之后,可以在app/build/interintermediate/debug/包名/找到你刚刚修改的class文件,将他拷贝出来,要连同包名路径一起拷贝出来。 将class文件打包成dex文件 我们前面知道热修复的原理是Dalvik/ART加载dex文件,所以接下来我们要将class文件打包成dex文件,首先我们找到AndroidSDK的build-tools 目录下,在控制台下进入该目录下的任意一个版本,执行dx命令,关于dx命令的使用帮助可以使用dx -- help,下面们通过 dx --dex [指定输出路径]/classes.dex [刚才拷贝的修复bug的类及包名的目录]这样我们就得到了.dex文件。 将打出来的dex文件放至我们指定的目录下 我们将打出来的dex文件放在我们指定的目录007下,当然这个目录也可以是包名。 重新启动有bug的APP 我们启动就会后发现bug已经修复了

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册