豪翔天下

Change My World by Program

0%

Git指南

这里所列举的通用配置无论是在Windows还是Linux,都要用到。

  • 一定一定要设置好nginx或者apache的权限,保护好.git目录,防止被黑客获取到,因为这个目录下的文件包含了所有的文件内容,例如:

    1
    2
    3
    4
    5
    6
    import zlib
    import requests

    url = "https://domain/.git/objects/9d/6d1ae673f15900b8efd9ad875364b3a651cc0e"
    re = requests.get(url)
    content = zlib.decompress(re.content) # 这就是文件内容

安装与配置

安装Git软件

Windows平台:Git for Windows(在安装的时候注意那些选项的设置)

Linux平台:sudo apt-get install git

基础配置

打开终端,在windows平台就用上一步安装上的git bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 全局配置,这里的用户名和邮箱填写你自己的帐号,可以是github上的帐号信息
git config --global user.name "haoflyent"
git config --global user.email "haoflynet@gmail.com"

# 如果要同一台机器多个git账户,那么可以取消全局设置
git config --global --unset user.name
git config --global --unset user.email
git config user.email "x@xx.com" # 在仓库里面单独设置
git config user.name "xx"
git config core.editor vim # 设置git的默认编辑器,不然在某些系统里面会用nano,不会用

# 生成密钥
ssh-keygen -t rsa -C "haoflynet@gmail.com"
# 然后一路默认就可以,注意看其中的密钥存放位置,其中公钥文件Windows默认为/c/Users/用户名/.ssh/id_rsa.pub# Linux默认为 ~/.ssh/rsa_pub
阅读全文 »

fpm打包工具

安装过程

1
2
3
4
yum install -y rpm-build ruby-devel virtualenv gcc
pip install virtualenv-tools
gem install -V fpm
fpm -h

常用参数

  • -a: 架构名称,值可以为 x86_64
  • –config-files: 指定配置文件,可以指定多个。需要注意的是并不是所有文件都适合以配置文件的方式放入包中,不要将动态的文件放入配置文件中
  • -d ‘名称’: 指定程序依赖,多个依赖的话就写多个-d
  • –debug:打印编译时的详细日志
  • -C: 指定在打包前需要进入的目录(打包时的相对路径),相当于把那个目录打包
  • -n: 包名
  • -s: 源的类型,值可以为dir,rpm,gem,python,virtualenv,empty,tar,deb,cpan,npm,osxpkg,pear,pkgin,zip
  • -t: 目标类型,值可以为rpm,deb,solaris,puppet,dir,osxpkg,p5p,puppet,sh,solaris,tar,zip
  • -epoch 0,比-v更优先级的一个版本号
  • -v: 版本号,例如1.0.0
  • –iteration 1: 比-v更低优先级的一个版本号
  • –rpm-dist el7: 定义系统的迭代版本,el6表示centos6el7表示centos7,会生成在包名中,例如: example-0.1.0-el7.x86_64.rpm
  • –before-install, –pre-install 名称.sh: 安装前执行的脚本
  • –after-install, –post-install 名称.sh: 安装后执行的脚本
  • –before-remove, –pre-uninstall 名称.sh: 卸载前执行的操作
  • –after-remove, –post-uninstall 名称.sh: 卸载后执行的操作
  • -p, –prefix=目录: 指定软件之后要安装的路径
  • –description ‘这里写描述’
  • –url: 软件的网官网
  • –license ‘2-clause BSD-like license’:
  • –vendor: 供应商名称
  • –verbose: 打印详细安装过程
  • -m, –maintainer: 维护者
  • –rpm-sumarry: 简介
  • –description: 详情
  • usr=/,以这种方式,可以直接将当前目录下的目录在打包后放到指定的目录,例如,这里将工程目录下的uer目录放到了打包后的根目录,这样usr下的所有文件或文件夹都会被递归地打入包中。

以Virtualenv的方式打包Python包

相比与-s python的方式,将源设置为virtualenv(即-s virtualenv)的好处是不会破坏系统本身的python环境,不会与已经安装的包或者其他程序依赖的包产生冲突。举例,有一个需要打包的python包源码目录结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.
├── Pipfile # 自己开发时使用的是pipfile进行依赖管理,当然对fpm打包没有影响
├── Pipfile.lock
├── README.md
├── etc # 一些典型的配置文件
│   ├── logrotate.d # 日志轮转配置文件
│   │   └── my-agent
│   ├── rc.d # 系统服务配置文件,该文件编写方式有点特殊,可以去谷歌一下
│   │   └── init.d
│   │   └── my-agent
│   └── my-agent # 程序本身配置文件
│   └── default.conf
├── usr # lib文件默认会被加入/usr/lib中,所以,这里直接以目录树形式存储
│   └── lib
│   └── my-agent
│      └── agent.state
├── scripts
│   ├── my-agent-after-install.sh
│   └── my-agent-before-remove.sh
├── setup.py # 正常打python egg包所需要的文件,里面定义了包的一些元信息
└── src # 程序源码
├── xxx.py
└── __init__.py

