0%

文章字数:1413,阅读全文大约需要5分钟

基本概念

  • 进程:进程拥有独立的代码和数据空间,是资源分配的最小单位。一个进程可以包含一个或多个线程。
  • 线程:同一类的线程共享代码和数据空间,每个线程都拥有独立的运行栈和程序计数器。线程是cpu调度的最小单位。
  • 进程/线程 的五个状态:创建就绪运行阻塞终止
  • 多进程代表操作系统可运行多程序,线程代表程序里运行多个顺序流

创建java线程

java创建线程有三种方法

  • 基础Thread类
  • 实现Runable接口
  • 实现Callable接口,配合Future、线程池

继承Thread

1
2
3
4
5
6
7
8
9
10
11
class threadLearn extends Thread{
public void run(){
//...
}
}
public class Main{
public static void main(){
ThreadLearn learn = new ThreadLearn ();
learn.start();
}
}

实现java.lang.Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
class ThreadLearn implements Runnable{
@Override
public void run(){
//...
}
}
public class Main{
public static void main(){
ThreadLearn learn = new ThreadLearn ();
learn.start();
}
}
  1. start()方法使线程变成可运行状态Runnable,操作系统决定何时调用运行。
  2. start()方法重复调用会抛出java.lang.IllegalThreadStateException异常
  3. run()方法是多线程程序的一个约定,所有的多线程代码都要写在里面。

ThreadRunnable比较

  1. Thread本质上是实现了Runnable接口。
  2. Thread类实现了Runnable并在其之上进行了拓展。

线程状态

线程状态转换

线程状态 状态分类 描述
新建状态New - 新创建了一个线程对象
就绪状态Runnable - 其它线程调用了该线程的start()方法,线程变为可运行
运行状态Running - 就绪状态的线程被系统调用。
阻塞状态Blocked - 分为三种情况
阻塞状态 等待阻塞 运行的线程执行wait()方法
阻塞状态 同步阻塞 获取同步锁时锁被其它线程占用,jvm会把该线程放入锁池之中
阻塞状态 其它阻塞 线程运行sleep()join()的线程结束或发出I/O请求。sleep不会释放锁
死亡状态 - 线程执行完毕或异常退出

线程调度

  1. 线程优先级
  • Thread的setPriortity()getPriortity()管理优先级
  • 优先级取值1~10整数,推荐使用常量,这三个级别的可移植性好。
    1
    2
    3
    static int MAX_PRIORITY=10;
    static int NORM_PRIORITY=5;//默认
    static int MIN_PRIORITY=1;
  1. 线程睡眠
    Thread.sleep(long millis):设定线程阻塞时间,阻塞结束后进入就绪状态。

  2. 线程等待
    Object类中的wait()方法,导致当前线程等待,直到其它线程调用此对象的notify()方法或者notifyAll()唤醒,等价于wait(0)

  3. 线程让步
    Thread.yield()暂停当前正在执行的线程对象,把执行的机会让给优先级相同或更高的线程。

  4. 线程加入
    join()当前线程进入阻塞态,调用其它线程,该线程运行完毕后当前线程再进入就绪态。

  5. 线程唤醒
    Object类的notify()方法,唤醒对象监听器上一个线程,如果有多个线程在此对象上等待,则唤醒随机一个。notifyAll()唤醒所有该对象上等待的线程。
    wait()sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException

sleep方法不会释放锁,wait
waitnotifynotifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

常用方法

  • sleep(): 强迫一个线程睡眠N毫秒。
  • isAlive(): 判断一个线程是否存活。
  • join(): 等待线程终止。
  • activeCount(): 程序中活跃的线程数。
  • enumerate(): 枚举程序中的线程。
  • currentThread(): 得到当前线程。
  • isDaemon(): 一个线程是否为守护线程。
  • setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于没有用户线程后守护线程终止)
  • setName(): 为线程设置一个名称。
  • wait(): 强迫一个线程等待。
  • notify(): 通知一个线程继续运行。
  • setPriority(): 设置一个线程的优先级。

线程同步

