0%

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

类似迅雷的点击下载后弹出软件的功能

一、在windows注册表中注册自定义协议

1.1 注册表编辑器内容

保存为 .reg文件,运行即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Browser]
@=""
"URL Protocol"=""

[HKEY_CLASSES_ROOT\Browser\DefaultIcon]
@="D:\\test.exe"

[HKEY_CLASSES_ROOT\Browser\Shell]
@=""

[HKEY_CLASSES_ROOT\Browser\Shell\Open]
@=""

[HKEY_CLASSES_ROOT\Browser\Shell\Open\Command]
@="D:\\MyCode\\learnTest\\RegisterURI\\wrapper.exe %1"
  1. Browser:自定义的协议名称;
  2. D:\\test.exe :调用的exe文件路径;
  3. %1 :代表传入的参数;

二、调用exe

2.1浏览器调用

1
<a href="Browser:// aaaaaaa bbbbbbb"></a>

多个参数之间使用空格分开,最后一个参数的结尾会多一个/,程序需要自己处理


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

介绍

洗牌算法, Fisher–Yates随机置乱算法也被称做高纳德置乱算法, 作用是生成一个有限集合的随机排列,即打乱集合,并使每个排列的可能都相等。

步骤

  1. 从n个数组中选取低(1到n-1)中随机选择一个,并和n替换
  2. 再从(1到n-2)中随机选择一个,和n-2个替换

    直到全部替换完成

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
int[] arr = new int[10];
int i;

// 初始的有序数组
for (i = 0; i < 10; i++) {
arr[i] = i + 1;
}

// 费雪耶兹置乱算法
// 每次生成的随机交换位置:
for (i = arr.length - 1; i > 0; i--) {
// 随机数生成器,范围[0, i]
int rand = (new Random()).nextInt(i + 1);

int temp = arr[i];
arr[i] = arr[rand];
arr[rand] = temp;
}
}

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

一直分不清热部署和热加载,感觉上是一样的。其实不一样

热加载

  • 直接修改Java虚拟机中的字节码文件
  • 内存不会释放
  • 不安全,无法监控。
  • 俗称开发者模式,一般是开发时候使用

热部署

  • 重新打包war包
  • 释放原有内存
  • 不需要重新tomcat,不影响服务器中其它项目

相同点

  • 都能在改变代码后更新改变
  • 静态文件都能够重新加载

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

策略模式是将算法分离出来,在使用的时候指定使用的算法。避免大量使用if..else的设计模式。将原来由if…else判断之后获取实现类转换成实现类和值绑定,直接通过值获取类。省略了if…else

实现

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
class Client {
public static void main(String[] args) {
//选择一个具体策略
IStrategy strategy = new ConcreteStrategyA();
//来一个上下文环境
Context context = new Context(strategy);
//客户端直接让上下文环境执行算法
context.algorithm();
}

//抽象策略类 Strategy
interface IStrategy {
void algorithm();
}

//具体策略类 ConcreteStrategy
static class ConcreteStrategyA implements IStrategy {

@Override
public void algorithm() {
System.out.println("Strategy A");
}
}

//具体策略类 ConcreteStrategy
static class ConcreteStrategyB implements IStrategy {

@Override
public void algorithm() {
System.out.println("Strategy B");
}
}

//上下文环境
static class Context {
private IStrategy mStrategy;

public Context(IStrategy strategy) {
this.mStrategy = strategy;
}

public void algorithm() {
this.mStrategy.algorithm();
}
}
}

基于Spring的应用

  1. 将策略类注册成为组件

  2. 通过组件名获取组件,获取策略时传入的策略名就是组件名。


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

本文主要是对上一篇文章简书文章转换成hexo文章的补充。上一次自动转换的代码创建时间固定是now,但是我希望能够和简书上的一样。这样就可以查看自己文章发布的大致历程。思路使用爬虫爬取信息,爬到的信息使用Map(文章名,日期)的形式保存。

环境

  1. jdk1.8
  2. 引入jar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/net.sourceforge.htmlunit/htmlunit -->
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>2.32</version>
<optional>true</optional>
</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
108
109
110
111
112
113
114
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.jsoup.nodes.Document;