然后使用这样的命令进行打包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fpm \
--debug \
-s virtualenv \ # 指定以virtualenv的形式进行打包
-t rpm \
-n my-agent \
-a 'x86_64' \
-v 0.1.0 \
--license '2-clause BSD-like license' \
--vendor 'Hao inc.' \
--category 'System Environment/Daemons' \
-m 'haofly' \
--config-files etc/my-agent/default.conf \
--config-files etc/logrotate.d/my-agent \
--config-files etc/rc.d/init.d/my-agent \
--after-install ./scripts/my-agent-after-install.sh \
--before-remove ./scripts/my-agent-before-uninstall.sh \
--url 'https://haofly.net' \
--rpm-summary 'rpm summary.' \
--description 'package description' \
--prefix /usr/share/my-agent \ # 指定安装目录,如果不指定会默认安装到/usr/share/python/下面
.

其他相关命令

1
2
rpm localinstall 包名 # 在新的机器上安装该软件
rpm -qpl 包名 # 查看包的信息

打包nginx例子

这是我打包tengine的命令fpm -s dir -t rpm -n tengine -v 2.1.2 -C /etc/nginx -d 'pcre-devel' -d 'openssl' -d 'openssl-devel' --before-install /share/before-install.sh --after-install /share/after-install.sh --before-remove /share/before-remove.sh --after-remove /share/after-remove.sh --description 'Haofly first fpm package' --url 'http://haofly.net' --prefix=/etc/nginx

安装

CentOS 6.4安装phpMyAdmin

Ubuntu安装

1
2
3
4
# apache
sudo apt-get install phpmyadmin apache2-utils
## vim /etc/apache2/apache2.conf,添加如下一行
Include /etc/phpmyadmin/apache.conf

连接多个主机的配置

参考文章
配置如下:

  1. 将phpMyAdmin根目录下的config.sample.inc.php,重命名为config.inc.php

  2. 修改config.inc.php文件,添加如下内容:

     $connect_hosts = array(
         1 => array(
             "host"    => "127.0.0.1",
             "port"    => "3307",
             "socket"=> "/tmp/mysql.sock2",
             "user"    => "root",
             "password"    => "mysql",
             "connect_type"    => "socket"
             ),
         2 => array(
             "host"    => "127.0.0.1",
             "port"    => "3309",
             "user"    => "root",
             "password"    => "mysql",
             "connect_type"    => "tcp"
             )
         );
    
     for ($i=1;$i<=count($connect_hosts);$i++){  
         $cfg['Servers'][$i]['auth_type'] = 'cookie';  
         $cfg['Servers'][$i]['host'] = $connect_hosts[$i]['host'];   //修改host  
         $cfg['Servers'][$i]['connect_type'] = $conenct_hosts[$i]['connect_type'];  
         $cfg['Servers'][$i]['port'] = $connect_hosts[$i]['port'];
         if (array_key_exists('socket', $connect_hosts[$i])){
         $cfg['Servers'][$i]['socket'] = $conenct_hosts[$i]['socket'];
         }
         $cfg['Servers'][$i]['compress'] = false;  
         $cfg['Servers'][$i]['extension'] = 'mysql';  
         $cfg['Servers'][$i]['AllowNoPassword'] = true;  
         $cfg['Servers'][$i]['user'] = $connect_hosts[$i]['user'];  //修改用户名  
         $cfg['Servers'][$i]['password'] = $connect_hosts[$i]['password']; //密码  
         $cfg['Servers'][$i]['bs_garbage_threshold'] = 50;  
         $cfg['Servers'][$i]['bs_repository_threshold'] = '32M';  
         $cfg['Servers'][$i]['bs_temp_blob_timeout'] = 600;  
         $cfg['Servers'][$i]['bs_temp_log_threshold'] = '32M';  
     }
    
  3. 重启apache

TroubleShooting

  • 如果出现#2002 无法登录 MySQL 服务器错误,可能是地址填写的是localhost而不是127.0.0.1所致

SaltStack

salt-minion是可以在单价运行的,相当于就在本机运行,以masterless方式运行。
master需要开启4505和4506端口

安装

# 服务器端
rpm -Uvh --force
取消/etc/salt/master里面的注释interface: 0.0.0.0

# 客户端
vim /etc/salt/minion
master: salt 取消注释,这里写master的地址
id:     # 指定ID,如果不指定,默认应该是hostname
log_file: /var/log/salt/minion # 取消注释,打开日志
key_logfile: /var/log/salt/key # 取消注释,打开日志

