分类:Java学习

Layui 实现火车站点选择功能

先看效果:


为什么要用这种方式?

答:数据量太大,直接采用下拉框会加载缓慢,采用数据表格分页的方式能够很好的适应,个人借鉴,各位有好的方法也可以相互交流或者提出建议。

下面正式开始:

1.表结构和sql

sql文件:点击下载

2.实体类 -Station.java

import java.io.Serializable;

/**
 * 
 * @description ...
 * @author  wujianyu
 * @email wjy329@vip.qq.com
 * @date  2019年2月12日 下午4:48:33
 */
public class Station implements Serializable {
private static final long serialVersionUID = 1L;

/**主键**/
private Integer id;
/**站点名称**/
private String station;

/**
 * 设置:主键
 */
public void setId(Integer id) {
this.id = id;
}
/**
 * 获取:主键
 */
public Integer getId() {
return id;
}
/**
 * 设置:站点名称
 */
public void setStation(String station) {
this.station = station;
}
/**
 * 获取:站点名称
 */
public String getStation() {
return station;
}
}

3.Dao

StationDao.java:

public interface StationDao{
  List<Station>  queryPage(@Param(value="page") PageInfo page);
  
  Integer getAllCnt();

/**
 * @description ...
 * @author  wujianyu
 * @email wjy329@vip.qq.com
 * @date  2019年2月13日 上午11:09:23
 */
Integer getCntByName(String station);

/**
 * @description ...
 * @author  wujianyu
 * @email wjy329@vip.qq.com
 * @date  2019年2月13日 上午11:09:29
 */
List<Station> queryPageByName(@Param(value="page")PageInfo pageInfo,@Param(value="station")String station);
}

StationDao.xml:  

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="xxx.StationDao">
<resultMap type="xxx.entity.Station" id="stationMap">
<result property="id" column="id" />
<result property="station" column="station" />
</resultMap>

<select id="getAllCnt" resultType="java.lang.Integer">
select count(id) from t_station
</select>

<select id="queryPage" parameterType="xxx.entity.PageInfo" resultMap="stationMap">
select
*
from t_station
order by id asc
<if test="page.limit != null">
<if test="page.offset != null">
limit ${page.offset}, ${page.limit}
</if>
<if test="page.offset == null">
limit ${limit}
</if>
</if>
</select>

<select id="getCntByName" resultType="java.lang.Integer">
    select count(id) from t_station where station LIKE #{station,jdbcType=VARCHAR}
  
</select>
  

  
<select id="queryPageByName"  parameterType="xxx.entity.PageInfo" resultMap="stationMap">
    select
*
from t_station 
where station LIKE #{station,jdbcType=VARCHAR}
    order by id asc
     <if test="page.limit != null">
      <if test="page.offset != null">
        limit ${page.offset}, ${page.limit}
      </if>
      <if test="page.offset == null">
        limit ${limit}
      </if>
    </if>
 
</select>
</mapper>

4.Service

StationService.java:

import com.alibaba.fastjson.JSONArray;

public interface StationService {

public List<Station> queryByPage(String station);

}

StationServiceImpl.java:

@Service("stationService")
@Transactional
public class StationServiceImpl implements StationService {
@Autowired
private StationDao stationDao;

@Override
public List<Station> queryByPage(String station) {
// 判断条数
Integer total = 0;
// 查询分页数据
List<Station> result = null;

if (!StringUtils.isEmpty(station)) {
station = "%" + station + "%";
total = this.stationDao.getCntByName(station);
result = this.stationDao.queryPageByName(SystemPageContext.getPageInfo(), station);
} else {
total = this.stationDao.getAllCnt();
result = this.stationDao.queryPage(SystemPageContext.getPageInfo());
}
// 设定总的数据量
SystemPageContext.setTotal(total);

return result;
}

}

5.Controller

StationController.java

/**
 * @description ...
 * @author wujianyu
 * @email wjy329@vip.qq.com
 * @date 2019年2月12日 下午4:37:56
 */
@Controller
@RequestMapping("/station")
public class StationController {
@Autowired
private StationService stationService;

/**
 * 
 * @description 跳转到站点选择界面
 * @author  wujianyu
 * @email wjy329@vip.qq.com
 * @date  2019年2月12日 下午4:39:01
 */
@RequestMapping("/list/{domId}")
public String list(Model model, @PathVariable("domId") String domId) {
model.addAttribute("domId", domId);
return "station/list.jsp";
}

@ResponseBody
@RequestMapping(value="/allStation",produces="application/json;charset=UTF-8")
public Map<String, Object> listData(String station){
//从数据库中获取到数据
List<Station> rs = this.stationService.queryByPage(station);
return WebUtils.getInstance().getLayuiPageResult(rs);
}

}

6.页面和js