synchronized[ˈsɪŋkrənaɪzd]是系统级的锁,一旦锁死除了线程自行释放没有其它方法。juc的lock锁是编码级别的,可以代码解锁。juc(java.util.concurrent)下次讨论

  • synchronized在对象里:标记于方法或是代码块都是对对象加锁。只要对象中出发了锁,整个对象都无法进入。
  • synchronized标记于静态方法,则是对于类加锁,和对象锁不冲突

线程数据传递

同步情况下使用参数传入,return返回的形式,多线程下运行和结束是不可预料的,所以无法和同步一样传参。

  1. 使用构造方法传参

    1
    2
    //线程使用构造函数就收这个参数
    Thread thread = new MyThread1("hello world");
  2. set方法
    线程里先设置set方法接受参数(不用多说了吧)
    然后start()之前设置参数

    1
    2
    3
    4
    MyThread2 myThread = new MyThread2();   
    myThread.setName("hello world");
    Thread thread = new Thread(myThread);
    thread.start();
  3. 回调函数

    将对象传入线程,线程在某一时间调用对象的函数。主线程通过传入的对象获取线程操作后的值。(还有静态类)

  4. 声明lambda函数的接口

    1
    2
    3
    public interface ICallback {   
    public void callback(Map<String, Object> params);
    }
  5. 线程调用接口返回数据

    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
    public static void doStm(final ICallback callback) {  
    // 初始化一个线程
    Thread t = new Thread() {
    public void run() {

    // 这里是业务逻辑处理
    System.out.println("子线任务执行:"+Thread.currentThread().getId());

    // 为了能看出效果 ,让当前线程阻塞5秒
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    // 处理完业务逻辑,
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("a1", "这是我返回的参数字符串...");
    callback.callback(params);
    };
    };

    es.execute(t);
    //一定要调用这个方法,不然executorService.isTerminated()永远不为true
    es.shutdown();
    }
    1
    2
    3
    doStm((params)->{
    System.out.println("单个线程也已经处理完毕了,返回参数a1=" + params.get("a1"));
    });

文章字数:200,阅读全文大约需要1分钟

主要思路是将快捷方式放入windows的开机自启目录中

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
// 写入快捷方式 是否自启动,快捷方式的名称,注意后缀是lnk
public boolean setAutoStart(boolean yesAutoStart, String lnk) {
File f = new File(lnk);
String p = f.getAbsolutePath();
String startFolder = "";
String osName = System.getProperty("os.name");
String str = System.getProperty("user.home");
if (osName.equals("Windows 7") || osName.equals("Windows 8") || osName.equals("Windows 10")
|| osName.equals("Windows Server 2012 R2") || osName.equals("Windows Server 2014 R2")
|| osName.equals("Windows Server 2016")) {
startFolder = System.getProperty("user.home")
+ "\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup";
}
if (osName.endsWith("Windows XP")) {
startFolder = System.getProperty("user.home") + "\\「开始」菜单\\程序\\启动";
}
if (setRunBySys(yesAutoStart, p, startFolder, lnk)) {
return true;
}
return false;
}