# 重启两个端过后
在master端执行:
salt-key -L     # 查看是否有证书签发请求
salt-key -a 刚才查出来的ID # 同意签发证书

# 测试,在master执行一条命令
salt '刚才的id' cmd.run 'hostname'

创建state

# 首先得有一个/srv/salt/top.sls,这是必须的
base:                # 相当于仓库
  '*':                # 对象名,*表示所有的minion
    - webserver    # 自定义的资源名

# 针对上面定义的每一个资源需要有相应的配置文件,例如/srv/salt/webserver.sls,以下配置来自http://blog.sctux.com/?p=278
apache:                # 自定义的资源ID
  pkg.installed:        # 包管理方式安装下面的包
    - names:
      - httpd
      - httpd-devel
  service.running:    # 模块名
    - name: httpd    # 要启动的service
    - enable: True    # 开机启动
    - reload: True    # 监视下面的文件,如果有变动就重启
    - watch:
      - file: /var/www/html/index.html
    - require:
      - pkg: httpd    # 启动服务的前提是有这个包
  file.managed:        # 模块名
    - name: /var/www/html/index.html    # 目的文件
    - source: salt://index.html        # 源文件
    - user: apache                        # 文件所有者
    - group: root
    - mode: 644
    - backup: minion                    # 改变之前备份
    - require:
      - pkg: httpd

# 官网提供的另外的写法
apache:
  pkg:                # 声明state
    - installed        # 声明函数

常用命令

# 执行远程命令
salt '目标' <function> [arguments]
salt '*' test.ping    # ping一下所有的主机
salt -G 'os:Ubuntu' test.ping # 可以指定某一种系统
salt -E 'virtmach[0-9]' test.ping # 还能使用正则
salt -L 'foo,bar,baz,quo' test.ping    # 多个minion
salt -C 'G@os:Ubuntu and webser* or E@database.*' test.ping    # 反正就是想要的语法都有

# 系统自带的函数
salt '*' sys.doc
salt '*' test.ping
salt '*' cmd.run 'uname -a'    # 这就是执行命令
salt '*' cmd.exec_code python 'import sys; print sys.version'
salt '*' pip.install salt timeout=5 upgrade=True
salt '*' state.highstate # 将配置发往minion

LAMP

Linux+Apache+MySQL+PHP,这是最流行也是我最熟悉的服务器架构了,其实网上有很多一键安装的版本,有些版本的linux甚至提供了_apt
-get install lamp
_这样的一键安装程序,但是虽然他们是一个整体,但为了保证其独立性,我还是每次安装都会分别安装的。特别是某些框架或者是l
inux下的管理软件(比如现在所使用的禅道管理软件)在安装的时候经常都会有独立的打包程序,但我觉得那样会破坏apache等服务的独立性,所以独立安装,感觉舒
服一点。下面介绍一下安装过程:

CentOS 6.x + Apache + Mariadb + PHP

阅读全文 »

web开发约定

web部署结构

仅用于轻量级web框架,来自于TornadoWheel

.工程目录
├── server.py            # 启动服务
├── application.py    # 服务基本设置
├── url.py            # 路由结构
├── handler/            # 各种请求处理类文件
├── static/            # 静态文件
├── optsql/            # 与数据库读写相关的文件
└── template            # 模板文件

我不知道有多少人因为池老师的一篇《先有Mac还是先有钱》而去买的苹果电脑,我现在也在用rmbp,但我可不是因为这个原因。从大一到大三,我那台宏碁的笔记本一直陪伴在我身边,经历了我各种摧残,更换了无数的操作系统(windows、linux系列的),曾经最长用了半年的deepin单系统。虽说我自认为我用电脑一定比其他人用得好,毕竟一台三千多的电脑能用出人家五六千电脑的流畅度,但是依然不尽如我意。折腾累了,最终在大三结束刚进入实习期的我就找父母拿钱买了一台15年乞丐版的rmbp。刚好快一年了,这一年,mac带给我最大的感受就是我几乎没有任何感受…我已经完全忘记了去折腾电脑了,重装系统、电脑卡顿、木马病毒,统统都没有,我甚至都忘记了他们的存在了。买windows是买电脑送系统,买mac则是买系统送电脑。mac带给我们更多的是一种享受,我愿意为这样的服务买单。

作为一本talk mac的书籍,当然不乏众多的mac使用技巧,osx的强大,远比我当前的使用方式更强大,还需要我去探索。当然,现在的我已经不再是工具控了,因为我已经找到了一套自己认为最适合自己的工具,不用再为新工具的出现而浪费时间了。除了一些使用技巧外,池健强老师还谈了一些对编程的看法。让我了解了一个人,王小波,对他几乎没有什么认识,我想,我也该去拜读一下他的书了。

