派大星的博客

很多事情不是会了才能做,而是做了才能学会


  • 首页

  • 标签

  • 分类

  • 关于

  • 搜索

深入理解Java虚拟机(1~8)

发表于 2019-02-17 | 分类于 面试,JVM

1. java代码是怎么运行的

Java将运行时的内存区域划分为五个部分,如下图所示

从虚拟机的视角来看

执行java代码首先需要将它编译后的class文件加载到虚拟机中,加载后的Java类会被存放到方法区中,实际运行时,虚拟机会执行方法区中的代码。在运行过程中,每当调用进入一个Java方法,java虚拟机就会在当前线程的Java方法栈中生成一个栈帧,用以存放该方法的局部变量和字节码操作数,当退出当前执行方法时,无论方法是正常返回还是异常返回,Java虚拟机均会弹出当前栈帧,并将之舍弃。

从硬件的视角来看

Java字节码无法直接执行,因此Java虚拟机需要将字节码翻译为机器码执行。在Hotspot虚拟机中,此翻译过程有两种方式:

  1. 解释执行:逐条将字节码翻译为机器码并执行,优点:无需等待
  2. 即时翻译:将一个方法中包含的字节码全部翻译成机器码后再执行。优点:实际运行速度快

Hotspot内置了多个即时编译器,C1,C2,Graal

  1. C1:又被叫做Client编译器,面向的是对启动性能有要求的GUI程序
  2. C2:又被叫做Server编译器,面向的是对峰值性能有要求的服务端程序

从Java7之后,Hotspot采用了分层编译模式:热点代码会被C1进行编译,而热点中的热点代码会进一步被C2进行编译。Hotspot会根据CPU数量及编译线程数目,按1:2的比例配置给C1和C2。

2. 类的加载过程

java虚拟机将字节流转化为java类的过程分为三个部分:加载、链接、初始化。

加载:加载是指查找字节流并以此创建类的过程。加载需要借助类加载器,在java虚拟机中类加载器采用双亲委派模型,即受到类加载请求,会先将请求交给父加载器加载,如果父加载器不能加载才由自己进行加载。

链接:链接是指将创建的类合并至java虚拟机中,此过程分为三个部分:验证、准备、解析,其中解析过程是非必须的。

初始化:初始化则是为标记为常量值的字段赋值,以及执行方法的过程,类的初始化仅会被执行一次。

类的初始化何时会被触发呢?JVM 规范枚举了下述多种触发情况:

  1. 当虚拟机启动时,初始化用户指定的主类;
  2. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;
  3. 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
  4. 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
  5. 子类的初始化会触发父类的初始化;
  6. 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接 口的初始化;
  7. 使用反射 API 对某个类进行反射调用时,初始化这个类;
  8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。

根据这个特性,我们可以实现单例的延迟初始化

1
2
3
4
5
6
7
8
9
public class Singleton {
private Singleton() {}
private static class LazyHolder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}

用命令查看类加载的先后顺序

1
2
3
4
// 编译
javac Singleton.java
// 加入参数-verbose:class,查看类加载的先后顺序
java -verbose:class Singleton

3. JVM是如何处理异常的

在编译生成的字节码中,都有一个异常表,异常表中每一个条目对应一个异常处理器,并且由from指针,to指针,target指针和type(异常捕获类型)组成。from和to指针标识了该异常的监控范围(也就是try代码块所覆盖的范围),而target指针则指向了异常处理器的起始位置(也就是catch代码块位置)

当程序触发异常时

当程序触发异常时,java虚拟机会从上至下遍历异常表中的条目,当触发异常的字节码索引在某个异常表条目的监控的范围之内,则会判断抛出的异常与该条目要捕获的异常是否匹配,如果匹配,Java虚拟机会将控制流转向该条目target指针所指向的字节码。如果遍历整个异常条目仍未匹配异常处理器,那么它会弹出当前方法对应的栈帧,并且在调用者用重复上诉操作,在最坏的情况,Java虚拟机需要遍历当前线程Java栈的上所有方法的异常表。

finally代码块如果保证必须执行的

Java编译器会复制finally块中的内容,分别放到try-catch块的正常执行路径和异常执行路径出口中。

为什么抛异常很耗费性能

JVM在构造异常实例时需要生成该异常的栈轨迹。这个操作会逐一访问当前线程的栈帧,并且记录下各种调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常等信息。

Supressed 异常

如果 catch 代码块捕获了异常,并且触发了另一个异常,那么 finally 捕获并且重抛的异常是哪个呢?答案是后者。也就是说原本的异常便会被忽略掉,这对于代码调试来说十分不利。为此Java 7 引入了 Supressed 异常来解决这个问题。这个新特性允许开发人员将一个异常附于另一个异常之上。因此,抛出的异常可以附带多个异常的信息。

try-with-resources

主要是为了精简资源打开关闭的用法。程序可以在 try 关键字后声明并实例化实现了 AutoCloseable 接口的类,编译器将自动添加对应的 close() 操作。在声明多个 AutoCloseable 实例的情况下,编译生成的字节码类似于上面手工编写代码的编译结果。与手工代码相比,try-with-resources 还会使用 Supressed 异常的功能,来避免原异常“被消失”

4. JVM是如何进行反射调用的

在默认情况下,方法的反射调用为委派实现,委派给本地实现来进行方法调用。在调用超过 15 次之后,委派实现便会将委派对象切换至动态实现。这个动态实现的字节码是自动生成的,它将直接使用 invoke 指令来调用目标方法。