// 设置是否随系统启动
public boolean setRunBySys(boolean b, String path, String path2, String lnk) {
File file = new File(path2 + "\\" + lnk);
Runtime run = Runtime.getRuntime();
File f = new File(lnk);

// 复制
try {
if (b) {
// 写入
// 判断是否隐藏,注意用系统copy布置为何隐藏文件不生效
if (f.isHidden()) {
// 取消隐藏
try {
Runtime.getRuntime().exec("attrib -H \"" + path + "\"");
} catch (IOException e) {
e.printStackTrace();
}
}
if (!file.exists()) {
run.exec("cmd /c copy " + formatPath(path) + " " + formatPath(path2));
}
// 延迟0.5秒防止复制需要时间
Thread.sleep(500);
} else {
// 删除
if (file.exists()) {
if (file.isHidden()) {
// 取消隐藏
try {
Runtime.getRuntime().exec("attrib -H \"" + file.getAbsolutePath() + "\"");
} catch (IOException e) {
e.printStackTrace();
}
Thread.sleep(500);
}
run.exec("cmd /c del " + formatPath(file.getAbsolutePath()));
}
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

// 解决路径中空格问题
private String formatPath(String path) {
if (path == null) {
return "";
}
return path.replaceAll(" ", "\" \"");
}

文章字数:306,阅读全文大约需要1分钟

异或(xor)两次之后的数据是相同的,根据这个原理可以加密数据

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
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
* 加密解密工具类
*/
public class XORUtils {
/**
* 加密、解密byte数据
* @param data 数据(密文/明文)
* @param key 密钥
* @return 返回解密/加密后的数据
*/
public static byte[] encrypt(byte[] data, byte[] key) {
if (data == null || data.length == 0 || key == null || key.length == 0) {
return data;
}
byte[] result = new byte[data.length];
// 使用密钥字节数组循环加密或解密
for (int i = 0; i < data.length; i++) {
// 数据与密钥异或, 再与循环变量的低8位异或(增加复杂度)
result[i] = (byte) (data[i] ^ key[i % key.length] ^ (i & 0xFF));
}
return result;
}

/**
* 加密、解密文件
* @param inFile 输入文件(密文/明文)
* @param outFile 结果输出文件
* @param key 密钥
*/
public static void encryptFile(File inFile, File outFile, byte[] key) throws Exception {
InputStream in = null;
OutputStream out = null;
try {
// 文件输入流
in = new FileInputStream(inFile);
// 结果输出流, 异或运算时, 字节是一个一个读取和写入, 这里必须使用缓冲流包装,
// 等缓冲到一定数量的字节(10240字节)后再写入磁盘(否则写磁盘次数太多, 速度会非常慢)
out = new BufferedOutputStream(new FileOutputStream(outFile), 10240);

int b = -1;
long i = 0;

// 每次循环读取文件的一个字节, 使用密钥字节数组循环加密或解密
while ((b = in.read()) != -1) {
// 数据与密钥异或, 再与循环变量的低8位异或(增加复杂度)
b = (b ^ key[(int) (i % key.length)] ^ (int) (i & 0xFF));
// 写入一个加密/解密后的字节
out.write(b);
// 循环变量递增
i++;
}
out.flush();
} finally {
close(in);
close(out);
}
}

private static void close(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException e) {
}
}
}
}

测试

1
2
3
4
5
6
7
import java.io.File;
public class Main {
public static void main(String[] args) throws Exception {
XORUtils.encryptFile(new File("demo.jpg"), new File("demo.jpg_cipher"), key.getBytes());
XORUtils.encryptFile(new File("demo.jpg_cipher"), new File("demo.jpg_plain"), key.getBytes());
}
}

文章字数:110,阅读全文大约需要1分钟

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
package com.test.jvm.oom.design;

import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;

public class Test {

/**
*1. 从剪切板获得文字。
*/
public static String getSysClipboardText() {
String ret = "";
Clipboard sysClip = Toolkit.getDefaultToolkit().getSystemClipboard();
// 获取剪切板中的内容
Transferable clipTf = sysClip.getContents(null);

if (clipTf != null) {
// 检查内容是否是文本类型
if (clipTf.isDataFlavorSupported(DataFlavor.stringFlavor)) {
try {
ret = (String) clipTf
.getTransferData(DataFlavor.stringFlavor);
} catch (Exception e) {
e.printStackTrace();
}
}
}

return ret;
}

/**
* 2.将字符串复制到剪切板。
*/
public static void setSysClipboardText(String writeMe) {
Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable tText = new StringSelection(writeMe);
clip.setContents(tText, null);
}

/**
*3. 从剪切板获得图片。
*/
public static Image getImageFromClipboard() throws Exception {
Clipboard sysc = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable cc = sysc.getContents(null);
if (cc == null)
return null;
else if (cc.isDataFlavorSupported(DataFlavor.imageFlavor))
return (Image) cc.getTransferData(DataFlavor.imageFlavor);
return null;

}

/**
* 4.复制图片到剪切板。
*/
public static void setClipboardImage(final Image image)throws Exception {
Transferable trans = new Transferable() {
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { DataFlavor.imageFlavor };
}

public boolean isDataFlavorSupported(DataFlavor flavor) {
return DataFlavor.imageFlavor.equals(flavor);
}

public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (isDataFlavorSupported(flavor))
return image;
throw new UnsupportedFlavorException(flavor);
}

};
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(trans,
null);
}

