0%

    本文首发于cartoon的博客

    转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/java/java%E9%81%8D%E5%8E%86%E6%9C%BA%E5%88%B6%E7%9A%84%E6%80%A7%E8%83%BD%E6%AF%94%E8%BE%83/

缘由

    近段时间在写leetcode的Lemonade Change时候,发现了for循环与forEach循环的耗时是不一致的,在提交记录上面差了一倍……

    平常开发绝大部分业务逻辑的实现都需要遍历机制的帮忙,虽说也有注意到各数据结构操作的性能比较,但是忽视了遍历机制性能的差异。原本前两天就开始动手写,拖延症……

正文

    现阶段我所知道JAVA遍历机制有三种

  • for循环

  • forEach循环

  • Iterator循环

    JAVA数据结构千千万,但是大部分都是对基础数据结构的封装,比较HashMap依赖于Node数组,LinkedList底层是链表,ArrayList对数组的再封装……扯远了

    总结来说,JAVA的基础数据结构,我觉得有两种

  • 数组
  • 链表

    如果是加上Hash(Hash的操作与数组以及链表不太一致),就是三种

    因为平常开发大部分都优先选择包装后的数据结构,所以下面我会使用

  • ArrayList(包装后的数组)
  • LinkedList(包装后的链表)
  • HashSet(包装后的Hash类型数组)

    这三种数据结构在遍历机制不同的时候时间的差异

    可能有人对我为什么不对比HashMap呢,因为JAVA设计中,是先实现了Map,再实现Set。如果你有阅读过源码就会发现:每个Set子类的实现中,都有一个序列化后的Map对应属性实现,而因为Hash的查找时间复杂度为O(1),得出key后查找value的时间大致是一致的,所以我不对比HashMap。

题外话

    我在阅读《疯狂JAVA》读到:JAVA的设计者将Map的内部entry数组中的value设为null进而实现了Set。因为我是以源码以及官方文档为准,具体我不清楚正确与否,但是因为Hash中的key互不相同,Set中元素也互不相同,所以我认为这个观点是正确的。

    为了测试的公平性,我会采取以下的限定

  • 每种数据结构的大小都设置三种量级
    • 10
    • 100
    • 1000
  • 元素都采用随机数生成
  • 遍历进行操作都为输出当前元素的值

    注:时间开销受本地环境的影响,可能测量值会出现变化,但是总体上比例是正确的

ArrayList的比较

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    public class TextArray {

    private static Random random;

    private static List<Integer> list1;

    private static List<Integer> list2;

    private static List<Integer> list3;

    public static void execute(){
    random=new Random();
    initArray();
    testForWith10Object();
    testForEachWith10Object();
    testIteratorWith10Object();
    testForWith100Object();
    testForEachWith100Object();
    testIteratorWith100Object();
    testForWith1000Object();
    testForEachWith1000Object();
    testIteratorWith1000Object();
    }

    private static void testForWith10Object(){
    printFor(list1);
    }

    private static void testForWith100Object(){
    printFor(list2);
    }

    private static void testForWith1000Object(){
    printFor(list3);
    }

    private static void testForEachWith10Object(){
    printForeach(list1);
    }

    private static void testForEachWith100Object(){
    printForeach(list2);
    }

    private static void testForEachWith1000Object(){
    printForeach(list3);
    }

    private static void testIteratorWith10Object() {
    printIterator(list1);
    }

    private static void testIteratorWith100Object() {
    printIterator(list2);
    }

    private static void testIteratorWith1000Object() {
    printIterator(list3);
    }

    private static void printFor(List<Integer> list){
    System.out.println();
    System.out.print("data:");
    long start=System.currentTimeMillis();
    for(int i=0,length=list.size();i<length;i++){
    System.out.print(list.get(i)+" ");
    }
    System.out.println();
    long end=System.currentTimeMillis();
    System.out.println("for for "+list.size()+":"+(end-start)+"ms");
    }

    private static void printForeach(List<Integer> list){
    System.out.println();
    System.out.print("data:");
    long start=System.currentTimeMillis();
    for(int temp:list){
    System.out.print(temp+" ");
    }
    System.out.println();
    long end=System.currentTimeMillis();
    System.out.println("foreach for "+list.size()+":"+(end-start)+"ms");
    }

    private static void printIterator(List<Integer> list){
    System.out.println();
    System.out.print("data:");
    Iterator<Integer> it=list.iterator();
    long start=System.currentTimeMillis();
    while(it.hasNext()){
    System.out.print(it.next()+" ");
    }
    System.out.println();
    long end=System.currentTimeMillis();
    System.out.println("iterator for "+list.size()+":"+(end-start)+"ms");
    }

    private static void initArray(){
    list1=new ArrayList<>();
    list2=new ArrayList<>();
    list3=new ArrayList<>();
    for(int i=0;i<10;i++){
    list1.add(random.nextInt());
    }
    for(int i=0;i<100;i++){
    list2.add(random.nextInt());
    }
    for(int i=0;i<1000;i++){
    list3.add(random.nextInt());
    }
    }
    }
  • 输出(忽略对元素的输出)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    for for 10:1ms
    foreach for 10:0ms
    iterator for 10:2ms

    for for 100:5ms
    foreach for 100:4ms
    iterator for 100:12ms

    for for 1000:33ms
    foreach for 1000:7ms
    iterator for 1000:16ms
    10 100 1000
    for 1ms 5ms 33ms
    forEach 0ms 4ms 7ms
    Iterator 2ms 12ms 16ms
  • 结论

    &nbsp;&nbsp;&nbsp;&nbsp;for的性能最不稳定,foreach次之,Iterator最好

  • 使用建议

    1. 在数据量不明确的情况下(可能1w,10w或其他),建议使用Iterator进行遍历

    2. 在数据量明确且量级小的时候,优先使用foreach

    3. 需要使用索引时,使用递增变量的开销比for的要小