list.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet"
href="${rc.contextPath}/statics/plugins/layui-v2.4.5/layui/css/layui.css"
media="all">
<script
src="${rc.contextPath}/statics/plugins/layui-v2.4.5/layui/layui.js"></script>
<title>Insert title here</title>
</head>
<body>
<input id="domId" type="hidden"
value="<c:out value="${domId}" escapeXml="true" />" />
<table class="layui-hide" id="station_list" lay-filter="station_list"></table>
<script type="text/html" id="toolbarDemo">
<div class="layui-inline">
             <div class="layui-btn-container">
                 <button class="layui-btn layui-btn-sm" lay-event="getCheckData">选择站点</button>
             </div>
        </div>
<div class="layui-inline">
    <div class="layui-input-inline">
    
<input type="text" value="" id="keyWords" placeholder="请输车站名查询" class="layui-input search_input" style="height:30px;">
    </div>
    <a class="layui-btn layui-btn-sm" lay-event="searchStation">查询</a>
</div>
<div class="layui-inline">
             <div class="layui-btn-container">
                 <button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="delSelect">删除已选站点</button>
             </div>
        </div>
    </script>
<script type="text/javascript" src="/page/station/js/list.js"></script>
</body>
</html>

list.js

layui.use(['form','table', 'laydate','jquery'], function () {
    var form = layui.form,
    
table = layui.table,
        layer = layui.layer,
        laydate = layui.laydate,
        $ = layui.jquery;
var domId = $('#domId').val();
    var stationTable = table.render({
     elem: '#station_list'
    ,url:'/station/allStation'
    ,toolbar: '#toolbarDemo'
    ,defaultToolbar: []
    ,id:'id'
    ,page: { //支持传入 laypage 组件的所有参数(某些参数除外,如:jump/elem) - 详见文档
        layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'] //自定义分页布局
      //,curr: 5 //设定初始在第 5 页
      ,groups: 1 //只显示 1 个连续页码
      ,first: false //不显示首页
      ,last: false //不显示尾页
      
    },request: {
    
  pageName: 'page' //页码的参数名称,默认:page
         ,limitName: 'limit' //每页数据量的参数名,默认:limit
},
    response: {
    
  statusCode: 1 //成功的状态码,默认:0
    }      
    ,cols: [[
    
{type:'radio'}
      ,{field:'id', width:'10%', title: 'ID',hidden:true, sort: true}
      ,{field:'station', title: '车站名称'}
    ]]
  });
    
    //头工具栏事件
    table.on('toolbar(station_list)', function(obj){
      var checkStatus = table.checkStatus(obj.config.id); //获取选中行状态
      switch(obj.event){
        case 'getCheckData':
          var data = checkStatus.data;  //获取选中行数据
          var station = data[0].station;
          parent.$("#"+domId+"").val(station);
          //关闭当前的frame
  
  var index = parent.layer.getFrameIndex(window.name); //先得到当前iframe层的索引
  
  parent.layer.close(index); //再执行关闭   
        break;
        case 'searchStation':
        
var keywords = $("#keyWords").val();
    
    stationTable.reload(
    
    
{page: {
    
            curr: 1 //重新从第 1 页开始
    
        }
    
        ,url: '/station/allStation',
    
    
where: {station:keywords}}
    
   );
    break;
        case 'delSelect':
        
parent.$("#"+domId+"").val("");
    break;
      };
    });
    
});

上面就是完整的代码,其他页面中使用时,只需获取dom的点击事件,弹出layer(或者其他)指向/station/list 页面即可。

其他需要类似的功能也可以使用上述方法,仅仅是本人的实践记录,如有其它方法,欢迎交流。

Spring Boot之Kaptcha验证码使用

1.引入Kaptcha依赖

<!-- 验证码 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

2.KaptchaBean.java 配置文件

package com.wjy329.shiro.filter;

import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.servlet.KaptchaServlet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class KaptchaBean {
    @Bean
    public org.springframework.boot.web.servlet.ServletRegistrationBean kaptchaServlet() {

        org.springframework.boot.web.servlet.ServletRegistrationBean registrationBean = new org.springframework.boot.web.servlet.ServletRegistrationBean(new KaptchaServlet(), "/kaptcha.jpg");

        registrationBean.addInitParameter(Constants.KAPTCHA_SESSION_CONFIG_KEY,
                Constants.KAPTCHA_SESSION_KEY);
        registrationBean.addInitParameter(Constants.KAPTCHA_IMAGE_HEIGHT, "38");//高度
        registrationBean.addInitParameter(Constants.KAPTCHA_IMAGE_WIDTH, "80");//宽度
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "26");//字体大小
        registrationBean.addInitParameter(Constants.KAPTCHA_BORDER_THICKNESS, "1"); //边框
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "red"); //文字颜色

        //可以设置很多属性,具体看com.google.code.kaptcha.Constants
