开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

排序算法那么多,我该怎么选

发表于 2021-10-30

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本篇文章并不是讲解各排序算法的具体的实现,而是讲解排序算法的核心特性。通过理解各排序算法的核心特性,从而在具体的场景中能选择合适的排序算法。

各排序算法的具体实现和学习可参考:

《搞懂基本排序算法》

《大排序算法全面解析-Java实现》

《十大经典排序算法总结(JavaScript描述)》

冒泡排序和选择排序

冒泡排序和选择排序是最好理解、最容易的实现的两种排序算法。

冒泡排序:通过交换相邻两个元素,将最大元素升至最后位置(像水中的冒泡)

选择排序:遍历所有元素,选择最小元素,与未排好序的元素的一个元素进行交换。以此遍历n趟,最终达到所有元素有序。

适用场景:相对其他排序算法,除了实现简单、容易理解外,并没有任何任何优势。所以在实际开发中,并不推荐这两种排序算法,除非你实在写不出其他排序算法了。

插入排序

插入排序也是一种非常简单的排序,核心就是每次将某个元素插入到合适的位置,而该元素之后的所有元素都需要往后挪一个位置。在生活中玩扑克牌时,想要对扑克牌进行排序,我们通常使用的也是「插入」排序,比如大王,就插入到最前面。

具体实现:

1
2
3
4
5
6
7
ini复制代码public void sort(Comparable[] data) {
for (int i = 1; i < data.length; i++) {
for (int j = i; j - 1 > 0 && less(data[j], data[j - 1]); j--) {
exch(data, j, j - 1);
}
}
}

说明:

  1. less(arg1, agr2) 方法为比较arg1,arg2大小,如果arg1小于arg2则返回true,否则返回false
  2. exch(data, j, j - 1) 为交换数组data中j和j-1位置。

适用场景:大部分数据距离它正确的位置很近,近乎有序,如原始数据根据订单完成时间排序,现在需要根据订单发起时间排序。这样,每个元素就能很快挪到正确位置,所以在这种场景中,其效率远高于其他元素。

快速排序

“快速排序是最快的通用排序算法”——《算法 第4版》

快速排序算法原理很简单,将数据分为两部分,其中一部分所有元素小于某个值,另外一部分所有元素大于某个值。然后再将这两部分的每一部分数据按刚才原则进行划分,以此类推,直至每部分只有一个元素。最终,数据就成为有序数据。

核心部分:

  1. 找出数据的标定点pivot,并将数据以标定点为间隔,分为两部分。

实现过程:

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
kotlin复制代码/**
* 将data数组数据分成两部分,并返回划分位置j,使得左边部分小于data[j],右边部分大于data[j]
* @param data
* @param lo
* @param hi
* @return
*/
private int partition(Comparable[] data, int lo, int hi) {
// 将第一个元素选为标定点
Comparable pivot = data[lo];
// 使用双指针遍历数组,使得数组左边部分小于pivot,右边部分大于pivot
// i 表示 较小部分的最后一个元素(左侧);j 表示 较大部分的第一个元素(右侧)。
// 初始值设为lo和hi+1,即可表示未开始划分
int i = lo;
int j = hi + 1;
while (true) {
// 从左侧开始寻找第一个比pivot大或等于的元素
while (less(data[++i], pivot)) {
// 已遍历到右侧
if (i >= hi) {
break;
}
}
// 从右侧开始寻找第一个比pivot小的元素
while (less(pivot, data[--j])) {
// 已遍历到右侧
if (j <= lo) {
break;
}
}
if (j <= i) {
break;
}
exch(data, i, j);
}
exch(data, lo, j);
return j;
}
  1. 递归调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码/**
* 对数组data[lo,hi]部分进行快速排序
* @param data
* @param lo
* @param hi
*/
private void quickSort(Comparable[] data, int lo, int hi) {
// 不需要再排了
if (lo >= hi) {
return;
}
int partition = partition(data, lo, hi);
quickSort(data, lo, partition - 1);
quickSort(data, partition + 1, hi);
}

适用场景:在大多数实际情况中,都适合使用快速排序。(但它不是稳定的排序算法,稳定是指:两个相等元素,排序前先后顺序与排序后先后顺序一致。比如元素a和元素b相等,排序前a在b前面,若排序后,a也b在前面,则说明其排序算法是稳定的)。

当包含大量重复的元素适合三路快排,三路快排是快速排序的优化版本,其核心思想与普通快速排序一致。但它多出「一路」为相等元素大小的处理。普通快速排序只找出一个标定点,即使其他元素与标定点相等,也还是会放入其余部分再次进行递归快速排序。

计数排序

计数排序核心原理:通过原数据取值范围大小的辅助数组,记录每个元素的出现的次数,然后再根据记录结果依次赋值。比如,对全省高考成绩进行排序。分数取值范围为0到750,记录每一分数的人数。最终在根据记录结果,依次重新赋值各分数已到达排序效果。