LinkedList的比较

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    public class TextLinkedList {

    private static Random random;

    private static List<Integer> list1;

    private static List<Integer> list2;

    private static List<Integer> list3;

    public static void execute(){
    random=new Random();
    initList();
    testForWith10Object();
    testForEachWith10Object();
    testIteratorWith10Object();
    testForWith100Object();
    testForEachWith100Object();
    testIteratorWith100Object();
    testForWith1000Object();
    testForEachWith1000Object();
    testIteratorWith1000Object();
    }

    private static void testForWith10Object() {
    printFor(list1);
    }

    private static void testForEachWith10Object() {
    printForeach(list1);
    }

    private static void testIteratorWith10Object() {
    printIterator(list1);
    }

    private static void testForWith100Object() {
    printFor(list2);
    }

    private static void testForEachWith100Object() {
    printForeach(list2);
    }

    private static void testIteratorWith100Object() {
    printIterator(list2);
    }

    private static void testForWith1000Object() {
    printFor(list3);
    }

    private static void testForEachWith1000Object() {
    printForeach(list3);
    }

    private static void testIteratorWith1000Object() {
    printIterator(list3);
    }

    private static void printFor(List<Integer> list){
    System.out.println();
    System.out.print("data:");
    long start=System.currentTimeMillis();
    for(int i=0,size=list.size();i<size;i++){
    System.out.print(list.get(i));
    }
    System.out.println();
    long end=System.currentTimeMillis();
    System.out.println("for for "+list.size()+":"+(end-start)+"ms");
    }

    private static void printForeach(List<Integer> list){
    System.out.println();
    System.out.print("data:");
    long start=System.currentTimeMillis();
    for(int temp:list){
    System.out.print(temp+" ");
    }
    System.out.println();
    long end=System.currentTimeMillis();
    System.out.println("foreach for "+list.size()+":"+(end-start)+"ms");
    }

    private static void printIterator(List<Integer> list){
    System.out.println();
    System.out.print("data:");
    Iterator<Integer> it=list.iterator();
    long start=System.currentTimeMillis();
    while(it.hasNext()){
    System.out.print(it.next()+" ");
    }
    System.out.println();
    long end=System.currentTimeMillis();
    System.out.println("iterator for "+list.size()+":"+(end-start)+"ms");
    }


    private static void initList() {
    list1=new LinkedList<>();
    list2=new LinkedList<>();
    list3=new LinkedList<>();
    for(int i=0;i<10;i++){
    list1.add(random.nextInt());
    }
    for(int i=0;i<100;i++){
    list2.add(random.nextInt());
    }
    for(int i=0;i<1000;i++){
    list3.add(random.nextInt());
    }
    }
    }
  • 输出(忽略对元素的输出)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    for for 10:0ms
    foreach for 10:1ms
    iterator for 10:0ms

    for for 100:1ms
    foreach for 100:0ms
    iterator for 100:3ms

    for for 1000:23ms
    foreach for 1000:25ms
    iterator for 1000:4ms
    10 100 1000
    for 0ms 1ms 23ms
    forEach 1ms 0ms 25ms
    Iterator 0ms 3ms 4ms
  • 结论

    &nbsp;&nbsp;&nbsp;&nbsp;foreach的性能最不稳定,for次之,Iterator最好

  • 使用建议

    1. 尽量使用Iterator进行遍历

    2. 需要使用索引时,使用递增变量的开销比for的要小

HashSet的比较