/**
* 5.通过流获取,可读取图文混合
*/
public void getImageAndTextFromClipboard() throws Exception{
Clipboard sysClip = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable clipTf = sysClip.getContents(null);
DataFlavor[] dataList = clipTf.getTransferDataFlavors();
int wholeLength = 0;
for (int i = 0; i < dataList.length; i++) {
DataFlavor data = dataList[i];
if (data.getSubType().equals("rtf")) {
Reader reader = data.getReaderForText(clipTf);
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("d:\\test.rtf"));
char[] c = new char[1024];
int leng = -1;
while ((leng = reader.read(c)) != -1) {
osw.write(c, wholeLength, leng);
}
osw.flush();
osw.close();
}
}
}
}

文章字数:119,阅读全文大约需要1分钟

依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>

代码

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
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
* excel操作工具类
*
* @author colin.cheng
* @date 2021-11
* @since 1.0.0
*/
public class ExcelWriter {

private XSSFWorkbook workbook;
private XSSFSheet sheet;
private String filePath;

/**
* 打开或者创建一个excel
*
* @param filePath 文件位置
* @param sheetName excel表格名
* @throws IOException
*/
public ExcelWriter(String filePath, String sheetName) throws IOException {
if(Files.exists(Paths.get(filePath))) {
try (FileInputStream fileInputStream = new FileInputStream(filePath)){
workbook = new XSSFWorkbook(fileInputStream);
}
} else {
workbook = new XSSFWorkbook();
}
sheet = workbook.getSheet(sheetName);
if(sheet == null) {
sheet = workbook.createSheet(sheetName);
}
this.filePath = filePath;
}

/**
* 根据行和列的索引写入单元格的数据
*
* @param row 行
* @param column 列
* @param val 值
*/
public void write(int row,int column, String val) {
XSSFRow xssRow = sheet.getRow(row);
if(xssRow == null) {
xssRow = sheet.createRow(row);
}
XSSFCell cell = xssRow.getCell(column);
if(cell == null) {
cell = xssRow.createCell(column);
}
if(val != null) {
cell.setCellType(CellType.STRING);
cell.setCellValue(val);
} else {
cell.setCellType(CellType.BLANK);
}
}

/**
* 根据行和列的索引获取单元格的数据
* @param row 行
* @param column 列
* @return
*/
public String read(int row,int column){
XSSFRow xssRow = sheet.getRow(row);
if(xssRow == null) {
return null;
}
XSSFCell cell = xssRow.getCell(column);
return cell == null ? null : cell.toString();
}

/**
* 关闭文件,并保存修改内容
*
* @throws IOException
*/
public void saveAndClose() throws IOException {
try (FileOutputStream fos = new FileOutputStream(filePath)) {
workbook.write(fos);
}
workbook.close();
}

/**
* 关闭文件,不保存修改
*
* @throws IOException
*/
public void close() throws IOException {
workbook.close();
}
}

文章字数:24,阅读全文大约需要1分钟

封装了一个删除文件的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean deleteFile(String fileName) {//传入绝对路径
File file = new File(fileName);
if (file.exists() && file.isFile() || file.exists() && file.isDirectory()) {
if (file.delete()) {
return true;
} else {
return false;
}
} else {
return false;
}
}

文章字数:149,阅读全文大约需要1分钟

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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
package com.colin.tool.file;

import java.io.File;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;