适合场景:数据的取值范围非常有限,比如对学生成绩排序、

归并排序

核心特性:将已排好序的两部分进行归并(合在一起)。

归并实现代码:

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
ini复制代码/**
* 归并data[lo, hi]数组,data[lo,mid]有序数据,data[mid+1,hi]有序
* @param data 原始数据
* @param lo
* @param mid
* @param hi
*/
private void merge(Comparable[] data, int lo, int mid, int hi) {
for (int k = lo; k <= hi; k++) {
aux[k] = data[k];
}
int i = lo;
int j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i > mid) {
data[k] = aux[j++];
} else if (j > hi) {
data[k] = aux[i++];
} else if (less(aux[i], aux[j])) {
data[k] = aux[i++];
} else {
data[k] = aux[j++];
}
}
}

归并排序有两种实现方式:递归方式和非递归方式

递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
scss复制代码/**
* 将数组data[lo, hi]排序(递归方式)
* @param data
* @param lo
* @param hi
*/
private void merge(Comparable[] data, int lo, int hi) {
// 表示单个元素已有序
if (lo >= hi) {
return;
}
int mid = lo + (hi - lo) / 2;
// 将data[lo,mid]部分排序
merge(data, lo, mid);
// 将data[mid+1, hi]部分排序
merge(data, mid + 1, hi);
// 将排好序部部分归并
merge(data, lo, mid, hi);
}

非递归

1
2
3
4
5
6
7
8
9
10
11
css复制代码/**
* 非递归的方式
* @param data
*/
private void mergeBU(Comparable[] data) {
for (int i = 1; i < data.length; i = i * 2) {
for (int j = 0; j < data.length - i; j = j + i * 2) {
merge(data, j, j + i - 1, Math.min(j + 2 * i - 1, data.length - 1));
}
}
}

堆排序

堆排序,借助堆特性(堆中某个结点的值总是不大于或不小于其父结点的值)获取最大元素,然后再使其变为堆。

堆的两个核心方法:

上浮(swim):添加元素时,新元素上浮,使数据保持堆结构不变。

下沉(sink):移除元素堆顶元素,把最后元素放置堆顶,再使其下沉到某个位置,保持堆结构不变

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
ini复制代码/**
* 元素上升,调整成堆
* @param data
* @param k
*/
private void swim(Comparable[] data, int k) {
while (k > 0 && less(data[(k - 1) / 2], data[k])) {
exch(data, (k - 1) / 2, k);
k = (k - 1) / 2;
}
}

/**
* 元素下层,调整成堆
* @param data
* @param k
* @param length
*/
private void sink(Comparable[] data, int k, int length) {
while ((k + 1) * 2 <= length) {
int j = (k + 1) * 2 - 1;
if (length > j + 1 && less(data[j],data[j + 1])) {
j++;
}
// 节点的最大子节点小于当前节点,则不需要
if (less(data[j], data[k])) {
break;
}
exch(data, k, j);
k = j;
}
}

堆排序的实现:将数据变为堆结构,再根据堆特性将元素排序。

1
2
3
4
5
6
7
8
9
10
11
scss复制代码public void sort(Comparable[] data) {
// 将数据组装成堆
for (int i = data.length / 2 - 1; i >= 0; i--) {
sink(data, i, data.length);
}
for (int i = data.length - 1; i > 0; i--) {
// 将堆顶元素与最后一个元素交换
exch(data, 0, i);
sink(data, 0, i);
}
}

总结

追求更快的性能是每个优秀程序员的必备素质。当需要对数据进行排序时,要根据实际情况来选择最合适的排序算法,而不是简单的「快速排序」走遍天下!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

RocketMQ 安装及快速入门

发表于 2021-10-29

安装条件

64位操作系统,建议使用 Linux / Unix / Mac

64位JDK 1.8+

Maven 3.2.x

下载和构建

从www.apache.org/dyn/closer.…下载源码版本,执行以下命令来解压源码并构建二进制文件。

1
2
3
4
shell复制代码  > unzip rocketmq-all-4.9.2-source-release.zip
> cd rocketmq-all-4.9.2/
> mvn -Prelease-all -DskipTests clean install -U
> cd distribution/target/rocketmq-4.9.2/rocketmq-4.9.2

1. 启动mqnamesrv

启动命令
1
bash复制代码nohup sh bin/mqnamesrv &

或者加上输出日志

1
bash复制代码nohup sh bin/mqnamesrv  >  ~/logs/rocketmq/logs/namesrv.log &
停止命令
1
bash复制代码nohup sh bin/mqshutdown namesrv

2. 启动broker

修改broker.conf文件

新增一行,填写安装的rocketmq机器的ip地址

1
ini复制代码brokerIP1=XX.XXX.XXX.XXX

注意,下面的ip地址以及端口请填写你安装的rocketmq机器的ip地址及端口。

1
bash复制代码nohup sh bin/mqbroker -c conf/broker.conf  -n xxx.xx.xx.xx:9876 autoCreateTopicEnable=true  >  ~/logs/rocketmqlogs/broker.log &