&nbsp;&nbsp;&nbsp;&nbsp;注:因Hash遍历算法与其他类型不一致,所以取消了for循环的比较

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    public class TextHash {

    private static Random random;

    private static Set<Integer> set1;

    private static Set<Integer> set2;

    private static Set<Integer> set3;

    public static void execute(){
    random=new Random();
    initHash();
    testIteratorWith10Object();
    testForEachWith10Object();
    testIteratorWith100Object();
    testForEachWith100Object();
    testIteratorWith1000Object();
    testForEachWith1000Object();
    }

    private static void testIteratorWith10Object() {
    printIterator(set1);
    }

    private static void testForEachWith10Object() {
    printForeach(set1);
    }

    private static void testIteratorWith100Object() {
    printIterator(set2);
    }

    private static void testForEachWith100Object() {
    printForeach(set2);
    }

    private static void testIteratorWith1000Object() {
    printIterator(set3);
    }

    private static void testForEachWith1000Object() {
    printForeach(set3);
    }

    private static void initHash() {
    set1=new HashSet<>();
    set2=new HashSet<>();
    set3=new HashSet<>();
    for(int i=0;i<10;i++){
    set1.add(random.nextInt());
    }
    for(int i=0;i<100;i++){
    set2.add(random.nextInt());
    }
    for(int i=0;i<1000;i++){
    set3.add(random.nextInt());
    }
    }

    private static void printIterator(Set<Integer> data){
    System.out.println();
    System.out.print("data:");
    long start=System.currentTimeMillis();
    Iterator<Integer> it=data.iterator();
    while (it.hasNext()){
    System.out.print(it.next()+" ");
    }
    System.out.println();
    long end=System.currentTimeMillis();
    System.out.println("iterator for "+data.size()+":"+(end-start)+"ms");
    }

    private static void printForeach(Set<Integer> data){
    System.out.println();
    System.out.print("data:");
    long start=System.currentTimeMillis();
    for(int temp:data){
    System.out.print(temp+" ");
    }
    System.out.println();
    long end=System.currentTimeMillis();
    System.out.println("foreach for "+data.size()+":"+(end-start)+"ms");
    }
    }
  • 输出(忽略对元素的输出)

    1
    2
    3
    4
    5
    6
    7
    8
    iterator for 10:0ms
    foreach for 10:0ms

    iterator for 100:6ms
    foreach for 100:0ms

    iterator for 1000:30ms
    foreach for 1000:9ms
    10 100 1000
    foreach 0ms 0ms 9ms
    Iterator 0ms 6ms 30ms
  • 结论

    &nbsp;&nbsp;&nbsp;&nbsp;foreach性能遥遥领先于Iterator

  • 使用建议

    &nbsp;&nbsp;&nbsp;&nbsp;以后就选foreach了,性能好,写起来也方便。

总结

  1. for循环性能在三者的对比中总体落于下风,而且开销递增幅度较大。以后即使在需要使用索引时我宁愿使用递增变量也不会使用for了。
  2. Iterator的性能在数组以及链表的表现都是最好的,应该是JAVA的设计者优化过了。在响应时间敏感的情况下(例如web响应),优先考虑。
  3. foreach的性能属于两者之间,写法简单,时间不敏感的情况下我会尽量选用。

&nbsp;&nbsp;&nbsp;&nbsp;以上就是我对常见数据结构遍历机制的一点比较,虽然只是很初步,但是从中我也学到了很多东西,希望你们也有所收获。

&nbsp;&nbsp;&nbsp;&nbsp;如果你喜欢本文章,可以收藏阅读,如果你对我的其他文章感兴趣,欢迎到我的博客查看。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/assorted/4种极大提升学习效率的工具

&nbsp;&nbsp;近段时间发现身边很多人都在用TUDO List,但是我觉得TUDO List的效率还是不够高,所以就写一下提升学习效率的工具。

TUDO List

​ 因为这阵子真的很多东西做,持续的时间比较长。而且会有时候(大概率)忘掉做某几件事,所以学习效率偏低,直到遇到了TUDO List。

​ TUDO List,可以理解为一个任务清单,它包含了你一天所有要做的事情,而且过一段时间你会发现自己已经依赖上TUDO List管理你的日程。

​ 市面上TUDO List的APP很多,有小黄条,TickTick,Mircosoft To-Do,Google Keep(好像是叫这个名字),还有很多很多,但是我还是喜欢TickTick。

​ 下面是我TickTick的收集箱。

(嗯,我知道我还有很多要做)

​ 我觉得TickTick比其他TUDO List好的主要有三点

  • 几乎全平台的支持
    • 安卓端是离线app加上联网更新形式,断网都可以继续用
    • 桌面端通过chrome插件提供服务
    • 苹果家的设备就不清楚了(穷)
  • 在界面舒适度与易用性取得平衡
    • 小黄条的界面,我就是看了一眼简介的截图就没有下载的欲望
    • Mircosoft家的,我不知道界面怎么样,但是我体验过小娜,我觉得不会好用
    • Google家的,好的,根本不能正常用
  • 自带番茄时钟
    • 我觉得现代人都摆脱不了手机(包括我),想认真看书的时候总是想看手机。所以番茄时钟能有效监督学习
    • 自带统计视图,能随时看到当周完成的情况

​ 有利也有弊,弊是有些功能需要付费使用,一个月10来块。始终还是要恰饭的嘛,能理解能理解。付费功能包括TUDO Task的统计,番茄时钟的统计建议。

​ 我对免费的功能已经满足了,所以我不是付费用户,付费解锁功能戳不到我的需求点。

思维导图

​ 不知道有多少读者是在做纸质笔记的,我的话已经很久没有做纸质笔记了。那么平常我在看书的时候,是怎么做笔记的呢。

​ 思维导图,我第一次接触已经爱上它了。

​ 下面是我学习帅张的git相关书籍做的思维导图,虽然过了三个月了,但是我还是可以根据思维导图讲出原书的大概内容。