反射调用的开销

  1. 避免返回数组

    getMethod+为代表的查找方法操作,会返回查找得到结果的一份拷贝。因此,我们应当避免在热点代码中使用返回+Method+数组的+getMethods+或者+getDeclaredMethods+方法,以减少不必要的堆空间消耗。

  2. 避免自动拆箱装箱

    由于变长参数会被编译器自动生成一个Object数组,而Object数据是不能存储基本数据类型的,所以这里就需要对int类型的进行自动装箱,默认int缓存范围为[-128~127],当数值在这个范围之内就会返回缓存的Integer对象,避免新建Integer对象,所以我们可以将这个缓存的范围扩大至覆盖 128(对应参数 -Djava.lang.Integer.IntegerCache.high=128

开销总结

方法的反射调用会带来不少性能开销,原因主要有三个:变长参数方法导致的 Object 数组,基本类型的自动装箱、拆箱,还有最重要的方法内联

方法内联

方法内联指的是编译器在编译一个方法时,将某个方法调用的目标方法也纳入编译范围内,并用其返回值替代原方法调用这么个过程。

Shell入门实例

发表于 2019-02-14 | 分类于 Linux,shell

控制字符

常见的控制字符有:&,&&,||,;,|

&: 将命令在后台执行
&&: 前面的命令执行成功,后面的命令才会执行
||: 前面的命令执行失败,后面的命令才会执行
;:命令顺序执行,前面的命令执行成功与否,后面的命令都会执行
|:管道符,前面命令的执行结果是后面命令的输入

实例

将命令在后台执行,可通过 jobs 命令查看当前后台执行的命令

1
java -jar Test &

当test.txt存在时,查看该文件,只有ls test.txt执行成功后,才会执行 后面的 cat test.txt

1
ls test.txt && cat test.txt

如果有gedit编辑器则打开该程序,否则打开vim编辑器

1
gedit || vim

命令依次执行,无论前面命令是否执行成功,后面的命令都会执行

1
ls /tmp ; ls /root ; ls /home

查看test.txt文件,并屏蔽其输出(扔到了 /dev/null黑洞里),如果前面命令执行成功,说明文件存在,则打印出文件存在,如果执行失败,说明文件不存在,则打印文件不存在

1
cat test.txt > /dev/null && echo "文件存在" || echo "文件不存在"

输出当前进程中有bash关键字的进程,前面ps -a的输出结果,是 grep bash 的输入参数

1
ps -a | grep bash

参数变量

  • $@所有参数列表,与 $*效果相同
  • $$ Shell本身的PID
  • $0: Shell本身的文件名
  • $1~n 参数值,$1为第一个参数,$2为第二个参数。。。

实例

1
2
3
4
#!/bin/sh
echo "shell脚本本身的名字: $0"
echo "传给shell的第一个参数: $1"
echo "传给shell的第二个参数: $2"

将其保存为 test.sh,执行

1
bash Test.sh 1 2

输出结果

1
2
3
shell脚本本身的名字: test.sh
传给shell的第一个参数: 1
传给shell的第二个参数: 2

常用脚本demo
函数声明一定要在函数调用前面,不然会找不到,切记,和高级语言不一样!

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
#!/bin/bash
run() {
echo "启动 $1 程序"
}

stop(){
echo "关闭 $1 程序"
}

restart() {
echo "重启动 $1 程序"
stop "$1"
run "$1"
}

# 帮助文档
usage() {
echo "start: 启动程序 build.sh start 程序名"
echo "stop: 停止程序 build.sh stop 程序名"
echo "restart: 重启程序 build.sh restart 程序名"
}

main() {
case $1 in
start)
run $2
;;
restart)
restart $2
;;
stop)
stop $2
;;
*)
usage
;;
esac
}
# 执行main 函数,并将所有参数传递过去
main "$@"

使用

1
./build.sh stop docker

echo输出彩色字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
echo -e "\033[30m 黑色字 \033[0m"
echo -e "\033[31m 红色字 \033[0m"
echo -e "\033[32m 绿色字 \033[0m"
echo -e "\033[33m 黄色字 \033[0m"
echo -e "\033[34m 蓝色字 \033[0m"
echo -e "\033[35m 紫色字 \033[0m"
echo -e "\033[36m 天蓝字 \033[0m"
echo -e "\033[37m 白色字 \033[0m"


echo -e "\033[40;37m 黑底白字 \033[0m"
echo -e "\033[41;37m 红底白字 \033[0m"
echo -e "\033[42;37m 绿底白字 \033[0m"
echo -e "\033[43;37m 黄底白字 \033[0m"
echo -e "\033[44;37m 蓝底白字 \033[0m"
echo -e "\033[45;37m 紫底白字 \033[0m"
echo -e "\033[46;37m 天蓝底白字 \033[0m"
echo -e "\033[47;30m 白底黑字 \033[0m"

Centos搭建kubernates集群

发表于 2019-02-14 | 分类于 k8s

1. 前期准备

Master节点和Node节点都需要配置这些准备

1.1 安装docker

删除原先docker

1
2
3
4
5
6
7
8
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

安装依赖

1
2
3
sudo yum update -y && sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2

添加官方yum库

1
2
3
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo

安装docker

1
sudo yum install docker-ce docker-ce-cli containerd.io

开机自启

1
systemctl enable --now docker

1.2 修改docker cgroup驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
cat > /etc/docker/daemon.json <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true"
]
}
EOF

重启生效

1
systemctl restart docker

1.3 更换kubernates源

1
2
3
4
5
6
7
8
9
10
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

关闭SElinux

1
2
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