关于盗版,我的博客很早就有一篇《学生应该尽量购买正版书籍》,上个月从学校回来,收拾了一箱子舍不得卖的书,几乎全是在当当或者亚马逊上买的,这,也算是我对这些作者表达的基本的尊重。同时,在使用了mac过后,我的电脑/手机都与盗版彻底绝缘了。从此可以问心无愧地进行开发,也希望自己开发的东西有一天能让大众享受,能让用户主动掏钱。以下是池老师关于盗版的言论:

1
1.盗版肯定是不对的,如果用了盗版软件,至少要有愧疚之心。如果你是个穷学生,学习软件开发用了盗版软件,谁忍心责备你呢?但大家千万不能无耻到开篇提到的那位父亲那样,不仅误己,而且误人。 2.程序员也是要吃饭的,你们每个人在自己的电脑上使用的每个软件都是程序员一行行的代码敲出来的。 3.在经济实力允许的基础上,尽可能用正版,尤其是程序员。程序员不支持程序员,还怎么指望别人呢?

池老师也算是很多新一代程序员的领路人,不知道他现在怎么样了,不过,我算是真正走上了这条路了,一切,才刚刚开始。

语录

  • 反观观国内,很多公司把企业文化作为一种“秀”或“工具”,这就比较扯淡了。我觉得搞好企业文化,就两点: 1.利益,把公司利益和员工联系在一起,好员工钱得给足。 2.人文,少搞或不搞办公室政治,让员工自由一点、开放一点、平等一点,你会获得回报。 对于员工本身来说,不管企业是什么文化,
  • 是啊,人怎么会有那么多时间学习那么多东西呢?其实这个不可能的设定,是在保证你有足够时间看电视、看美剧、刷微博、上网闲逛的基础之上的。只要把上述这些事情消费的时间减少一半,拿来持续学习,你就会发现学习效果是惊人的。
  • 有一些穿高跟鞋走不到的路,有一些喷着香水闻不到的空气,有一些在楼宇里永远遇不到的人。
  • 很多时候我们初入江湖,不知深浅,不知道什么事能做,怎么做,为什么要这么做,即使怯生生问了,得到的答复往往是,That’s just the way it’s done(我们向来这么做),于是我们慢慢也变得成熟、圆滑和懒惰,不再去从深层次思考『为什么要这么做?』因为别人也这么做。慢慢的,这些东西就成为了folklore(陈规陋习)。 一个坏的习惯或传统,可能延续十年、百年、千年,直到那个打破陈规陋习的人出现! 提问、思考和努力工作,你就会抛弃这些陈规陋习,找到布满荆棘也满是鲜花的另一条鲜活的路。
  • 经验告诉我,优秀的人才是那些一心想着产品的人,而不是关注管理和流程本身。
  • 乔布斯认为,人活着是为了追求极致并分享美好的东西给人类,而不是做三流产品并赚钱。这样社会才能进步,让更多的人欣赏到更美好的东西。微软不过是另一个麦当劳,哈哈。
  • 我以为我是个盖世程序猿,有一天我的程序会奔跑在千万台服务器上。我猜中了前头,可是我猜不着这结局……我们说,呸,你丫从头就错了!
  • 有人问,你为什么要从事IT技术研发工作?如果是乔布斯,可能的答案是改变世界;如果是人生导师,可能的答案是跟随你心。如果是我回答呢,答案就是如果不从事这个行业的话呢,我还真不知道该如何养家糊口

MhA

很强大,不过我认为已经是老古董了,本身没有自带MySQL代理,配合Atlas能发挥很好的作用,不过由于很久没更新,导致很多问题至今未解决,比如,更换主从过后会宕掉,主机恢复后不能自动加入等特性。
安装教程1
安装教程2

[server default]
ssh_user=root
user=mha_test
password=mysql
secondary_check_script=masterha_secondary_check -s remote_host1 -s remote_host2 # 这里要修改名字哟
master_ip_failover_script=/script/masterha/master_ip_failover # 该脚本就是主机宕掉后的触发脚本

常用快捷键

Ctrl + u: 删除当前行的输入

实用命令

open 文件/文件夹:以默认方式打开文件或文件夹,太好用了
say "": 让终端说话,居然支持中文

安装方法

参考How To Install Java with Apt-Get on Ubuntu 16.04

  • JDK是Java开发的一个工具包,其他的工具包还有J2SE、JAVA SE

  • JDK8和JDK1.8是两种新旧的命名方式,其实是一个东西

    Java SE JDK 发布时间
    Java SE 8 JDK 1.8 2014
    Java SE 11 JDK11 2018
    Java SE 17 JDK 17 2021