/**
* @author colin.cheng
* @version V1.0
* @date Created In 9:29 2019/9/30
*/
public class jsTest {

static Map<String,String> countMap = new HashMap<>();

public static void main(String[] args) {
Map<String,String> urlMap = new HashMap<>(255);
String baseUrl = "https://www.jianshu.com/u/2eb26c0a6d3b?order_by=shared_at&page=";
getTime(baseUrl,urlMap);
//爬取结果 Map(title,time)
urlMap.forEach((k,v)->{
try {
System.out.println(k + " = " + v);
}catch (Exception e){
e.printStackTrace();
}
});
}


private static void getTime(String baseUrl,Map urlMap){
try{
// Map<String,String> urlMap = new HashMap<>(255);
// String baseUrl = "https://www.jianshu.com/u/2eb26c0a6d3b?order_by=shared_at&page=";
String currentUrl = "";
int pageIndex=0;
do{
currentUrl = baseUrl+pageIndex;
pageIndex++;
System.out.println("currentUrl = " + currentUrl);
}while (getJianShuArticleUrlList(currentUrl,urlMap));
// System.out.println("pageIndex = " + pageIndex);
}catch (Exception e){
e.printStackTrace();
}
}

public static boolean getJianShuArticleUrlList(String oneUrl, Map<String,String> urlMap){
boolean res = true;
// 模拟浏览器操作
WebClient webClient = new WebClient();
// 关闭css代码功能
webClient.getOptions().setThrowExceptionOnScriptError(false);
webClient.getOptions().setCssEnabled(false);
// 如若有可能找不到文件js则加上这句代码
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
// 获取第一级网页html
try{
HtmlPage page = webClient.getPage(oneUrl);
if(page==null||!page.isHtmlPage()){
return false;
}
Document doc = Jsoup.parse(page.asXml());
Elements dates = doc.select("[class=note-list] li");
Iterator var2 = dates.iterator();
Element element = null;
Element titleElement = null;
Element timeElement = null;
while(var2.hasNext()) {
element = (Element)var2.next();
titleElement = element.select("[class=title]").first();
timeElement = element.select("[class=time]").first();
//爬取的时候发现有一些数据的样式发生了改变,这里设置成如果找不到就使用另一种样式。
if(timeElement==null){
timeElement = element.select("[data-type=share_note]").first();
}
if(timeElement!=null&&titleElement!=null){
//爬取的时候发现有一些数据的样式发生了改变,这里设置成如果找不到就使用另一种样式。
String tag = "data-shared-at";
if(StringUtils.isBlank(timeElement.attr(tag))){
tag = "data-datetime";
}
if(StringUtils.isNotBlank(titleElement.text())&&StringUtils.isNotBlank(timeElement.attr(tag))){
//爬取时发现有数据重复的情况,两页相连的数据会重复。这里判断出现次数,到达三次结束爬取。
if(StringUtils.isNotBlank(urlMap.get(titleElement.text()))){
int count = countMap.get(titleElement.text())==null?1:Integer.valueOf(countMap.get(titleElement.text())).intValue();
count++;
countMap.put(titleElement.text(),count+"");
if(count==3){
return false;
}
}
//时间格式转换一下
String dateStr = timeElement.attr(tag);
dateStr = dateStr.replace("T"," ");
dateStr = dateStr.replace("+08:00","");
urlMap.put(titleElement.text(),dateStr);
}
}else {
return false;
}
}
}catch (Exception e){
e.printStackTrace();
res=false;
}
return res;
}
}

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

简书的文章需要搬运到hexo上,手动太麻烦。于是用Java写了一个自动转换的工具。文章不在简书上也可以,转换规则为 总目录(root)->子目录(分类名)->文章(文件名就是标题)