1.4 安装kubelet kubeadm kubectl

1
yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

开机自启kubelet

1
systemctl enable --now kubelet

1.5 设置路由

1
yum install -y bridge-utils.x86_64

加载br_netfilter模块

1
modprobe  br_netfilter
1
2
3
4
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

重新加载所有配置

1
sysctl --system

关闭防火墙

1
2
3
systemctl disable --now firewalld
systemctl daemon-reload
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X

k8s要求关闭swap

1
2
3
4
# 关闭swap
swapoff -a && sysctl -w vm.swappiness=0
# 取消开机挂载
sed -ri '/^[^#]*swap/s@^@#@' /etc/fstab

2. 配置Master和Node端

2.1 Master端拉取集群所需镜像

需要翻墙

1
kubeadm config images pull

不翻墙可以尝试下列方法

  1. 列出所需镜像
1
kubeadm config images list
  1. 根据所需镜像名字先拉取国内资源

    1
    2
    3
    4
    5
    6
    7
    docker pull mirrorgooglecontainers/kube-apiserver:v1.14.1
    docker pull mirrorgooglecontainers/kube-controller-manager:v1.14.1
    docker pull mirrorgooglecontainers/kube-scheduler:v1.14.1
    docker pull mirrorgooglecontainers/kube-proxy:v1.14.1
    docker pull mirrorgooglecontainers/pause:3.1
    docker pull mirrorgooglecontainers/etcd:3.3.10
    docker pull coredns/coredns:1.3.1
  2. 修改镜像tag

1
2
3
4
5
6
7
8
9
10
11
docker tag mirrorgooglecontainers/kube-apiserver:v1.14.1 k8s.gcr.io/kube-apiserver:v1.14.1

docker tag mirrorgooglecontainers/kube-controller-manager:v1.14.1 k8s.gcr.io/kube-controller-manager:v1.14.1

docker tag mirrorgooglecontainers/kube-scheduler:v1.14.1 k8s.gcr.io/kube-scheduler:v1.14.1

docker tag mirrorgooglecontainers/kube-proxy:v1.14.1 k8s.gcr.io/kube-proxy:v1.14.1

docker tag mirrorgooglecontainers/pause:3.1 k8s.gcr.io/pause:3.1
docker tag mirrorgooglecontainers/etcd:3.3.10 k8s.gcr.io/etcd:3.3.10
docker tag coredns/coredns:1.3.1 k8s.gcr.io/coredns:1.3.1
  1. 删除原来镜像
1
2
3
4
5
6
7
docker rmi mirrorgooglecontainers/kube-apiserver:v1.14.1
docker rmi mirrorgooglecontainers/kube-controller-manager:v1.14.1
docker rmi mirrorgooglecontainers/kube-scheduler:v1.14.1
docker rmi mirrorgooglecontainers/kube-proxy:v1.14.1
docker rmi mirrorgooglecontainers/pause:3.1
docker rmi mirrorgooglecontainers/etcd:3.3.10
docker rmi coredns/coredns:1.3.1

2.2 Node拉取所需镜像

需要翻墙

1
kubeadm config images pull

不需要翻墙的安装方式与上面一样

3. 创建集群

使用kubeadm创建集群,这是在Master节点中需要执行的(至少是2核)

  • –apiserver-advertise-address 是你本机的ip地址
  • –pod-network-cidr 指定pod网络子网,使用fannel网络必须使用这个CIDR,不用改
1
kubeadm init --apiserver-advertise-address 108.61.187.245 --pod-network-cidr 10.244.0.0/16

创建成功后会提示你成功,这时要记录token,我们后面将其他节点加入到该集群中需要使用到它

注意 :如果出现 Error writing Crisocket information for the control-plane node: timed out waiting for the condition
重启 kubeadm 然后再执行init

1
sudo kubeadm reset

设置权限(一条条执行)

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

应用flannel网络

1
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

4. Node节点加入集群

这句话其实就是Master节点执行kubeadm init成功之后输出的最后一句话,我们拿到Node节点中直接执行即可

1
2
kubeadm join 108.61.187.245:6443 --token t0dx7r.jjmf3pnmwj3shbc6 \
--discovery-token-ca-cert-hash sha256:794376ec13c98bdc0aa0c2f762a4a0864079638eb4665f9397ee68c0187e800b

当看到下面这句话就说明加入成功了

5. 查看节点状态

回到Maser节点,运行下面命令

获取namespace信息

1
kubectl get namespace

查看pod状态

1
kubectl get pods --all-namespaces

查看有多少个节点

1
kubectl get nodes

查看kubelet进程日志

1
journalctl -f -u kubelet

查看所有的token

1
kubeadm token list

创建新的token

1
kubeadm token create

Netty入门Demo

发表于 2019-02-14 | 分类于 netty

1. 添加依赖

1
2
3
4
5
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.34.Final</version>
</dependency>

2. 编写消息接收处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NettyServerHandler extends ChannelInboundHandlerAdapter{

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf in = (ByteBuf)msg;
System.out.println(ctx.name()+"发送:"+in.toString(io.netty.util.CharsetUtil.US_ASCII));
} finally {
//释放传递给处理程序的引用计数对象
ReferenceCountUtil.release(msg);
}
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}

3. 编写服务器

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
public class NettyServerDemo {
private int port;

public NettyServerDemo(int port) {
this.port = port;
}

public void run() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);

//绑定端口,并等待连接
ChannelFuture f = b.bind(port).sync();

// 关闭服务
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}

public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new NettyServerDemo(port).run();
}
}