//      kaptcha.border  是否有边框  默认为true  我们可以自己设置yes,no
//      kaptcha.border.color   边框颜色   默认为Color.BLACK
//      kaptcha.border.thickness  边框粗细度  默认为1
//      kaptcha.producer.impl   验证码生成器  默认为DefaultKaptcha
//      kaptcha.textproducer.impl   验证码文本生成器  默认为DefaultTextCreator
//      kaptcha.textproducer.char.string   验证码文本字符内容范围  默认为abcde2345678gfynmnpwx
//      kaptcha.textproducer.char.length   验证码文本字符长度  默认为5
//      kaptcha.textproducer.font.names    验证码文本字体样式  默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
//      kaptcha.textproducer.font.size   验证码文本字符大小  默认为40
//      kaptcha.textproducer.font.color  验证码文本字符颜色  默认为Color.BLACK
//      kaptcha.textproducer.char.space  验证码文本字符间距  默认为2
//      kaptcha.noise.impl    验证码噪点生成对象  默认为DefaultNoise
//      kaptcha.noise.color   验证码噪点颜色   默认为Color.BLACK
//      kaptcha.obscurificator.impl   验证码样式引擎  默认为WaterRipple
//      kaptcha.word.impl   验证码文本字符渲染   默认为DefaultWordRenderer
//      kaptcha.background.impl   验证码背景生成器   默认为DefaultBackground
//      kaptcha.background.clear.from   验证码背景颜色渐进   默认为Color.LIGHT_GRAY
//      kaptcha.background.clear.to   验证码背景颜色渐进   默认为Color.WHITE
//      kaptcha.image.width   验证码图片宽度  默认为200
//      kaptcha.image.height  验证码图片高度  默认为50
        return registrationBean;

    }
}

3.前端页面

<div class="layui-form-item">
    <div class="layui-inline">
    <label class="layui-form-label">验证码</label>
    <div class="layui-input-inline">
        <input type="text" name="captcha" required lay-verify="required" placeholder="请输入验证码" autocomplete="off" class="layui-input" style="width: 110px;float: left">
        <img src="kaptcha.jpg" name="captcha"  id="captcha" style="float: right" onclick="refreshCode(this)"/>
    </div>
</div>

这里的 onclick="refreshCode(this)"  是点击刷新验证码;

配置完上面3步可以访问你的页面看看验证码是否显示,如果显示则配置成功,就可以进行后续判断验证码阶段。

image.png

4.判断验证码

我这里由于使用了shiro(其他也一样),我简化了判断的方法,直接在你登陆的控制器内判断即可,Kaptcha会把前端的验证码信息存放到session中,所有我们在登陆的控制器用session中的验证码和前端表单传过来的验证码比较即可;

public String loginUser(@RequestParam("username")String username,
                        @RequestParam("password")String password,
                        @RequestParam("captcha") String captcha,
                        HttpSession httpSession){
    String sessionCaptcha = httpSession.getAttribute("KAPTCHA_SESSION_KEY").toString();
    if(!captcha.equals(sessionCaptcha)){
        return WebUtils.getInstance().writeMsg(ResultCode.ERROR, "验证码输入错误");
    }
    ......
 }

上面是我的验证代码,可以参考一下,注意获取session中的验证码的key为   “KAPTCHA_SESSION_KEY”  

5.点击刷新验证码

前面也说了点击刷新,就是一个方法,需要加入时间更新,代码如下:

function refreshCode(){
    var captcha = document.getElementById("captcha");
    captcha.src = "/kaptcha.jpg?t=" + new Date().getTime();
}

以上就是在项目中添加验证码的方法,希望有所帮助。一些配置项可以查看第2步中的配置类或者百度(-_-)。

Spring Boot之分页

分页是我们 常用的功能,MySQL实现分页的原理 就是用limit,比如 select * from table limit 0,5;  意思就是从0行开始查询5条数据,那么要实现分页我们需要两个参数,起始位置和每页的数据量,起始位置需要计算,为了前端传值的方便,前端只需传入当前页码每页数据量即可,下面我们来具体实现:

数据准备:

-- ----------------------------
-- Table structure for person
-- ----------------------------
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of person
-- ----------------------------
INSERT INTO `person` VALUES ('1', 'wjy', '24');
INSERT INTO `person` VALUES ('2', 'wjy1', '25');
INSERT INTO `person` VALUES ('3', '25', '25');
INSERT INTO `person` VALUES ('4', 'wjy2', '25');
INSERT INTO `person` VALUES ('5', 'wjy3', '25');
INSERT INTO `person` VALUES ('6', 'wjy4', '26');
INSERT INTO `person` VALUES ('7', 'wjy5', '27');
INSERT INTO `person` VALUES ('8', 'wjy0', '24');