效果可以看我的博客

  1. 环境jdk1.8
  2. 简书设置里的打包下载全部文章,解压。解压后目录就是源文件目录root
  3. 根据文集自动标记hexo的分类
  4. 增加字数统计,推荐阅读时间功能。(英文单词和中文的个数)
  5. 我自己是写java的,文件名出现@字符,统一替换成了注解。(文件名即标题)
    如果不需要可以删除.replaceAll("@","注解")
  6. 使用字节流,没有用字符流。懒得换了,不影响使用。
    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
    import java.io.*;
    import java.nio.file.*;
    import java.nio.file.attribute.BasicFileAttributes;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;

    /**
    * @author colin.cheng
    * @version V1.0
    * @date Created In 17:54 2019/6/26
    */
    public class test1 {
    //文章计数器
    public static int count = 0;
    //字数计数器
    public static int charCount=0;
    public static Pattern p = Pattern.compile("[\u4e00-\u9fa5]|[a-zA-Z]+|[1-9]|[,.,。??]", Pattern.CASE_INSENSITIVE);
    public static void main(String[] args)throws Exception {
    //源文件根目录root
    final String rootPath = "G:\\chromedownload\\user-xxx\\mynote";
    //转换后文章存放目录
    final String toPathAdd = "F:\\hexoBlog\\greatcolin\\source\\_posts";
    Path root = Paths.get(rootPath);
    Path target = Paths.get(toPathAdd);
    //存放目录则不存在新建
    if(!Files.exists(target)){
    Files.createDirectories(target);
    }
    Files.walkFileTree(root,new SimpleFileVisitor<Path>(){
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
    int oneNoteCount = 0;
    String fileName = file.getFileName().toString().replaceAll("@","注解");
    //创建转换后的文件
    Path toPath = Paths.get(toPathAdd+"\\"+fileName);
    if(!Files.exists(toPath)){
    toPath = Files.createFile(toPath);
    System.out.println("create"+toPath);
    }
    FileOutputStream fop;
    if(Files.exists(toPath)){
    fop = new FileOutputStream(toPath.toFile());
    }else {
    System.err.println("不存在"+toPath);
    return null;
    }
    FileInputStream fip = new FileInputStream(file.toFile());
    //头
    String noteClass = file.getParent().toString();
    String[] className = noteClass.split("\\\\");
    //根据文件夹名称获取分类名
    noteClass = className[className.length-1];
    //创建头部,写入
    byte[] headBuf = createHead(fileName,noteClass);
    fop.write(headBuf);
    //读取文件,写入
    int read = 0;
    byte[] buf = new byte[1024];
    while((read=fip.read(buf))>=0){
    String a = new String(buf).replaceAll("[^0-9a-zA-Z\u4e00-\u9fa5]+","");
    int count = countNote(a);
    charCount += count;
    oneNoteCount += count;
    }
    int time = Math.ceil(oneNoteCount/250)>=1?(int)Math.ceil(oneNoteCount/250):1;
    fop.write(new String("文章字数:"+oneNoteCount+",阅读全文大约需要"+time+"分钟\r\n").getBytes());
    fip.close();
    fip = new FileInputStream(file.toFile());
    while((read=fip.read(buf))>=0){
    fop.write(buf,0,read);
    }
    fip.close();
    fop.close();
    System.out.println(fileName+" 转换成功");
    count++;
    return FileVisitResult.CONTINUE;
    }
    });
    System.out.println("执行完成,总共转换"+count+"篇文章。中英文总字/词数达到"+charCount);
    }

    /**
    * 生成文章头部信息
    * @param title 标题
    * @param noteClass 分类
    * @return
    */
    public static byte[] createHead(String title,String noteClass){
    StringBuilder headStr = new StringBuilder("---\r\n");
    title = title.replaceAll(".md","");
    headStr.append("title: "+title+"\r\n");
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    headStr.append("date: "+sdf.format(new Date())+"\r\n");
    headStr.append("categories: "+noteClass+"\r\n");
    headStr.append("tags: \r\n");
    headStr.append("---\r\n");
    return headStr.toString().getBytes();
    }

    /**
    * 计算字数
    * @param note
    * @return
    */
    public static int countNote(String note){
    Matcher m = p.matcher(note);
    int count = 0;
    while (m.find()) {
    count++;
    }
    return count;
    }
    }

此文中的文件创建时间是固定的,我另写了一个工具可以获取文章的发布日期。简书文章获取时间


文章字数:543,阅读全文大约需要2分钟

尽量全值匹配

  • 如果使用了符合索引,查询的列是索引列,不要使用select * 使用索引列。
  • 不要使用select *,避免查询不需要的列,避免表结构增加列对于查询的影响,同时有助于引擎的索引优化。

最佳左前缀法则

  • 创建了一个复合索引key(a, b, c)时,查询的where语句一定要出现a, 且只有a, b, c同时出现在where的条件中时,这个索引才是最高效的。

索引不要有任何操作

  • 各种函数之类的,不要在索引列上使用

范围条件放最后

  • 创建联合索引时,between < =等访问查询,如果范围查询的列是a则创建的索引中a应该是最后一个如key(b, c, a)而不是key(a, b, c)

like查询

  • 尽量使用关键字%而不是使用%关键字,后者会导致索引失效

搜索字符类型

  • 字符类型需要加引号,否则会使索引失效。使用where a='1'而不是where a=1

文章字数:751,阅读全文大约需要3分钟

一、基本概念

1.CPU核心数和线程数关系

  • 一般为1:1关系
  • 英特尔超线程技术可以实现1个核心数2线程
  1. CPU时间片轮转机制
  • RR调度,上线文切换(操作系统处理,程序无关)
  1. 进程和线程
    进程:程序资源分配的最小单位,进程下有多个线程。
    线程:cpu调度的最小单位,需要依附于进程。需要使用进程的资源

  2. 并行和并发
    并发:与时间相关,一定时间内可处理的能力
    并行:程序设计的结构,同一时刻可以处理能力(瞬时)

  3. 高并发编程意义

  • 无关操作可以不用等待
  • 模块化,异步化
  • 可能造成冲突,死锁,资源分配不足等问题。

二、java线程

2.1天然多线程,无需手动开启既有一些线程

1
2
// 虚拟机线程管理接口
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
1
2
3
4
5
Attach Listener
Signal Dispatcher
Finalizer
Reference Handler
main