数据类型

  • final关键字: 修饰类表示该类不能被继承,内部所有成员变量都是final的; 类的private方法也会隐式地指定为final方法。修饰变量时,如果是基本数据类型的变量,则其数值在初始化之后就不能更改; 如果是引用类型的变量,则初始化后不能被指向另一个对象。

  • object.getField() == 1: 这种比较可能出现空指针异常

  • 获取对象的类: object.getClass()

  • 使用Optional来减少空指针异常:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static Optional<List<String>> getA(boolean a) {
    if (a) {
    String [] results = {"a", "b", "c"};
    return Optional.of(Arrays.asList(results));
    }
    return Optional.empty(); // 不用返回null或者空数组了
    }

    public static void test() {
    Optional<List<String>> re = getA(true);
    re.isPresent(); // 对象是否存在
    re.ifPresent(result -> {
    console.log(result);
    });
    }

Integer/Long/Double/Float/BigDecimal/AtomicInteger数字

  • 千万不要用Double/Float来定义金额,因为经常会出现浮点数的精度问题,最好用大数类,例如BigDecimal/BigInteger
  • AtomicInteger是一个线程安全整数类,同时只有一个线程可以对其操作
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
a.longValue();	// 整型转长整型
longValue.intValue(); // long转换为int
1L; // 直接将数字转换成Long型
String.valueOf(123); // 整型转字符串,避免用toString出现空指针异常
(byte)1; // int to byte,int转字节
(int) myByte; // byte to int,字节转int
(float) 1; // int to float
new Long(12); // Integer转Long

Math.ceil(9.2); // 向上取整
Math.floor(9.2);// 向下取整
Math.round(9.2); // 四舍五入
Math.abs(-0.9); // 取绝对值

a == 0 ? false : true; // 整型转换为布尔
a ? 1 : 0; // 布尔转换为整型

BigDecimal.ZERO; // 直接就是BigDecimal类型的0
a.add(b); // BigDecimal加法
a.subtract(b); // BigDecimal减法
a.multiply(b); // BigDecimal乘法
a.divide(b); // BigDecimal除法
a.compareTo(b); // 比较BigDecimal,结果为0表示相当,为-1表示小于,为1表示大于,>-1表示大于等于,小于1表示小于等于。不要用equals方法来比较BigDecimal对象,如果scale不一样,会直接返回false
a.setScale(2); // 四舍五入保留两位小数
a.setScale(2, BigDecimal.ROUND_DOWN); // 向下取整
a.setScale(2, BigDecimal.ROUND_UP); // 向上取整

String/StringBuffer字符串

String不同的是,StringBufferStringBuilder类的对戏那个能够被多次修改,并且不产生新的未使用的对戏那个。

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
// String
String a = "World";
String b = new String(array); // 将array拼接成字符串
String[] c = new String[] {"A", "B", "C"};
List<String> list = Arrays.asList(c); // String[] 转换为 List<String>
int len = b.length(); // 得到字符串长度
b.concat(a); // 连接字符串
b + a; // 连接字符串
System.out.printf("test %s", a);
String.format("test %s", a);// 格式化字符串
b.charAt(0); // 得到指定索引的字符
a.compareTo(Object o);
a.compareToIgnoreCase(String str);// 比较字符串
a.startsWith(String prefix);
a.endsWith(String suffix); // 验证字符串是否以某个子字符串结尾
a.indexOf(String str); // 返回子字符串首次出现的位置,验证是否包含某个子字符串,没找到返回-1
a.contains(str); // 直接检验是否包含某个子字符串
a.matches(".*?"); // 验证字符串是否复合正则表达式
a.replaceAll(String regex, String replacement); // 替换字符串
String[] strArr = a.split(String regex); // 拆分字符串,字符串分隔/字符串分割
a.trim(); // 移除首尾空白
Integer.parseInt(str); // 字符串转整型
Long.parseLong(str); // 字符串转Long型
String.join(",", new String[]{"foo", "bar"}) // 合并字符串,类似PHP的implode,字符串中间添加空格
new BigDecimal("1.00"); // String转BigDecimal

// 判断字符串是否为空
str == null;
"".equals(str);
str.length <= 0;
str.isEmpty();

// StringBuffer
StringBuffer c = new StringBuffer('Hello World');
c.append(String s); // 在字符串尾部追加
c.reverse(); // 反转字符串
c.capacity(); // 返回当前字符串的容量

// 日期时间
Date date = new Date();
System.out.println(date.toString());

// 字符数组转字符串,不用toString方法
char[] data = {'a', 'b', 'c'};
String str = new String(data);

// ArrayList<Chracter> to String
String getStringRepresentation(ArrayList<Character> list)
{
StringBuilder builder = new StringBuilder(list.size());
for(Character ch: list)
{
builder.append(ch);
}
return builder.toString();
}

// 字符串反转
StringBuilder sb = new StringBuilder("content");
StringBuilder re = sb.reverse();

List list = new ArrayList(myCollections); // Collections转list