autoCreateTopicEnable=true 是允许自动创建topic

停止命令

1
bash复制代码nohup sh bin/mqshutdown broker

3. 发送消息

1
2
3
bash复制代码export NAMESRV_ADDR=localhost:9876
​
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer

4. 接受消息

1
bash复制代码sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

啥,你居然不知道MySQL主从复制,那还不赶紧来补一补 主从

发表于 2021-10-29

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

MySQL主从复制可以说在实际的生产应用都会有一套主从机制来保证业务数据的稳定,防止出现故障导致生产事故。
在了解主从复制原理之前,先带大家手把手完成MySQL主从配置,知己知彼,方能百战百胜,Let’s go….

主从 服务器配置

第一步,主服务器配置

  1. 首先设置my.cnf配置文件

这一步就是要启用bnlog日志,并且设置全局唯一server id

1
2
3
4
5
6
7
8
9
10
ini复制代码
# vim /etc/my.cnf

[mysqld]  

server_id=51           //server_id

log-bin=master51        //日志名

:wq
  1. 然后进行用户授权

授权指定从服务器复制数据的权限,用户名自定义、客户端地址使用% 或 只指定 从服务器的地址 都可以、只给复制数据的权限即可。

#mysql -uroot -p

mysql> grant replication slave on . to repluser@”%” identified by”123qqq…A”;

mysql>quit;

第二步从服务器配置

配置my.cnf文件

指定server_id,不能与主服务器相同

1
2
3
4
5
6
7
8
ini复制代码
# vim /etc/my.cnf

[mysqld]  

server_id=52          //server_id

:wq
  1. 指定主服务器信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ini复制代码
数据库管理员root本机登录,指定主服务器信息,其中日志文件名和偏移量 写allbak.sql文件记录的。

# mysql -uroot –p密码     //管理员root 本机登录

mysql> show slave status;  //查看状态信息,还不是从服务器

Empty set (0.00 sec)

mysql> change   master  to //指定主服务器   

-> master_host=“192.168.4.51”,                 //主服务器ip地址 

-> master_user=“repluser”,                        //主服务器授权用户 

-> master_password=“123qqq…A”,            //主服务器授权用户密码   

-> master_log_file=“master51-bin.000001”,//主服务器日志文件   

-> master_log_pos=441;                  //主服务器日志偏移量
  1. 开启主从复制命令

start slave

经过上面的一顿骚操作,主从就搭好了,当然一般在公司基本都是会有专门的人负责MySQL服务的搭建,但作为当代最优秀的程序员,还需要自己动手实现一波,实践是检验真理的唯一标准。

主从复制原理

MySQL主从复制是指数据可以从一个MySQL服务器主节点复制到一个或多个从节点。

MySQL默认采用异步复制方式,从节点可以复制主数据库中所有数据库或者特定的数据库,或者特定的表。

MySQL主从复制主要用途

进行读写分离,主库负责写入,从库负责读取

数据实时备份,当系统某个节点发送故障,可以方便故障切换。

架构扩展:随着系统中业务访问量增大,如果单机部署数据库,就会导致I/O访问频率过高。通过主从复制,增加多个数据存储节点,将负载分布在多个节点,降低磁盘IO方法频率,提高单台机器IO性能。

MySQL主从形式

  • 一主一从,一主多从
  • 多主一从:多主一从可以将多个mysql数据库备份到一台存储性能比较好的服务器上
  • 双主复制:双主复制,也就是互做主从复制,每个master即是master,也是另外一台服务器的slave.
  • 级联复制:部分slave的数据同步不连接主节点,而是连接从节点。其他从节点作为二级或者三级与从节点连接,这样不仅可以缓解主节点的压力,并且对数据一致性没有负面影响。

复制如何工作

  • 在主库中会把数据记录到二进制文件(bin log )中
  • 从库将主库上的日志复制到自己的中继日志(Relay Log)中
  • 备库读取中继日志中的事件,将其重放到备库数据之上。

image.png

image.png
通过图可以看打主从复制的工作流程

连接主库,主库会分配dump 线程和从库 IO线程通信

从库IO线程请求新日志,主库dump线程接收请求,截取二进制日志,返回给从库 IO线程

从库IO将binlog最终写入到 relay log中,并将读取到的bin log文件名和位置存并更新到Master.info文件中,一边下一次读取的时候能够清楚的告诉主节点我需要从哪个位置开始往后复制。

SQL线程读取relay.info,获取上次执行到的位置点

SQL线程向后执行新的relay-log,再次更新realy.info

主从复制方式

主从复制有三种方式:基于SQL语句的复制,基于行的复制,混合模式复制

基于SQL语句复制,就是复制sql语句在bin log中,mysql 5.1.4 及之前的版本都是使用的这种复制格式。优点是只需要记录会修改数据的sql语句到binlog中,减少了binlog日志量,节约I/0,提高性能。