image.png

1.分页实体 

PageBean.java:

package com.wjy329.pagedemo.utils;

import java.util.List;

public class PageBean<T> {
    //起始
    private int offset;
    // 当前页
    private int currentPage;
    // 每页显示的数据量
    private int pageCount;
    // 总记录数
    private int totalCount;
    // 总页数 = 总记录数 / 每页显示的行数  (+ 1)
    private int totalPage;
    // 分页查询到的数据
    private List<T> pageData;

    public int getOffset() {
        return offset;
    }

    public void setOffset(int offset) {
        this.offset = offset;
    }

    // 返回总页数
    public int getTotalPage() {
        if (totalCount % pageCount == 0) {
            totalPage = totalCount / pageCount;
        } else {
            totalPage = totalCount / pageCount + 1;
        }
        return totalPage;
    }
    public void setTotalPage(int totalPage) {
        this.totalPage = totalPage;
    }

    public int getCurrentPage() {
        return currentPage;
    }
    public void setCurrentPage(int currentPage){
        this.currentPage = currentPage;
    }
    public int getPageCount() {
        return pageCount;
    }
    public void setPageCount(int pageCount) {
        this.pageCount = pageCount;
    }
    public int getTotalCount() {
        return totalCount;
    }
    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public List<T> getPageData() {
        return pageData;
    }
    public void setPageData(List<T> pageData) {
        this.pageData = pageData;
    }
}

2.分页工具类

PageUtils.java:

package com.wjy329.pagedemo.utils;

import java.util.List;

public class PageUtils {
    public static PageBean getPage(Integer currentPage, Integer pageCount, Integer total){
        PageBean pageBean = new PageBean();
        //如果传入的当前页面为空,那么默认为第一页
        if(currentPage == null){
            currentPage = 1;
        }
        //如果传入的数据量为空,默认显示5条
        if(pageCount == null){
            pageCount = 5;
        }
        //起始位置的计算
        int offset  = (currentPage-1)*pageCount;
        pageBean.setCurrentPage(currentPage);
        pageBean.setPageCount(pageCount);
        pageBean.setTotalCount(total);
        pageBean.setOffset(offset);

        return pageBean;
    }
}

3.实体类

person.java:

package com.wjy329.pagedemo.entity;


import java.io.Serializable;

public class Person implements Serializable {
    private Integer id;

    private String name;

    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

4.PersonMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.wjy329.pagedemo.dao.PersonDao">
    <resultMap id="BaseResultMap" type="com.wjy329.pagedemo.entity.Person">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="age" jdbcType="INTEGER" property="age" />
    </resultMap>

    <select id="findAllByPage" parameterType="com.wjy329.pagedemo.utils.PageBean" resultMap="BaseResultMap">
        SELECT id,name,age
        FROM person
        LIMIT #{page.offset},#{page.pageCount}
    </select>

    <select id="getAllCnt"  resultType="java.lang.Integer">
    select count(id) from person
  </select>

</mapper>

5.PersonDao.java

package com.wjy329.pagedemo.dao;

import com.wjy329.pagedemo.entity.Person;
import com.wjy329.pagedemo.utils.PageBean;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface PersonDao {
    List<Person> findAllByPage(@Param(value="page")PageBean page);

    Integer getAllCnt();
}

6.PersonService.java

package com.wjy329.pagedemo.service;

import com.wjy329.pagedemo.entity.Person;
import com.wjy329.pagedemo.utils.PageBean;

import java.util.List;

public interface PersonService {
     PageBean queryByPage(Integer currentPage, Integer pageCount);
}

7.PersonServiceImpl.java

package com.wjy329.pagedemo.service.impl;

import com.wjy329.pagedemo.dao.PersonDao;
import com.wjy329.pagedemo.entity.Person;
import com.wjy329.pagedemo.service.PersonService;
import com.wjy329.pagedemo.utils.PageBean;
import com.wjy329.pagedemo.utils.PageUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PersonServiceImpl implements PersonService {

    @Autowired
    private PersonDao personDao;

    @Override
    public PageBean queryByPage(Integer currentPage,Integer pageCount) {
        Integer total = 0;
        List<Person> persons  = null;
        total = this.personDao.getAllCnt();
        PageBean pageBean = PageUtils.getPage(currentPage,pageCount,total);
        //查询出来的分页数据
        persons = this.personDao.findAllByPage(pageBean);
        pageBean.setPageData(persons);
        return pageBean;
    }
}

8.PersonController.java

package com.wjy329.pagedemo.controller;

import com.wjy329.pagedemo.entity.Person;
import com.wjy329.pagedemo.service.PersonService;
import com.wjy329.pagedemo.utils.PageBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Controller
@RequestMapping("/person")
public class PersonController {
    @Autowired
    private PersonService personService;