// URLDecode/URLEncode,需要注意的是,如果出现特殊符号%,后面跟着中文,那么decode居然会报错
URLDecode.decode("test", "utf-8");
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Json字符串转换为Dto
MyDto myDto = new Gson().fromJson(jsonString, MyDto.class);
new JsonParser().parse(jsonString).getAsJsonObject().get("key1").toString(); // 直接获取指定的key的值,而不用新建一个对象。但是有个坑是这样得到的字符串两边会带上引号。。。

// 验证是否是Json字符串
try {
JSONObject result = JSONObject.parseObject(string);
return null != result;
} catch (Exception e) {
return false;
}

// 任意对象转JSON字符串
import com.google.gson.Gson;
Gson gson = new Gson();
String jsonString = gson.toJson(myObj);
System.out.println(jsonString);
正则匹配
  • java的正则匹配没有findAll的概念,需要自己在正则中加入类似()*来实现多次匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Pattern p = Pattern.compile("(a.*?a)*", Pattern.CASE_INSENSITIVE|Pattern.MULTILINE);	// 可以配置大小写不敏感;查找多行
Matcher matcher = p.matcher("content");
// 遍历匹配结果方式一
while (matcher.find()) {
System.out.println(matcher.group());
}
// 遍历匹配结果方式二
if (matcher.find() && matcher.groupCount() >= 1) {
matches = new ArrayList();
for (int i = 1; i <= matcher.groupCount(); i++) {
System.out.println(matcher.group(i));
}
}

// 正则替换
str.replaceAll(reg, "");

Array/Vector/Stack/Enumeration/Collections/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
// 初始化&赋值
typeName[] arrayName; // 声明数组的基本方式,也可以typeName arrayName[]
typeName arrayName[][] = new typeName[3][4]; // 定义多维数组
double[] myList = new double[5]; // 创建指定长度的数组
List<String> names = Arrays.asList("xxx","yyy","zzz"); // 直接初始化固定长度的数组,但是要超过一个元素才行
List<String> list1 = new ArrayList<>(); // 初始化一个空数组,之后用add添加元素
list1.addAll(list2); // 将数组2合并到数组1
List<String> names = new ArrayList<String>() {
{
for (int i = 0; i< 10; i++) {
add("add"+i);
}
}
};

Arrays.asList("a", "b").size(); // 获取数组长度length
Arrays.asList("a", "b").contains("c"); // 数组是否包含某个值

// 遍历数组
for (double element: myList) {}
for (int i = 0 ; i < myList.size(); i++) {}

// 遍历数组并移除元素
Iterator<String> iterator = myList.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("banana")) {
iterator.remove(); // 根据条件删除元素
}
}


// Vector类,动态数组
// Stack栈
Stack<Integer> d = new Stack<integer>();
d.push(int a);

// Enumeration枚举类型
Enumeration<String> days; // 定义枚举变量
Vector<String> dayNames = new Vector<StringL>();
dayNames.add("Sunday"); // 添加枚举元素
days = dayNames.elements();

// 数组反转
List<String> new = Lists.reverse(lists1);

// 数组分片
List<E> subList(fromIndex, toIndex);

Set/HashSet/Stream集合

1
2
String[] myList = new String[] { "a", "b" };
Set<String> mySet = new HashSet<String>(Arrays.asList(myList)); // 初始化
Stream API
  • 是一系列对集合便利操作的工具集,类似Laravel里面的Collection
  • Concat: 合并两个流: Stream.concat(stream1, stream2)
  • foreach: 遍历
  • map: 映射,返回新的元素
  • mapToInt/mapToDouble/mapToLong: 映射成指定的数字类型,映射完成后可以使用summaryStatistics方法得到统计的结果然后使用getMax/getMin/getSum/getAverage等方法
  • filter: 过滤,仅保留返回true的元素
  • limit: 仅保留指定数量的元素
  • sorted: 排序
  • Collectors: 用于返回列表或字符串
1
2
3
4
5
6
7
8
9
10
11
// 过滤
Record record = list.stream()
.filter(record -> "name".equals(record.getName()))
.findFirst()
.orElse(null);

// 排序
Record record = list.stream()
.sorted(Comparator.comparingInt(Record::getTime))
.reversed()
.collect(Collectors.toList());

Dictionary/Hashtable/Map/ConcurrentHashMap字典

  • ConcurrentHashMap是线程安全的
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
// Dictionary字典
// Hashtable

// map的初始化
HashMap<String, String> map = new HashMap<String, String>();
map.put("key", "value");
HashMap<String, String> map = new HashMap<String, String>() {
{
map.put("key1", "value1");
map.put("key2", "value2");
}
};

map.containsKey(Object key); // 是否包含某个key
map.containsValue(Object value); // 是否包含某个value
map.equals(Object o); // 比较指定的对象与此映射是否相等个
map.get(Object key); // 获取某个key的值
map.isEmpty(); // 是否为空
map.put(K key, V value); // 设置值
map.remove(Object key); // 移除某个键值对
map.size(); // 获取键值对数量
map.values(); // 返回所有的value,是一个Collection对象

