分类: 编程技巧

  • 帅呆!接口开发不用写Controller、Service、Dao、Mapper、XML、VO,全自动生成

    快乐分享,Java干货及时送达👇

    今天给小伙伴们介绍一个Java接口快速开发框架-magic-api

    简介

    magic-api 是一个基于 Java 的接口快速开发框架,编写接口将通过 magic-api 提供的 UI 界面完成,自动映射为 HTTP 接口,无需定义 Controller、Service、Dao、Mapper、XML、VO 等 Java 对象即可完成常见的 HTTP API 接口开发

    访问 http://localhost:9999/magic/web 进行操作

    文档地址:

    • https://ssssssss.org

    在线演示:

    • https://magic-api.ssssssss.org

    开源地址:

    • https://gitee.com/ssssssss-team/magic-api

    特性

    • 支持MySQL、MariaDB、Oracle、DB2、PostgreSQL、SQLServer 等支持jdbc规范的数据库
    • 支持非关系型数据库Redis、Mongodb
    • 支持集群部署、接口自动同步。
    • 支持分页查询以及自定义分页查询
    • 支持多数据源配置,支持在线配置数据源
    • 支持SQL缓存,以及自定义SQL缓存
    • 支持自定义JSON结果、自定义分页结果
    • 支持对接口权限配置、拦截器等功能
    • 支持运行时动态修改数据源
    • 支持Swagger接口文档生成
    • 基于magic-script脚本引擎,动态编译,无需重启,实时发布
    • 支持Linq式查询,关联、转换更简单
    • 支持数据库事务、SQL支持拼接,占位符,判断等语法
    • 支持文件上传、下载、输出图片
    • 支持脚本历史版本对比与恢复
    • 支持脚本代码自动提示、参数提示、悬浮提示、错误提示
    • 支持导入Spring中的Bean、Java中的类
    • 支持在线调试
    • 支持自定义工具类、自定义模块包、自定义类型扩展、自定义方言、自定义列名转换等自定义操作

    快速开始

    maven引入


     org.ssssssss
        magic-api-spring-boot-starter
        1.7.1

    修改application.properties
    server.port=9999
    #配置web页面入口
    magic-api.web=/magic/web
    #配置文件存储位置。当以classpath开头时,为只读模式
    magic-api.resource.location=/data/magic-api
    项目截图

    整体截图

    代码提示

    DEBUG

    参数提示

    远程推送

    历史记录

    数据源

    全局搜索

  • 这款 IDEA 插件太好用了,堪称日志管理神器!

    快乐分享,Java干货及时送达👇

    1.简介

    Grep Console是一款方便开发者对idea控制台输出日志进行个性化管理的插件。

    2.功能特性

    Grep Console的主要功能特性:

    • 支持自定义规则来过滤日志信息;
    • 支持不同级别的日志的输出样式的个性化配置;

    总结:通过过滤功能、输出日志样式配置功能,可以更方便开发者在大量的日志信息中筛选出自己比较关注的日志信息。

    3.下载

    官网下载地址:https://plugins.jetbrains.com/,在搜索输入中输入“Grep Console”,就可以到达下载页面。

    图片

    4.安装

    Grep Console的安装方式有两种,优化推荐第一种在线安装:

    4.1 在线安装

    在线安装,需要可以连接到外网,file-->settings-->plugins-->browse repositories,在查询输入框中输入“grep console”,然后点击install,最后重启idea就可以使用了。

    图片

    4.2 离线安装

    有的时候,无法连接到外网的时候,可以使用离线安装的方法进行idea插件安装 :

    a.从官网下载与自己使用idea的版本号相匹配的插件安装包;(我的idea版本是2018.2),所以我下载的grep console版本是9.6.162.000.1

    图片

    图片

    b.file-->settings-->plugins-->install plugin from disk,然后选中下载好的插件安装包进行安装,完装完成后重启idea就可以使用了;

    图片

    4.3 卸载插件

    file-->settings-->plugins,在搜索输入框中输入“grep console”,然后点击uninstall进行插件卸载;

    图片

    5.使用方法

    5.1 配置

    1、grep console的配置界面弹出有两种方法:

    第一种:file-->other settings-->grep console

    图片

    第二种,项目启动后,点击控制台上左上角的小图标,也可以弹出配置界面

    图片
    2、比较关注的配置有两处:

    第一处:input filtering,这里主要是对输入到控制台的日志进行过滤。

    • expression:配置正则表达式;
    • unless expression:和expression表达的意义相反;
    • whole line:勾选中,表示匹配整行;
    • case insensitive:表示忽略大小写
    • action:表示命中正则表达式后,要作出什么样的操作,有三种:1、移除(除非前面已有其他配置项匹配不移除),实际意思是说如果前面有其他不移除的配置项已经匹配上,这里就可以不移除,否则还是要移除匹配上的内容;2、移除(匹配上就移除);3、什么也不做;
    • continue matching:勾选中,表示下一个匹配项依然可以继续匹配当前匹配项命中的行,即多个匹配项可以同时去匹配同一行日志信息;
    • clear console:勾选中,表示清除控制台内除被当前配置项命中的其他日志信息,即只显示与配置项匹配的日志信息;
    • soud:这个就比较厉害了,命中配置项时会有声音提示,有兴趣的可以尝试一下;

    注:实际使用的时候,无论我怎么写expression表达式都没有办法完全匹配整行,遇到空格就不匹配,只能匹配到一部分,有可能是我写的expression表达式不正确,也有可能插件本身在这块有问题,有知道原因的小伙伴,可以在评论区告诉我吧,非常感谢。

    图片

    图片

    第二处:highlighting&folding

    • expression:配置正则表达式;
    • unless expression:和expression表达的意义相反;
    • whole line:勾选中,表示匹配整行;
    • case insensitive:表示忽略大小写
    • continue matching:勾选中,表示下一个匹配项依然可以继续匹配当前匹配项命中的行,即多个匹配项可以同时去匹配同一行日志信息;
    • bold:勾选中,表示命中的日志信息粗体显示;
    • italic:勾选中,表示命中的日志信息斜体显示;
    • background:设置命中日志信息的背景颜色;
    • foreground:设置命中日志信息的字体颜色;
    • statusbar count:勾选中,可以在Status Bar statistics panel中显示命中日志信息的次数(找了很久,没找到这个面板,有知道的小伙伴在评论区告诉我,多谢);
    • console count:勾选中,可以在Console statistics panel上显示命中日志信息的次数(找了很久,没找到这个面板,有知道的小伙伴在评论区告诉我,多谢);
    • fold:勾选中,可以把命中的日志信息折叠在一起(感觉这个功能没什么用);
    • sound:这个就比较厉害了,根据配置项命中日志信息时会有声音提示,有兴趣的可以尝试一下;

    5.2 实战

    下面实际演示一个这个插件怎么用,测试用的源代码:https://gitcode.net/fox9916/fanfu-web.gitgrep-console-test分支。

    1、有三个定时调度类,在被执行的时候会输出info级别日志信息;

    2、对这三个调度任务输出的日志信息进行配置,要求:匹配整行日志信息;忽略大小;EatTask调度任务输出日志信息背景色为黄色;DrinkTask调度任务输出日志信息为绿色;SportTask调用任务输出日志信息为蓝色;配置信息如下:

    图片

    3、启动项目,输出日志信息如下:

    图片

    4、在控制台输出的日志信息选中“com.fanfu.task.EatTask”,然后在选中的信息上右键弹窗中选中“Grep”,可以把包含“com.fanfu.task.EatTask”的日志信息在单独的弹窗中显示,可以通过这个功能把自己比较关注的日志信息集中显示。右键中还有另外一个功能“Add highlight”,可以把自己关注的日志信息标记为高亮,很方便在众多的日志里找到自己最想要的。

    图片

    图片

    6.总结

    这个插件还是很用的,终于可以在满屏的日志中,迅速找到自己关注的内容,调试程序的绝佳小帮手呀,以上就是这个插件的主要内容,可以根据自己的实际需要动手操作起来了,祝各位早点下班,bug绕着走,如果觉得很有用,麻烦各位关注加收藏,永远不迷路哦。

    作者:凡夫贩夫

    来源:blog.csdn.net/fox9916/article/

    details/128568466

    
    

  • 灵魂一问:SELECT COUNT(*) 会造成全表扫描吗?

    快乐分享,Java干货及时送达👇


    来源:程序员大彬

    • 前言
    • SQL 选用索引的执行成本如何计算
    • 实例说明
    • 总结

    前言

    SELECT COUNT(*)会不会导致全表扫描引起慢查询呢?

    SELECT COUNT(*) FROM SomeTable  

    网上有一种说法,针对无 where_clauseCOUNT(*),MySQL 是有优化的,优化器会选择成本最小的辅助索引查询计数,其实反而性能最高,这种说法对不对呢

    针对这个疑问,我首先去生产上找了一个千万级别的表使用  EXPLAIN 来查询了一下执行计划

    EXPLAIN SELECT COUNT(*) FROM SomeTable  

    结果如下

    图片

    如图所示: 发现确实此条语句在此例中用到的并不是主键索引,而是辅助索引,实际上在此例中我试验了,不管是 COUNT(1),还是 COUNT(*),MySQL 都会用成本最小 的辅助索引查询方式来计数,也就是使用 COUNT(*) 由于 MySQL 的优化已经保证了它的查询性能是最好的!随带提一句,COUNT(*)是 SQL92 定义的标准统计行数的语法,并且效率高,所以请直接使用COUNT(*)查询表的行数!

    所以这种说法确实是对的。但有个前提,在 MySQL 5.6 之后的版本中才有这种优化。

    那么这个成本最小该怎么定义呢,有时候在 WHERE 中指定了多个条件,为啥最终 MySQL 执行的时候却选择了另一个索引,甚至不选索引?

    本文将会给你答案,本文将会从以下两方面来分析

    • SQL 选用索引的执行成本如何计算
    • 实例说明

    SQL 选用索引的执行成本如何计算

    就如前文所述,在有多个索引的情况下, 在查询数据前,MySQL 会选择成本最小原则来选择使用对应的索引,这里的成本主要包含两个方面。

    • IO 成本: 即从磁盘把数据加载到内存的成本,默认情况下,读取数据页的 IO 成本是 1,MySQL 是以页的形式读取数据的,即当用到某个数据时,并不会只读取这个数据,而会把这个数据相邻的数据也一起读到内存中,这就是有名的程序局部性原理,所以 MySQL 每次会读取一整页,一页的成本就是 1。所以 IO 的成本主要和页的大小有关
    • CPU 成本:将数据读入内存后,还要检测数据是否满足条件和排序等 CPU 操作的成本,显然它与行数有关,默认情况下,检测记录的成本是 0.2。

    实例说明

    为了根据以上两个成本来算出使用索引的最终成本,我们先准备一个表(以下操作基于 MySQL 5.7.18)

    CREATE TABLE `person` (  
      `id` bigint(20) NOT NULL AUTO_INCREMENT,  
      `name` varchar(255) NOT NULL,  
      `score` int(11) NOT NULL,  
      `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  
      PRIMARY KEY (`id`),  
      KEY `name_score` (`name`(191),`score`),  
      KEY `create_time` (`create_time`)  
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;  

    这个表除了主键索引之外,还有另外两个索引, name_scorecreate_time。然后我们在此表中插入 10 w 行数据,只要写一个存储过程调用即可,如下:

    CREATE PROCEDURE insert_person()  
    begin  
        declare c_id integer default 1;  
        while c_iddo  
        insert into person values(c_id, concat('name',c_id), c_id+100, date_sub(NOW(), interval c_id second));  
        set c_id=c_id+1;  
        end while;  
    end  

    插入之后我们现在使用 EXPLAIN 来计算下统计总行数到底使用的是哪个索引

    EXPLAIN SELECT COUNT(*) FROM person  

    图片

    从结果上看它选择了 create_time 辅助索引,显然 MySQL 认为使用此索引进行查询成本最小,这也是符合我们的预期,使用辅助索引来查询确实是性能最高的!

    我们再来看以下 SQL 会使用哪个索引

    SELECT * FROM person WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'   

    图片

    用了全表扫描!理论上应该用 name_score 或者 create_time 索引才对,从 WHERE 的查询条件来看确实都能命中索引,那是否是使用 SELECT * 造成的回表代价太大所致呢,我们改成覆盖索引的形式试一下

    SELECT create_time FROM person WHERE NAME >'name84059' AND create_time > '2020-05-23 14:39:18'   

    结果 MySQL 依然选择了全表扫描!这就比较有意思了,理论上采用了覆盖索引的方式进行查找性能肯定是比全表扫描更好的,为啥 MySQL 选择了全表扫描呢,既然它认为全表扫描比使用覆盖索引的形式性能更好,那我们分别用这两者执行来比较下查询时间吧

    -- 全表扫描执行时间: 4.0 ms  
    SELECT create_time FROM person WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'   
      
    -- 使用覆盖索引执行时间: 2.0 ms  
    SELECT create_time FROM person force index(create_time) WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'   

    从实际执行的效果看使用覆盖索引查询比使用全表扫描执行的时间快了一倍!说明 MySQL 在查询前做的成本估算不准!我们先来看看 MySQL 做全表扫描的成本有多少。

    前面我们说了成本主要 IO 成本和 CPU 成本有关,对于全表扫描来说也就是分别和聚簇索引占用的页面数和表中的记录数。执行以下命令

    SHOW TABLE STATUS LIKE 'person'  

    图片

    可以发现

    1. 行数是 100264,我们不是插入了 10 w 行的数据了吗,怎么算出的数据反而多了,其实这里的计算是估算 ,也有可能这里的行数统计出来比 10 w 少了,估算方式有兴趣大家去网上查找,这里不是本文重点,就不展开了。得知行数,那我们知道 CPU 成本是 100264 * 0.2 = 20052.8
    2. 数据长度是 5783552,InnoDB 每个页面的大小是 16 KB,可以算出页面数量是 353。

    也就是说全表扫描的成本是 20052.8 + 353 = 20406

    这个结果对不对呢,我们可以用一个工具验证一下。在 MySQL 5.6 及之后的版本中,我们可以用 optimizer trace 功能来查看优化器生成计划的整个过程 ,它列出了选择每个索引的执行计划成本以及最终的选择结果,我们可以依赖这些信息来进一步优化我们的 SQL。

    optimizer_trace 功能使用如下

    SET optimizer_trace="enabled=on";  
    SELECT create_time FROM person WHERE NAME >'name84059' AND create_time > '2020-05-23 14:39:18';  
    SELECT * FROM information_schema.OPTIMIZER_TRACE;  
    SET optimizer_trace="enabled=off";  

    执行之后我们主要观察使用 name_scorecreate_time 索引及全表扫描的成本。

    先来看下使用 name_score 索引执行的的预估执行成本:

    {  
        "index""name_score",  
        "ranges": [  
          "name84059   
        ],  
        "index_dives_for_eq_ranges"true,  
        "rows": 25372,  
        "cost": 30447  
    }  

    可以看到执行成本为 30447,高于我们之前算出来的全表扫描成本:20406。所以没选择此索引执行

    注意:这里的 30447 是查询二级索引的 IO 成本和 CPU 成本之和,再加上回表查询聚簇索引的 IO 成本和 CPU 成本之和。

    再来看下使用 create_time 索引执行的的预估执行成本:

    {  
        "index""create_time",  
        "ranges": [  
          "0x5ec8c516   
        ],  
        "index_dives_for_eq_ranges"true,  
        "rows": 50132,  
        "cost": 60159,  
        "cause""cost"  
    }  

    可以看到成本是 60159,远大于全表扫描成本 20406,自然也没选择此索引。

    再来看计算出的全表扫描成本:

    {  
        "considered_execution_plans": [  
          {  
            "plan_prefix": [  
            ],  
            "table""`person`",  
            "best_access_path": {  
              "considered_access_paths": [  
                {  
                  "rows_to_scan": 100264,  
                  "access_type""scan",  
                  "resulting_rows": 100264,  
                  "cost": 20406,  
                  "chosen"true  
                }  
              ]  
            },  
            "condition_filtering_pct": 100,  
            "rows_for_plan": 100264,  
            "cost_for_plan": 20406,  
            "chosen"true  
          }  
        ]  
    }  

    注意看 cost:20406,与我们之前算出来的完全一样!这个值在以上三者算出的执行成本中最小,所以最终 MySQL 选择了用全表扫描的方式来执行此 SQL。

    实际上 optimizer trace 详细列出了覆盖索引,回表的成本统计情况,有兴趣的可以去研究一下。

    从以上分析可以看出, MySQL 选择的执行计划未必是最佳的,原因有挺多,就比如上文说的行数统计信息不准,再比如 MySQL 认为的最优跟我们认为不一样,我们可以认为执行时间短的是最优的,但 MySQL 认为的成本小未必意味着执行时间短。

    总结

    本文通过一个例子深入剖析了 MySQL 的执行计划是如何选择的,以及为什么它的选择未必是我们认为的最优的,这也提醒我们,在生产中如果有多个索引的情况,使用 WHERE 进行过滤未必会选中你认为的索引,我们可以提前使用  EXPLAIN, optimizer trace 来优化我们的查询语句。

  • 针对 jar 和 vue 的一键自动化部署工具,界面友好,操作简单(已开源)

    前言

    easy-jenkins是一款对vue和jar的部署工具,操作简单,实行一键部署,内部结构采用流水线形式架构,每次部署,时时提供部署过程,部署记录,界面友好简洁,使用方便,符合用户常规操作

    easy-jenkins面向分支形式,无需登录,默认分支为jenkins,每个分支可以配置多个数据源,切换不同分支可以管理不同数据源

    easy-jenkins采用本地存储的结构无需配置数据库,简单易上手

    提示:以下是本篇文章正文内容,下面案例可供参考

    一、项目地址

    开源地址:

    • https://gitee.com/susantyp/easy-jenkins

    二、使用步骤

    先把代码拉入你的本地

    1.项目结构

    2.启动主类 EasyJenkinsApplication

    3.安装

    启动后弹出当前窗体 点击下一步

    来到这里后,填写相应的信息

    • 安装路径
    • maven路径 打包需要
    • 以及项目端口的启动,避免不要和本地端口冲突,我们可以设置 8332 8899 9900 等端口

    点击安装并启动, 点击确认 等待几秒,项目自动启动

    4.项目启动图

    三、功能点介绍

    • 部署列表
    • 部署记录
    • 数据分支
    • 基本设置

    1.部署列表

    部署列表主要显示我们的连接信息

    1.添加连接(部署jar)

    我们点击按钮,添加连接

    添加本地项目地址

    添加本地项目地址后,它下面的文本款会根据本地项目地址自动生成,如图

    在这边需要确保:

    • 你的本地项目地址是正确的
    • jar名称正确的
    • pom.xml文件是正确的

    我部署项目的端口为8080

    根据你自己的项目设置端口

    添加服务器相关信息

    在我们的右边填写我们的服务器信息

    • 服务器ip
    • 账号
    • 密码
    • 端口

    上传的位置 后面不需要带 /

    上传的位置 默认生成一个命令

    如果上传位置是 /home/springboot

    则生成如下

    nohup java -jar /home/springboot/wall.jar & tailf /home/springboot/nohup.out

    可以修改为你自己需要运行的命令

    或者直接使用当前命令

    2.部署jar

    我们点击部署按钮即可

    部署过程效果图

    后台会实时返回部署的消息,返回给前端显示

    3.部署成功

    部署成功返回:Successfully deployed

    4.删除

    点击table 直接删除

    5.编辑

    编辑小伙伴可以自己玩一下

    2.部署记录

    部署记录主要记录了,最近部署的情况和统计信息

    3.数据分支

    easy-jenkins 是面向分支的

    不同分支存储不同的连接,默认分支为jenkins

    1.创建分支

    创建一个root的分支

    2.切换分支

    切换完成后,可以查看当前分支的状态

    当前我们就是root分支的环境下

    我们点击部署记录

    部署记录此时为空的,刚刚创建分支下面是没有连接数据的,需要重新添加连接

    我们切换为jenkins分支后,前面我们在jenkins添加了一条连接数据,下面就显示数据了,同时上面会标注当前的环境为jenkins

    注意了,正在使用的分支是不可以删除

    4.基本设置

    • 安装路径
    • maven路径
    • 项目端口号

    当前这三个值,是我们最初刚刚开始安装的时候的需要录入的值,我们可以点击编辑操作

    5.启动

    • 第一次启动会启动安装向导程序
    • 第二次启动直接启动浏览器,则不再启动安装向导程序

    6.如何部署vue

    dist 是vue项目默认build的位置

    同样 后面 不需要 ‘/’

    需要注意

    上传位置名字保持跟本地相同的名字,如图:

    然后填写你相应的服务器信息即可

    7.exe启动项目

    在我们exe文件夹下面,有一个easy-jenkins.exe文件

    可以将他拷贝到桌面,直接点击它运行即可,不需要每次启动springboot程序

    总结

    此部署工具主要针对于个人本地的部署

    针对于小型项目的部署,轻量级的,一键部署,操作简单

    作者:来自上海的这位朋友

    来源:blog.csdn.net/Susan003/article/

    details/128223343

  • 切记,任何时候都不要在 for 循环中删除 List 集合元素!!!

    前言

    首先说结论:无论什么场景,都不要对List使用for循环的同时,删除List集合元素,因为这么做就是不对的。

    阿里开发手册也明确说明禁止使用foreach删除、增加List元素。

    正确删除元素的方式是使用迭代器(Iterator),代码如下:

    List list = new ArrayList();
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        // 删除元素
        iterator.remove();
    }

    JDK8后lambda写法:list.removeIf(s -> s.contains("a"));

    不想知道为什么不能使用for循环删除List集合元素的,看完前言就可以关闭本页面了,想知道原因的继续往下看

    实例

    下面举个实例场景,看一下为什么不能使用for循环。

    需求

    一个List集合,元素类型为String,有N个元素,删除这些元素中包含字符”a”的元素。

    假设集合内容如下:

    List list = new ArrayList(4);
    list.add("a");
    list.add("ab");
    list.add("abc");
    list.add("abcd");

    正确答案

    先上正确答案

    public static void main(String[] args) {
        List list = new ArrayList(4);
        list.add("a");
        list.add("ab");
        list.add("abc");
        list.add("abcd");

        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            if (iterator.next().contains("a")) {
                // 删除元素
                iterator.remove();
            }
        }
        System.out.println(list);
    }

    输出结果为

    []
    错误答案1:普通for循环(for-i)
    public static void main(String[] args) {
        List list = new ArrayList(4);
        list.add("a");
        list.add("ab");
        list.add("abc");
        list.add("abcd");

        for (int i = 0; i         if (list.get(i).contains("a")) {
                list.remove(i);
            }
        }
        System.out.println(list);
    }

    输出结果为

    [ab, abcd]

    分析

    普通for循环遍历List集合的同时,删除List中的元素是可以运行的代码,但在大多数场景下,不能使用这种方式,上边的结果也印证了这一点,虽然你的代码不会报错,运行也正常,但在本实例中,这么写就是BUG。

    BUG原因:索引为i的元素删除后,后边元素的索引自动向前补位,即原来索引为i+1的元素,变为了索引为i的元素,但是下一次循环取的索引是i+1,此时你以为取到的是原来索引为i+1的元素,其实取到是原来索引为i+2的元素。

    如下图示例:

    看图可以发现,只要每删除一个元素,就会漏掉下一个元素,所以这种方式从逻辑上来说是存在bug的,无论什么需求场景,都不建议用这种方式,因为不可控因素太多(鬼知道生产环境中他会删掉多少元素,同时漏掉多少元素)。

    既然这么写不报错,那么个别特殊场景确实可以使用这种普通for循环删除元素的,比如我们把实例的需求变动一下,改为:一个List集合,元素类型为String,有N个元素,删除这些元素中包含字符’a’的元素,如果有连续两个或以上元素包含’a’,那么只删除当前连续元素中的奇数位元素。虽然这种场景适用,但仍然不推荐,还是因为太不可控。

    错误答案2:增强for循环(foreach)
    public static void main(String[] args) {
        List list = new ArrayList(4);
        list.add("a");
        list.add("ab");
        list.add("abc");
        list.add("abcd");

        for (String str : list) {
            if (str.contains("a")) {
                list.remove(str);
            }
        }
        System.out.println(list);
    }

    运行报错:

    Exception in thread "main" java.util.ConcurrentModificationException
     at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
     at java.util.ArrayList$Itr.next(ArrayList.java:859)
     at top.oldmoon.learn.test.ListTest.main(ListTest.java:24)

    使用百度翻译可以知道:Concurrent Modification Exception:并发修改异常

    分析

    其实这里没啥好分析的,直接报错了,你还这么写干嘛?没事找罪受吗。。。

    可以简单的理解为:foreach就不支持对集合中的元素进行增删操作,但是可以修改。

    作者:DingDangDog

    来源:https://oldmoon.top/post/12

    
    

  • 牛逼哄哄的 BitMap,到底牛逼在哪?

    快乐分享,Java干货及时送达👇

    文章来源:https://www.cnblogs.com/cjsblog/p/11613708.html


    BitMap


    Bit-map的基本思想就是用一个bit位来标记某个元素对应的Value,而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。(PS:划重点 节省存储空间 )

    假设有这样一个需求:在20亿个随机整数中找出某个数m是否存在其中,并假设32位操作系统,4G内存

    在Java中,int占4字节,1字节=8位(1 byte = 8 bit)

    如果每个数字用int存储,那就是20亿个int,因而占用的空间约为 (2000000000*4/1024/1024/1024)≈7.45 G

    如果按位存储就不一样了,20亿个数就是20亿位,占用空间约为 (2000000000/8/1024/1024/1024)≈0.233 G

    高下立判,无需多言

    那么,问题来了,如何表示一个数呢?

    刚才说了,每一位表示一个数,0表示不存在,1表示存在,这正符合二进制

    这样我们可以很容易表示{1,2,4,6}这几个数:

    计算机内存分配的最小单位是字节,也就是8位,那如果要表示{12,13,15}怎么办呢?

    当然是在另一个8位上表示了:

    图片

    这样的话,好像变成一个二维数组了

    1个int占32位,那么我们只需要申请一个int数组长度为 int tmp[1+N/32] 即可存储,其中N表示要存储的这些数中的最大值,于是乎:

    tmp[0]:可以表示0~31

    tmp[1]:可以表示32~63

    tmp[2]:可以表示64~95

    。。。

    如此一来,给定任意整数M,那么M/32就得到下标,M%32就知道它在此下标的哪个位置


    添加


    这里有个问题,我们怎么把一个数放进去呢?例如,想把5这个数字放进去,怎么做呢?

    首先,5/32=0,5%32=5,也是说它应该在tmp[0]的第5个位置,那我们把1向左移动5位,然后按位或

    换成二进制就是

    这就相当于 86 | 32 = 118

    86 | (1

    b[0] = b[0] | (1

    也就是说,要想插入一个数,将1左移带代表该数字的那一位,然后与原数进行按位或操作

    化简一下,就是 86 + (5/8) | (1

    因此,公式可以概括为:p + (i/8)|(1


    清除


    以上是添加,那如果要清除该怎么做呢?

    还是上面的例子,假设我们要6移除,该怎么做呢?

    从图上看,只需将该数所在的位置为0即可

    1左移6位,就到达6这个数字所代表的位,然后按位取反,最后与原数按位与,这样就把该位置为0了

    b[0] = b[0] & (~(1

    b[0] = b[0] & (~(1


    查找


    前面我们也说了,每一位代表一个数字,1表示有(或者说存在),0表示无(或者说不存在)。通过把该为置为1或者0来达到添加和清除的小伙,那么判断一个数存不存在就是判断该数所在的位是0还是1

    假设,我们想知道3在不在,那么只需判断 b[0] & (1


    Bitmap有什么用


    量数据的快速排序、查找、去重



    快速排序


    假设我们要对0-7内的5个元素(4,7,2,5,3)排序(这里假设这些元素没有重复),我们就可以采用Bit-map的方法来达到排序的目的。

    要表示8个数,我们就只需要8个Bit(1Bytes),首先我们开辟1Byte的空间,将这些空间的所有Bit位都置为0,然后将对应位置为1。

    最后,遍历一遍Bit区域,将该位是一的位的编号输出(2,3,4,5,7),这样就达到了排序的目的,时间复杂度O(n)。

    优点:

    • 运算效率高,不需要进行比较和移位;
    • 占用内存少,比如N=10000000;只需占用内存为N/8=1250000Byte=1.25M

    缺点:

    • 所有的数据不能重复。即不可对重复的数据进行排序和查找。
    • 只有当数据比较密集时才有优势

    快速去重


    20亿个整数中找出不重复的整数的个数,内存不足以容纳这20亿个整数。

    首先,根据“内存空间不足以容纳这05亿个整数”我们可以快速的联想到Bit-map。下边关键的问题就是怎么设计我们的Bit-map来表示这20亿个数字的状态了。其实这个问题很简单,一个数字的状态只有三种,分别为不存在,只有一个,有重复。因此,我们只需要2bits就可以对一个数字的状态进行存储了,假设我们设定一个数字不存在为00,存在一次01,存在两次及其以上为11。那我们大概需要存储空间2G左右。

    接下来的任务就是把这20亿个数字放进去(存储),如果对应的状态位为00,则将其变为01,表示存在一次;如果对应的状态位为01,则将其变为11,表示已经有一个了,即出现多次;如果为11,则对应的状态位保持不变,仍表示出现多次。

    最后,统计状态位为01的个数,就得到了不重复的数字个数,时间复杂度为O(n)。

    快速查找


    这就是我们前面所说的了,int数组中的一个元素是4字节占32位,那么除以32就知道元素的下标,对32求余数(%32)就知道它在哪一位,如果该位是1,则表示存在。



    小结&回顾


    Bitmap主要用于快速检索关键字状态,通常要求关键字是一个连续的序列(或者关键字是一个连续序列中的大部分), 最基本的情况,使用1bit表示一个关键字的状态(可标示两种状态),但根据需要也可以使用2bit(表示4种状态),3bit(表示8种状态)。

    Bitmap的主要应用场合:表示连续(或接近连续,即大部分会出现)的关键字序列的状态(状态数/关键字个数 越小越好)。

    32位机器上,对于一个整型数,比如int a=1 在内存中占32bit位,这是为了方便计算机的运算。但是对于某些应用场景而言,这属于一种巨大的浪费,因为我们可以用对应的32bit位对应存储十进制的0-31个数,而这就是Bit-map的基本思想。Bit-map算法利用这种思想处理大量数据的排序、查询以及去重。

    补充1


    在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方,右移一位相当于除2,右移n位相当于除以2的n次方。

    >> 右移,相当于除以2的n次方,例如:64>>3 相当于64÷8=8

    ^ 异或,相当于求余数,例如:48^32 相当于 48%32=16

    补充2


    不使用第三方变量,交换两个变量的值

    // 方式一
    a = a + b;
    b = a - b;
    a = a - b;

    // 方式二
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;


    BitSet


    BitSet实现了一个位向量,它可以根据需要增长。每一位都有一个布尔值。一个BitSet的位可以被非负整数索引(PS:意思就是每一位都可以表示一个非负整数)。可以查找、设置、清除某一位。通过逻辑运算符可以修改另一个BitSet的内容。默认情况下,所有的位都有一个默认值false。

    图片
    图片
    图片
    图片
    图片

    可以看到,跟我们前面想的差不多

    用一个long数组来存储,初始长度64,set值的时候首先右移6位(相当于除以64)计算在数组的什么位置,然后更改状态位

    别的看不懂不要紧,看懂这两句就够了:

    int wordIndex = wordIndex(bitIndex);
    words[wordIndex] |= (1L 


    Bloom Filters


    图片

    Bloom filter 是一个数据结构,它可以用来判断某个元素是否在集合内,具有运行快速,内存占用小的特点。

    而高效插入和查询的代价就是,Bloom Filter 是一个基于概率的数据结构:它只能告诉我们一个元素绝对不在集合内或可能在集合内。

    Bloom filter 的基础数据结构是一个 比特向量(可理解为数组)。

    主要应用于大规模数据下不需要精确过滤的场景,如检查垃圾邮件地址,爬虫URL地址去重,解决缓存穿透问题等

    如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(哈希表)等等数据结构都是这种思路,但是随着集合中元素的增加,需要的存储空间越来越大;同时检索速度也越来越慢,检索时间复杂度分别是O(n)、O(log n)、O(1)。

    布隆过滤器的原理是,当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组(Bit array)中的 K 个点,把它们置为 1 。检索时,只要看看这些点是不是都是1就知道元素是否在集合中;如果这些点有任何一个 0,则被检元素一定不在;如果都是1,则被检元素很可能在(之所以说“可能”是误差的存在)。


    BloomFilter 流程

    1、 首先需要 k 个 hash 函数,每个函数可以把 key 散列成为 1 个整数;

    2、初始化时,需要一个长度为 n 比特的数组,每个比特位初始化为 0;

    3、某个 key 加入集合时,用 k 个 hash 函数计算出 k 个散列值,并把数组中对应的比特位置为 1;

    4、判断某个 key 是否在集合时,用 k 个 hash 函数计算出 k 个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。

    dependency>
        groupId>com.google.guavagroupId>
        artifactId>guavaartifactId>
        version>28.1-jreversion>
    dependency>