/**
* 文件搜索流
*
* @author colin.cheng
* @since 1.0.0
*/
public class FileSearchStream {

private File[] root;
private boolean parallel;
private Consumer<File> afterConsumer;
private Function<File, Boolean> filter;
private boolean deep = true;

private FileSearchStream() {}

public FileSearchStream(File... root) {
init(root);
}

/**
* 获取一个查询对象
*
* @param root
* @return
*/
public static FileSearchStream of(File... root) {
return new FileSearchStream(root);
}

public boolean isDeep() {
return deep;
}

/** 如果是文件夹,是否遍历 */
public FileSearchStream setDeep(boolean deep) {
this.deep = deep;
return this;
}

/**
* 获取本地所有盘符
*/
public static File[] getSystemRoot() {
return File.listRoots();
}

/**
* 过滤条件
*
* @param filter
* @return
*/
public FileSearchStream filter(Function<File, Boolean> filter) {
this.filter = filter;
return this;
}

/**
* 找到之后如何处理
*
* @param afterConsumer
* @return
*/
public void forEach(Consumer<File> afterConsumer) {
this.afterConsumer = afterConsumer;
searchRoot();
}

/**
* 转换成list
*
* @param listFunction
* @param <T>
* @return
*/
public <T> List<T> toList(Function<File, T> listFunction) {
List<T> list = new LinkedList<>();
this.afterConsumer = file -> list.add(listFunction.apply(file));
searchRoot();
return list;
}

/**
* 分组保存
*
* @param keyFunction
* @param valFunction
* @param <T>
* @param <R>
* @return
*/
public <T, R> Map<T, List<R>> groupingBy(Function<File, T> keyFunction, Function<File, R> valFunction) {
Map<T, List<R>> res = new LinkedHashMap<>();
this.afterConsumer = file -> {
T key = keyFunction.apply(file);
R val = valFunction.apply(file);
if (res.containsKey(key)) {
res.get(key).add(val);
} else {
List<R> list = new LinkedList<>();
list.add(val);
res.put(key, list);
}
};
searchRoot();
return res;
}

/**
* 根据父级文件夹分组
*
* @return
*/
public Map<String, List<String>> groupingByPath() {
return groupingBy(File::getParent, File::getName);
}

/**
* 重置
*
* @return
*/
public FileSearchStream reset(File... root) {
init(root);
return this;
}

/**
* 初始化
*
* @param root
*/
private void init(File... root) {
this.root = root;
this.parallel = false;
this.afterConsumer = null;
this.filter = (file) -> true;
}

/**
* 开始搜索
*
* @return
*/
private void searchRoot() {
final Stream<File> fileStream = Arrays.stream(root);
if (parallel) {
fileStream.parallel().forEach(this::search);
} else {
fileStream.forEach(this::search);
}
}

/**
* 获取对应盘符下符合要求的文件
*/
private void search(File file) {
File[] f = file.listFiles();
if (f != null) {
if (f.length > 0) {
for (File files : f) {
if(deep) {
if (files.isDirectory()) {
search(files);
} else {
if (filter.apply(files)) {
if (afterConsumer != null) {
afterConsumer.accept(files);
}
}
}
} else {
if (filter.apply(files)) {
if (afterConsumer != null) {
afterConsumer.accept(files);
}
}
}
}
}
}
}

/**
* 文件后缀
*/
public enum FileSuffix {
/** 图片 */
IMG("psd", "pdd", "gif", "jpeg", "jpg", "png"),
/** 文档 */
DOCUMENT("doc", "docx", "xls", "xlsx", "csv", "ppt", "pptx", "txt"),
/** 程序 */
APPLICATION("exe", "bat", "cmd", "sh", "ink", "py", "class", "java"),
/** 音频 */
AUDIO("mp3", "flac", "ape", "cd", "wave", "aiff"),
/** 视频 */
VIDEO(
"avi", "mov", "qt", "asf", "rm", "navi", "divX", "mpeg", "mpg", "ogg", "mod", "rmvb", "flv", "mp4", "3gp");

private String[] suffix;

FileSuffix(String... suffix) {
this.suffix = suffix;
}

public boolean check(String fileName) {
if (fileName.contains(".")) {
String fileSuffix = fileName.split("\\.")[1];
fileSuffix = fileSuffix.toLowerCase();
for (String s : suffix) {
String b = s.toLowerCase();
if (fileSuffix.equals(b)) {
return true;
}
}
}
return false;
}

public String[] getSuffix() {
return suffix;
}
}
}

文章字数:439,阅读全文大约需要1分钟

java8也新加入了功能更强大的日期,但是本次主要是记录常用的日期格式化,所以讨论的是java.util.Date