// Map的遍历
Map<String, String> map = new HashMap<String, String>();
// 遍历方法一
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey(), entry.getValue);
}
// 遍历方法二
for (String key : map.keySet()) {String value = map.get(Key);}
for (String value : map.values()) {}

// Map转为Json格式字符串
String jsonStr = new Gson().toJson(myMap);

Queue队列

1
2
3
4
5
6
7
Queue<String> queue = new LinkedList<String>();	// 定义队列
queue.offer("a"); // 添加元素,如果无法添加会返回false
queue.add("a"); // 添加元素,如果无法添加会抛出来异常
queue.poll(); // 返回第一个元素,并在队列中删除,没有会返回null
queue.remove(); // 从队列删除第一个元素,没有会抛出异常
queue.element(); // 返回第一个元素,没有会抛出异常
queue.peek(); // 返回第一个元素,没有会返回null

时间处理

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
Date date = new Date();	// 获取时间对象
Long timestamp = date.getTime(); // 获取时间戳(毫秒)
System.currentTimeMillis(); // 毫秒级时间戳
Date date = new Date(1234567890000); // 毫秒级时间戳转Date对象
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2020-05-01 00:00:00"); // 获取指定日期的date

// 获取今天开始的时间
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
Date zero = calendar.getTime();

// 获取ISO8601格式的时间,类似2019-12-12T12:12:12Z
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
df.setTimeZone(tz);
return df.format(new Date());

// 解析CST格式的时间
String dateStr = "Wed Sep 11 10:10:10 CST 2020";
Date date = (Date) df.parse(df);

// 时间计算
date1.before(date2); // 判断date1是否在date2之前

Calendar now = Calendar.getInstance()
now.setTime(date); // 可以指定其他的date,不用非要是进Tina
now.set(Calendar.DATE, now.get(Calendar.DATE) + 7); // 计算7天后的时间

类/对象/方法

  • 类中可以使用static {}设置静态代码块,有助于优化程序性能,static块可以放置于类中的任何地方,当类初次被加载的时候,会按照static块的顺序来执行每个块,并且只会执行一次。
  • 泛型类使用<T>来表示,? extends 类名(上边界限定)表示只要继承某个类的都可以,? super 类名(下边界限定)表示只要是某个类的父类都可以,单独的?(无边界限定)表示没有任何限制
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
// 一个类可以有多个构造方法
public class Sample {
static { // 静态代码块
long a = System.nanoTime();
testMethod();
}
public Sample() {} // 不带参数的构造方法
public Sample(String param1) {} // 带参数的构造方法
private static void testMethod() {}
}

// 泛型类,T可以传入任意类型
public class MyClass<T> {
// 成员变量
private T t;
public MyClass(T t) {
super();
this.t = t;
}
public T getT() {return t;}
}

// 方法中使用Optinal表示可能为null,很大程度上能帮助调用者了解内部可能返回null
public Optinal<User> getUser(Long id) {
if (null != id) {return Optinal.of(new User());}
return Optinal.empty();
}
Optional<user> userOp = getUser(1L);
if (userOp.isPresent()) {...} else {...}

Function接口

  • Java8新增的函数式编程方法,主要用来做lambda表达式

  • 任何标注了@FunctionalInterfaced都接口都表示是一个函数式的接口

  • Functiond源码简介:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @FunctionalInterface
    public interface Function<T, R> { // T表示入参,R表示出参
    R apply(T t);
    // compose接收一个Function参数,返回时先用传入的逻辑执行apply,然后在执行当前Function的apply,
    // 相当于 a.compose(b).apply(1) = a.apply(b.apply(1))
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    Objects.requireNonNull(before);
    return (V v) -> apply(before.apply(v));
    };
    // andTthen是先执行当前的逻辑,再执行传入的逻辑。
    // 相当于a.andThen(b).apply(1) = b.apply(a.apply(1))
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
    };
    static <T> Function<T, T> identity() {
    return t -> t;
    };
    }
  • 例子:

1
2
Function<Integer,Integer> test=i->i+1;
test.apply(1); // 会得到2

异常处理

  • 异常类的getMessage()toString()方法的区别,前者仅仅返回错误的信息,如果是空指针异常,一般返回的是null,而后者则会包含异常类的类型信息。建议如果是打印日志则使用toString() 方法,因为更详细,如果是给用户看的信息可以使用getMessage方法仅展示给用户关键信息

文件/文件夹

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
// 以行为单位读取文件
File file = new File(fileName);
BufferedReader reader = null;
reader = new BufferedReader(new FileReader(file));
String tempString = null;
int line = 1;
while ((tempString = reader.readLine()) != null) {
System.out.println("line " + line + ": " + tempString);
line++;
}
reader.close();