2.2 线程创建

  1. Thread类(自带的)
  2. Runnable接口(自带的)
  3. Callable有返回值的Runnable,需要用FutureTask包装才能实现一个完整的Runnable并被Thread运行。并且提供了接受返回值的方法(没有结果会阻塞)

2.3 线程退出

  1. 自然执行完
  2. 一场
  3. stop() resume()等方法(不推荐使用,无法释放线程资源已废弃)

2.4线程中断(协作式)

  • myThread.interrupt()中断线程(不是强制型,知识中断通知)设置线程中断标志
  • Thread.currentThread().isInterrupted()判断是否处于中断状态,读取中断标志位。(需要线程内部自己读取状态,实现中断)
  • Thread.interrupted()中断标志设置为false
  • 线程抛出InterruptedException时会复位线程中断位sleepjoin等阻塞的方法都会抛出。需要在catch里重新设置中断标志位,再次中断
  • 线程中阻塞时设置了interrupt()则会抛出InterruptedException异常。即强制通知线程状态的改变。

2.5线程状态

  • 新建,还没有start()
  • 就绪,start()之后,等待操作系统分配运行资源(join方法可以获取执行权)。
    yield()方法可以让线程进入就绪状态,让出当前的运行资源,重写加入等待队列
  • 阻塞,wait()方法进入阻塞,notify()notifyAll()接触阻塞。sleep()休眠固定时间
  • 死亡,stop()setDeamon(),运行结束

2.6线程优先级

Thread.setPriority,1-10的值,不可靠。操作系统不一定会按照此值执行。

2.7守护线程

  1. 和主线程共同退出
  2. 设置setDeamon(true)
  3. 守护线程中try...finally不一定生效finally中不一定会执行,因为守护线程可能回合父级线程一起结束了。

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

一、等待和通知(Object方法)

1.1等待和通知

  • wait()等待方
  1. 获取对象的锁
  2. 循环里判断条件是否满足,不满足调用wait()方法
  3. 满足执行业务
  • notify/notifyAll通知方
  1. 获取对象锁
  2. 改变对象条件
  3. 通知所有等待对象的线程
  • wait()用于休眠当前线程,需要调用同对象的notify()或者notifyAll()方法唤醒。
  • wait()notify()notifyAll()需要先获取到线程的锁,即synchronized包裹。
  • notify()唤醒一个线程,notifyAll()唤醒所有。建议都使用nofityAll()方法,内部用while()循环判断是否满足条件,不满足继续wait()。否则会出现信号丢失情况。

1.2利用等待通知实现超时连接池

  • 连接池
    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
    // 保存链接的地方
    private static LinkdList<Connection> pool = new LinkedList<>();

    // 构造方法里初始化若干数量的数据库链接
    public DBPool(int initalSize) {
    if(initalSize > 0) {
    for(int i = 0; i < initalSize; i++) {
    pool.addLast(SqlConnectServer.fetchConnection());
    }
    }
    }

    // 获取线程,设置超时时间为mills,超时返回null。
    public Connection fetchConn(long mills) {
    synchronized(pool) {
    // 不超时,等到有为止
    if (mills < 0) {
    while(pool.isEmpty()) {
    pool.wait();
    }
    return pool.removeFirst();
    } else{
    // 什么时候超时
    long overtime = System.currentTimeMillis() + mills;
    // 剩余时间
    long remain = mills;
    // 池为空且还有等待时间
    while(pool.isEmpty() && remain > 0) {
    pool.wait(remain);
    remain = overtime - System.currentTimeMillis();
    }
    Connection result = null;
    if(!pool.isEmpty()) {
    result = pool.removeFirst();
    }
    return result;
    }
    }
    }

    public void releseConn(Connection conn) {
    if(conn != null) {
    synchronized(pool) {
    pool.addLast(conn);
    pool.notifyAll();
    }
    }
    }

二、join方法(Thread的)

功能

  1. 线程A执行了线程B的join()方法,则线程A必须等线程B执行之后才能继续自己的工作。

三、yield()、sleep()、wait()、notify()等对锁的影响

  1. yield()交出执行权,不释放持有的锁
  2. sleep()休眠,不释放持有的锁
  3. wait()方法执行前必须要持有锁,调用了wait()方法之后,自动释放锁。被唤醒时自动持有锁
  4. notify()方法执行前必须要持有锁,调用时本身不会释放锁。所以一般在synchroized代码块最后。

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

synchronized

  • 对象锁,锁定具体对象
  • 类锁,锁定整个class

volatile

  • 最轻量级的同步机制
  • 只能保证数据是从主内存中读取的,而不是当前cpu内存的缓存数据。
  • 一个线程写,多个线程读,只能保证可见性

threadLocal

  • 线程直接独立的变量
  • 线程之间不会冲突
  • 一个绑定在线程上的Map
  • 每个变量每个线程都有一个自己的变量,所以最好是小的值。