4. 测试

  1. 启动NettyServerDemo
  2. 命令行中输入: telnet localhost 8080
    如果找不到Telnet命令,说明Telnet客户端没有打开,在控制面板中—> 程序 ——> 打开或关闭Windows功能

    当输入信息后,服务器端会收到点击的键,并输出出来

Java面试通关秘籍(一)

发表于 2019-02-14 | 分类于 Java,面试

1. 基础篇

1.1 Java基础

  • 面向对象的特征:继承、封装和多态

    1.继承: 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

    好处:提高了代码的复用性。让类与类之间产生了关系,提供了多态的前提。

**2.封装:** 隐藏对象的属性和实现细节,仅对外提供公共访问方式。

好处:     
①将变化隔离。
②便于使用。
③提高重用性。
④提高安全性。

**3.多态:**     在同一个方法中,由于参数不同而导致执行效果各异的现象就是多态。
前提条件:
A:要有继承关系。
B:要有方法重写。
C:要有父类引用指向子类对象。

**小结:**
 > 封装即隐藏对象的属性和实现细节,仅对外提供公共访问方式,保证了模块具有较好的独立性,使得程序维护修改较为容易。继承提高了代码的复用性,多态提高代码的扩展性和可维护性。三者共同达到了一个目的:使得对应用程序的修改带来的影响更加局部化,程序更加健壮,而且易维护、更新和升级。
  • final, finally, finalize 的区别

    final:标识此变量为不可变,。对基本类型使用是不能改变的是他的数值。而对于对象引用,不能改变的是他的引用
    finally: 异常捕获的一个代码块,代码块中的内容无论是否发生异常都会去执行,一般用来释放资源
    finalize:垃圾回收机制,主要用来回收对象

  • Exception、Error、运行时异常与一般异常有何异同

    Throwable 是所有 Java 程序中错误处理的父类 ,有两种子类: Error 和 Exception

    Error:属于严重错误,JVM无法捕获,也无法采取任何恢复手段。比如:内存溢出
    Exception :可以捕获的,可以恢复程序正常运行
    运行时异常:运行时异常不要求我们必须捕获,他是由虚拟机接管,比如:空指针,除零异常
    一般异常:也即是checked exception,这种异常必须自己写一大堆try catch块去处理。比如:SQL异常,IO异常

  • 请写出5种常见到的Runtime exception
    NullPointerException、ArrayIndexOutOfBoundsException 、ClassNotFoundException、ClassCastException、ArithmeticException(除零异常)、NegativeArraySizeException(当创建数组时传递参数为负数)

  • int 和 Integer 有什么区别,Integer的值缓存范围

    int为基本数据类型,Integer为int的包装类,int默认值为0,而Integer默认值为null
    Integer缓存范围为 -128-127

    1
    2
    3
    Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150; 
    System.out.println(f1 == f2); true
    System.out.println(f3 == f4); false
  • 包装类,装箱和拆箱

    包装类:一切皆对象,八种基本数据类型不是对象。把这八种基本数据类型包装成每一个类,这样每一个类就可以以对象的形式操作基本数据类型。

    装箱:将基本数据类型变成包装类称为装箱。

    拆箱:将包装类的类型变为基本数据类型称为拆箱。

  • String、StringBuilder、StringBuffer

    String类被final修饰,所以为不可变的,当字符串拼接时他会重新创建一个新的对象,速度会很慢
    StringBuilder:线程不安全的,但是是最快的
    StringBuffer:线程安全的,速度仅此于StringBuilder

  • 重载和重写的区别

    重写方法的规则:(存在于子类与父类之间)

    1)参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载。
    2)返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。
    3)访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
    4)重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。例如:
    父类的一个方法申明了一个检查异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常。
    **重载的规则:**(存在于子类与父类之间、同类中)
    1)必须具有不同的参数列表;
    2)可以有不同的返回类型,只要参数列表不同就可以了;
    3)可以有不同的访问修饰符;
    4)可以抛出不同的异常;
  • 抽象类和接口有什么区别

    1.抽象类可以有构造方法而接口不可以
    2.抽象中可以有普通方法,而接口中只能有抽象 方法
    3.抽象类中可以有普通变量,而接口中只能由常量
    4.抽象类中可以有静态方法,而接口中不可以
    5.一个类可以实现多个接口,但只能继承一个抽象类

  • 说说反射的用途及实现

    用途:它允许程序在运行时进行自我检查,同时也允许对其内部成员进行操作。反射机制提供的功能主要有:得到一个对象所属的类;获取一个类的所有成员变量和方法;在运行时创建对象;在运行时调用对象的方法。
    实现: ①静态代理 ②动态代理 ③ CGLB方法

  • 说说自定义注解的场景及实现

    场景:跟踪代码的依赖性,实现代替配置文件的功能
    实现: 使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节,在定义注解时,不能继承其他注解或接口。

  • HTTP请求的GET与POST方式的区别

    1. Get请求会将参数显示到地址栏中,而POST请求不会
    2. GET后退按钮/刷新无害,POST数据会被重新提交
    3. GET能被缓存,POST不能缓存
    4. GET对数据长度有限制,当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的
  • Session与Cookie区别

    cookie数据保存在客户端,session数据保存在服务器端。 详情请点击这里
    cookie一般用来保存用户的登录状态,一些隐私信息不能放到cookie中

  • 列出自己常用的JDK包

    java.lang、java.util、java.sql、java.math、java.io、java.net、java.text

  • MVC设计思想

    M:模型
    V:视图
    C:控制
    点击查看详情

  • equals与==的区别
    ==

    比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象,如果是具体的阿拉伯数字的比较,值相等则为true

    equals

    用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断
    hashCode和equals方法的区别与联系

  • 什么是Java序列化和反序列化,如何实现Java序列化?或者请解释Serializable 接口的作用

    序列化:将java对象用二进制表示出来,而且这个二进制可以保存到磁盘或进行网络传输而不会破坏其结构
    反序列化:将这个二进制还原为java对象本身
    实现序列化:实现java.io.Serializable 接口
    Serializable 接口的作用:标识此对象可以被序列化

  • Object类中常见的方法,为什么wait notify会放在Object里边?

    wait、hashCode、equals、toString、notify、clone、getClass

    简单说:因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。
    专业说:因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。

  • JDK和JRE的区别

    JDK就是Java Development Kit.简单的说JDK是面向开发人员使用的SDK,它提供了Java的开发环境和运行环境。SDK是Software Development Kit 一般指软件开发包,可以包括函数库、编译程序等。
    JRE是Java Runtime Enviroment是指Java的运行环境,是面向Java程序的使用者,而不是开发者

    JDK = JRE + 开发工具