基于行的复制,是mysql 主节点将sql语句分解为基于行更改的语句并记录在bin log中,也就是只记录哪条数据被修改,修改成什么样。优点是不会出现某些特定情况下的存储过程、或者函数、或者trigger的调用或者触发无法被正确复制的问题。缺点是会产生大量的日志。

混合模式复制是以上两种复制模式的混合,对于一般的复制使用记录sql语句的方式,对于sql语句模式无法复制的操作则使用行模式来保存,mysql会根据执行的sql语句选择日志保存方式。

主从复制get到了没,如果还意犹未尽的话,就关注小蛋吧,持续输出干货,奥利给

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

野生程序员养成手册

发表于 2021-10-29

个人经历

  1. 本人是普本机械类跨行的程序员,借机会向大家分享一下个人经历,以及自己遇到的一些问题,各位看官拿好瓜子,坐在凳子上待我轻轻道来.

程序员.jpg

Java入门篇

  1. 前期以线上的视频为主,它入门最快(推荐B站黑马程序员,或者拼嘻嘻上买个教学视频)
  2. 如果没有一点基础的话,可能会和听天书一样,或者听着会,但是实际动手时还是无从下手(正常情况),硬着头皮往下走就是啦
  3. 看完视频以后最好边练习边记笔记
  4. 从开始的JavaEE基础,再到集合,io流,反射,redis缓存,数据库,Mybatis,Spring,SpringMvc,前端基础,(网络编程,数据结构和算法,设计模式,多线程,SpringBoot,SpringCloud,消息队列等等)

Java实战篇

  1. 当学完SSM以后就要试着自己做项目了,推荐若伊框架
  2. 等掌握框架精髓的时候就可以准备面试了

常见问题

  1. 不知道自己适不适合做程序员
    1. 程序员待遇相对高,但是随着入行的人数越来多,竞争还是有的,所以还是做好心理准备
    2. 需要较强的逻辑思维能力
    3. 没有其它好的选择,也对编程感兴趣(当时我就是),可以试一下
  2. 没有时间学习怎么办
    1. 其实每个人都有同样的问题,但是只要愿意挤总是有的
    2. 我都是每天下班以后抽空学的,每天平均两个小时以上
  3. 学习周期多久
    1. 学无止境,找到初级程序员工作,从开始到找到工作全职的话半年左右,业余学习的话可能需要一年左右
  4. 学不会想放弃怎么办
    1. 我也有过好几次这样的想法,但是自己选择的何不逼自己一把呐
  5. 有没有什么捷径
    1. 没有

结尾

  1. 大家有好的方法或相似的经历,欢迎留言,评论.

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

你会几种语言编写 Hello World ?

发表于 2021-10-29

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

Hello world的由来

Hello, World最早是由 Brian Kernighan 创建的。1978年,Brian Kernighan写了一本名叫《C程序设计语言》的编程书,在程序员中广为流传。他在这本书中第一次引用的Hello World程序,源自他在1973年编写的一部讲授 B语言的编程教程:

1
2
3
4
5
6
7
C复制代码main (){
extrn a,b,c;
putchar (a); putchar (b); putchar (c); putchar ('!*n');
}
a 'hell';
b 'o, w';
c 'orld';

但是非常不幸的是,当 Forbes India 杂志采访他的时候,他自己对这段传奇故事中一些记忆已经有点儿模糊了。当他被问及为什么选择『Hello, World!』时,他回答说,『我只记得,我好像看过一幅漫画,讲述一枚鸡蛋和一只小鸡的故事,在那副漫画中,小鸡说了一句‘Hello World’』。

原文:blog.hackerrank.com/the-history…

多种语言实现 Hello World

Hello World,几乎是程序猿学习各种语言的第一个程序

C 语言

1
2
3
4
5
6
c复制代码#include "stdio.h"

int main(){
printf("Hello World");
return 0;
}

我的第一个Hello World 程序是用C语言编写的,还记得你第一次编写hello world程序使用是什么语言吗?

Java

1
2
3
4
5
6
7
java复制代码public class HelloWorld {  

public static void main(String[] args){
System.out.println("Hello,World");
}

}

Html

1
2
3
4
5
6
7
html复制代码<!DOCTYPE html>  
<html>
<title>我的第一个网页</title>
<body>
<p>Hello World</p>
</body>
</html>

可以直接把代码复制到记事本中然后保存,然后把文件的后缀名改成html就可以直接用浏览器打开看效果

JavaScript

1
2
3
4
5
javascript复制代码<script>
console.log('Hello World'); # 浏览器控制台打印

alert("Hello World"); # 浏览器对话框形式显示
</script>

随便用浏览器打开一个页面,然后按下F12键,就会显示出浏览器自带的调试工具,在Console界面就可以编写JavaScript脚本语言

注意: 有些电脑F12键有其他功能,会与浏览器的快捷键有冲突,一般的电脑都会有一个fn键我们可以fn + F12键打开浏览器的调试工具,如果这样还不行,直接在浏览器的某个页面鼠标右击点击检查即可