​ 因为我是比较怕写字的(包括打字),所以我现在只会用思维导图做笔记了。

​ 市面上思维导图的软件很多,有XMind(开源,全平台支持),百度脑图(只有web端),MindMaster(苹果专属),MindLine(国人开发,支持云同步)等等。

​ 我说说我在用的XMind吧,优点有很多

  • 模板简洁而且漂亮。即使免费版也内置很多模板,ZEN版本跟Pro版本会更多
  • 操作简单。桌面端新建子分支或者兄弟分支会使用到快捷键(我比较少用,但是上手了非常方便),移动端(我常用)主要操作底下四颗按钮完成日常功能
  • 支持导出多格式。免费版只支持导出图片以及pdf,PRO版支持更多,但是我觉得这方面日常使用免费版已经足够了。

​ 下面到缺点的部分了。

​ 其实在我看来缺点只有一个,就是不支持云同步,确实有点反人类。

​ 我记得上次我双清平板忘记备份思维导图了(因为平板上独占数据只有思维导图),结果除了之前上传到OneDrive的几个之外,全都没了。。。大概10个上面图片的规模的思维导图吧。。。。

​ 我断断续续找了一个月,找到一个MindLine符合我使用场景之余是支持云同步的,但是界面说实话还有待改进,所以我只能继续用XMind。

Markdown

​ 如果不习惯用思维导图但是又想摆脱纸张的限制,我也可以提供选择。

​ 可能有人已经注意到,这篇文章的风格有点怪怪的。确实,是有点朴素(打死都不承认是怪怪的),因为我是用markdown去写的,具体原因看第二条推文。

​ 效果不截图了,整篇文章都是效果,或者可以去我的博客逛一下,有时间我也会写搭建博客的教程放在博客上。

​ 首先先说一下,markdown是一种标记语言,不是什么软件。具体介绍可以点击查看

​ 在我看来,markdown浑身都是优点

  • 操作简单。只需要掌握特定语法就可以写出比word整洁很多的文章,而且可以完全摆脱鼠标(纯文字文章的话)

  • 花样很多。markdown原生支持html语言,就是如果你懂html,可以像写网页那样写文章

  • 可选编辑器很多。微软家的VSCode,GitHub家的Sublime(实际上也是微软家的),Typora(我在用的,支持实时渲染),有道云笔记,印象笔记等等

  • 将主要精力放在思考上。整体风格更加符合思考的习惯,而且摆脱了鼠标的使用,可以将主要精力放在思考上。

    缺点嘛,也有两个,但是都是面对新手的。

  • 上手复杂。因为需要特定语法,所以上手需要一个阵痛期,但是practice makes prefect。习惯了之后效率会飞涨。我觉得这个教程不错

  • 插入图片比较麻烦,markdown支持三种图片插入方式

    • (不推荐)插入相对目录下的图片,但是需要确保路径的正确
    • (还行,但是不推荐)插入转码后的图片。这个比较麻烦,首先需要将图片转换成Base64字符串,再将字符串复制到markdown底部(转码后的字符串会很长),再利用语法引用。
    • (推荐)引用图床图片。图床这个不解释了,可以将它理解为一个网络的图片仓库。图床主要有三种
      • 免费的公有图床。百度一搜会很多。不推荐,容易出现图片丢失
      • 免费的私人图床。我所知道的只有微博图床,但是操作上有点麻烦,还行,但是不推荐。
      • 收费的私人图床。简单来说就是自己用OSS搭一个图床,本地上传到云端OSS。七牛云(一定容量免费),阿里云(我在用),腾讯云。七牛云就算了,需要拍身份证。个人推荐阿里云(9块/年)。

云盘

​ 可能很多人bb我:云盘谁没有,某度云盘几个T。

​ 是的,某度云盘确实占领了天朝大部分的市场。但是我想说的是,免费的是最贵的。想想某度的广告,想想几十K的下载速度,我不想吐槽了。

​ 云盘为什么能提升效率呢。

​ 我个人需求是

  • 平板是Wifi版的,需要经常带出去,出去的地方大多没有网络或者网络很差
  • 平常数据种类多而且量级比较大,而且需要多设备同步
  • 需要在线或缓存中阅读pdf

​ 我日常使用情况

  • 电脑下载需要看的电子书,直接放在OneDrive文件夹上,Win10自动上传

  • 平板脱机缓存电子书,网络环境不允许时可以随时使用电子书

  • 在平板上做好XMind之后,上传到OneDrive,需要再继续时下载

​ 所以我OneDrive是这样子的。

​ 通过OneDrive,我的数据能随时同步而且不丢失(再次为我丢失的思维导图伤心。。。)

​ 市面上有很多很多云盘。有我们能用到的,也有我们用不到的:OneDrive,某度云盘,亚马逊,iCloud,Google Drive,坚果云等等。

​ 苹果党,iCloud;

​ Win10党,OneDrive(虽然免费空间只有5G,但是好用,我愿意付费);

​ 有特殊手段的安卓党,Google Drive;