1.2 Java常见集合

  • List 和 Set 区别

    List底层使用数组实现,可以重复,并且有序
    Set底层是用HashMap实现,无序且不可重复

  • Set和hashCode以及equals方法的联系

    Set集合为保证内部元素不重复,当调用add()方法时,先判断此对象的hashCode是否在Set集合中已存在,如果不存在则直接添加进去,如果已存在则进一步判断 equals 方法,判断两对象的值是否相同。

  • List 和 Map 区别

    List是单列集合,而Map是双列集合,是通过键值对进行存储

  • Arraylist 与 LinkedList 区别
    ArrayList :线程不安全,底层用数组实现,按插入先后排序,随机访问快,插入与删除慢。
    LinkedList :线程不安全的,底层基于链表的数据结构实现,访问慢但插入元素与删除元素比较快。LinkedList类实现了Queue接口,可当做队列使用

  • ArrayList 与 Vector 区别
    ArrayList是线程不安全的,而Vector是线程安全的

  • HashMap 和 Hashtable 的区别

    HashMap是线程不安全的,他的键和值都可以为null
    Hashtable是线程安全的,他的键和值不可以为null

  • HashSet 和 HashMap 区别

    HashSet基础AbstractSet 而 HashMap继承 AbstractMap

  • HashMap 和 ConcurrentHashMap 的区别
    HashMap是线程不安全的,而ConcurrentHashMap是线程安全的,而且效率要大于Hashtable

  • HashMap和ArrayList的默认大小

    HashMap默认大小为 16 增量因子为 0.75
    ArrayList默认大小为 10 每次扩容为原来的1.5 倍

1.3 进程和线程

  • 线程和进程的概念、并行和并发的概念

线程:是程序执行流的最小单元,是系统独立调度和分配CPU(独立运行)的基本单位。

进程:是资源分配的基本单位。一个进程包括多个线程。

区别:
1.线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。
2.每个进程都有自己一套独立的资源(数据),供其内的所有线程共享。
3.不论是大小,开销线程要更“轻量级”
4.一个进程内的线程通信比进程之间的通信更快速,有效。(因为共享变量)

并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。

并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

  • 创建线程的方式及实现

    实现Runnable接口、继承Thead

进程间通信的方式

说说 CountDownLatch、CyclicBarrier 原理和区别

说说 Semaphore 原理

说说 Exchanger 原理

ThreadLocal 原理分析,ThreadLocal为什么会出现OOM,出现的深层次原理

讲讲线程池的实现原理

线程池的几种实现方式

线程的生命周期,状态是如何转移的

Mybatis使用小记

发表于 2019-01-30 | 分类于 mybatis,SpringBoot技能大全

@[toc]

1. ${} 和 #{}的区别

Mybatis中的#{}用于传递查询的参数,用于从dao层传递一个string参数过来(也可以是其他参数),select * from 表名 order by age=#{age}

Mybatis会把这个参数转换成一个字符串。select * from 表名 order by age=”age” 相当于jdbc中的预编译,安全。
${}一般用于order by的后面,Mybatis不会对这个参数进行任何的处理,直接生成了sql语句。例:传入一个年龄age的参数,select * from 表名 order by ${age}

所以首选使用 #{}

但是传递参数为一个以上的时候,需要我们用注解去绑定参数
通过使用 @Param注解的方式(org.apache.ibatis.annotations.Param)

1
2
@Select("select * from seckill order by create_time desc limit #{offset},#{limit}")
List<Seckill> queryAll(@Param("offset")int offset,@Param("limit")int limit);

2. 传递参数

2.1 第一种

通过#{0},#{1} 方式,#{0} 代码第一个参数,#{1} 代码第二个参数

1
public Good selectGood(String id, String name);

xml

1
2
3
<select id="selectGood" resultMap="GoodMap">
select * from good where id = #{0} and name=#{1}
</select>

2.2 第二种

通过固定参数的方式
java

1
public Good selectGood(@param("id")String id,@param("name")String name);

xml

1
2
3
<select id="selectGood" resultMap="GoodMap">
select * from good where id = #{id} and name=#{name}
</select>

2.3 第三种

通过Map集合去传递(推荐)
java

1
2
3
4
5
6
7
public Good selectGood(){
Map map = new HashMap();
map.put("id",1);
map.put("name","pibigstar");
Good good = goodService.selectGood(map);
return good;
}

xml