Python

1
2
3
python复制代码print "Hello World"	 # python 2.x

print("Hello World") # python 3.x

python编写的是不是很简单

Sql

1
2
3
4
5
6
7
sql复制代码mysql> select 'Hello World';
+-------------+
| Hello World |
+-------------+
| Hello World |
+-------------+
1 row in set (0.03 sec)

没想到吧Sql语句的select还可以这样用,你们还可以去试试select算术运算

在这里推荐一个W3Cschool的各种语言在线开发编译IDE,这样要调试其他语言代码的时候就方便多了

在线开发编译IDE

尾语

✍ 用 Code 谱写世界,让生活更有趣。❤️

✍ 万水千山总是情,点赞再走行不行。❤️

✍ 码字不易,还望各位大侠多多支持。❤️

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

屡次跳槽失败!经阿里P8大佬的指点迷津,转身入职字节!

发表于 2021-10-29

前言

一转眼,2021年马上就要过完了,金九银十也已经过去了,现在的你工作不如意,薪资达不到自己预期的话,是要等年后再跳槽呢还是现在跳呢?

现在跳的话,还没有下定决心,可能也有面试过两三家,但是回答一些专业问题上会卡壳,基本上算凉凉了。也清楚的知道现在自己的不足,还需要一些时间继续深造才能进想去的大厂。但是如果现在不跳的话,上班也不顺心,薪资又低,完全影响到了自己的生活和情绪。关键是,年后跳的话就得到三四月了,离现在还有那么久,真的是越想越焦虑… …

以上是朋友的吐槽,你是否也有相同的心事呢?其实我觉得要跳就趁现在,等到年后的话还有那么长的时间,天天上班受气,更别说提升自己再去学其他的技能了,一心扑在工作上,付出和结果不成正比的话,长时间自己还会陷入自我怀疑。那现在跳槽的话,自己储备的知识不够啊怎么办?为了帮朋友解决这个问题,我这边从阿里P8架构师这位大佬那里获得了他的心血,他整理了一年的心血啊!全部文档266页,10W字!本身是为了给我的朋友,安抚他现在低沉的情绪,结果他真的在两个月后拿到了字节跳动的offer! 大佬非常自豪,并本着乐于助人的心态,所以让我拿出来和大家一起分享。现准备这些全套的面试资料,助你考前突破,早日拿到心仪的offer !早日入职在年前把新工作稳定下去,过个好年。

需要完整版文档资料的点击此处即可免费获取。

Redis篇

分布式篇

以上redis及分布式,还有网络协议,spring源码,微服务,数据库等具体分类由于文件过于庞大,图太多(总共266页),所以我就不一一截图了,还望大家海涵。有需要完整版文档资料的点击此处即可免费获取。

每个面试题细分的小点都希望你们拿到文档后可以认真的看看,对你们的面试会有极大的帮助,最后祝你们早日涨薪!准备面试的祝你们早日入职心仪的大厂,offer多多!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

关于汉字转拼音的Pinyin4j工具使用 1 Pinyin4

发表于 2021-10-29

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

关于汉字转为拼音的相关实现,通常使用Pinyin4j框架,整理一下Pinyin4j的使用

官方文档:

pinyin4j.sourceforge.net/

1 Pinyin4j的简介

Pinyin4j 是一个流行的Java开源类库,支持中文字符和拼音之间的转换。拼音输出格式可以定制。

支持功能:

  • 支持简体中文和繁体中文字符
  • 支持转换到汉语拼音,通用拼音
  • 支持多音字,即可以获取一个中文字符的多种发音
  • 支持多种字符串输出格式

应用场景:

  • 用户输入汉字转拼音
  • 数据库中添加姓名的首字母存储

源代码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码/* 主要使用中类是: net.sourceforge.pinyin4j.PinyinHelper和net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat

PinyinHelper对象处理汉语转拼音
HanyuPinyinOutputFormat对象用于处理汉语拼音输出格式

PinyinHelper对象相关方法
toHanyuPinyinStringArray(char ch)
toHanyuPinyinStringArray(char ch, HanyuPinyinOutputFormat outputFormat)

HanyuPinyinOutputFormat对象
setCaseType()方法 设置拼音大小写
HanyuPinyinCaseType.UPPERCASE:大写
HanyuPinyinCaseType.LOWERCASE:小写

setVCharType()方法 设置字符V的显示方式
HanyuPinyinVCharType.WITH_U_AND_COLON:显示为u:(默认)
HanyuPinyinVCharType.WITH_V :显示为v
HanyuPinyinVCharType.WITH_U_UNICODE:显示为ü

setToneType()方法
HanyuPinyinToneType.WITH_TONE_NUMBER:设置数字声调(默认)
HanyuPinyinToneType.WITHOUT_TONE:设置不显示声调
HanyuPinyinToneType.WITH_TONE_MARK:设置显示声调,前提必须设置V如何显示
*/