获取日期

  • Date()获取当前时间
  • Date(long millisec)距离格林威治时间1970年1月1日millisec毫秒的时间
  • date.getTime()>date2.getTime()判断时间的前后

    格式化

    1
    2
    3
    Date now = new Date( );
    SimpleDateFormat ft = new SimpleDateFormat ("E yyyy.MM.dd 'at' hh:mm:ss a zzz");
    System.out.println("Current Date: " + ft.format(now));

解析格式

字母 日期 示例
G era标志 AD
y 1996 或 96
M July Jul 07
w 年中的第几周 27
W 月份中的第几周 2
D 年中的第几天 189
d 月中的第几天 10
F 月中的第几星期 2
E 星期中的第几天 Tuesday Tue
a Am/Pm 上下午 PM
H 今天的第几小时0-23 0
k 今天的第几小时1-24 24
K 上下午几点0-11 11
h 上下午几点1-12 12
m 小时中的分钟 30
s 分钟中的秒数 30
S 毫秒 978
z 时区 Pacific standard Time;PST;GMT-08:00
Z 时区 -0800

日期格式化成字符串

1
2
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sdf.format(date));

字符串转日期

把对应格式的字符串转解析成为日期,如果字符串不匹配规则(比指定的规则数据少)则报错。
并且需要捕获ParseException

1
Exception in thread "main" java.text.ParseException: Unparseable date: "2019-10-24"

解析

1
2
3
String string = "2016-10-24 21:59:06";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sdf.parse(string));

总结,1.先创建SimpleDateFormat(“规则”);2.format转换,parse解析。


文章字数:185,阅读全文大约需要1分钟

多个线程在竞争锁的过程中彼此之间形成堵塞的现象

排查

  1. jstack查看线程以及堆栈信息
  2. jconsole可视化工具,点击线程-检查死锁
  3. VisualVM强大的排查问题工具,可以查看jvm配置、堆快照、线程堆栈信息等

避免死锁

  1. 修正获取锁的顺序
    死锁的根本原因就是获取锁的顺序混乱,将获取锁的代码从业务逻辑中抽离,在公共的方法里获取锁

  2. 超时放弃
    synchronized没有获取到锁就不会放弃,但是Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException的方法,可以按照固定时长等待锁