1
2
3
<select id="selectGood" resultMap="GoodMap">
select * from good where id = #{id} and name=#{name}
</select>

3. 开启驼峰命名规范

1
2
//在application.properties中设置
mybatis.configuration.map-underscore-to-camel-case=true //开启驼峰命名

如果不设置需要手动在查询的时候设置:

1
2
3
4
5
6
7
8
9
@Select("select * from seckill")
@Results({
//如果不开启驼峰命名,就要手动这样设置
@Result(property="seckillId",column="seckill_id"),
@Result(property="createTime",column="create_time"),
@Result(property="startTime",column="start_time"),
@Result(property="endTime",column="end_time"),
})
List<Seckill> list();

4. 设置扫描包路径

1
2
//application.properties中设置
mybatis.type-aliases-package=com.pibigstar.domain

如果不设置可以在启动类中手动设置

1
2
3
4
5
6
7
8
@SpringBootApplication
@MapperScan("com.pibigstar.mapper")
public class SeckillApplication{

public static void main(String[] args) {
SpringApplication.run(SeckillApplication.class, args);
}
}

5. 联表查询

1
2
3
4
5
6
7
8
9
10
	/**
* 根据id拿到SuccessSeckilled并携带秒杀商品的对象
* @param seckillId
* @return
*/
@Select("select sk.seckill_id,sk.user_phone,sk.create_time,sk.state,"
+ " s.seckill_id 'seckill.seckill_id',s.name 'seckill.name',s.number 'seckill.number',s.create_time 'seckill.create_time',s.start_time 'seckill.start_time',s.end_time 'seckill.end_time'"
+ " from success_killed sk inner join seckill s on sk.seckill_id=s.seckill_id "
+ " where sk.seckill_id=#{seckillId} and sk.user_phone=#{userPhone}")
SuccessKilled queryByIdWithSeckill(@Param("seckillId")long seckillId,@Param("userPhone")long userPhone);

6. 事务

事务方法的执行时间要尽可能短,不要穿插一些网络操作(RPC/HTTP)请求,如果必须需要,那么将这些网络操作剥离出来(定义一个网络操作方法,将拿到的数据再传递给此事务方法)

不是所有的方法都需要事务,比如: 只有一条修改操作,或只有 只读操作
一般都是读操作和写操作要在同一个方法中时需要申明事务

6.1 添加事务

  • 在xml中配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="mySessionFactory"/>
</property>
</bean>

<!-- 配置事务传播特性 -->
<tx:advice id="TestAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置参与事务的类 -->
<aop:config>
<aop:pointcut id="allTestServiceMethod" expression="execution(* com.test.testAda.test.model.service.*.*(..))"/>
<aop:advisor pointcut-ref="allTestServiceMethod" advice-ref="TestAdvice" />
</aop:config>
  • 在方法中添加注解@Transactional
    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
    @Transactional
    public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
    throws SeckillException, RepeatkillException, SeckillCloseException {

    if (md5==null||!md5.equals(MD5Util.getMD5(seckillId))) {
    throw new SeckillException("MD5值不合法");
    }
    try {
    //执行秒杀,减少库存
    Date nowTime = new Date();
    int updateCount = seckillMapper.reduceNumber(seckillId, nowTime);
    if (updateCount<=0) {
    //没有更新到记录,秒杀结束
    throw new SeckillCloseException("秒杀结束");
    }else {
    //记录购买行为
    int insertCount = successKilledMapper.insert(seckillId, userPhone);
    if (insertCount<=0) {
    //重复插入(主键为seckill和userphone联合主键)
    //返回0,重复秒杀
    throw new RepeatkillException("重复秒杀");
    }else {
    //返回1,秒杀成功
    SuccessKilled successKilled = successKilledMapper.queryByIdWithSeckill(seckillId, userPhone);
    return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS,successKilled);
    }

    }
    }catch (SeckillCloseException e1) {
    throw e1;
    }catch (RepeatkillException e2) {
    throw e2;
    }
    catch (Exception e) {
    logger.error(e.getMessage(),e);
    //所有编译期异常 转换为了运行期异常
    throw new SeckillException("seckill inner error:"+e.getMessage());
    }

    }

6.2 事务回滚