2 Pinyin4j的使用

1 添加maven依赖

1
2
3
4
5
xml复制代码    <dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.1</version>
</dependency>

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
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
java复制代码@Slf4j
public class PinYinUtils {


public static void main(String[] args) {
PinYinUtils pinYinUtils = new PinYinUtils();
String str = "唐代诗人李白-libai";
System.out.println(pinYinUtils.toHanyuPinyin(str));
System.out.println(pinYinUtils.getFirstLetter(str));
}

/**
* 汉字转拼音
*/
public String toHanyuPinyin(String hanzi) {
char[] chars = hanzi.trim().toCharArray();
String hanyupinyin = "";

//输出格式设置
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
/**
* 输出大小写设置
*
* LOWERCASE:输出小写
* UPPERCASE:输出大写
*/
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);

/**
* 输出音标设置
*
* WITH_TONE_MARK:直接用音标符(必须设置WITH_U_UNICODE,否则会抛出异常)
* WITH_TONE_NUMBER:1-4数字表示音标
* WITHOUT_TONE:没有音标
*/
// defaultFormat.setToneType(HanyuPinyinToneType.WITH_TONE_MARK); // 必须设置WITH_U_UNICODE,否则会抛出异常
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);

/**
* 特殊音标ü设置
*
* WITH_V:用v表示ü
* WITH_U_AND_COLON:用"u:"表示ü
* WITH_U_UNICODE:直接用ü
*/
// defaultFormat.setVCharType(HanyuPinyinVCharType.WITH_U_UNICODE);
defaultFormat.setVCharType(HanyuPinyinVCharType.WITH_V);

// 中文的正则表达式
String hanziRegex = "[\\u4E00-\\u9FA5]+";

try {
for (int i = 0; i < chars.length; i++) {
// 判断为中文,则转换为汉语拼音
if (String.valueOf(chars[i]).matches(hanziRegex)) {
hanyupinyin += PinyinHelper
.toHanyuPinyinStringArray(chars[i], defaultFormat)[0];
} else {
// 不为中文,则不转换
hanyupinyin += chars[i];
}
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
log.error("字符不能转成汉语拼音");
}

return hanyupinyin;
}
/**
* 取汉字的第一个字符
*/
public String getFirstLetter(String hanzi) {
char[] chars = hanzi.trim().toCharArray();
StringBuilder firstPinyin = new StringBuilder();

HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
// 拼音大写
defaultFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);
// 不带声调
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);

try {
String str = String.valueOf(chars[0]);
// 中文的正则表达式
String hanziRegex = "[\\u4E00-\\u9FA5]+";
// 数字表达式
String numberRegex = "[0-9]+";
// 字母表达式
String charRegex = "[a-zA-Z]+";

for (int i = 0, len = chars.length; i < len; i++) {
// 判断为汉字,则转为拼音,并取第一个字母
if (Character.toString(chars[i]).matches("[\\u4E00-\\u9FA5]+")) {
// 如果是多音字,只取第一个
String[] pys = PinyinHelper.toHanyuPinyinStringArray(chars[i], defaultFormat);
firstPinyin.append(pys[0].charAt(0));
} else {
firstPinyin.append(chars[i]);
}
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
log.error("字符不能转为汉语拼音");
}

return firstPinyin.toString();
}
}

/*
结果:
tangdaishirenlibai-libai
TDSRLB-libai

*/

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot + 从零创建一个多模块项目 说明 环

发表于 2021-10-29

说明

  本文手把手演示如果利用Gradle + SpringBoot创建一个多模块项目,作为以后功能点验证、持续开发的基础。

环境信息

  1. 操作系统:win 10
  2. JDK:1..8.0-181
  3. Gradle:gradle-6.8.2
  4. IDEA:2020.1 ULTIMATE
  5. SpringBoot:2.5.6

新建项目

  1. 打开IDEA,显示如下,点击 “创建项目”
    image.png
  2. 选择以Gradle方式创建项目,指定项目的JDK版本后,点击Next进入下一步

image.png

  1. 填写项目基本信息,点击完成

image.png

Location:项目目录,选定目录后ArtifactId和Name也即确定

GroupId:分组名,一般以域名反转形式设置,如域名是<www.baidu.com>, 则此处设置为com.baidu

ArtifictId:理解为项目名

Name:同ArtifactId

  1. 如果你需要更换gradle依赖,配置如下