文章字数:370,阅读全文大约需要1分钟

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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
* 模拟
*
* @author colin.cheng
* @since 1.0.0
*/
public class Mock {

/** 双击间隔 */
private int doubleClickInterval = 50;
/** 基础操作间隔 */
private int operationInterval = 100;
/** 随机操作间隔(0-?) 操作间隔 = 基础操作间隔 + 随机操作间隔 */
private int randomInterval = 50;

private Robot robot;
static Map<Character, Integer> shiftKeys = new HashMap<>();

public Mock () {
try {
robot = new Robot();
} catch (AWTException e) {
e.printStackTrace();
}
}

/**
* 是否启用随机间隔
* @return
*/
private boolean enableRandomInterval() {
return randomInterval > 0;
}

// 键盘

/**
* 输入字符,支持大小写字母,特殊字符。需要保证输入法处于小写状态下。
* @param str
*/
public void inputByStr(String str) {
for (char c : str.toCharArray()) {
if(shiftKeys.containsKey(c)) {
inputKeyCombination(KeyEvent.VK_SHIFT, shiftKeys.get(c));
} else if (Pattern.matches("[A-Z]", c + "")){
String s = ("" + c).toLowerCase();
inputKeyCombination(KeyEvent.VK_SHIFT, getCode(s.charAt(0)));
} else {
inputByCode(getCode(c));
}
}
}

/**
* 输入按键对应的KeyEventCode
* @param keycode KeyEvent
*/
public void inputByCode(int keycode) {
robot.keyPress(keycode);
robot.keyRelease(keycode);
robot.delay(getOperationInterval());
}

/**
* 获取f1-f12的code
* @param num
* @return
*/
public int getFXCode(int num) {
return 0x70 + num - 1;
}

/**
* 输入组合键
* @param keycodes KeyEvent.VK_xxx
*/
public void inputKeyCombination(int... keycodes) {
for (int keycode : keycodes) {
robot.keyPress(keycode);
}
for (int keycode : keycodes) {
robot.keyRelease(keycode);
}
robot.delay(getOperationInterval());
}

// 鼠标

/**
* 在指定位置单击左键
* @param x x轴位置
* @param y y轴位置
*/
public void click(Integer x, Integer y) {
robot.mouseMove(x, y);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(getOperationInterval());
}

/**
* 在指定位置单击右键
* @param x x轴位置
* @param y y轴位置
*/
public void clickRight(Integer x, Integer y) {
robot.mouseMove(x, y);
robot.mousePress(InputEvent.BUTTON3_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK);
robot.delay(getOperationInterval());
}

/**
* 在指定位置双击左键
* @param x x轴位置
* @param y y轴位置
*/
public void doubleClick(Integer x, Integer y) {
robot.mouseMove(x, y);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(doubleClickInterval);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(getOperationInterval());
}

/**
* 鼠标移动到指定位置
* @param x x轴位置
* @param y y轴位置
*/
public void move(Integer x, Integer y) {
robot.mouseMove(x, y);
robot.delay(getOperationInterval());
}

// 其它


/**
* 计算单次操作休眠时间
* @return
*/
private int getOperationInterval() {
if(enableRandomInterval()) {
double random = randomInterval * Math.random();
return operationInterval + (int)random;
}
return operationInterval;
}

public int getDoubleClickInterval() {
return doubleClickInterval;
}

public void setDoubleClickInterval(int doubleClickInterval) {
this.doubleClickInterval = doubleClickInterval;
}

public void setOperationInterval(int operationInterval) {
this.operationInterval = operationInterval;
}

public int getRandomInterval() {
return randomInterval;
}

public void setRandomInterval(int randomInterval) {
this.randomInterval = randomInterval;
}

/**
* 通过字符查找keyEventCode
* @param key
* @return
*/
private static int getCode(char key) {
return KeyEvent.getExtendedKeyCodeForChar(key);
}

static {
// 特殊字符转换
shiftKeys.put('~', getCode('`'));
shiftKeys.put('!', getCode('1'));
shiftKeys.put('@', getCode('2'));
shiftKeys.put('#', getCode('3'));
shiftKeys.put('$', getCode('4'));
shiftKeys.put('%', getCode('5'));
shiftKeys.put('^', getCode('6'));
shiftKeys.put('&', getCode('7'));
shiftKeys.put('*', getCode('8'));
shiftKeys.put('(', getCode('9'));
shiftKeys.put(')', getCode('0'));
shiftKeys.put('_', getCode('-'));
shiftKeys.put('+', getCode('='));
shiftKeys.put('{', getCode('['));
shiftKeys.put('}', getCode(']'));
shiftKeys.put('|', getCode('\\'));
shiftKeys.put(':', getCode(';'));
shiftKeys.put('"', getCode('\''));
shiftKeys.put('<', getCode(','));
shiftKeys.put('>', getCode('.'));
shiftKeys.put('?', getCode('/'));
}
}

使用例子

  • 自动抢票,循环点击抢票和取消弹窗。
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
public class MockTest {

// 每次操作间隔200-300毫秒,增加随机值,以防被屏蔽
final Mock mock = new Mock().setOperationInterval(200).setRandomInterval(100);

public static void main(String[] args) {
MockTest t = new MockTest();
// 五秒后开始执行,给用户切换窗口的时间
for (int i = 5; i > 0; i--) {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
}
}
// 循环点击 (380,181) 和 (790,641) 总共循环5秒
t.clickAndCancel(380,181,790,641, 5);
}

private void clickAndCancel(int clickX, int clickY, int cancelX, int cancelY, int times) {
System.out.println("start at [" + new Date() + "]");
final long begin = System.currentTimeMillis();
long current;
do {
current = System.currentTimeMillis();
mock.click(clickX, clickY);
mock.click(cancelX, cancelY);
} while (current - begin < TimeUnit.SECONDS.toMillis(times));
System.out.println("end at [" + new Date() + "]");
}
}