spring默认当方法抛出运行期异常的时候会回滚事务
所以我们一般要将编译期异常转化为运行期异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
@Transactional
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatkillException, SeckillCloseException {

if (md5==null||!md5.equals(MD5Util.getMD5(seckillId))) {
throw new SeckillException("MD5值不合法");
}
try {
//执行秒杀,减少库存
Date nowTime = new Date();
int updateCount = seckillMapper.reduceNumber(seckillId, nowTime);
}
catch (Exception e) {
logger.error(e.getMessage(),e);
//将所有编译期异常 转换为了运行期异常
throw new SeckillException("seckill inner error:"+e.getMessage());
}

SeckillException是我们自定义的运行期异常,主要是处理各种业务异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.pibigstar.exception;

/**
* 秒杀相关业务异常
* @author pibigstar
*/
public class SeckillException extends RuntimeException{
private static final long serialVersionUID = 1L;

public SeckillException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public SeckillException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}

7. 插入返回主键

  1. 第一种:
    加入这两个属性:useGeneratedKeys="true" keyProperty="id"

    1
    2
    3
    4
    <insert id="insert" useGeneratedKeys="true" keyProperty="id" parameterType="com.pibigstar.User">
    insert into user(userName,password,comment)
    values(#{userName},#{password},#{comment})
    </insert>
  2. 第二种

    1
    2
    3
    4
    5
    6
    7
    <insert id="insertProduct" parameterType="com.pibigstar.model.User" >
    <selectKey resultType="java.lang.Long" order="AFTER" keyProperty="id">
    SELECT LAST_INSERT_ID()
    </selectKey>
    insert into user(userName,password,comment)
    values(#{userName},#{password},#{comment})
    </insert>

order为AFTER表示插入之后再进行查询一次

1
2
3
<selectKey resultType="java.lang.Long" order="AFTER" keyProperty="id">
SELECT LAST_INSERT_ID()
</selectKey>

微信小程序部署踩坑

发表于 2019-01-30 | 分类于 微信小程序

1. 配置request合法域名

、
注意

  • 必须是https
  • 域名已经备案了

2. 申请证书

去腾讯云免费申请一年证书:https://console.qcloud.com/ssl

3. 下载证书

4. 制作完整证书

进入这个网址:https://www.myssl.cn/tools/merge-pem-cert.html

勾选这两个,将Apache下的 2_todo.pibigstar.com.crt 放到 第一个文本域里面,3_todo.pibigstar.com.key放到 第二个文本域里面(你勾选之后,文本域就会出来了)

5. 将生成好的PEM文件放到合适位置

6. 问题说明

1. 对应的服务器证书无效。控制台输入 showRequestInfo() 可以获取更详细信息。

这个问题,是证书没有配置好,或没配置证书

2. request failed:ssl hand shake error

1、先检测网站证书是否正常,检测地址:https://www.myssl.cn/tools/check-server-cert.html
我检测完成,提示缺少中间证书,然后用根证书生成中间证书
2、用crt根证书生成中间证书,生成地址:https://www.myssl.cn/tools/downloadchain.html
3、将根证书和中间证书进行拼接:cat root.crt chain.crt>server.crt (root为原本根证书,chain.crt是新生成的中间证书)
4、将合并好的server.crt上传至服务器,覆盖原本的根证书,重启nginx,OK~

注意
经测试,GoFrame需要的pem文件,而不是这个server.crt,如果用这个,会直接报错。

程序员开发必备工具(二)之浏览器插件

发表于 2019-01-27 | 分类于 工具使用

@[TOC]

2. 浏览器插件

所有插件我都放到了我的微信公众号上面了,关注我微信公众号,发送【浏览器插件】就可以获取了。

2.1 插件扩展管理器(Extension Manager)

快速启动和关闭插件,非常好用。

2.2 web前端访问助手(FeHelper)

自动格式化JSON数据,网页取色,字符串加解密,JS正则

2.3 油猴插件

这是一个运行脚本的插件,你可以自己写一下小脚本放到油猴上面。我目前用的有VIP视频破解,百度下载助手等等。你可以在点击这里下载一些好玩使用的脚本。

2.4 西瓜插件

运营微信公众号必不可少的插件!!!非常好用,可以采集别人的文字,采集平时的图片,同时还可以订阅其他公众号来进行采集。

2.5 Octotree

这个是程序员必备的,可以将GitHub中的项目进行树结构化,非常方便浏览GitHub上的项目。

2.6 Infinity(标签页)

一个非常漂亮的标签页。

2.7 掘金插件

掘金开发的一款插件,当你打开新的标签页时,显示的是掘金中优秀的文章。

java使用poi反射读取写入Excel

发表于 2019-01-27 | 分类于 Java,java工具类,SpringBoot技能大全

通过反射来读取写入Excel

添加依赖

1
2
3
4
5
6
<!-- excel操作 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>

注解

1
2
3
4
5
6
7
8
9
10
/**
* @Author:pibigstar
* @Description: Excel注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excel {
String value() default "";
boolean ignore() default false;
}

代码

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package com.pibgstar.demo.utils;

import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.xml.crypto.Data;
import java.beans.PropertyDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

/**
* @author pibigstar
* @create 2018-12-04 16:44
* @desc Excel工具类
**/
public class ExcelUtil {

/**
* @Author:pibigstar
* @Description: 读取Excel数据到集合中
*/
public static <T> List<T> readExcel(String filePath, Class<T> clazz) {
List<T> list = new ArrayList<>();
try {
FileInputStream fis = new FileInputStream(filePath);
Field[] fields = clazz.getDeclaredFields();

Workbook workbook = null;
if (filePath.endsWith(".xlsx")) {
workbook = new XSSFWorkbook(fis);
} else {
workbook = new HSSFWorkbook(fis);
}
Sheet sheet = workbook.getSheetAt(0);
int startNum = sheet.getFirstRowNum() + 1;//去掉表头
int endNum = sheet.getLastRowNum();
int colNum = fields.length; // 列数

for (int i = startNum; i <= endNum; i++) {
Row row = sheet.getRow(i);
T t = clazz.newInstance();
for (int j = 0; j < colNum; j++) {
Field field = fields[j];
Cell cell = row.getCell(j);
field.setAccessible(true);
String value = getValue(cell);
setValue(t, field, value);
}
list.add(t);
}

} catch (Exception e) {
e.printStackTrace();
}
return list;
}

/**
* @Author:pibigstar
* @Description: 为字段赋值
*/
private static <T> void setValue(T t, Field field, String value) {
Class<?> type = field.getType();
Object fieldValue = null;
if (type.equals(String.class)) {
fieldValue = value;
} else if (type.equals(Integer.class)) {
fieldValue = Integer.parseInt(value);
} else if (type.equals(int.class)) {
fieldValue = Integer.parseInt(value);
} else if (type.equals(Boolean.class)) {
fieldValue = Boolean.parseBoolean(value);
} else if (type.equals(boolean.class)) {
fieldValue = Boolean.parseBoolean(value);
} else if (type.equals(Float.class)) {
fieldValue = Float.parseFloat(value);
} else if (type.equals(Double.class)) {
fieldValue = Double.parseDouble(value);
} else if (type.equals(Data.class)) {
fieldValue = DateUtil.parseYYYYMMDDDate(value);
}
try {
field.set(t, fieldValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

/**
* @Author:pibigstar
* @Description: 得到此格的值
*/
private static String getValue(Cell cell) {
if (cell == null) return null;
String result = "";
CellType cellType = cell.getCellType();
switch (cell.getCellType()) {
case NUMERIC:
if (HSSFDateUtil.isCellDateFormatted(cell)) {
SimpleDateFormat format = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
result = format.format(cell.getNumericCellValue());
return result;
}
case BOOLEAN:
result = String.valueOf(cell.getBooleanCellValue());
return result;
case STRING:
return cell.getStringCellValue();
}
return null;
}

/**
* @Author:pibigstar
* @Description: 将集合中对象导入到Excel中
*/
public static <T> void writeExcel(List<T> list, String outPath) {
int size = list.size();
if (size == 0) return;
T t = null;
String fileName = "";
FileOutputStream fos = null;
try {
if (outPath.contains("/")) {
fileName = outPath.substring(outPath.lastIndexOf("/") + 1, outPath.lastIndexOf("."));
} else {
fileName = outPath.substring(0, outPath.lastIndexOf("."));
}
Workbook workbook = new HSSFWorkbook();
// 创建表单
Sheet sheet = workbook.createSheet(fileName);
// 设置表头
t = list.get(0);
Class<?> clazz = t.getClass();
setTitle(sheet, workbook, clazz);

// 写入内容
for (int i = 0; i < size; i++) {
Row row = sheet.createRow(i + 1);
t = list.get(i);
Field[] fs = t.getClass().getDeclaredFields();
int colNum = fs.length;
PropertyDescriptor pd = null;
int temp = 0;
for (int j = 0; j < colNum; j++) {
Field field = fs[j];
Excel annotation = field.getAnnotation(Excel.class);
if(annotation!=null && annotation.ignore()){
continue;
}
Cell cell = row.createCell(temp);
String fieldName = field.getName();
pd = new PropertyDescriptor(fieldName, t.getClass());
Method readMethod = pd.getReadMethod();
Object result = readMethod.invoke(t);
cell.setCellValue(result.toString());
temp++;
}
}
fos = new FileOutputStream(outPath);
workbook.write(fos);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* @Author:pibigstar
* @Description: 设置表头
*/
private static void setTitle(Sheet sheet, Workbook workbook, Class<?> clazz) {
Row row = sheet.createRow(0);// 第一行为表头
//设置为居中加粗
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
Field[] fields = clazz.getDeclaredFields();
int colNum = fields.length;
for (int i = 0; i < colNum; i++) {
sheet.setColumnWidth(i, 20 * 256);
Cell cell = row.createCell(i);
Field field = fields[i];
cell.setCellValue(field.getName());
cell.setCellStyle(style);
}
}
}

实体对象

1
2
3
4
5
6
7
8
9
public class User {
@Excel(value = "我是ID",ignore = true)
private String id;
@Excel(value = "名字")
private String name;
@Excel(value = "年龄",ignore = true)
private int age;
//setter,getter方法
}

测试

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

public class TestExcelUtil {

public static void main(String[] args) {
List<User> users = new ArrayList<>();
User user1 = new User();
user1.setId("1");
user1.setName("派大星");
user1.setAge(20);
user1.setPassword("1234556");
User user2 = new User();
user2.setId("2");
user2.setName("海绵宝宝");
user2.setAge(18);
user2.setPassword("6666666");
users.add(user1);
users.add(user2);
ExcelUtil.writeExcel(users, "D://Document And Settings3//Admin//Desktop//test.xls");
System.out.println("done");
List<User> list = ExcelUtil.readExcel("D://Document And Settings3//Admin//Desktop//test.xls", User.class);
for (User u:list) {
System.out.println(u);
}
}
}

前端随笔记

发表于 2019-01-27 | 分类于 前端

文本输入框提示

1
2
3
4
5
6
<input type="text" list="category" aria-describedby="basic-addon1" >
<datalist id="category">
<option value="派大星">
<option value="海绵宝宝">
<option value="章鱼哥">
</datalist>

复选框—搜索提示下拉框

引入JS:bootstrap-select.js

1
2
3
4
5
6
<select id="selectTest" data-live-search="true" title="请选择" class="selectpicker" multiple>
<option user_id=“1”>小华</option>
<option user_id=“2”>小明</option>
<option user_id=“3”>小心</option>
<option user_id=“4”>天天</option>
</select>

JS中初始

1
2
3
4
5
6
7
8
9
// 初始化
$('.selectpicker').selectpicker();
// 选中事件
$('.selectpicker').on('changed.bs.select',function(){
$('#selectTest').find("option:selected").each(function () {
console.log($(this).attr('user_id'));
})
console.log($('.selectpicker').selectpicker('val'))
});

更详细配置请参考文档:https://developer.snapappointments.com/bootstrap-select/options/

下拉框列表自定义样式

将html样式写到data-content中

1
2
3
<select class="selectpicker">
<option data-content="<span class='label label-success'>Relish</span>">刷新</option>
</select>
1…121314
派大星

派大星

137 日志
64 分类
230 标签
GitHub CSDN
© 2019 派大星