​ 数据量大且不常使用,某度还是可以选择一下的;

​ 以上几款软件都是开源或者免费+付费形式的。免费的部分基本上够用了,付费也在承受范围之内(真希望XMind出一个云同步的功能,我绝对付费)。

题外话:

​ 这段时间在混帅张的星球,受到了很多启发。关于软件跟知识是否付费的争论,我是站在付费的那一边。

​ 曾经在星球上面看到一个观点:免费是最贵的。软件都是程序猿一块块砖搬出来的,也是有成本的。免费使用,意味着在某些方面软件开发方是赚钱的,广告,流量,会员等等。

​ 自己本身也是学编程的,明白写程序是很辛苦的。免费产品我是不排斥的,没人嫌钱多。付费产品?我也愿意付费的,前提是有足够理由让我付费以及我能承担起这个费用。

​ 盗版我是不提倡的,但是有时候超出我的承受范围,我会选择某宝。而且盗版可能有一部分功能缺失掉了,恰好缺失的功能能极大提高你的效率。

​ 再次希望以上几款软件能对你们有帮助!

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

当修改gitnore文件后,常常出现文件不生效的情况,是因为之前的修改已经提交到暂存区上了。
解决方法

1
2
3
git add .     //防止已有修改还没到暂存区的情况
git rm -r --cached . //清除暂存区记录
git add . //提交修改记录到暂存区中

执行到第三步即能使gitnore文件生效,后续操作会按照gitnore规则执行

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

今天在做leetcode的时候,遇到了运算符的不同而导致结果不一致的问题。记录一下提醒自己

中文名称与英文名称

&:按位与(Bitwise and)
&&:逻辑与(logical and)
|:按位或(Bitwise or)
||:逻辑或(logical or)

区别

若第一个条件就可以决定表达式的值,逻辑运算符不会继续检查后续条件,而位运算符则会全部检查。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;这段时间把疯狂JAVA再看了一遍,发现Stack,ArrayDeque,LinkedList都可以作为栈使用,所以就稍微从性能以及实现的细节对比这三者的区别。

类继承树

区别

底层数据存储方式

&nbsp; 存储方式
Stack 长度为10的数组
ArrayDeque 长度为16的数组
LinkedList 链表

方法参照表

Stack ArrayDeque LinkedList
push(e) addFirst(e)/offerFirst(e) addFirst(e)/offerFirst(e)
pop() removeFirst()/pollFirst() removeFirst()/pollFirst()
peek() getFirst()/peekFirst() getFirst()/peekFirst()

线程安全

&nbsp; 线程安全
Stack 线程同步
ArrayDeque 线程不同步
LinkedList 线程不同步

性能选项

&nbsp;&nbsp;&nbsp;通常情况下,不推荐使用Vector以及其子类Stack

1.需要线程同步

&nbsp;&nbsp;&nbsp;&nbsp;使用Collections工具类中synchronizedXxx()将线程不同步的ArrayDeque以及LinkedList转换成线程同步。

2.频繁的插入、删除操作:LinkedList

3.频繁的随机访问操作:ArrayDeque

4.未知的初始数据量:LinkedList

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;近段时间把自己电脑(win)、虚拟机(Ubuntu)以及阿里云(ubuntu)都重置了一遍,三个地方都有用到JDK,不想之后找教程找的那么麻烦。所以就自己总结一遍,一次性把轮子造好。

  • 环境

    1
    2
    3
    Win10 1803 Home
    Ubuntu 16.04.3
    Ubuntu 18.04.1

    &nbsp;&nbsp;&nbsp;&nbsp;其中服务器与虚拟机配置方法一致,只是目录不同,就归成Ubuntu一类好了。

  • Windows环境下安装

  1. 下载JDK的安装程序

    1
    https://www.oracle.com/technetwork/java/javase/downloads/jdk10-downloads-4416644.html
  2. 按照步骤安装

  3. 配置环境变量

&nbsp;&nbsp;&nbsp;&nbsp;此电脑–属性–编辑系统设置–环境变量

  • 新建系统变量JAVA_HOME,并在变量值选择Java的安装目录

  • 新建变量CLASSPATH,写入图中的变量值

  • 选择变量Path–编辑文本–在最后追加图中的值

  • 验证安装是否成功

&nbsp;&nbsp;&nbsp;&nbsp;cmd下输入java -version

&nbsp;&nbsp;&nbsp;&nbsp;出现图中的输出语句即为成功

  • Ubuntu环境下安装
  1. 下载JDK的压缩包

    1
    https://www.oracle.com/technetwork/java/javase/downloads/jdk10-downloads-4416644.html

    &nbsp;&nbsp;&nbsp;&nbsp;勾选同意协议后选择.tar.gz结尾的选项

  2. 解压到指定文件夹

    1
    tar -zxvf packageName.tar.gz  //packageName为jdk压缩包包名

    &nbsp;&nbsp;&nbsp;&nbsp;在服务器中我是直接通过xftp的GUI进行移动,虚拟机中受限于权限,我在终端上用root打开文件管理器移动的。

  3. 配置环境变量

    • 打开etc目录下的profile或者bashrc

      1
      2
      vim /etc/profile
      vim ~/.bashrc

      &nbsp;&nbsp;&nbsp;&nbsp;vim的安装以及基本命令可以戳此查看

    • 在末尾追加以下信息并保存

      1
      2
      3
      4
      export JAVA_HOME=/root/usr/jdk
      export JRE_HOME=/root/usr/jre
      export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
      export PATH=${JAVA_HOME}/bin:$PATH
    • 刷新配置文件

      1
      2
      source /etc/profile
      source ~/.bashrc
  4. 验证安装是否成功

    &nbsp;&nbsp;&nbsp;&nbsp;终端下输入java -version