image.png

  1. 完整的bild.gradle设置如下
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
csharp复制代码//build.gradle配置初识:https://blog.csdn.net/lw_power/article/details/51241720
//gradle默认属性:http://www.cnblogs.com/langtianya/p/5209515.html
plugins {
  id 'java'
}
​
//全局使用的值
ext {
  springBootVersion = '2.5.6'
}
​
//该项目与全部子项目的属性
allprojects {
  //指定编译.java文件的jdk版本
  sourceCompatibility = 1.8
  //确保class文件与targetCompatibility指定版本,或者更新的java虚拟机兼容
  targetCompatibility = 1.8
​
  //项目所属组,中间以.(英文逗号)隔开
  group 'com.yibenzhenjing'
  //项目版本号
  version '1.0.1-SNAPSHOT'
​
  //应用插件
  apply plugin: 'java'
  apply plugin: 'idea'
​
  // java编译的时候缺省状态下会因为中文字符而失败,设置字符集
  [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'
​
  //仓库,国内外仓库访问、下载速度会不同
  repositories {
      mavenLocal()
      maven { url "http://maven.aliyun.com/nexus/content/groups/public" } //国内仓库,下载会快一点
  }
​
  //依赖,testCompile表示依赖范围是test,其他依赖写compile
  dependencies {
      compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.5.6'
  }
}
​
subprojects {
​
}
​
  1. 在项目中创建.gitignore文件,填写指定格式的文件
    image.png

至此,项目主模块创建完成。

创建子模块

  1. 选中项目后, File -> New -> Module 或 右击项目,选择 New -> Module

image.png

image.png

  1. 选择以Gradle方式创建模块,指定JDK版本后,进入下一步

image.png

  1. 填写模块信息,点击完成

image.png

Parent:父级,选择之前创建的core项目

Name:填写模块名

其他配置不需要填写

  1. 可以看到项目下多出了util模块,build.gradle配置清空即可
1
2
3
4
5
6
ini复制代码// 如果单独构建,产物包名称可自定义
archivesBaseName = 'core-util'
​
dependencies {
​
}
  1. 在util模块下创建类

image.png

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
typescript复制代码package com.yibenzhenjing.core.util;
​
/**
* 字符拼接工具
*
* @author tangjialiang
* @since 2021-10-20
**/
public class JoinUtils {
  /**
    * 分隔符:短横杠
    */
  public static final String SEPARATOR_DASH = "-";
​
  /**
    * 用默认连接符拼接多个字符串
    *
    * @param key 待拼接的字符串
    * @return 拼接结果
    */
  public static String joinString(String... key) {
      return joinStringWithSeprator(SEPARATOR_DASH, key);
  }
​
  /**
    * 指定连接符拼接多个字符串
    *
    * @param separator 连接符
    * @param key       待拼接的字符串
    * @return 拼接结果
    */
  public static String joinStringWithSeprator(String separator, String... key) {
      StringBuilder sBuiler = new StringBuilder();
      int paramSize = key.length;
      int endKeyIndex = paramSize - 1;
      for (int i = 0; i < paramSize; i++) {
          sBuiler.append(key[i]);
          if (i > 0 && i < endKeyIndex) {
              sBuiler.append(separator);
          }
      }
      return sBuiler.toString();
  }
​
}
  1. 将util构建为二方库

image.png

执行成功后看到如下

image.png

  1. 创建应用模块site,过程同util,build.gradle如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scss复制代码buildscript {
  repositories {
      mavenLocal()
      maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
  }
  dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
  }
}
apply plugin: 'org.springframework.boot'
​
archivesBaseName = 'site'
​
dependencies {
  compile project(':util')
}
  1. 创建项目启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typescript复制代码package com.yibenzhenjing.core.site;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
/**
* 项目启动类
*
* @author tangjialiang
* @since 2021-10-27
**/
@SpringBootApplication
public class SiteApplication {
  public static void main(String[] args) {
      SpringApplication.run(SiteApplication.class, args);
  }
}
  1. site模块下创建测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kotlin复制代码package com.yibenzhenjing.core.site.controller;
​
import com.yibenzhenjing.core.util.JoinUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
​
/**
* 验证项目正常启动的类
*
* @author tangjialiang
* @since 2021-10-28
**/
@RestController
public class HelloController {
​
  @GetMapping("hello")
  public String hello() {
      return "hello" + JoinUtils.joinString(",", "author");
  }
}
​
  1. 启动项目,

image.png

并在浏览器中打开 http://localhost:8080/hello ,结果如下,满足预期

image.png

至此,通过IDEA创建的 Gradle + SpringBoot的项目成功开发,如果您正在新建类似项目,希望能给您写参考。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

什么?你居然不知道Spring为什么要设计成单例?

发表于 2021-10-29

面试官:你知道spring默认是单例,还是多例的?

我:好像是单例的。。

面试官:为什么spring设计成单例,也顺便讲讲spring是不是单例中线程安全问题?

我:单例我到知道,线程安全是啥。。。 还是回家吧。

很多人都知道spring是单例的,也知道单例模式,但是融合到一起却经常讲不清楚为什么spring是单例的,这么设计的好处,下面就给大家分享一下我的几点总结。

首先我们来看看单例和多例的优缺点:

单例

优点 缺点
1 减少请求时候创建对象的开销,提升性能 2 减少jvm垃圾回收 1 对于有状态的变量可能会造成线程安全问题,因为只有一个实例,如果操作的是有状态的全局变量,多个线程之间可能会操作同一个变量和对象导致线程不安全问题

多例