    @RequestMapping("/getAll")
    @ResponseBody
    public PageBean getAll(Integer currentPage, Integer pageCount){
        PageBean list = personService.queryByPage(currentPage, pageCount);
        return list;
    }
}

上面就是全部的代码,然后我们开始访问: localhost:80/person/getAll   由于没有传入参数,所以按工具类中的默认 值查询,也就是第一页,5条数据:

image.png

访问: localhost:80/person/getAll?currentPage=2&pageCount=5   返回第2页的5条数据

image.png

上面就是分页的简单实现,然后据需要配合前端 的页面传值和获取 数据等。

demo地址: https://github.com/wjy329/page-demo

JAVA学习之HASHMAP底层(二)

这篇文章让我们了解下高并发下的HashMap。

HashMap随着插入的数据的增多,使得key值的冲突会越来越多,这时候HashMap就会进行扩容。

先看一下扩容的条件:

HashMap.size()>=Capacity*LoadFactor

其中HashMap.size()是HashMap的长度,Capacity是HashMap的容量,LoadFactor是负载因子(默认值为0.75f)

当满足扩容条件时,HashMap会先新建一个容量为之前2倍的Entry,然后遍历之前的Entry,重新Hash到新的数组

因为index = HashCode(key) & (length -1 ) ,新的数组长度变了,所以都得重新计算。

HashMap在多线程下会出现死循环,死循环发生在扩容的第二步,也就是重新Hash那一步,先把源码附上,以后能看懂了再分析。。。

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

JPress使用

JPress是一个用Java语言编写的开源的快速建站平台,一般用于博客的快速搭建,类似于大名鼎鼎的WordPress。

JPress官方网站:http://www.jpress.io

首先  git clone https://gitee.com/fuhai/jpress.git  下载项目

然后用shell进入项目的目录,执行mvn package 命令

image.png

执行完毕后,可以看到在start-tomcat/target下生成stater-tomcat-1.0.war 

image.png

然后我们在MySQL中新建一个数据库,运行项目目录下的db.sql,创建表;

将刚才生成的war包拷贝到tomcat的webapps目录下,然后解压。

image.png

找到WEB-INF下的classes文件夹下的jboot.properties,修改为自己的数据库配置

image.png

然后启动tomcat 就可以访问了。。。

image.png

访问 http://127.0.0.1:8080/starter-tomcat-1.0/

跳转到初始化界面

image.png

填好自己的信息后,就可以管理自己的博客了。

默认的后台界面:

image.png

默认的主页:

image.png

剩下的东西自己探索,或者参考官方的文档,写的很是详细。

Java学习之HashMap底层(一)

HashMap作为常用和常考的Java映射,有必要在详细的学习一下,结合多篇博客和资料的理解,简单记录下个人的想法。

HashMap的每一个键值对都是一个Entry,一个个的键值对(Entry)分散存储在数组中,构成了HashMap的主干,每个Entry实际上是作为一个链表的头结点,当后面的key的index冲突的时候,就在Entry之后添加一个新的节点。

面试中,我们常常回答:HashMap 的底层是数组加链表的结合体,JDK1.8中添加了红黑树。这个以后再研究。

下面我们通过一个图来直观的感受一下:

image.png

HashMap中用来处理数组索引的哈希函数是: index  = hashCode(key) & (length – 1)  其中length是HashMap 的长度。

HashMap的默认初始长度是16,扩容的阈值是0.75,每次扩容为原表数量的2倍(必须是2的幂)

当计算出来的key的index冲突后,新的key就会充当头结点,因为HashMap的发明人认为后插入的数据可能被查找的几率更大。

再谈一下为什么初始长度是16,简单的来讲是为了让index均匀分布:

根据上面的哈希函数,length-1 = 16 -1 =15,二进制为:1111 

假设有三个key的hashCode是1001、1011、1111,它们分别与1111进行& 运算

image.png

将它们再转为10进制会得到三个不同的index

当长度为10时,10-1=9,9的二进制为 1001,将上面三个hashCode再分别与1001进行&运算

image.png

这时我们发现,得到的二进制数一样,这就意味着index的重复几率也太大了,不符合均匀分布的原则,所以长度设置为16或其它2的幂。

Get方法原理:

先将key通过index = Hash(key) 得到相应的index的值,然后根据index的值查找相对应的entry,由于可能有hash冲突,所以会在链表中匹配key。

Mac IDEA快捷键记录

Mac键盘符号和修饰键说明

⌘ Command

⇧ Shift

⌥ Option

⌃ Control

↩︎ Return/Enter

⌫ Delete

⌦ 向前删除键(Fn+Delete)

↑ 上箭头

↓ 下箭头

← 左箭头

→ 右箭头

⇞ Page Up(Fn+↑)

⇟ Page Down(Fn+↓)

Home Fn + ←

End Fn + →

⇥ 右制表符(Tab键)

⇤ 左制表符(Shift+Tab)

⎋ Escape (Esc)

一、Editing(编辑)

⌃Space 基本的代码补全(补全任何类、方法、变量)

⌃⇧Space 智能代码补全(过滤器方法列表和变量的预期类型)

⌘⇧↩ 自动结束代码,行末自动添加分号

⌘P 显示方法的参数信息

⌃J, Mid. button click 快速查看文档

⇧F1 查看外部文档(在某些代码上会触发打开浏览器显示相关文档)

⌘+鼠标放在代码上 显示代码简要信息

⌘F1 在错误或警告处显示具体描述信息

⌘N, ⌃↩, ⌃N 生成代码(getter、setter、构造函数、hashCode/equals,toString)

⌃O 覆盖方法(重写父类方法)

⌃I 实现方法(实现接口中的方法)

⌘⌥T 包围代码(使用if..else, try..catch, for, synchronized等包围选中的代码)

⌘/ 注释/取消注释与行注释

⌘⌥/ 注释/取消注释与块注释

⌥↑ 连续选中代码块

⌥↓ 减少当前选中的代码块

⌃⇧Q 显示上下文信息

⌥↩ 显示意向动作和快速修复代码

⌘⌥L 格式化代码

⌃⌥O 优化import

⌃⌥I 自动缩进线

⇥ / ⇧⇥ 缩进代码 / 反缩进代码

⌘X 剪切当前行或选定的块到剪贴板

⌘C 复制当前行或选定的块到剪贴板

⌘V 从剪贴板粘贴

⌘⇧V 从最近的缓冲区粘贴

⌘D 复制当前行或选定的块

⌘⌫ 删除当前行或选定的块的行

⌃⇧J 智能的将代码拼接成一行

⌘↩ 智能的拆分拼接的行

⇧↩ 开始新的一行

⌘⇧U 大小写切换

⌘⇧] / ⌘⇧[ 选择直到代码块结束/开始

⌥⌦ 删除到单词的末尾(⌦键为Fn+Delete)

⌥⌫ 删除到单词的开头

⌘+ / ⌘- 展开 / 折叠代码块

⌘⇧+ 展开所以代码块

⌘⇧- 折叠所有代码块

⌘W 关闭活动的编辑器选项卡

二、Search/Replace(查询/替换)

Double ⇧ 查询任何东西

⌘F 文件内查找

⌘G 查找模式下,向下查找

⌘⇧G 查找模式下,向上查找

⌘R 文件内替换

⌘⇧F 全局查找(根据路径)

⌘⇧R 全局替换(根据路径)

⌘⇧S 查询结构(Ultimate Edition 版专用,需要在Keymap中设置)

⌘⇧M 替换结构(Ultimate Edition 版专用,需要在Keymap中设置)

三、Usage Search(使用查询)

⌥F7 / ⌘F7 在文件中查找用法 / 在类中查找用法

⌘⇧F7 在文件中突出显示的用法

⌘⌥F7 显示用法

四、Compile and Run(编译和运行)

⌘F9 编译Project

⌘⇧F9 编译选择的文件、包或模块

⌃⌥R 弹出 Run 的可选择菜单

⌃⌥D 弹出 Debug 的可选择菜单

⌃R 运行

⌃D 调试

⌃⇧R, ⌃⇧D 从编辑器运行上下文环境配置

五、Debugging(调试)

F8 进入下一步,如果当前行断点是一个方法,则不进入当前方法体内

F7 进入下一步,如果当前行断点是一个方法,则进入当前方法体内,如果该方法体还有方法,则不会进入该内嵌的方法中

⇧F7 智能步入,断点所在行上有多个方法调用,会弹出进入哪个方法

⇧F8 跳出

⌥F9 运行到光标处,如果光标前有其他断点会进入到该断点

⌥F8 计算表达式(可以更改变量值使其生效)

⌘⌥R 恢复程序运行,如果该断点下面代码还有断点则停在下一个断点上

⌘F8 切换断点(若光标当前行有断点则取消断点,没有则加上断点)

⌘⇧F8 查看断点信息

六、Navigation(导航)

⌘O 查找类文件

⌘⇧O 查找所有类型文件、打开文件、打开目录,打开目录需要在输入的内容前面或后面加一个反斜杠/

⌘⌥O 前往指定的变量 / 方法

⌃← / ⌃→ 左右切换打开的编辑tab页

F12 返回到前一个工具窗口

⎋ 从工具窗口进入代码文件窗口

⇧⎋ 隐藏当前或最后一个活动的窗口,且光标进入代码文件窗口

⌘⇧F4 关闭活动run/messages/find/… tab

⌘L 在当前文件跳转到某一行的指定处

⌘E 显示最近打开的文件记录列表

⌘⌥← / ⌘⌥→ 退回 / 前进到上一个操作的地方

⌘⇧⌫ 跳转到最后一个编辑的地方

⌥F1 显示当前文件选择目标弹出层,弹出层中有很多目标可以进行选择(如在代码编辑窗口可以选择显示该文件的Finder)

⌘B / ⌘ 鼠标点击 进入光标所在的方法/变量的接口或是定义处

⌘⌥B 跳转到实现处,在某个调用的方法名上使用会跳到具体的实现处,可以跳过接口

⌥ Space, ⌘Y 快速打开光标所在方法、类的定义

⌃⇧B 跳转到类型声明处

⌘U 前往当前光标所在方法的父类的方法 / 接口定义

⌃↓ / ⌃↑ 当前光标跳转到当前文件的前一个/后一个方法名位置

⌘] / ⌘[ 移动光标到当前所在代码的花括号开始/结束位置

⌘F12 弹出当前文件结构层,可以在弹出的层上直接输入进行筛选(可用于搜索类中的方法)

⌃H 显示当前类的层次结构

⌘⇧H 显示方法层次结构

⌃⌥H 显示调用层次结构

F2 / ⇧F2 跳转到下一个/上一个突出错误或警告的位置

F4 / ⌘↓ 编辑/查看代码源

⌥ Home 显示到当前文件的导航条

F3选中文件/文件夹/代码行,添加/取消书签

⌥F3 选中文件/文件夹/代码行,使用助记符添加/取消书签

⌃0…⌃9 定位到对应数值的书签位置

⌘F3 显示所有书签

七、Refactoring(重构)

F5 复制文件到指定目录

F6 移动文件到指定目录

⌘⌫ 在文件上为安全删除文件,弹出确认框

⇧F6 重命名文件

⌘F6 更改签名

⌘⌥N 一致性

⌘⌥M 将选中的代码提取为方法

⌘⌥V 提取变量

⌘⌥F 提取字段

⌘⌥C 提取常量

⌘⌥P 提取参数

八、VCS/Local History(版本控制/本地历史记录)

⌘K 提交代码到版本控制器

⌘T 从版本控制器更新代码

⌥⇧C 查看最近的变更记录

⌃C 快速弹出版本控制器操作面板

九、Live Templates(动态代码模板)

⌘⌥J 弹出模板选择窗口,将选定的代码使用动态模板包住

⌘J 插入自定义动态代码模板

十、General(通用)

⌘1…⌘9 打开相应编号的工具窗口

⌘S 保存所有

⌘⌥Y 同步、刷新

⌃⌘F 切换全屏模式

⌘⇧F12 切换最大化编辑器

⌥⇧F 添加到收藏夹

⌥⇧I 检查当前文件与当前的配置文件

§⌃, ⌃` 快速切换当前的scheme(切换主题、代码样式等)

⌘, 打开IDEA系统设置

⌘; 打开项目结构对话框

⇧⌘A 查找动作(可设置相关选项)

⌃⇥ 编辑窗口标签和工具窗口之间切换(如果在切换的过程加按上delete,则是关闭对应选中的窗口)

十一、Other(一些官方文档上没有体现的快捷键)

⌘⇧8 竖编辑模式

 

导航

⌘O 查找类文件 Ctrl + N

⌘⌥O 前往指定的变量 / 方法 Ctrl + Shift + Alt + N

⌃← / ⌃→ 左右切换打开的编辑tab页 Alt← / Alt→

⎋ 从工具窗口进入代码文件窗口 ESC

⌘L 在当前文件跳转到某一行的指定处 Ctrl + G

⌘E 显示最近打开的文件记录列表 Ctrl + E

⌘⌥← / ⌘⌥→ 退回 / 前进到上一个操作的地方 Ctrl + Alt + ← Ctrl + Alt + →

⌘⇧⌫ 跳转到最后一个编辑的地方

⌃H 显示当前类的层次结构 Ctrl + H

⌘⇧H 显示方法层次结构

⌃⌥H 显示调用层次结构

F4 / ⌘↓ 编辑/查看代码源

⌘⌥U 显示类UML图

⌃J 查看注释

编辑

⌥⌦ 删除到单词的末尾(⌦键为Fn+Delete)

⌥⌫ 删除到单词的开头

⌘+ / ⌘- 展开 / 折叠代码块

⌘F1 在错误或警告处显示具体描述信息

⌘⌥L 格式化代码

⌃⌥O 优化import

⇧↩ 开始新的一行

⌘⇧↩ 自动结束代码,行末自动添加分号

⌃I 实现方法(实现接口中的方法)

⇧F6 重命名文件或者变量

⌘N, ⌃↩, ⌃N 生成代码(getter、setter、构造函数、hashCode/equals,toString)

⌘P 显示方法的参数信息

查找

Double⇧ 查找任何东西

⌘⇧F 全局查找(根据路径)

⌘F 文件内查找

⌘G 查找模式下,向下查找

⌘⇧G 查找模式下,向上查找

导航

⌘⌥B 跳转到接口的实现

⌘U 查看接口定义

⌘⌥← / ⌘⌥→ 退回 / 前进到上一个操作的地方

⌘B / ⌘ 鼠标点击 进入光标所在的方法/变量的接口或是定义处

⌃⇧B 跳转到类型声明处

⌥ Space, ⌘Y 快速打开光标所在方法、类的定义

⌘O 查找类文件

⌘⇧O 查找所有类型文件、打开文件、打开目录,打开目录需要在输入的内容前面或后面加一个反斜杠/

F12 返回到前一个工具窗口

⎋ 从工具窗口进入代码文件窗口

⇧⎋ 隐藏当前或最后一个活动的窗口,且光标进入代码文件窗口

F3选中文件/文件夹/代码行,添加/取消书签

⌥F3 选中文件/文件夹/代码行,使用助记符添加/取消书签

⌃0…⌃9 定位到对应数值的书签位置

⌘F3 显示所有书签

⌥F1 显示当前文件选择目标弹出层,弹出层中有很多目标可以进行选择(如在代码编辑窗口可以选择显示该文件的Finder)

⌘F12 弹出当前文件结构层,可以在弹出的层上直接输入进行筛选(可用于搜索类中的方法)

通用

⌃⌘F 切换全屏模式

Java学习之异常

今天复习一下Java的异常,先看下图:

image.png

我们从图中可以看出Java中异常的顶级类是Throwable,所有的异常都继承于这个类;

Error和Exception是异常类的两个大的分类;

Error是非程序异常,是程序不能捕捉的异常,一般是编译或者是系统性的错误;

Exception是程序异常类,由程序内部产生;Exception又分为运行时异常(非检查性异常)和非运行时异常(检查性异常)。

Java中一般分为3种类型的异常:

  • 非运行时性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。例:IOException…

  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。例:NullPointerException…

  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

日志框架-SLF4J+Logback

SLF4J全称:Simple Logging Facade for Java,即简单日志门面它是把不同的日志系统的实现进行了具体的抽象化,只提供了统一的日志使用接口,使用时只需要按照其提供的接口方法进行调用即可,由于它只是一个接口,并不是一个具体的可以直接单独使用的日志框架,所以最终日志的格式、记录级别、输出方式等都要通过接口绑定的具体的日志系统来实现,这些具体的日志系统就有log4j,logback,java.util.logging等,它们才实现了具体的日志系统的功能。


简单的说,SLF4J提供了日志的接口,日志的实现需要其它具体的框架来实现;

今天我就记录下SLF4J+Logback的组合来提供日志的框架。

新建一个Springboot的工程,Springboot中已经提供了SLF4J,所以我们不需要添加它的相关依赖;

工程目录结构:

image.png


在测试类中编写测试代码:

package com.wjy329.logdemo;/**
 * @Author wjy329
 * @Time 2018/11/68:53 PM
 * @description
 */

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class logTest {

    private final Logger logger = LoggerFactory.getLogger(logTest.class);

    @Test
    public void testLog(){
        logger.info("这是info日志");
        logger.error("这是错误日志");
        logger.debug("这是debug日志");
    }
}

控制台输出的结果是:

image.png

我们可以看到info和error级别的日志输出了,原因是默认的级别是info级别,所以debug级别的日志就没有打印。

日志的级别:ERROR>WARN>INFO>DEBUG>TRACE   级别越高优先级越高,当级别为info时,优先级比info低的debug和trace就不打印了,当级别为error时,只会打印error级别的日志。

上面仅仅是日志的简单使用,下面我们介绍一般在项目中是如何配置日志的。

在resource目录下新建一个日志的配置文件,我这里叫logback.xml  

image.png

具体的代码:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>
    <!-- 输出到控制台的日志 -->
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d - %msg%n
            </pattern>
        </encoder>

    </appender>

    <!-- 写出到日志文件 -->
    <appender name="testLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 输出路径 -->
            <fileNamePattern>/Users/wjy329/desktop/testLog/wjy329-%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>
                %d - %msg%n
            </pattern>
        </encoder>
    </appender>
    <!-- 日志级别 -->
    <root level="info">
        <appender-ref ref="consoleLog"/>
        <appender-ref ref="testLog"/>
    </root>

</configuration>

然后我们运行测试代码:

控制台输出如下:

image.png

输出到日志文件如下:

image.png

上面就是简单的演示日志系统的使用,后面如果遇到更详细的东西也会继续更新。