前言

近段时间在使用easyopen时,发现定义的请求体与实际参数不符时会出现参数无法正常传递的现象,于是就把easyopen的源码 clone 下来研究了一波。

easyopen 测试版本

1
1.16.6.1

场景复现

  • 请求体定义如下
1
2
3
4
5
6
7
8
9
10
11
12
13
public class GoodsParam {

@ApiDocField(description = "商品名称", required = true, example = "iphoneX", name = "goods")
private String goodsName;

public String getGoodsName() {
return goodsName;
}

public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
}
  • 接口定义如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ApiService
@ApiDoc("商品模块")
public class GoodsApi {

private static final Logger log = LoggerFactory.getLogger(GoodsApi.class);

@Api(name = "goods.get")
@ApiDocMethod(description = "获取商品")
public String getGoods(GoodsParam param) {
log.info("incoming param: {}", JSONObject.toJSONString(param));
return param.getGoodsName();
}

}
上述代码使用[官方示例](https://gitee.com/durcframework/easyopen/tree/master/easyopen-demo/easyopen-server-normal)修改。

因为我是要复现出错的场景,所以我对请求体属性 goodsName 进行修改,并修改原有接口逻辑,使得接口打印请求体数据并直接返回请求体 goodsName 属性。

启动项目,请求接口 [goods.get](http://localhost:8081/api/doc#201),日志与结果截图如下

接口并未如文档定义一般拿到请求体的参数,场景复现。

寻找原因

代码的好处是,0 即是 0,1 就是 1,如果 0 变成 1,那就是你写的 bug 或者你有意为之,这个场景必定有代码作为支撑。

  因为研读源码的经验较浅,所以一开始从框架打印日志入手。

 程序在启动时总会打这几行日志,所以就全局搜索了一波,定位到 src\main\java\com\gitee\easyopen\register\ApiRegister.java 的 doWith 方法中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
checkTransactionalAnnotation(method);
ReflectionUtils.makeAccessible(method);
Api api = AnnotationUtils.findAnnotation(method, Api.class);
boolean ignoreSign = api.ignoreSign() ? true : this.apiServiceAnno.ignoreSign();
boolean ignoreValidate = api.ignoreValidate() ? true : this.apiServiceAnno.ignoreValidate();
boolean isWrapResult = this.apiServiceAnno.wrapResult() ? api.wrapResult() : false;
ApiDefinition apiDefinition = new ApiDefinition();
//apiDefinition setter....
Parameter[] parameters = method.getParameters();
Class<?> paramClass = null;
if (parameters != null && parameters.length > 0) {
Parameter parameter = parameters[0];
paramClass = parameter.getType();
boolean isNumberOrStringType = FieldUtil.isNumberOrStringType(paramClass);
apiDefinition.setSingleParameter(isNumberOrStringType);
apiDefinition.setMethodArguClass(paramClass);
if (isNumberOrStringType) {
SingleParameterContext.add(handler, method, parameter, api);
}
}
logger.debug("注册接口name={},version={},method={} {}({})", api.name(), api.version(),
method.getReturnType().getName(), method.getName(), paramClass == null ? "" : paramClass.getName());
try {
DefinitionHolder.addApiDefinition(apiDefinition);
apiConfig.getApiRegistEvent().onSuccess(apiDefinition);
} catch (DuplicateApiNameException e) {
logger.error(e.getMessage(), e);
System.exit(0);
}
apiCount++;
}

public static void addApiDefinition(ApiDefinition apiDefinition) throws DuplicateApiNameException {
String key = getKey(apiDefinition);
boolean hasApi = apiDefinitionMap.containsKey(key);
if (hasApi) {
throw new DuplicateApiNameException("重复申明接口,name:" + apiDefinition.getName() + " ,version:"+ apiDefinition.getVersion() + ",method:" + apiDefinition.getMethod().getName());
}
apiDefinitionMap.put(key, apiDefinition);
}
阅读代码发现,这一段代码只负责定义接口的解析并形成一个 String to ApiDefinition 的 Map,并没有请求体参数转换逻辑,所以这不是目的地。

但是这么大段代码只形成一个 Map,这个 Map 必定有其作用。于是寻找调用这个 Map 的 getter 方法,最后定位到 src\main\java\com\gitee\easyopen\register\ApiInvoker.java 的 doInvoke 方法中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
protected Object doInvoke(ApiParam param, HttpServletRequest request, HttpServletResponse response) throws Throwable {
ApiDefinition apiDefinition = this.getApiDefinition(param);
ApiContext.setApiMeta(apiDefinition);
if (!apiDefinition.isIgnoreJWT()) {
this.initJwtInfo(request, param);
}
// 方法参数
Object methodArgu = null;
// 返回结果
Object invokeResult = null;
Validator validator = ApiContext.getApiConfig().getValidator();
param.setIgnoreSign(apiDefinition.isIgnoreSign());
param.setIgnoreValidate(apiDefinition.isIgnoreValidate());
// 验证操作,这里有负责验证签名参数
validator.validate(param);
// 业务参数json格式
String busiJsonData = ApiContext.getApiConfig().getDataDecoder().decode(param);
// 业务参数Class
Class<?> arguClass = apiDefinition.getMethodArguClass();
boolean isSingleParameter = apiDefinition.isSingleParameter();
Object singleParamProxy = null;
int interceptorIndex = 0;
try {
// 将参数绑定到业务方法参数上,业务方法参数可以定义的类型:JSONObject,Map<String,Object>,String,业务参数类
if (arguClass != null) {
if(arguClass == JSONObject.class) {
methodArgu = JSON.parseObject(busiJsonData);
} else if(arguClass == Map.class) {
methodArgu = new HashMap<String,Object>(JSON.parseObject(busiJsonData));
} else if(isSingleParameter) {
SingleParameterContext.SingleParameterContextValue value = SingleParameterContext.get(param.fatchName(), param.fatchVersion());
if (value != null) {
JSONObject jsonObj = JSON.parseObject(busiJsonData);
methodArgu = jsonObj.getObject(value.getParamName(), arguClass);
singleParamProxy = jsonObj.toJavaObject(value.getWrapClass());
}
} else {
methodArgu = JSON.parseObject(busiJsonData, arguClass);
}
this.bindUploadFile(methodArgu);
}
// 拦截器
ApiInterceptor[] interceptors = ApiContext.getApiConfig().getInterceptors();
if(interceptors == null) {
interceptors = EMPTY_INTERCEPTOR_ARRAY;
}
//1. 调用preHandle
for (int i = 0; i < interceptors.length; i++) {
ApiInterceptor interceptor = interceptors[i];
if (interceptor.match(apiDefinition) && !interceptor.preHandle(request, response, apiDefinition.getHandler(),methodArgu)) {
//1.1、失败时触发afterCompletion的调用
triggerAfterCompletion(apiDefinition, interceptorIndex, request, response, methodArgu, null,null);
return null;
}
//1.2、记录当前预处理成功的索引
interceptorIndex = i;
}
// 验证业务参数JSR-303
this.validateBizArgu(validator, methodArgu, singleParamProxy);
/* *** 调用业务方法,被@Api标记的方法 ***/
MethodCaller methodCaller = apiDefinition.getMethodCaller();
if (methodCaller != null) {
invokeResult = methodCaller.call(new ApiInvocation(apiDefinition, methodArgu));
} else {
invokeResult = Callers.call(apiDefinition, methodArgu);
}
//3、调用postHandle,业务方法调用后处理(逆序)
for (int i = interceptors.length - 1; i >= 0; i--) {
ApiInterceptor interceptor = interceptors[i];
if(interceptor.match(apiDefinition)) {
interceptor.postHandle(request, response, apiDefinition.getHandler(), methodArgu, invokeResult);
}
}
if(invokeResult == null) {
invokeResult = EMPTY_OBJECT;
}
// 最终返回的对象
Object finalReturn = this.wrapResult(apiDefinition, invokeResult);
setMsg(finalReturn);
//4、触发整个请求处理完毕回调方法afterCompletion
triggerAfterCompletion(apiDefinition, interceptorIndex, request, response, methodArgu, finalReturn, null);
return finalReturn;
} catch (Exception e) {
this.triggerAfterCompletion(apiDefinition, interceptorIndex, request, response, methodArgu, invokeResult, e);
throw e;
}
}
 阅读代码发现,此段代码主要做了四个事情:解析网络请求数据,从  apiDefinitionMap 获取接口对应配置,反射调用对应接口,包装调用接口返回结果并返回。参数解析必定在此段代码中处理!
 
 通过断点 debug 查看数据得知,参数解析集中于以下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (arguClass != null) {
if (arguClass == JSONObject.class) {
methodArgu = JSON.parseObject(busiJsonData);
} else if (arguClass == Map.class) {
methodArgu = new HashMap(JSON.parseObject(busiJsonData));
} else if (isSingleParameter) {
SingleParameterContextValue value = SingleParameterContext.get(apiDefinition.getMethod());
if (value != null) {
JSONObject jsonObj = JSON.parseObject(busiJsonData);
methodArgu = jsonObj.getObject(value.getParamName(), arguClass);
singleParamProxy = jsonObj.toJavaObject(value.getWrapClass());
}
} else {
methodArgu = JSON.parseObject(busiJsonData, arguClass);
}

this.bindUploadFile(methodArgu);
}

step into断点得知,easyopen 依赖 fastjson 的序列化机制进行参数的序列化,原因得知:easyopen 使用 fastjson 序列化参数成为参数类获取参数,所以当 @ApiDocField 的属性 name 与参数类对应的属性名不一致时,属性值获取失败。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于 cartoon的博客
转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/source-code-unscramble/easyopen参数无法正常传递现象解析

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;众所周知,Linux与Win之间的区别是Linux需要大量的命令行操作,而有些配置文件也是在命令行中输入的。

&nbsp;&nbsp;&nbsp;&nbsp;这些操作就需要编辑器的帮助了。

&nbsp;&nbsp;&nbsp;&nbsp;我接触Ubuntu的时间不长,碰到过三个编辑器

1
2
3
gedit  //好像是叫这个名字
vi //Linux和Unix上最基本的文本编辑器
vim //比vi更好用的文本编辑器
  • 安装vim

&nbsp;&nbsp;&nbsp;&nbsp;终端内输入

1
sudo apt-get install vim
  • 常用命令
    1
    2
    3
    4
    5
    6
    vim fileName  //vim打开文件,文件需带有绝对路径
    i //编辑文件
    esc //退出编辑状态
    :q //不保存直接退出
    :wq //保存并退出
    ! //覆盖原文件

&nbsp;&nbsp;&nbsp;&nbsp;后三个命令必须与esc配合使用,可以组合使用

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;近段时间把自己电脑(win)、虚拟机(Ubuntu)以及阿里云(ubuntu)都重置了一遍,其中本机以及阿里云都有用到MySQL,不想之后找教程找的那么麻烦。所以就自己总结一遍,一次性把轮子造好。

&nbsp;&nbsp;&nbsp;&nbsp;环境

1
2
Win10 1803 Home
Ubuntu 16.04.3

Windows环境下安装

  1. 下载MySQL压缩包
    1
    https://dev.mysql.com/downloads/mysql/
  2. 解压到本地目录,并添加一个配置文件,命名为my.ini,内容如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [mysqld] 
    # 设置为自己MYSQL的安装目录
    # 注意是双斜杆,我就入过坑
    basedir=D:\\MySQL
    # 设置为MYSQL的数据目录
    # 文件夹需手动创建
    datadir=D:\\MySQL\\data
    default-storage-engine=INNODB
    character-set-server=utf8
    [mysql]
    # 设置mysql客户端默认字符集
    default-character-set=utf8
    [client]
    # 设置mysql客户端连接服务端时默认使用的端口
    port=3306
    default-character-set=utf8
  3. 以管理员权限进入MySQL的bin目录
    1
    2
    d: //MySQL的安装目录盘符
    cd MySQL/bin
  4. 安装MySQL服务
    1
    2
    mysqld --install
    //出现Service successfully installed即安装成功
  5. 初始化日志文件
    1
    mysqld --initialize --console
    &nbsp;&nbsp;&nbsp;&nbsp;记住mysql反馈语句中最后一条:a temporary password is….为第八步出现的问题作铺垫。

  1. 配置环境变量
    1
    2
    ;D:\MySQL\bin;
    //分号分隔前属性,bin目录的路径换成自己电脑的路径
  2. 启动服务
    1
    net start mysql
  3. 登录服务器
    1
    mysql -uroot -p
    &nbsp;&nbsp;&nbsp;&nbsp;输入密码提示Access denied for user ‘root‘@’localhost’ (using password: YES)

    &nbsp;&nbsp;&nbsp;&nbsp;解决方法及后续步骤:解决MySQL 8.0登录Access denied for user ‘root‘@’localhost’ (using password: YES)

Ubuntu环境下安装

  1. 打开终端

  2. 输入命令

    1
    2
    3
    sudo apt-get install mysql-server
    sudo apt-get isntall mysql-client
    sudo apt-get install libmysqlclient-dev

    &nbsp;&nbsp;&nbsp;&nbsp;在安装过程中会让你输入密码以及确认密码,不要忘掉,那是mysql的root用户密码

  3. 检查是否安装成功

    1
    sudo netstat -tap | grep mysql

    &nbsp;&nbsp;&nbsp;&nbsp;若出现截图内的语句即说明安装成功

&nbsp;&nbsp;&nbsp;&nbsp;存在的一点点问题

&nbsp;&nbsp;&nbsp;&nbsp;原本我是想在ubuntu下像在win一样通过配置文件进行安装的,但是试了很久都不行最后再次重置了服务器。

&nbsp;&nbsp;&nbsp;&nbsp;如果你们有什么方法是可以的可以在评论告诉我。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;近段时间在ubuntu中搭建jdk并在jdk的基础上建设Oracle数据库,遇到一个问题:常规GUI不能把文件移到usr目录下,提示permission denied。

&nbsp;&nbsp;&nbsp;&nbsp;取了一个巧,利用终端获取管理员权限后移动文件。

&nbsp;&nbsp;&nbsp;&nbsp;方法:终端输入

1
sudo nautilus

&nbsp;&nbsp;&nbsp;&nbsp;然后新创建的窗口默认带有管理员权限,问题就解决了。