优点 缺点
1 线程安全,每个请求过来都分配一个新的对象,里面的所有东西属性方法都是该线程独享的 1 资源开销大,创建对象需要消耗性能 2 会产生大量的垃圾对象

Spring框架中的bean 或者说组件,默认是单例的。
单例模式确保了某个类只有一个实例,并且自行实例化,向整个系统提供这个实例。主要的优点就是减少对象的创建开销;减少jvm垃圾回收,因为单例是静态变量不会回收;
在多线程的情况下,Web容器会向每个请求分配一个线程。这些线程会执行对应的业务逻辑。如果在执行的时候对单例对象进行了修改,则必须考虑到线程同步的问题,所以一般我们在单例的对象中使用成员变量,就需要考虑在多线程中,有可能两个线程操作的成员变量是一个,这样就可能会造成线程安全问题;

举个例子有个类A,里面有个变量num,初始化为0,两个线程都访问spring的类A,分别对num + 1;
可能大家会写出以下的伪代码
image.png
这就是单例中新手程序员经常会犯的低级错误,那么spring又是有什么解决方案呢?
显然第一种就是变成多例模式,但是开销过大;那么第二种解决方案就是ThreadLocal对象了;

同步机制
ThreadLocal 和 线程同步机制
  线程同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题。
  ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

在spring 中是使用 ThreadLocal 解决线程安全问题
线程安全问题主要是全局变量和静态变量引起的。
若每个线程中对全局变量、静态变量读操作,而无写操作,一般来说这个全局变量是线程安全的。
若多个线程同时执行写操作,需要考虑线程同步问题,否则影响线程安全。
spring 使用ThreadLocal 实现高并发下 共享资源的同步。

原理:
  为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线
程都完全拥有该变量。【每个线程其实是改变的是自己线程的副本,而不是真正要改变的变量,所以效果就是每个线程都有自己的,“这其实就将共享变相为人人有份!”】

ThreadLocal 如何实现为每一个变量维护变量的副本。

public void set(T value) {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

TRAP Two-level Regularized Au

发表于 2021-10-29

小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动

本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

摘要:

最近,基于自动编码器 (AE) 的嵌入方法已经
在许多任务中取得了最先进的性能,尤其是
在具有用户嵌入的 top-k 推荐或具有节点嵌入的节点分类中。然而,我们发现许多现实世界
数据服从关于数据的幂律分布
对象稀疏性。在学习这些数据的基于 AE 的嵌入时,
密集输入远离嵌入空间中的稀疏输入
即使它们高度相关。这种现象,我们
调用极化,显然会扭曲嵌入。在本文中,我们
提出利用两级正则化器有效地
缓解极化问题。宏观正则化器通常防止密集输入对象远离其他
稀疏输入对象,以及单独的微观正则化器
将每个对象吸引到相关的邻居对象而不是不相关的对象。重要的是,TRAP 是一种元算法,可以
很容易与现有的基于 AE 的嵌入方法结合使用
简单的修改。在使用六个真实世界数据集的两个代表性嵌入任务的广泛实验中,TRAP 提升了
最先进算法的性能高达 31.53%
和 94.99%。

介绍:
最近,许多基于自动编码器 (AE) 的嵌入方法得到了积极的研究,并且它们是经验性的
被证明不仅可以实现低维,而且可以实现高信息量的嵌入,因为它具有强大的表示能力
神经网络。因此,即使在高维数据中也可以平滑地捕获数据流形 [4]。这个家庭
众所周知,基于 AE 的方法达到了最先进的水平
在众多机器学习任务中的表现,尤其是在
具有用户嵌入的 top-k 推荐任务 [13, 29, 30] 或
链接预测和节点分类(或聚类)任务
节点嵌入 [4, 5, 8]。
尽管他们取得了巨大的成功,但我们声称偏斜的稀疏性
现实世界中输入向量(例如电影评分)的分布
数据严重损害了嵌入方法的性能。
具体来说,如图1a所示,稀疏分布
的输入向量强烈倾向于稀疏。这个
考虑到各种现象,观察是很自然的
在现实世界中大致遵循一个广泛的幂律
幅度范围 [3]。然而,如图 1b 所示,这
偏态分布带来极化问题,导致
密集输入远离嵌入中的稀疏输入
空间(详见第 3.2 节),即使两个输入是
高度相关。显然,这些极化的潜在表征
降低嵌入方法的性能。
直觉上,极化的有害影响非常普遍
在现实世界的场景中。让我们考虑一个推荐任务
其中其数据遵循幂律分布[28]。目标
任务的主要内容是向您推荐没有经验但有趣的项目
用户基于他们在假设下的估计偏好
人们的口味是高度相关的。然而,
极化问题阻碍了密集输入紧密
定位到潜在空间中的稀疏输入,而不管它们之间的相关性,因此,现有的嵌入方法
可能会不准确地估计用户的偏好。这个限制
呼吁采取新方法来缓解两极分化问题

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…453454455…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%