中企动力 > 商学院 > java 读取excel
  • ?

    Python——一个简单的自动化测试框架

    方静芙

    展开

    这是柠檬班的第171篇文章

    最近办公室的开发哥哥,每天都嚣张的很,说让我弄死他。于是我本着善良助人的天性,来学习接口自动化,并准备在他快下班的时候,给他多提几十个bug。嗯,就是这样,对的,没错

    开始接口自动化首先,我们要选择一门语言,测试人员的标配,当然是Python3,不要问为什么,简单粗暴,上手快没有基础的同学可以先去学习一下python基础。首推菜鸟教程和廖雪峰接下来我们看一下我们的接口自动化框架的主要构成。嘿嘿嘿

    接口自动化框架一共分为6个模块common:公共模块,包括配置和共用的代码source:接口封装的类test_case:存放单元测试用例test_data:存放测试数据(这里用的是Excel)test_report:存放测试报告的地址test_result:存放测试结果的地址

    公共类首先开始编写我们的公共类

    HTTP类这里封装了2个函数,一个get请求,一个post请求,并以data形式返回测试结果

    读取配置文件的类其实就是为了把什么固定的ip地址,端口,数据库连接信息啦放在配置文件里面,并通过类读取调用,实现数据分离,更加灵活

    读取Excel的类将测试数据放在Excel中,通过Excel进行数据管理,如下图

    存放测试数据然后通过Excel类读取调用,这些就实现了Excel管理测试用例,然后想测什么接口,就直接在Excel里面加就行。想想都爽,成功的走向了,弄死同事的第一步。

    这是我们的核心代码

    将测试结果写入Excel,并生成HTML测试报告发送到邮箱,这样,我们就可以坐在靠椅上,喝着咖啡,看着代码,然后等着邮件,最后再提上一大堆bug,嗯,大概生活就是如此的美好

    最后让我们来执行我们的main函数对,没错,真相只有一个。见证奇迹的时候到了,然后就会生成我们想要的测试报告和测试结果

    主函数入口

    执行main的日志测试报告呐,一个崭新的测试报告就这么出来了,惊不惊喜,意不意外。就这样我们一起走向弄死同事的不归路。(ps:其实我们还是很相亲相爱的,当然,是他死我活)

    测试报告下次有时间会再聊上五毛钱的基于Jenkins持续集成自动化的实践,当然这都是自己瞎捣鼓的,有大神的,请留言,一起共同进步。

  • ?

    处理Java大数据有什么技巧

    萧依波

    展开

    Java在处理数据量比较大的时候,加载到内存必然会导致内存溢出,而在一些数据处理中我们不得不去处理海量数据,那Java是如何处理大数据的呢?有什么技巧吗?在做数据处理中,我们常见的手段是分解,压缩,并行,临时文件等方法。

    例如,我们要将数据库(不论是什么数据库)的数据导出到一个文件,一般是Excel或文本格式的CSV;对于Excel来讲,对于POI和JXL的接口,你很多时候没有办法去控制内存什么时候向磁盘写入,很恶心,而且这些API在内存构造的对象大小将比数据原有的大小要大很多倍数,所以你不得不去拆分Excel,还好,POI开始意识到这个问题,在3.8.4的版本后,开始提供cache的行数,提供了SXSSFWorkbook的接口,可以设置在内存中的行数,不过可惜的是,他当你超过这个行数,每添加一行,它就将相对行数前面的一行写入磁盘(如你设置2000行的话,当你写第20001行的时候,他会将第一行写入磁盘),其实这个时候他的临时文件,以至于不消耗内存,不过这样你会发现,刷磁盘的频率会非常高,我们的确不想这样,因为我们想让他达到一个范围一次性将数据刷入磁盘,比如一次刷1M之类的做法,可惜现在还没有这种API,很痛苦,我自己做过测试,通过写小的Excel比使用目前提供刷磁盘的API来写大文件,效率要高一些,而且这样如果访问的人稍微多一些磁盘IO可能会扛不住,因为IO资源是非常有限的,所以还是拆文件才是上策;而当我们写CSV,也就是文本类型的文件,我们很多时候是可以自己控制的,不过你不要用CSV自己提供的API,也是不太可控的,CSV本身就是文本文件,你按照文本格式写入即可被CSV识别出来;如何写入呢?下面来说说。。。

    在处理数据层面,如从数据库中读取数据,生成本地文件,写代码为了方便,我们未必要1M怎么来处理,这个交给底层的驱动程序去拆分,对于我们的程序来讲我们认为它是连续写即可;我们比如想将一个1000W数据的数据库表,导出到文件;此时,你要么进行分页,Oracle当然用三层包装即可,MySQL用limit,不过分页每次都会新的查询,而且随着翻页,会越来越慢,其实我们想拿到一个句柄,然后向下游动,编译一部分数据(如10000行)将写文件一次(写文件细节不多说了,这个是最基本的),需要注意的时候每次buffer的数据,在用outputstream写入的时候,最好flush一下,将缓冲区清空下。

    来设置游标的方式,以至于游标不是将数据直接cache到本地内存,然后通过设置statement.setFetchSize(200);设置游标每次遍历的大小;OK,这个其实我用过,oracle用了和没用没区别,因为oracle的jdbc API默认就是不会将数据cache到java的内存中的,而mysql里头设置根本无效,我上面说了一堆废话,呵呵,我只是想说,java提供的标准API也未必有效,很多时候要看厂商的实现机制,还有这个设置是很多网上说有效的,但是这纯属抄袭;对于oracle上面说了不用关心,他本身就不是cache到内存,所以java内存不会导致什么问题,如果是mysql,首先必须使用5以上的版本,然后在连接参数上加上useCursorFetch=true这个参数,至于游标大小可以通过连接参数上加上:defaultFetchSize=1000来设置,

    上次被这个问题纠结了很久(mysql的数据老导致程序内存膨胀,并行2个直接系统就宕了),还去看了很多源码才发现奇迹竟然在这里,最后经过mysql文档的确认,然后进行测试,并行多个,而且数据量都是500W以上的,都不会导致内存膨胀,GC一切正常,这个问题终于完结了。

    Java在处理大数据的时候还是有一些小技巧的,正确运用这些小技巧,有时候在处理问题时往往会事半功倍。

  • ?

    送给那些有代码基础但仍旧不会学自动化测试的朋友们

    元芹

    展开

    我经常遇到这样的场景:老师,我有python or java基础,但还是不知道怎么写代码?老师,我有代码基础,简单的不用学,我想学高级的。老师,我有编程基础,但是我看不懂代码。。。。。。诸如此类的问题,我想大家也一定有这些内心的OS

    面对这样的场景,这样的问题,我也问了不少童鞋,得到的答案其实听悲哀的。莫过于说些代码太难了,不会不懂;这个代码写的不详细导致我看不懂;我有编程基础的,是你写的代码太烂了。。。。。诸如此类把问题全部归于他因的。。。可悲之处明理之人应该懂的了。。。。

    好了,言归正传吧,就类似于这样的问题我这里统一说说我的想法和学习技巧吧,有不对的地方请多多包涵,我也是low逼之人,所以难免也有说的不对不妥的地方,喜欢赞之、转之,不喜欢笑之、弃之即可。

    1、学习自动化测试,或者说些代码,核心是学习思想,实现一个框架或功能的思想,没有这个思想你就算会77种编程语言都没卵用。那这里说的思想是什么呢?举个栗子,你想完成一个从excel读取用户名和密码并进行登录的case,你要怎么设计,这就是思路。

    大部分我见的童鞋包括我带的学员,一上来就是写代码,其实这是最忌讳的,构思逻辑、代码结构才是第一步,最后一步才是写代码,本末倒置,你说能写的出来吗?

    2、所谓我有编码基础,在我碰到的大部分人中,可以解读为我学会编程,但现在忘了,我会hello world,这个不算有基础,撑死算有编码的概念而已。这也就是你为啥写不出来,看不懂的最根本原因。

    上面指出了大家的问题,那么接下来就I看看应对方法。这里我特别想多说一句:能真诚的给你指出问题的人值得感恩(虽然刺耳),多去提升自己的不足少去抱怨别人的不好是使你成为大神最快的捷径。

    学习自动化测试或编程基本的路径:

    第一条:认知(包括概念,意义等)>熟悉某一门编程语言(python or java)>从简单固定的例子开始不断练习(跑起来最重要,理解最不重要)>练习中培养自己解决问题的思路(理解的过程)>组织更复杂的框架>不断总结

    第二条:模仿>跑起来>理解>转换为自己的

    1、自动化测试的认知我就不多说了,在我的新书《小强软件测试疯狂讲义》中已经详细说过了

    2、熟悉一门编程语言,注意我的用词是熟悉,不是知道,不是了解,不是会hello world,而是你可以熟练的随便写一个简单的demo出来,掌握了一般的编程、调试方法。至于大家纠结的学python还是java,没所谓的,都可以,你喜欢哪个就学哪个,有java基础的就学java,没有的学python入门会快点。编程语言这个东西本质都一样,只是语法有差异,思想都想通的。

    3、不要看不上简单的例子,有多少人口口声声说的我有代码基础,结果却学的一塌涂地。。。。用个词语形容:眼高手低。学代码就要先模仿,让代码跑起来,然后再去理解。很多童鞋一上来就要理解理解,为什么为什么,代码都没跑起来理解各毛线啊,为什么个p啊(sorry,情不自禁的爆粗口了,哈哈,原谅我的任性)

    这和你学开车一样,模仿,练习,慢慢的明白汽车的各个原理,加什么油,为什么后排中间有突起啥的了。我经常和我的学员说,学习最忌讳的是开始就陷入细节,一味追求理解。因为很多所谓的理解是需要你有足够的知识、阅历和经历沉淀才能做到的!

    4、回到上面问的那个登陆的问题,我们就说说核心的思想。拿到这个问题不是去实现写代码,而是去想实现他的思路和构成,这个过程不涉及具体的细节,不考虑是否能真的实现。我们来看下这个过程:

    a、拆分问题。本题可拆分为python读取excel(还要考虑excel怎么设计更方便读取)、读取出来的数据如何存储、存储之后的数据如何放到对应的用户名和密码上

    b、逐步实现。以selenium为例,先把用户名和密码元素识别出来且写死数据,至少保证代码可以运行起来。不要上来就想我用户名要是好几个怎么实现呀。一个写死的用户名你都没跑起来代码想那么多干毛线啊。

    c、实现python读取excel数据并保存

    d、用读取保存的数据去替换对应的用户名和密码

    e、完善代码加入异常处理机制。不要一开始就想报错了怎么办,用户名密码不对了怎么办,测试的基本思想怎么关键时间都丢失了呢。。。做功能测试我们都知道先正,后反啊,怎么到了写代码的时候就忘记了呢。

    f、运行好了,理解了,可以慢慢在优化,比如加入unittest、htmlreport等

    在整个上述的过程中,很多代码都是固定的,比如unittest的用法格式就是固定的,先记住怎么用,用起来再说。很多人老学别人去看源码,你的有一定基础了才行啊,不然看源码简直是雪上加霜。

    最后,学习是一个过程,是一个步骤,和生成一辆汽车一样,不要跳着学习,能跳级的童鞋毕竟是少数,踏踏实实的一步步学,越往后你会发现效率越高,有时候我们以为的我们以为真的不是我们以为的那样。。。。。。就和你开车,你的先让车跑起来才能想怎么变道安全,怎么超车安全,怎么侧方停车啊。。。。

    PS:有些东西文字不好表达,还的靠大家意会。。。。。。。幸苦大家了

  • ?

    基础知识整理之Java基础知识点梳理(详细)

    Heidi

    展开

    基本数据类型Java内置8种基本类型 -> 6种数字类型 -> 4种整数类型: byte, short, int, long-> 2种浮点类型: float, double-> 1种布尔类型: boolean-> 1种字符类型: char(类型大小写注意,比如boolean是原始数据类型,Boolean是对应的封装对象)

    数据类型特征表

    类型位数最小值最大值默认值其他byte8-128(-2^7)127(2^7-1)0有符号、二进制补码表示short16-32768(-2^15)32767(2^15-1)0有符号、二进制补码表示int32-2^312^31-10有符号、二进制补码表示long64-2^632^63-10L(0l)有符号、二进制补码表示float322^(-149)2^128-10.0f单精度、IEEE754标准double642^(-1074)2^1024-10.0d双精度、IEEE754标准char16\u0000(0)\uffff(65535)\u0000(0)单一的、Unicode字符

    浮点数内存结构

    类型位数符号位指数位尾数位float321823double6411152

    原始数据类型对应的封装对象(byte, Byte), (short, Short), (long, Long), (float,Float), (double, Double), (boolean, Boolean)(int, Integer), (char, Character)小题Integer i = null;int j = i.intValue();

    编译通过,但运行时报错NullPointerException。因为调用了null.somemethod()。自动装箱和拆箱Integer i = 100; //自动装箱,编译器执行Integer.valueOf(100)int j = i; //自动拆箱,编译器执行i.intValue()

    小题Integer i1 =200;Integer i2 =200;System.out.println("i1==i2: "+(i1==i2));Integer i3 =100;Integer i4 =100;System.out.println("i3==i4: "+(i3==i4));

    运行结果为false,true.首先,==和equals()的区别:==比较的是两个对象的引用是否相同,或者是比较原始数据类型是否相等;equals()比较的是两个对象的内容是否相同。其次,-128~127的Integer值可以从缓存中取得。其他情况要重新创建。

    小题String str1 ="abc";String str2 ="abc";System.out.println(str2==str1);System.out.println(str2.equals(str1));String str3 =new String("abc");String str4 =new String("abc");System.out.println(str3==str4);System.out.println(str3.equals(str4));

    结果是true,true, false, true.前两个String对象从String池中获取,后两个对象是新创建的,内容相同但引用不同。小题String d ="2";String e ="23";e = e.substring(0, 1);System.out.println(e.equals(d));System.out.println(e==d);

    结果是true,false.直接上源码:

    关于基本数据类型的几点补充

    short s1=1; s1=s1+1;

    这一句编译错误,因为执行s1+1返回的结果是int类型(执行隐式类型转换)。修改的话要强制转换为short型才可以。short s1=1; s1+=4;这一句没有任何问题。

    switch语句不能作用于long类型中,可以作用于char, byte, short, int, Character, Byte, Short, Integer, String or an enum.

    小题public static void main(String[] args) throws Throwable {int j=0;for(int i=0;i<1000;i++) {j=j++;}System.out.println(j);}

    运行结果为0。解释一(未十分确信):Java使用中间缓存变量机制,j=j++语句会执行为:temp=j;j=j+1;j=temp;

    解释二(靠谱):使用javap反汇编命令进行反汇编,其中j=j++对应的结果是(j对应的变量编号是1):11: iload_1 //将局部变量j的值放到栈顶:012: iinc 1, 1 //将局部变量j的值加1,j=115: istore_1 //将栈顶的值放到局部变量j中,j=0所以从底层实现看,j=j++这一句中的自增操作只是对局部变量的操作,局部变量变化后没有存储到栈顶,反而被之前栈顶的值覆盖了,所以相当于不起作用。

    关键字

    final:修饰变量时,用以定义常量;修饰方法时,方法不能被重写(Override);修饰类时,类不能被继承。

    数组

    3种创建方式int[] arr1 = {1,2,3,4}; //正确int[] arr2 = new int[4]; //正确int[] arr3 = new int[]{1,2,3,4}; //正确int[] arr4 = new int[4]{1,2,3,4};s //错误,编译不通过

    数组越界,抛出ArrayIndexOutOfBoundsException数组具有length属性如果不对数组指定初值,默认初始化为相应数据类型的默认值。多维数组,嵌套中括号即可。

    String

    不属于基本类型创建String s1="ss"; //先将"ss"存储在池中,然后返回引用String s2=new String("ss"); //创建新对象,使用的"ss"已经在池中String s3="Prog"+"gramming"; //创建3个对象,均存储在池中

    字符串常量池和不可变(Immutable)字符串

    小题String str1 = "str";String str2 = "ing";String str3 = "str" + "ing";String str4 = str1 + str2;System.out.println(str3 == str4);String str5 = "string";System.out.println(str3 == str5);

    结果是false,true。后一个结果应该很好理解,因为第3行代码执行时已经将”string”存储在常量池中,第6行代码返回的是常量池中同一字符串”string”的引用,所以结果为true。前一个结果可能有点疑惑,后来把这段代码编译反汇编后发现执行第3行代码时是直接生成的String对象,内容为”string”;而执行第四行代码时是借助new StringBuilder().append(“str”).append(“ing”).toString(),关键在于StringBuilder中定义的toString()方法,返回的是一个重新创建的String对象,并没有存在池中,所以前一个结果为false。

    基本数据类型封装类的parse方法可能会报NumberFormatException,比如Integer.parseInt("era");intern()方法:若由equals()判断池中包含相同的对象则返回池中该对象的引用,否则将新对象加入池中并返回引用。注意不管是怎么创建的,都先从池里找。对于字符串拼接,考虑性能,如果循环进行拼接操作,生成的字符串都会存在池里,并且每次拼接都要重新构造StringBuilder对象,影响性能。因此可以使用StringBuilder优化:StringBuilder builder...append(String.valueOf("fsdasf"));1StringBuilder/StringBufferStringBuilder线程不安全,但效率高;StringBuffer线程安全,但效率低。

    集合

    类型:List, Set, Map,都是接口而非具体实现。List特点是有序并且按线性方式存储,可以根据元素的下标进行精准的控制。ArrayList是顺序表的体现,底层用数组实现。

    LinkedList是链表的体现,使用双向链表实现。(链表的三种形式:单向链表、双向链表、循环链表)

    Stack是Vector的子类,而Vector实现了List接口;是栈结构的代表。

    Queue队列Queue直接继承自Collection接口,是队列结构的代表,使用链表结构实现。

    (注:Queue继承自Collection接口,而LinkedList实现了Deque接口,Deque接口继承自Queue接口,即实现了Queue接口的所有方法)Queue接口的主要方法:add, offer(添加元素), poll(返回并删除队列头部元素)。根据offer()方法的官方注解,更加推荐使用offer()方法:

    Set直接继承自Collection接口,HashSet内部使用HashMap实现;特点是无序但是不包含重复的元素;元素存放方式为散列存放,即通过计算hash值存放元素。MapMap单独为一个接口,HashMap是基于哈希表的对Map接口的实现,而哈希表的底层数据结构是数组和链表;特点是能够根据key快速查找value;键必须唯一,put一个键相同的值时该键会被更新,即同一个键只能映射到一个值。键值对以Entry类型的对象实例存在(注:Entry是Map接口中定义的内部接口)

    面向对象

    三大特征:封装、继承和多态构造函数:创建对象时调用。若未显式定义构造函数,系统自动生成无参构造函数。重载:发生在一个类中,函数名相同,参数列表不同(类型/个数)重写:发生在两个类中,函数名相同,参数列表相同。继承:初始化子类时先初始化父类,即调用构造函数时隐式执行父类构造函数。单继承性:Java允许一个类只能有一个父类。super关键字:既可以作为父类对象的引用调用父类方法,也可以作为父类构造函数名显式调用父类构造函数。垃圾回收:对象被回收时会调用finalize()方法。垃圾回收机制:当垃圾回收器(Garbage Collector)认定对象没有任何引用时会将其回收,在回收前调用finalize方法。但是《Java编程思想》中还提到了这样一句话:

    看起来Java的垃圾清理机制并不是那么完美。调用System.gc()方法可以强制进行垃圾回收。附一篇讲Java垃圾回收机制比较好的文章多态:Employee e=new Manager();假定Employee类没有定义abc(),而Manager类定义abc(),如果调用e.abc()则编译不通过。封装:访问修饰权限:private, default, public, protected

    修饰符当前类同一包内子孙类其他包其他包子孙类publicYYYYYprotectedYYYNY/N(说明)defaultYYNNNprivateYNNNNprotected关键字特别说明:

    子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。

    抽象类和接口

    抽象类不能被实例化,实例化由子类完成;但是抽象类也是类,可以有构造方法!抽象类不一定要有抽象方法;抽象方法所在的类一定是抽象类;abstract和final不能同时修饰一个类,否则编译不通过。因为final声明的类不能被继承,如果同时修饰意味着这个类永远是抽象类。abstract不能与private, static(?), final, native(?)的同时修饰一个方法,否则编译不通过。模板方法模式

    接口是更高级别的抽象,接口中的方法必须都是抽象方法;接口中声明的方法都是没有方法主体的;接口中的属性默认修饰符public static final,方法默认修饰符public abstract,这些都可以省略。一个类可以实现多个接口,但只能继承一个父类。接口和抽象类的主要区别抽象类是一种对事物的抽象,而接口是一种对行为的抽象;

    抽象类是一种模板式设计,而接口是一种行为规范,是一种辐射式设计。模板式设计的含义是:如果B和C都使用了公共模板A,如果他们的公共部分需要改动,那么只改动A就可以了;辐射式设计的含义是:如果B和C都实现了公共接口A,如果现在要向A中添加新的方法,那么B和C都必须进行相应改动。

    异常处理

    Error和Exception, checked异常和unchecked异常Error(unchecked异常)

    Exception(checked异常)

    (注:以上内容引用他人文章,原作者声明:作者:胖先森链接:https://juejin.im/post/5a81941ff265da4e976e9004来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。)try-catch-finally可以用多个catch子句处理不同异常,级别由低到高。在Java SE 7或者更高版本中,一个catch块可以同时处理多种异常类型,有助于减少重复代码。//...} catch (ClassNotFoundException | SQLException e) {e.printStackTrace();}

    注意当catch语句要捕获多种异常类型时,参数(在这里是e)隐式地成为final变量。方法声明异常throws,抛出异常throw。try-with-resources子句(Java SE 7以后)援引官网的介绍吧:

    线程

    线程是操作系统调度的最小单元,也叫轻量级进程。同一进程可以创建多个线程,而他们都拥有各自的计算器、堆栈和局部变量等属性,并且能够访问共享的内存变量。线程的5个状态创建(New):使用new关键字创建一个线程就绪(Ready):调用start方法,等待CPU调度运行(Running):执行run方法阻塞(Blocked):由于一些原因放弃CPU使用权,暂时停止执行死亡(Dead):run方法执行完毕或者执行时产生异常几个重要的名词区分同步和异步同步方法调用后必须等待方法返回才能执行后续行为;异步方法调用后可以立刻执行后续行为。并发和并行并行是真正意义上的多个任务同时执行;并发是支持处理多个任务,不一定要同时,多个任务可能是串行的,但每个任务只能获取CPU很短的占用时间,多个任务在很短的时间内交替执行。

    阻塞和非阻塞阻塞是指某一线程访问一公共资源时其他线程必须等待该线程释放资源才可以使用,否则就要挂起线程等待;非阻塞是指线程之间不会发生资源争夺。原子性原子性是指一个操作是不可被中断的,即使多个线程是同时执行的。可见性可见性是指当某个线程修改了共享变量的值,其他线程能否立刻知晓。有序性

    Java中提供了关键字volatile和synchronized关键字来保证线程之间操作的有序性。volatile包含了禁止指令重排序的语义,并保证不同线程对同一变量操作时的可见性;synchronized关键字对同一时刻同一变量只允许一个线程对其进行lock操作。线程创建继承Thread类重写run()方法匿名内部类,即在new Thread()后跟具体的定义体,其中重写了run()方法实现Runnable接口,重写run()方法开启线程的run()和start()方法区别run()方法不能新建一个线程,而是在当前线程中调用run()方法;start()方法新建一个线程并调用其run()方法。终止线程不要使用stop()

    wait() 和notify()这两个方法不是Thread类特有的,而是所有类的父类Object中的。

    (注:以上内容援引他人文章,原作者声明:作者:kk_miles链接:https://juejin.im/post/5a30a7466fb9a0450f21ec5c来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。)小题public class WaitNotifyDemo {final static Object object = new Object();public static class Thread1 extends Thread{@Overridepublic void run() {synchronized (object){System.out.println(System.currentTimeMillis()+" 线程1开启。。");try {object.wait(); //1.先获取object的锁,然后开始等待,并再次释放object的锁。} catch (InterruptedException e) {e.printStackTrace();}System.out.println(System.currentTi...

  • ?

    「教程」终于有人把Java内存模型说清楚了!

    逐浪客

    展开

    网上有很多关于 Java 内存模型的文章,但是很多人读完之后还是搞不清楚,甚至有的人说自己更懵了。

    本文就来整体的介绍一下 Java 内存模型,读完本文以后,你就知道到底 Java 内存模型是什么,为什么要有 Java 内存模型,Java 内存模型解决了什么问题等。

    本文中很多说法都是笔者自己理解后定义出来的。希望能够让读者可以对 Java 内存模型有更加清晰的认识。

    为什么要有内存模型

    在介绍 Java 内存模型之前,我们先来看一下到底什么是计算机内存模型,然后再来看 Java 内存模型在计算机内存模型的基础上都做了哪些事情。

    要说计算机的内存模型,就要说一段古老的历史,看一下为什么要有内存模型。

    内存模型:英文名 Memory Model,它是一个老古董了。它是与计算机硬件有关的一个概念。那么,我先介绍下它和硬件到底有啥关系。

    CPU 和缓存一致性

    我们应该知道,计算机在执行程序的时候,每条指令都是在 CPU 中执行的,而执行的时候,又免不了和数据打交道。

    而计算机上面的数据,是存放在主存当中的,也就是计算机的物理内存。

    刚开始,还相安无事,但是随着 CPU 技术的发展,CPU 的执行速度越来越快。

    而由于内存的技术并没有太大的变化,所以从内存中读取和写入数据的过程和 CPU 的执行速度比起来差距就会越来越大,这就导致 CPU 每次操作内存都要耗费很多等待时间。

    这就像一家创业公司,刚开始,创始人和员工之间工作关系其乐融融,但是随着创始人的能力和野心越来越大,逐渐和员工之间出现了差距,普通员工越来越跟不上 CEO 的脚步。

    老板的每一个命令,传达到基层员工之后,由于基层员工的理解能力、执行能力的欠缺,就会耗费很多时间。这也就无形中拖慢了整家公司的工作效率。

    可是,不能因为内存的读写速度慢,就不发展 CPU 技术了吧?总不能让内存成为计算机处理的瓶颈吧?

    所以,人们想出来了一个好的办法,就是在 CPU 和内存之间增加高速缓存。

    缓存的概念大家都知道,就是保存一份数据拷贝。它的特点是速度快,内存小,并且价格昂贵。

    那么,程序的执行过程就变成了:程序在运行过程中,会将运算需要的数据从主存复制一份到 CPU 的高速缓存当中。

    那么 CPU 进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。

    之后,这家公司开始设立中层管理人员,管理人员直接归 CEO 领导,领导有什么指示,直接告诉管理人员,然后就可以去做自己的事情了。管理人员负责去协调底层员工的工作。

    因为管理人员是了解手下的人员以及自己负责的事情的。所以大多数时候,公司的各种决策,通知等,CEO 只要和管理人员之间沟通就够了。

    而随着 CPU 能力的不断提升,一层缓存就慢慢的无法满足要求了,就逐渐的衍生出多级缓存。

    按照数据读取顺序和与 CPU 结合的紧密程度,CPU 缓存可以分为一级缓存(L1),二级缓存(L2),部分高端 CPU 还具有三级缓存(L3),每一级缓存中所储存的全部数据都是下一级缓存的一部分。

    这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。

    那么,在有了多级缓存之后,程序的执行就变成了:当 CPU 要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。

    随着公司越来越大,老板要管的事情越来越多,公司的管理部门开始改革,开始出现高层,中层,底层等管理者。一级一级之间逐层管理。

    单核 CPU 只含有一套 L1,L2,L3 缓存;如果 CPU 含有多个核心,即多核 CPU,则每个核心都含有一套 L1(甚至和 L2)缓存,而共享 L3(或者和 L2)缓存。

    公司也分很多种,有些公司只有一个大 Boss,他一个人说了算。但是有些公司有比如联席总经理、合伙人等机制。

    单核 CPU 就像一家公司只有一个老板,所有命令都来自于他,那么就只需要一套管理班底就够了。

    多核 CPU 就像一家公司是由多个合伙人共同创办的,那么,就需要给每个合伙人都设立一套供自己直接领导的高层管理人员,多个合伙人共享使用的是公司的底层员工。

    还有的公司,不断壮大,开始拆分出各个子公司。各个子公司就是多个 CPU 了,互相之前没有共用的资源。互不影响。

    下图为一个单 CPU 双核的缓存结构:

    随着计算机能力不断提升,开始支持多线程。那么问题就来了,我们分别来分析下单线程、多线程在单核 CPU、多核 CPU 中的影响。

    单线程:CPU 核心的缓存只被一个线程访问。缓存独占,不会出现访问冲突等问题。

    单核 CPU,多线程:进程中的多个线程会同时访问进程中的共享数据,CPU 将某块内存加载到缓存后,不同线程在访问相同的物理地址的时候,都会映射到相同的缓存位置,这样即使发生线程的切换,缓存仍然不会失效。

    但由于任何时刻只能有一个线程在执行,因此不会出现缓存访问冲突。

    多核 CPU,多线程:每个核都至少有一个 L1 缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行,则每个核心都会在各自的 Cache 中保留一份共享内存的缓冲。

    由于多核是可以并行的,可能会出现多个线程同时写各自的缓存的情况,而各自的 Cache 之间的数据就有可能不同。

    在 CPU 和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核 CPU 中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致。

    如果这家公司的命令都是串行下发的话,那么就没有任何问题。

    如果这家公司的命令都是并行下发的话,并且这些命令都是由同一个 CEO 下发的,这种机制是也没有什么问题。因为他的命令执行者只有一套管理体系。

    如果这家公司的命令都是并行下发的话,并且这些命令是由多个合伙人下发的,这就有问题了。

    因为每个合伙人只会把命令下达给自己直属的管理人员,而多个管理人员管理的底层员工可能是公用的。

    比如,合伙人 1 要辞退员工 a,合伙人 2 要给员工 a 升职,升职后的话他再被辞退需要多个合伙人开会决议。两个合伙人分别把命令下发给了自己的管理人员。

    合伙人 1 命令下达后,管理人员 a 在辞退了员工后,他就知道这个员工被开除了。

    而合伙人 2 的管理人员 2 这时候在没得到消息之前,还认为员工 a 是在职的,他就欣然的接收了合伙人给他的升职 a 的命令。

    处理器优化和指令重排

    上面提到在 CPU 和主存之间增加缓存,在多线程场景下会存在缓存一致性问题。

    除了这种情况,还有一种硬件问题也比较重要。那就是为了使处理器内部的运算单元能够被充分利用,处理器可能会对输入代码进行乱序执行处理。这就是处理器优化。

    除了现在很多流行的处理器会对代码进行优化乱序处理,很多编程语言的编译器也会有类似的优化,比如 Java 虚拟机的即时编译器(JIT)也会做指令重排。

    可想而知,如果任由处理器优化和编译器对指令重排的话,就可能导致各种各样的问题。

    关于员工组织调整的情况,如果允许人事部在接到多个命令后进行随意拆分乱序执行或者重排的话,那么对于这个员工以及这家公司的影响是非常大的。

    并发编程的问题

    前面说的和硬件有关的概念你可能听得有点蒙,还不知道他到底和软件有啥关系。

    但是关于并发编程的问题你应该有所了解了,比如原子性问题,可见性问题和有序性问题。

    其实,原子性问题,可见性问题和有序性问题是人们抽象定义出来的。而这个抽象的底层问题就是前面提到的缓存一致性问题、处理器优化问题和指令重排问题等。

    这里简单回顾下这三个问题,我们说,并发编程,为了保证数据的安全,需要满足以下三个特性:

    原子性,是指在一个操作中,CPU 不可以在中途暂停然后再调度,即不被中断操作,要不执行完成,要不就不执行。可见性,是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。有序性,即程序执行的顺序按照代码的先后顺序执行。

    有没有发现,缓存一致性问题其实就是可见性问题。而处理器优化是可以导致原子性问题的。指令重排即会导致有序性问题。

    所以,后文将不再提起硬件层面的那些概念,而是直接使用大家熟悉的原子性、可见性和有序性。

    什么是内存模型

    前面提到的,缓存一致性问题、处理器优化的指令重排问题是硬件的不断升级导致的。那么,有没有什么机制可以很好的解决上面的这些问题呢?

    最简单直接的做法就是废除处理器和处理器的优化技术、废除 CPU 缓存,让 CPU 直接和主存交互。

    但是,这么做虽然可以保证多线程下的并发问题。但是,这就有点因噎废食了。

    所以,为了保证并发编程中可以满足原子性、可见性及有序性。有一个重要的概念,那就是——内存模型。

    为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。

    通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。

    它解决了 CPU 多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

    内存模型解决并发问题主要采用两种方式:

    限制处理器优化使用内存屏障

    本文就不深入底层原理来展开介绍了,感兴趣的朋友可以自行学习。

    什么是 Java 内存模型

    前面介绍了计算机内存模型,这是解决多线程场景下并发问题的一个重要规范。

    那么具体的实现是如何的呢?不同的编程语言,在实现上可能有所不同。

    我们知道,Java 程序是需要运行在 Java 虚拟机上面的,Java 内存模型(Java Memory Model,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了 Java 程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

    提到 Java 内存模型,一般指的是 JDK 5 开始使用的新内存模型,主要由 JSR-133:JavaTM Memory Model and Thread Specification 描述。

    感兴趣的可以参看下这份PDF文档:

    http://cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf

    Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存。

    线程的工作内存中保存了该线程中用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。

    不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

    而 JMM 就作用于工作内存和主存之间数据同步过程。它规定了如何做数据同步以及什么时候做数据同步。

    这里面提到的主内存和工作内存,读者可以简单的类比成计算机内存模型中的主存和缓存的概念。

    特别需要注意的是,主内存和工作内存与 JVM 内存结构中的 Java 堆、栈、方法区等并不是同一个层次的内存划分,无法直接类比。

    《深入理解Java虚拟机》中认为:如果一定要勉强对应起来的话,从变量、主内存、工作内存的定义来看,主内存主要对应于 Java 堆中的对象实例数据部分。而工作内存则对应于虚拟机栈中的部分区域。

    所以,再来总结下,JMM 是一种规范,是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。

    目的是保证并发编程场景中的原子性、可见性和有序性。

    Java 内存模型的实现

    了解 Java 多线程的朋友都知道,在 Java 中提供了一系列和并发处理相关的关键字,比如 Volatile、Synchronized、Final、Concurren 包等。

    其实这些就是 Java 内存模型封装了底层的实现后提供给程序员使用的一些关键字。

    在开发多线程的代码的时候,我们可以直接使用 Synchronized 等关键字来控制并发,这样就不需要关心底层的编译器优化、缓存一致性等问题。

    所以,Java 内存模型,除了定义了一套规范,还提供了一系列原语,封装了底层实现后,供开发者直接使用。

    我们前面提到,并发编程要解决原子性、有序性和一致性的问题。下面我们就再来看下,在 Java 中,分别使用什么方式来保证。

    原子性

    在 Java 中,为了保证原子性,提供了两个高级的字节码指令 Monitorenter 和 Monitorexit。

    在 Synchronized 的实现原理文章中,介绍过,这两个字节码,在 Java 中对应的关键字就是 Synchronized。

    因此,在 Java 中可以使用 Synchronized 来保证方法和代码块内的操作是原子性的。

    可见性

    Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。

    Java 中的 Volatile 关键字提供了一个...

  • ?

    Java开发人员必知必会的20种常用类库和API

    小龙女

    展开

    一个有经验的Java开发人员特征之一就是善于使用已有的轮子来造车。《Effective Java》的作者Joshua Bloch曾经说过:“建议使用现有的API来开发,而不是重复造轮子”。在本文中,我将分享一些Java开发人员应该熟悉的最有用的和必要的库和API。顺便说一句,这里不包括框架,如Spring和Hibernate因为他们非常有名,都有特定的功能。

    本文总结了日志、JSON解析、单测、XML解析、字节码处理、数据库连接池、集合类、邮件、加密、嵌入式SQL数据库、JDBC故障诊断以及序列化等20个方面的常用类库。都是你日常开发经常可能要用到的,现在不用不要紧,但是你要知道有这么一篇文章可以供你参考。

    不知道不可怕,可怕的是你不知道你不知道。

    一、日志相关类库

    日志库是很常见的,因为你在每一个项目中都需要他们。打印日志是服务器端应用中最重要的事情,因为日志是你了解你的程序发生了什么的唯一途径。尽管JDK附带自己的日志库,但是还是有很多更好的选择可用,例如Log4j、SLF4j和LogBack。

    Java开发人员应该熟悉日志记录的利弊,并且了解为什么SLF4J要比Log4J要好。

    二、JSON解析库

    在当今世界的web服务和物联网中(IoT),JSON已经取代了XML,成为从客户端到服务器传送信息的首选协议。有一个好消息和一个坏消息。坏消息是JDK没有提供JSON库。好消息是有许多优秀的第三方库可以用来解析和创建JSON消息,如Jackson 和 Gson

    一个Java web开发人员应该熟悉Jackson 和 Gson这两种中的至少一种库。

    三、单元测试库

    单元测试技术的使用,是区分一个一般的开发者和好的开发者的重要指标。程序员经常有各种借口不写单元测试,但最常见的借口就是缺乏经验和知识。常见的单测框架有JUnit,Mockito和PowerMock。

    四、通用类库

    有几个很好的第三方通用库可供Java开发人员使用,例如Apache Commons和Google Guava。我会经常在我的代码中使用这些通用类库,因为这些类库都是经过无数开发者实践过的,无论是实用性还是在性能等方面都是最佳的。

    五、Http 库

    我不是很喜欢JDK的一个重要原因就包括他们缺乏对HTTP的支持。虽然可以使用java包类,但是这和直接使用像Apache HttpClient和HttpCore等开源类库比起来麻烦太多了。

    尽管JDK 9将开始HTTP 2.0,也对HTTP的支持做了优化,但是我还是强烈建议所有的Java开发人员熟悉流行的HTTP处理类库,例如HttpClient和HttpCore HTTP等库。

    六、XML解析库

    市面上有很多XML解析的类库,如Xerces, JAXB, JAXP, Dom4j, Xstream等。Xerces2是下一代高性能,完全兼容的XML解析工具。Xerces2定义了 Xerces Native Interface (XNI)规范,并提供了一个完整、兼容标准的 XNI 规范实现。该解析器是完全重新设计和实现的,更简单以及模块化。

    七、Excel读写库

    许多应用程序需要提供把数据导出到Excel的功能,如果你要做相同的Java应用程序,那么你需要Apache POI API。

    这是一个非常丰富的类库,你可以从Java程序读写XLS文件。

    八、字节码库

    如果你正在编写一个框架或者类库。有一些受欢迎的字节码库如javassist和Cglib Nodep可以供你选择,他们可以让你阅读和修改应用程序生成的字节码。

    Javassist使得JAVA字节码操作非常简单。它是一个为编辑Java字节码而生的类库。ASM是另一个有用的字节码编辑库。

    九、数据库连接池库

    如果你的Java应用程序与数据库交互不是使用数据库连接池库的话,那么你就大错特错了。因为在运行时创建数据库连接非常耗时并且会拖慢你的程序。所以墙裂建议使用,有些好用的连接池可供选择,如Commons Pool 和 DBCP。

    在web应用程序中,web服务器通常提供了这些功能。但是在java项目中需要把数据库连接池的类库导入到应用中。

    十、消息传递库

    像日志和数据库连接池一样,消息传递也是很多实际的Java项目中必备的。Java提供了JMS Java消息服务,但这不是JDK的一部分,你需要单独的引入jms.jar。类似地,如果您准备使用第三方消息传递协议,Tibco RV是个不错的选择。

    十一、PDF处理库

    除了Excel和Word,PDF也是一种常用的文件格式。如果你的应用程序要支持PDF格式的文件处理,你可以使用iText和Apache FOP类库。两者都提供了非常有用的PDF处理功能。

    十二、日期和时间库

    在Java之前,JDK的日期和时间库一直被人们所诟病,比如其非线程安全的、不可变的、容易出错等。很多开发人员会选择更好用的JodaTime类库。

    但是在Java8推出之后,我们就可以彻底放弃JodaTime了,因为Java 8提供了其所有功能。但是,如果你的代码运行在一个低版本的JDK中,那么JodaTime还是值得使用的。

    十三、集合类库

    虽然JDK有丰富的集合类,但还是有很多第三方类库可以提供更多更好的功能。如Apache Commons Collections、 Goldman Sachs collections、 Google Collections和Trove。Trove尤其有用,因为它提供所有标准Collections 类的更快的版本以及能够直接在原语(primitive)(例如包含int 键或值的Map 等)上操作的Collections 类的功能。

    FastUtil是另一个类似的API,它继承了Java Collection Framework,提供了数种特定类型的容器,包括映射map、集合set、列表list、优先级队列(prority queue),实现了java.util包的标准接口(还提供了标准类所没有的双向迭代器),还提供了很大的(64位)的array、set、list,以及快速、实用的二进制或文本文件的I/O操作类。

    十四、邮件API

    javax.mail 和 Apache Commons Email 提供了发送邮件的api。它们建立在JavaMail API的基础上,提供简化的用法。

    十五、HTML解析库

    和XML与JSON类似,HTML是另外一种我们可能要打交道的传输格式。值得庆幸的是,我们有jsoup可以大大简化Java应用程序使用HTML。你不仅可以使用JSoup解析HTML还可以创建HTML文档。

    十六、加密库

    Apache Commons家族中的Commons Codec就提供了一些公共的编解码实现,比如Base64, Hex, MD5,Phonetic and URLs等等。

    十七、嵌入式SQL数据库库

    我真的是非常喜欢像H2这种内存数据库,他可以嵌入到你的Java应用中。在你跑单测的时候如果你需要一个数据库,用来验证你的SQL的话,他是个很好的选择。顺便说一句,H2不是唯一嵌入式DB,你还有Apache Derby和HSQL可供选择。

    十八、JDBC故障诊断库

    有不错的JDBC扩展库的存在使得调试变得很容易,例如P6spy,这是一个针对数据库访问操作的动态监测框架,它使得数据库数据可无缝截取和操纵,而不必对现有应用程序的代码作任何修改。P6Spy 分发包包括P6Log,它是一个可记录任何 Java 应用程序的所有JDBC事务的应用程序。其配置完成使用时,可以进行数据访问性能的监测。

    十九、序列化库

    Google Protocol Buffer 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

    二十、网络库

    一些有用的网络库主要有Netty的和Apache MINA。如果您正在编写一个应用程序,你需要做的底层网络任务,可以考虑使用这些库。

    这都是每位Java开发人员应该熟悉的,并且十分有用的库。Java生态系统非常庞大的,你会发现有很多不同的类库可以做不同的事情。每个你想到的东西,都可能有一个库可以做到。

    要相信,你遇到的问题,肯定不止你一个遇到过。

    要相信,也许有很多人比你更勤奋。

    要相信,你用或不用,轮子就在那里。

    要相信,使用这些类库,你和你的代码都会变得更好。

    作者:Hollis

  • ?

    Java注解解析-运行时注解详解(RUNTIME)

    笑槐

    展开

    一 注解的定义

    注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明 。如果要对于元数据的作用进行分类,还没有明确的定义,不过我们可以根据它所起的作用,注解不会改变编译器的编译方式,也不会改变虚拟机指令执行的顺序,它更可以理解为是一种特殊的注释,本身不会起到任何作用,需要工具方法或者编译器本身读取注解的内容继而控制进行某种操作。大致可分为三类:

    编写文档:通过代码里标识的元数据生成文档。

    代码分析:通过代码里标识的元数据对代码进行分析。

    编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查。

    二 用途

    因为注解可以再代码编译期间帮我们完成一些复杂的准备工作,所以我们可以利用注解去完成我们的一些准备工作。比如Greendao,我们注解一个实体类,它要处理成好多逻辑关系类,这些逻辑类让我们自己去书写的话那将是一个庞大的代码量, extends AbstractDao等这些类。比如BufferKnife,我们用注解将控件的属性传递给它,它将生成一些功能类去处理这些值,***_ViewBinding等这些类型的类。运行时注解的使用,比如Retrofit的@GET或者@POST等等都是运行时注解,它的注解的处理必须跟Retrofit的对象有关联,所以必须定义成运行时的。所以注解已经成为一种趋势,比如BufferKnife,EventBus,Darrger,Greendao,Arouter,Retrofit。。。看来我们也要去完成我们的一个注解了。

    三 知识准备

    Java JDK中包含了三个注解分别为@Override(校验格式),@Deprecated:(标记过时的方法或者类),@SuppressWarnnings(注解主要用于抑制编译器警告)等等java1.8之后有新增了一些注解像@FunctionalInterface()这样的,对于每个注解的具体使用细节这里不再论述。我们可以通过点击这里来看一下专业解释! 来看一下@Override的源码。

    @Target(ElementType.METHOD)

    @Retention(RetentionPolicy.SOURCE)

    }

    通过源代码的阅读我们可以看出生命注解的方式为@interface,每个注解都需要不少于一个的元注解的修饰,这里的元注解其实就是修饰注解的注解,可以理解成最小的注解单位吧。。。下面详细的看下每个注释注解的意义吧:

    @Target:

    说明了Annotation所修饰的对象范围,也就是我们这个注解是用在那个对象上面的:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。以下属性是多选状态,我们可以定义多个注解作用域,比如:

    @Target({ElementType.METHOD,ElementType.FIELD}),单个的使用 @Target(ElementType.FIELD)。

    (1).CONSTRUCTOR:构造方法声明。

    (2).FIELD:用于描述域也就是类属性之类的,字段声明(包括枚举常量)。

    (3).LOCAL_VARIABLE:用于描述局部变量。

    (4).METHOD:用于描述方法。

    (5).PACKAGE:包声明。

    (6).PARAMETER:参数声明。

    (7).TYPE:类、接口(包括注释类型)或枚举声明 。

    (8) .ANNOTATION_TYPE:注释类型声明,只能用于注释注解。

    官方解释:指示注释类型所适用的程序元素的种类。如果注释类型声明中不存在 Target 元注释,则声明的类型可以用在任一程序元素上。如果存在这样的元注释,则编译器强制实施指定的使用限制。 例如,此元注释指示该声明类型是其自身,即元注释类型。它只能用在注释类型声明上:

    @Target(ElementType.ANNOTATION_TYPE)

    public @interface MetaAnnotationType {

    }

    此元注释指示该声明类型只可作为复杂注释类型声明中的成员类型使用。它不能直接用于注释:

    @Target({})

    public @interface MemberType {

    ...

    }

    这是一个编译时错误,它表明一个 ElementType 常量在 Target 注释中出现了不只一次。例如,以下元注释是非法的:

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})

    public @interface Bogus {

    ...

    }

    @Retention:

    定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。来源于java.lang.annotation.RetentionPolicy的枚举类型值:

    (1).SOURCE:在源文件中有效(即源文件保留)编译成class文件将舍弃该注解。

    (2).CLASS:在class文件中有效(即class保留) 编译成dex文件将舍弃该注解。

    (3).RUNTIME:在运行时有效(即运行时保留) 运行时可见。

    也就是说注解处理器能处理这三类的注解,我们通过反射的话只能处理RUNTIME类型的注解.

    官方解释:指示注释类型的注释要保留多久。如果注释类型声明中不存在 Retention 注释,则保留策略默认为 RetentionPolicy.CLASS。只有元注释类型直接用于注释时,Target 元注释才有效。如果元注释类型用作另一种注释类型的成员,则无效。

    @Documented:

    指示某一类型的注释将通过 javadoc 和类似的默认工具进行文档化。应使用此类型来注释这些类型的声明:其注释会影响由其客户端注释的元素的使用。如果类型声明是用 Documented 来注释的,则其注释将成为注释元素的公共 API 的一部。Documented是一个标记注解,没有成员。

    @Inherited:

    元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。 注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

    官方解释:指示注释类型被自动继承。如果在注释类型声明中存在 Inherited 元注释,并且用户在某一类声明中查询该注释类型,同时该类声明中没有此类型的注释,则将在该类的超类中自动查询该注释类型。此过程会重复进行,直到找到此类型的注释或到达了该类层次结构的顶层 (Object) 为止。如果没有超类具有该类型的注释,则查询将指示当前类没有这样的注释。

    注意,如果使用注释类型注释类以外的任何事物,此元注释类型都是无效的。还要注意,此元注释仅促成从超类继承注释;对已实现接口的注释无效。

    @Repeatable:

    Repeatable可重复性,Java 1.8新特性,其实就是把标注的注解放到该元注解所属的注解容器里面。可重复性的意思还是用demo来解释一下吧:

    //定义了一个 注解里面属性的返回值是其他注解的数组

    @Target(ElementType.FIELD)

    @Retention(RetentionPolicy.RUNTIME)

    public @interface MyCar {

    MyTag[] value(); ----MyTag 这里就是MyTag注解的容器。

    }

    //另外一个注解 就是上一个注解返回的注解

    @Target({ElementType.METHOD,ElementType.FIELD})

    @Retention(RetentionPolicy.CLASS)

    @Repeatable(MyCar.class) --------这里添加这个属性之后 我们的这个注解就可以重复的添加到我们定义的容器中了,注意里面的值时 我们定义的容器注解的class对象.

    public @interface MyTag { ........MyTag

    String name () default "" ;

    int size () default 0 ;

    }

    //使用

    @MyTag(name = "BWM", size = 100)

    @MyTag()

    public Car car;

    //如果我们的注解没有@Repeatable的话,这样写的话是报错的,加上之后就是这样的了

    这个注解是很特殊的,我们的注解中有@Repeatable(MyCar.class)这样的元注解的话,就是说当前标注的注解(MyTag注解)放到我们的值(MyCar.class)这个注解容器里面。那么我们再处理注解的时候获取到的是我们最后的注解容器(MyCar注解),这样说有点生硬下面看demo:

    使用:

    public class HomeActivity extends AppCompatActivity {

    @MyTag(name = "BWM", size = 100)

    @MyTag(name = "大众" ,size = 200) ......这里用了它的重复性.

    Car car;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_home);

    AnnotationProccessor.instance().inject(this); //这里去处理注解

    // Log.e("WANG", "HomeActivity.onCreate." + car.toString());

    }

    }

    处理过程:

    Class aClass = o.getClass();

    Field[] declaredFields = aClass.getDeclaredFields();

    for (Field field:declaredFields) {

    if(field.getName().equals("car")){

    Annotation[] annotations = field.getAnnotations();

    for (int i = 0; i

    Annotation annotation = annotations[i];

    //我们获取的该字段上面的注解只有一个 那就是 MyCar注解,看结果1的打印.

    //但是我们明明标注的是 MyTag. 为什么获取的是注解容器呢.

    //这就是@Repeatable的强大之处.

    Class aClass1 = annotation.annotationType();

    Log.e("WANG","AnnotationProccessor.MyCar"+aClass1 );

    }

    MyCar annotation = field.getAnnotation(MyCar.class);

    MyTag[] value = annotation.value();

    for (int i = 0; i

    MyTag myTag = value[i];

    Log.e("WANG","AnnotationProccessor.MyTag name value is "+myTag.name() );

    }

    }

    结果是:

    AnnotationProccessor.MyCarinterface cn.example.wang.routerdemo.annotation.MyCar.1

    AnnotationProccessor.MyTag name value is BWM.2

    AnnotationProccessor.MyTag name value is 大众.3

    三 自定义运行时注解

    通过以上的学习我们知道@interface是声明注解的关键字,每个注解需要注明生命周期以及作用范围.你可以给注解定义值.也就是再注解内部定义我们需要的方法.这样注解就可以再自己的生命周期内为我们做事.这里我们就自定义一个为一个对象属性初始化的注解吧,类似于Dagger的功能。

    public @interface MyTag {

    }

    注解里面的定义也是有规定的:

    注解方法不能带有参数。注解方法返回值类型限定为:基本类型、String、Enums、Annotation或者这些类型的数组。注解方法可以有默认值。注解本身能够包含元注解,元注解被用来注解其他注解。

    我们就来试一下吧!

    public @interface MyTag {

    //声明返回值类型,这里可没有大括号啊,可以设置默认返回值,然后就直接";"了啊。

    String name () default "" ;

    int size () default 0 ;

    }

    定义好了注解我们就来规定我们自定义的注解要在哪里用?要何时用?因为我们这里使用了反射来处理注解,反射就是在代码的运行的时候通过class对象反相的去获取类内部的东西,不熟悉反射机制的请移步这里Android开发者必须了解的反射基础,所以我们定义该注解的生命周期在运行时,并且该注解的的目的是为自定义属性赋值,那么我们的作用域就是FIELD。这里面定义了我们要初始化的bean的基本属性,给了默认值。这样我们就可以用该注解去创建我们需要的bean对象。

    @Target(ElementType.FIELD)

    @Inherited

    @Retention(RetentionPolicy.RUNTIME)

    public @interface MyTag {

    String name () default "" ;

    int size () default 0 ;

    }

    好了接下来看怎么使用我们的这个自定义的注解!

    public class HomeActivity extends AppCompatActivity {

    @MyTag(name = "BMW",size = 100)

    Car car;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_home);

    //这里我们要首先注册一下这个类

    AnnotationCar.instance().inject(this);

    //当程序运行的时候这里将会输出该类Car的属性值。

    Log.e("WANG","Car is "+car.toString());

    }

    }

    注解如果没有注解处理器,那么该注解将毫无意义。这里呢我们在这个Activity里面定义了一个Car类的属性,然后再car这个变量上面定义我们的注解,并且给我们的注解赋值。然后我们再onCreate方法里面初始化我们的注解处理器。然后运行代码,log日志将打印Car类的信息,先来看下结果吧

    cn.example.wang.routerdemo E/WANG: Car is Car [name=BMW, size=100]

    这样我们的自定义注解就有作用了,下面是”注解处理器“的代码,这里都是我们自己编写的处理注解的代码,其实系统是自带注解处理器的,不过它一般用来处理源码注释和编译时注释。

    //自定义的类

    /**

    * Created by WANG on 17/11/21.

    */

    public class AnnotationCar {

    private static AnnotationCar annotationCar;

    public static AnnotationCar instance(){

    synchronized (AnnotationCar.class){

    if(annotationCar == null){

    annotationCar = new AnnotationCar();

    }

    return annotationCar;

    }

    }

    public void inject(Object o){

    Class aClass = o.getClass();

    Field[] declaredFields = aClass.get...

  • ?

    POI导致内存溢出的一个不能忽视的问题

    化骨龙

    展开

    最近,由于项目需要,用到poi来操作Excel完成数据的导入。

    一开始,以为是JVM内存不足,故意将内存调整到4G,足够大了,然后在运行时仍然曝出内存溢出。

    然后又想到会不是循环行时创建的对象太多,又将对象的定义移到循环的外面,问题仍然没有解决。

    接下来,又给FileInputStream的读取增加BufferedInputStream缓冲区读取,问题依然存在。

    百无聊赖,仍然找不到问题的突破口。这时候,随便点了一下Excel表格的内容,发现了问题的所在,Excel中使用了公式,这里一定要注意:

    使用POI读取Excel时,Excel里面一定不要使用公式,这样一方面会造成程序崩溃,一方面造成内存溢出,也就是会发生上面我们提到的后果(java.lang.OutOfMemoryError)java heap space.好了,很简单的一个问题,折磨了一下午,头疼。

    最后,再次重申:当遇到采用POI读取Excel发生内存溢出时,一定要先检查Excel中各列是否有采用公式,如果有公式,一定要去掉改用手写。

    如果你感觉本文对你有用,请分享给更多有需要的人。

  • ?

    一行代码完成Java的Excel读写

    伍寄风

    展开

    前段时间在 github 上发现了阿里的 EasyExcel 项目,觉得挺不错的,就写了一个简单的方法封装,做到只用一个函数就完成 Excel 的导入或者导。刚好前段时间更新修复了一些 BUG,就把我的这个封装分享出来,请多多指教。

    附上源码:

    https://github/HowieYuan/easyexcel-method-encapsulation

    EasyExcel

    EasyExcel 的 github 地址:

    https://github/alibaba/easyexcel

    EasyExcel 的官方介绍:

    可以看到 EasyExcel 最大的特点就是使用内存少,当然现在它的功能还比较简单,能够面对的复杂场景比较少,不过基本的读写完全可以满足。

    一. 依赖

    首先是添加该项目的依赖,目前的版本是 1.0.2。

    com.alibabaeasyexcel1.0.2

    二. 需要的类

    1. ExcelUtil

    工具类,可以直接调用该工具类的方法完成 Excel 的读或者写。

    2. ExcelListener

    监听类,可以根据需要与自己的情况,自定义处理获取到的数据,我这里只是简单地把数据添加到一个 List 里面。

    publicclassExcelListenerextendsAnalysisEventListener {//自定义用于暂时存储data。//可以通过实例获取该值private List datas = new ArrayList<>();/** * 通过 AnalysisContext 对象还可以获取当前 sheet,当前行等数据 */ @Overridepublicvoidinvoke(Object object, AnalysisContext context) {//数据存储到list,供批量处理,或后续自己业务逻辑处理。 datas.add(object);//根据自己业务做处理 doSomething(object); }privatevoiddoSomething(Object object) { } @OverridepublicvoiddoAfterAllAnalysed(AnalysisContext context) { }public List getDatas() {return datas; }publicvoidsetDatas(List datas) {this.datas = datas; }}

    3. ExcelWriterFactroy

    用于导出多个 sheet 的 Excel,通过多次调用 write 方法写入多个 sheet。

    4. ExcelException

    捕获相关 Exception。

    三. 读取 Excel

    读取 Excel 时只需要调用 ExcelUtil.readExcel() 方法:

    @RequestMapping(value = "readExcel", method = RequestMethod.POST)public Object readExcel(MultipartFile excel) {return ExcelUtil.readExcel(excel, new ImportInfo());}

    其中 excel 是 MultipartFile 类型的文件对象,而 new ImportInfo() 是该 Excel 所映射的实体对象,需要继承 BaseRowModel 类,如:

    publicclass ImportInfo extends BaseRowModel {@ExcelProperty(index = 0)privateString name;@ExcelProperty(index = 1)privateString age;@ExcelProperty(index = 2)privateString email;publicString getName() {return name; }publicvoid setName(String name) {this.name = name; }publicString getAge() {return age; }publicvoid setAge(String age) {this.age = age; }publicString getEmail() {return email; }publicvoid setEmail(String email) {this.email = email; }}

    作为映射实体类,通过 @ExcelProperty 注解与 index 变量可以标注成员变量所映射的列,同时不可缺少 setter 方法。

    四. 导出 Excel

    1. 导出的 Excel 只拥有一个 sheet

    只需要调用 ExcelUtil.writeExcelWithSheets() 方法:

    @RequestMapping(value = "writeExcel", method = RequestMethod.GET)publicvoidwriteExcel(HttpServletResponse response) throws IOException { List list = getList(); String fileName = "一个 Excel 文件"; String sheetName = "第一个 sheet"; ExcelUtil.writeExcel(response, list, fileName, sheetName, new ExportInfo()); }

    fileName,sheetName 分别是导出文件的文件名和 sheet 名,new ExportInfo() 为导出数据的映射实体对象,list 为导出数据。

    对于映射实体类,可以根据需要通过 @ExcelProperty 注解自定义表头,当然同样需要继承 BaseRowModel 类,如:

    publicclass ExportInfo extends BaseRowModel {@ExcelProperty(value = "姓名" ,index = 0)privateString name;@ExcelProperty(value = "年龄",index = 1)privateString age;@ExcelProperty(value = "邮箱",index = 2)privateString email;@ExcelProperty(value = "地址",index = 3)privateString address;}

    value 为列名,index 为列的序号。

    如果需要复杂一点,可以实现如下图的效果:

    对应的实体类写法如下:

    publicclass MultiLineHeadExcelModel extends BaseRowModel {@ExcelProperty(value = {"表头1","表头1","表头31"},index = 0)privateString p1;@ExcelProperty(value = {"表头1","表头1","表头32"},index = 1)privateString p2;@ExcelProperty(value = {"表头3","表头3","表头3"},index = 2)private int p3;@ExcelProperty(value = {"表头4","表头4","表头4"},index = 3)private long p4;@ExcelProperty(value = {"表头5","表头51","表头52"},index = 4)privateString p5;@ExcelProperty(value = {"表头6","表头61","表头611"},index = 5)privateString p6;@ExcelProperty(value = {"表头6","表头61","表头612"},index = 6)privateString p7;@ExcelProperty(value = {"表头6","表头62","表头621"},index = 7)privateString p8;@ExcelProperty(value = {"表头6","表头62","表头622"},index = 8)privateString p9;}

    2. 导出的 Excel 拥有多个 sheet

    调用 ExcelUtil.writeExcelWithSheets() 处理第一个 sheet,之后调用 write() 方法依次处理之后的 sheet,最后使用 finish() 方法结束。

    publicvoidwriteExcelWithSheets(HttpServletResponse response) throws IOException { List list = getList(); String fileName = "一个 Excel 文件"; String sheetName1 = "第一个 sheet"; String sheetName2 = "第二个 sheet"; String sheetName3 = "第三个 sheet"; ExcelUtil.writeExcelWithSheets(response, list, fileName, sheetName1, new ExportInfo()) .write(list, sheetName2, new ExportInfo()) .write(list, sheetName3, new ExportInfo()) .finish();}

    write 方法的参数为当前 sheet 的 list 数据,当前 sheet 名以及对应的映射类。

    扩展阅读

    代码整洁之道|最佳实践小结

    Javascript 将 HTML 页面生成 PDF 并下载

    Java POI 导出EXCEL经典实现 Java导出Excel弹出下载框

    来源:https://juejin.im/post/5ba320546fb9a05d0b14304b

    文章来源网络,版权归作者本人所有,如侵犯到原作者权益,请与我们联系删除

  • ?

    整理关于java读取excel内容的例子供大家参考

    堪鹰

    展开

    1POI简介

    Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。

    2POI下载

    poi下载地址:https://apache.org/dyn/closer.lua/poi/release/bin/poi-bin-3.17-20170915.tar.gz

    下载之后把压缩包解压,然后在项目里面引用解压文件夹里面的jar

    3新建java project,引入poi相关的jar

    4创建一个java类,编写代码

    package test;

    import java.io.File;

    import java.io.FileInputStream;

    import java.io.InputStream;

    import org.apache.poi.hssf.usermodel.HSSFRow;

    import org.apache.poi.hssf.usermodel.HSSFSheet;

    import org.apache.poi.hssf.usermodel.HSSFWorkbook;

    public class ReadExcel {

    /**

    * @param args

    */

    @SuppressWarnings("resource")

    public static void main(String[] args) {

    try {

    InputStream is = new FileInputStream(new File(

    "D:/360bizhi/0_TITLE_CODE.xls"));

    HSSFWorkbook hssfWorkbook = new HSSFWorkbook(is);

    // 获取每一个工作薄

    for (int numSheet = 0; numSheet < hssfWorkbook.getNumberOfSheets(); numSheet++) {

    HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(numSheet);

    if (hssfSheet == null) {

    continue;

    }

    // 获取当前工作薄的每一行

    for (int rowNum = 0; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {

    HSSFRow hssfRow = hssfSheet.getRow(rowNum);

    if (hssfRow != null) {

    String title_id = String.valueOf(hssfRow.getCell(0));

    // 读取第一列数据

    String title_name = String.valueOf(hssfRow.getCell(1));

    // 读取第二列数据

    String title_name_en = String.valueOf(hssfRow

    .getCell(2));

    System.out.println("===============title_id:"

    + title_id + ",title_name:" + title_name

    + ",title_name_en:" + title_name_en);

    } catch (Exception e) {

    e.printStackTrace();

    5运行main进行测试,查看打印结果

    打印结果和xls里面的值是一样的,说明读取excel成功

    请大家多多关注我的头条号,谢谢大家

  • java 读取excel