// 读取整个文件
File file = new File(fileName);
if (file.isFile() && file.exists()) {
long fileLength = file.length();
byte[] fileContent = new byte[(int) fileLength];
FileInputStream in = new FileInputStream(file);
in.read(fileContent);
in.close();
String[] fileContentArr = new String(fileContent); // 结果字符串数组
}

// 写入文件/新建文件
File file = new File(path); // new File第二个参数如果为true,表示追加
if (!file.exists()) {
file.createNewFile();
}
FileWriter fw = new FileWriter(file.getAbsoluteFile());
BufferedWriter bw = new BufferedWriter(fw);
bw.write(content);
bw.close();

// 新建文件夹
File dir = new File("/tmp/test/deep");
if (!dir.exists()) {
dir.mkdirs();
}

Shell

1
2
3
4
5
6
7
8
// Java执行shell命令
String cmd = "ls | grep abc";
String[] commands = {"/bin/sh", "-c", cmd}; // 加入/bin/sh可以防止很多命令执行出错或者转义出错
Process process = Runtime.getRuntime().exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((s = stdInput.readline()) != null) {
System.out.println(s);
}

  • JavaSE程序可以打包成Jar包(与平台无关的格式),JavaWeb程序可以打包成War包
1
2
3
import java.io.*;		// 导入java_installation/java/io下的所有类
java -jar myjar.jar; // 直接用命令行运行jar包
java -cp myjar.jar com.example.MainClass // 指定jar入口

线程/进程

  • ThreadLocal: 保证线程安全(一次HTTP请求从开始到响应都是在一个线程内部,其他用户是在其他的线程里面)
1
2
Thread current = THread.currentThread();	// 获取当前进程
current.getId(); // 获取当前进程Id

多线程

  • 线程池只能放入实现Runnable/callable类的线程,不能放入继承Thread的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方法一、继承Thread类,缺点是无法多重继承
public class MyThread extends Thread {
@Override
public void run()
{
System.out.println("线程执行");
}
}
new MyThread().start();

// 方法二、实现Runnable接口,适合多线程共享资源
public class MyThread implements Runnable {
public void run () {
System.out.println("线程执行");
}
}

MyThread mythread = new MyThread();
new Thread(mythread).start();

三方库

Jsch SSH连接工具

一个很老很久没有更新的工具,文档example比较全,但是只有这个工具用户量大一点,其他的用户量太少不敢用(Apache sshd则是因为文档太少,官方文档是针对它的server端)。执行shell命令的时候建议使用ChannelExec而不是ChannelShell(因为后者的输出流里面类似于一个终端,会包含一些没用的命令提示符).

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
Jsch jSch = new JSch();
jSch.addIdentity("name", prvKeyStr.getBytes, pubKeyStr.getBytes, keyPass.getBytes); // 加载私钥公钥和私钥密码
Session session = jSch.getSession(username, ip, port); // 新建session
session.setConfig("StrictHostKeyChecking", "no"); // 不进行静态host-key校验,否则可能出现UnknownHostKey错误
session.setTimeout(10000); // 设置连接超时时间毫秒
session.connect(); // 连接

// 执行命令并获取返回结果
ChannelExec channelExec = (ChannelExec) this.session.openChannel("exec");
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream error = new ByteArrayOutputStream();
channelExec.setCommand("ls"); // 实际执行的命令
channelExec.setOutputStream(out);
channelExec.setErrStream(error);
channelExec.connect();
int sleepCount = 0;
do { // 等待命令返回,官方手册是用的这种方法
try {
Thread.sleep(100);
} catch (InterruptedException e) {
result.setExitCode(1);
result.setStderr(SERVER_EXEC_ERROR + e.getMessage());
return result;
}
} while (!channelExec.isClosed() && sleepCount++ < 60);
out.toString(); // 标准输出
error.toString(); // 标准错误输出
channelExec.getExitStatus(); // 获取返回状态码

TroubleShooting

  • **Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $**,这是在使用Gson解析字符串时的报错,一般是因为字符串非标准Json格式造成的

  • Unchecked assignment for ‘java.util.ArrayList’ to ‘java.util.ArrayList <…>: 可能是定义ArrayList的时候没有使用<>,可以用下面两种方法进行定义:

    1
    2
    ArrayList<MyList> myList = new ArrayList<>();
    ArrayList<MyList> myList = new ArrayList<MyList>();
  • **com.alibaba.fastjson.JSONException: default constructor not found. **: fastjson的坑,要求对应class必须有默认的构造函数(空参数)

  • fastjson出现$ref: $.data[2].indexs[0]: 又是fastjson的坑,如果是需要序列化的对象中有对同一个对象的依赖,那么在JSON序列化中可能会将后续的对象转成这种字符串

扩展阅读