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

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


  • 首页

  • 归档

  • 搜索

Java垃圾回收算法详解 1 概述 2 垃圾回收算法

发表于 2021-10-16

1 概述

在前一篇文章中讲到了Java虚拟机的基础知识和运行时数据区的划分,在运行时数据区的划分中,可分为线程共享区域和线程私有区域,而Java的垃圾回收就发生在线程共享区域中,更直观的说法就是Java的垃圾回收大部分都发生在Java的堆(Heap)区域内。

1.1 哪些对象需要回收

在了解了Java垃圾回收主要发生在哪些区域之后,然后我们就需要知道在Java中哪些对象是需要被当做垃圾回收的,这是一个很简单的问题,既然是需要回收的,那肯定就是在程序中已经使用过且后续不会再用到的对象,那怎么判断一个对象是否还会被用到呢?答案就是引用。

假设一个对象A,在程序中被其他对象所引用,那么对象A就不能被回收,如果对象A在程序中没有被任何其他对象引用,那么对象A就会在发生垃圾回收的时候被回收掉。

1.1.1 引用计数算法

那么问题又来了,在Java中如何判断一个对象是否被引用呢?其实最简单的一个算法就是引用计数法。引用计数法的原理也很简单,在每一个对象中添加一个引用计数器,每当有其他地方引用到当前对象时,计数器的值就加1;当引用失效时,计数器的值就减1,这样在垃圾回收的时候就直接判断当前对象的引用计数器的值,如果值为0,就表示当前对象已经没有任何地方引用,可以回收,反之若计数器的值不为0,则表示当前对象还被其他地方引用,不回收。

但是引用计数法有一个最大的缺陷,就是循环引用问题,见下图:

image.png

如图所示,有两个对象ObjectA和ObjectB,ObjectA对象引用ObjectB对象,ObjectB对象引用ObjectA对象,两个对象之间形成了一个相互引用的关系,所以两个对象的计数器值都是1,即使这两个对象在程序中已经没有被用到了,但是垃圾回收的时候还是不会去回收这两个对象。

这还只是两个对象的相互引用,如果是成百上千个不使用的对象之间相互引用形成一个循环引用,那么对Java堆空间会造成极大的浪费。

因此,在Java垃圾回收中并没有使用引用计数法,使用的而是另外一种算法:可达性分析算法。

1.1.2 可达性分析算法

可达性分析算法的思路就是通过一系列GC Roots作为根对象,这些GC Roots根对象都是不会被垃圾回收的一些对象,然后根据这些GC Roots的引用关系向下搜索,在搜索过程中所走过的路径就是“引用链”,如果一个对象从GC Roots开始无法通过引用链搜索到的话,那么这个对象就是不可达的,则表示这个对象在程序中不再被使用,可以被回收。

image.png

如上图所示,由多个GC Roots根对象可组成一个GC Root Set集合,只要是从GC Roots根对象通过引用链能够达到的对象就是在使用的对象,而图中的Object5,Object6,Object7三个对象之间虽然相互有引用,但是它们到GC Roots是不可达的,因此这三个对象会被判定为可回收对象。

在Java中,可以被作为GC Roots的对象有以下这些:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常亮引用到的对象。
  • 本地方法栈中引用到的对象(也就是Native中引用到的对象)。
  • Java虚拟机的内部引用,如基本数据类型对应的Class对象,异常对象,系统类加载器。
  • synchronized持有的对象。
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

2 垃圾回收算法

在了解完垃圾回收主要发生的区域以及如何判断对象是否可被回收之后,再需要了解的就是垃圾回收算法,垃圾回收算法就会使用到以上提到的可达性分析算法标记对象是否可被回收。

2.1 标记清除算法

标记清除算法是最早出现并且也是最简单的一种垃圾回收算法,在整个垃圾回收过程中总共分为标记和清除两个步骤:

  1. 标记需要回收的对象。
  2. 将第一步标记的对象进行统一的回收。

image.png

上图就是标记清除算法的两个步骤,使用标记清除算法虽然简单,但是标记清除算法存在两个缺点:

  1. 执行效率不稳定,如果Java堆中包含有大量的需要回收的对象,那么使用该回收算法的时候就需要去标记大量的对象,然后再去对标记的对象进行回收,导致该回收算法的效率会随着对象数量的增长而降低。
  2. 内存碎片化问题,通过上图可以看到,当完成对象回收之后,未使用的内存区域呈现无规律的随机分布,这就造成了内存碎片化的问题,如果当前Java程序中需要实例化一个大对象,但是由于内存碎片化的问题导致无法分配一个连续的大内存空间,就会导致提前又触发一次垃圾回收。

标记清除算法常被使用在老年代中,因为老年代中的对象大部分都不会被清理掉,只存在于少部分的对象会被清理,所以在老年代中不会存在标记大量对象并清除的情况。

2.2 标记复制算法

针对于标记清除算法存在的问题,在标记清除算法的基础之上,又提出了一种算法:标记复制算法,标记复制算法中将内存区域划分为两块大小一致的区域,每次只使用其中一块,当这一块的内存用完了之后就将还存活的对象复制到另一块区域上去,然后将当前块的内存空间一次清理掉,假设内存分为A和B两块,首先使用A内存,B作为保留,则标记复制算法步骤如下:

  1. 在A中标记需要回收的对象
  2. 将A中存活的对象复制到B中
  3. 将A的内存空间一次全部清理掉

image.png

上图是标记复制算法的回收步骤,该算法同样存在以下问题:

  1. 复制回收算法将原有的内存区域划分为两块,且每次只使用其中一块,可使用内存相比标记清除算法缩小了一半,对空间浪费太大。
  2. 如果内存中存活的对象太多,在复制的过程中复制大量的存活对象会造成效率低下,额外开销大,但如果内存中存活的对象为极少数,那么复制算法无疑是很好的回收算法。

标记复制算法适用于Java堆中的新生代部分的垃圾回收,因为在新生代区域中,正常情况下能够存活下来的对象只有极少数(2%或3%?),这样复制算法就不存在大量复制对象的情况,针对于空间浪费问题,将新生代区域划分为一块较大的Eden区域和两块较小的Survivor区域,每次分配对象时使用Eden和其中一块Survivor,当发生垃圾回收时,将Eden中和Survivor中存活的对象复制到另一块Survivor中,然后将Eden和已使用过的Survivor内存空间直接清理掉,下一轮分配对象时使用Eden和之前保存了存活对象的那一块Survivor,依次循环。

关于新生代划分可看上一篇文章:JVM入门

HotSpot虚拟机默认的Eden和Survivor的大小比例为8:1,Eden占新生代80%,两个Survivor各占10%,这样每次可使用的内存空间就是90%,比标记复制算法默认的等分50%要多得多。

逃生门安全设计:当Survivor空间不足以容纳一次垃圾回收之后存活的对象时,就需要依赖于其他区域(老年代)进行分配担保。

2.3 标记整理算法

在老年代中大部分对象都会存活下来,所以不适合标记复制算法,而且使用标记清除算法又会造成大量内存碎片,因此针对于老年代对象的死亡特征,又提出了一种回收算法:标记整理算法,标记整理算法的标记步骤和标记清除算法一致,但是在标记之后不会直接进行清除,而是首先将存活的对象向内存空间的一端进行移动,将所有存活的对象都连续的放在一起,然后直接清理掉边界以外的内存,标记整理算法步骤如下:

  1. 标记需要回收的对象
  2. 将存活的对象向内存空间的一端移动
  3. 直接清理掉边界以外的内存

image.png

在老年代中常使用的垃圾回收算法是标记清除算法和标记整理算法。

如果使用标记整理算法,在老年代区域中每次回收都有大量的对象存活,并且移动对象时需要更新所有对象的引用,所以会造成比较大的系统开销,而且对象移动操作必须全程暂停用户应用程序(Stop The Word)才能进行。

如果使用标记清除算法,则会造成内存碎片。

基于以上两种垃圾回收方式,各自都有优点和缺点,所以不同的垃圾收集器在老年代中所采用的垃圾回收算法也是有区别的,例如Parallel Scavenge收集器采用的就是标记整理算法,而CMS收集器则采用的是标记清除算法。

还有就是两种算法配合使用,首先采用标记清除算法,当内存碎片达到一定程度的时候使用标记整理算法回收一次,整理一下内存碎片,然后再又继续使用标记清除算法,前面提到的CMS收集器就是首先采用标记清除算法,当内存碎片过多然后采用标记整理算法。

关于垃圾收集器下一篇文章详解。

参考资料:《深入理解Java虚拟机 第三版》 周志明

本文转载自: 掘金

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

SpringBoot实现文件在线预览 背景 系统设计 系统实

发表于 2021-10-16

背景

最近公司内部oa系统升级,需要增加文件在线预览服务,最常见的文件就是office文档,一开始构思几个方案,比如office软件自带的文件转换,openoffice转换,offce365服务,aspose组件转换,最终采用了aspose转换,原因是组件功能完善,不依赖其它软件安装环境

系统设计

文件类型及方案

文件类型 预览方案
word aspsoe-word转换图片预览(版本21.1)
ppt aspose-slides转化你图片预览(版本20.4)
excel aspose-cell转换html预览(版本20.4)
pdf pdfbox缓缓图片预览(版本2.0.15)
png,jpg,gif 整合viewer.js预览(版本1.5.0)
mp4 整合vedio.js预览(js版本7.10.2)
txt 读取文件内容预览

注:aspose因版权问题,工程示例代码中全部使用试用版,转换图片会出现水印

流程设计

系统实现

识别文件后缀

URL指向文件真实路径时根据后缀名判断

1
2
3
4
5
6
7
8
9
10
11
java复制代码    public static String getTypeByExtenssion(String linkUrl) {
if (linkUrl == null)
return null;
linkUrl = linkUrl.toLowerCase();
for (String ext : extensions) {
if (linkUrl.endsWith(ext)) {
return ext;
}
}
return null;
}

URL为文件输出流时

  • 根据文件输出流的disposition
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码    private static String getTypeByDisposition(String disposition) {
String ext = null;
if (!StringUtils.isEmpty(disposition)) {
disposition = StringUtils.replace(disposition, "\"", "");
String[] strs = disposition.split(";");
for (String string : strs) {
if (string.toLowerCase().indexOf("filename=") >= 0) {
ext = StringUtils.substring(string, string.lastIndexOf("."));
break;
}
}
}
return ext;
}
  • 根据文件输出流content-type
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码        types = new HashMap<String, String>();
types.put("application/pdf", ".pdf");
types.put("application/msword", ".doc");
types.put("text/plain", ".txt");
types.put("application/javascript", ".js");
types.put("application/x-xls", ".xls");
types.put("application/-excel", ".xls");
types.put("text/html", ".html");
types.put("application/x-rtf", ".rtf");
types.put("application/x-ppt", ".ppt");
types.put("image/jpeg", ".jpg");
types.put("application/vnd.openxmlformats-officedocument.wordprocessingml.template", ".docx");
types.put("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx");
types.put("application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx");
types.put("message/rfc822", ".eml");
types.put("application/xml", ".xml");
  • 根据stream的固定字节判断
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
java复制代码        FILE_TYPE_MAP.put(".pdf", "255044462D312E"); // Adobe Acrobat (pdf)
FILE_TYPE_MAP.put(".doc", "D0CF11E0"); // MS Word
FILE_TYPE_MAP.put(".xls", "D0CF11E0"); // MS Excel 注意:word 和 excel的文件头一样
FILE_TYPE_MAP.put(".jpg", "FFD8FF"); // JPEG (jpg)
FILE_TYPE_MAP.put(".png", "89504E47"); // PNG (png)
FILE_TYPE_MAP.put(".gif", "47494638"); // GIF (gif)
FILE_TYPE_MAP.put(".tif", "49492A00"); // TIFF (tif)
FILE_TYPE_MAP.put(".bmp", "424D"); // Windows Bitmap (bmp)
FILE_TYPE_MAP.put(".dwg", "41433130"); // CAD (dwg)
FILE_TYPE_MAP.put(".html", "68746D6C3E"); // HTML (html)
FILE_TYPE_MAP.put(".rtf", "7B5C727466"); // Rich Text Format (rtf)
FILE_TYPE_MAP.put(".xml", "3C3F786D6C");
FILE_TYPE_MAP.put(".zip", "504B0304"); // docx的文件头与zip的一样
FILE_TYPE_MAP.put(".rar", "52617221");
FILE_TYPE_MAP.put(".psd", "38425053"); // Photoshop (psd)
FILE_TYPE_MAP.put(".eml", "44656C69766572792D646174653A"); // Email
FILE_TYPE_MAP.put(".dbx", "CFAD12FEC5FD746F"); // Outlook Express (dbx)
FILE_TYPE_MAP.put(".pst", "2142444E"); // Outlook (pst)
FILE_TYPE_MAP.put(".mdb", "5374616E64617264204A"); // MS Access (mdb)
FILE_TYPE_MAP.put(".wpd", "FF575043"); // WordPerfect (wpd)
FILE_TYPE_MAP.put(".eps", "252150532D41646F6265");
FILE_TYPE_MAP.put(".ps", "252150532D41646F6265");
FILE_TYPE_MAP.put(".qdf", "AC9EBD8F"); // Quicken (qdf)
FILE_TYPE_MAP.put(".pwl", "E3828596"); // Windows Password (pwl)
FILE_TYPE_MAP.put(".wav", "57415645"); // Wave (wav)
FILE_TYPE_MAP.put(".avi", "41564920");
FILE_TYPE_MAP.put(".ram", "2E7261FD"); // Real Audio (ram)
FILE_TYPE_MAP.put(".rm", "2E524D46"); // Real Media (rm)
FILE_TYPE_MAP.put(".mpg", "000001BA"); //
FILE_TYPE_MAP.put(".mov", "6D6F6F76"); // Quicktime (mov)
FILE_TYPE_MAP.put(".asf", "3026B2758E66CF11"); // Windows Media (asf)
FILE_TYPE_MAP.put(".mid", "4D546864"); // MIDI (mid)

文件解析

word分页转换图片

1
2
3
4
5
java复制代码Document doc = new Document(fileConvertInfo.getFilePath());
for (int i = 0; i < doc.getPageCount(); i++) {
Document extractedPage = doc.extractPages(i, 1);
extractedPage.save(fileConvertInfo.getFileDirPath() + "split_" + (i + 1) + ".jpeg", SaveFormat.JPEG);
}

ppt分页转换图片

1
2
3
4
5
6
7
8
9
10
java复制代码Presentation ppt = new Presentation(fileConvertInfo.getFilePath());
for (int i = 0; i < ppt.getSlides().size(); i++) {
ISlide slide = ppt.getSlides().get_Item(i);
int height = (int) (ppt.getSlideSize().getSize().getHeight() - 150);
int width = (int) (ppt.getSlideSize().getSize().getWidth() - 150);
BufferedImage image = slide.getThumbnail(new java.awt.Dimension(width, height));
//每一页输出一张图片
File outImage = new File(fileConvertInfo.getFileDirPath() + "split_" + (i + 1) + ".jpeg");
ImageIO.write(image, "jpeg", outImage);
}

excel转换html

1
2
3
4
5
6
java复制代码Workbook wb = new Workbook(fileConvertInfo.getFilePath());
HtmlSaveOptions opts = new HtmlSaveOptions();
opts.setExportWorksheetCSSSeparately(true);
opts.setExportSimilarBorderStyle(true);
Worksheet ws = wb.getWorksheets().get(0);
wb.save(fileConvertInfo.getFileDirPath() + "convert.html", opts);

excel分页转换图片(另一种预览方式)

1
2
3
4
5
6
7
8
java复制代码Workbook wb = new Workbook(fileConvertInfo.getFilePath());
ImageOrPrintOptions imgOptions = new ImageOrPrintOptions();
imgOptions.setImageFormat(ImageFormat.getJpeg());
for (int i = 0; i < wb.getWorksheets().getCount(); i++) {
Worksheet sheet = wb.getWorksheets().get(i);
SheetRender sr = new SheetRender(sheet, imgOptions);
sr.toImage(i, fileConvertInfo.getFileDirPath() + "split_" + (i + 1) + ".jpeg");
}

pdf分页转换图片

1
2
3
4
5
6
7
8
java复制代码PDDocument pdf = PDDocument.load(new File((fileConvertInfo.getFilePath())));
int pageCount = pdf.getNumberOfPages();
PDFRenderer renderer = new PDFRenderer(pdf);
for (int i = 0; i < pageCount; i++) {
BufferedImage image = renderer.renderImage(i, 1.25f); // 第二个参数越大生成图片分辨率越高,转换时间也就越长
ImageIO.write(image, "JPEG", new File(fileConvertInfo.getFileDirPath() + "split_" + (i + 1) + ".jpeg"));
}
pdf.close();

预览图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
html复制代码<body>
<div id="app">
<img id="image" style="display: none">
</div>
</body>

</html>
<script>
$(function () {
$("#image").attr("src", getQueryString("file"));
var image = new Viewer(document.getElementById('image'),{
url: 'data-original',
button:false,
navbar:false,
backdrop: false
});
document.getElementById('image').click();
})
</script>

预览视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
html复制代码<body>
<div id="app">
<video id="myvideo" class="video-js vjs-big-play-centered" controls data-setup="{}" width="1366" height="768" preload="auto">
<source id="vedio" src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4"></source>
</video>
</div>
</body>

</html>
<script>
$(function () {
$("#vedio").attr("src", getQueryString("file"));
})
</script>

系统效果

使用方法

直接运行项目,输入预览地址

http://localhost:8098/fastpreview/plaform/index.html?file=(文件地址)

文件地址支持文件访问路径与流输出

项目源码地址

gitee.com/code2roc/fa…

预览界面

word

excel

ppt

pdf

image

vedio

txt

本文转载自: 掘金

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

Python爬虫网页解析神器Xpath快速入门教学!!!

发表于 2021-10-16

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

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

Code皮皮虾 一个沙雕而又有趣的憨憨少年,和大多数小伙伴们一样喜欢听歌、游戏,当然除此之外还有写作的兴趣,emm…,日子还很长,让我们一起加油努力叭🌈

如果觉得写得不错的话,球球一个关注哦😉


1、Xpath介绍

XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。

2、Xpath路径表达式

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性

3、结合实例讲解

这里我就是使用百度的界面为大家进行讲解

在这里插入图片描述

==例==:我想获取图中的百度热榜,打开控制台,我们可直接根据div标签的class值进行定位(这是我们平时使用xpath语法比较多的地方)
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python复制代码from lxml import etree
import requests


headers = {
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36"
}
url = "https://www.baidu.com/"
response = requests.get(url=url,headers=headers)
#使用etree进行解析
data = etree.HTML(response.text)

#可参考上表格进行对比,//div可理解为任意路径下的一个div标签,@class表示选取class属性,text()表示获取text文本
name = data.xpath("//div[@class='title-text c-font-medium c-color-t']/text()")
print(name[0])

在这里插入图片描述

==例==:我想获取热榜有哪些信息,参考下图可见其全部在ul标签下,每一个信息对于一个li标签

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
python复制代码data = etree.HTML(response.text)
#//ul表示任意路径下的ul标签,
#表示获取ul下的所有li标签
ul = data.xpath("//ul[@class='s-hotsearch-content']/li")
#当然,大家在爬取过程中可能会遇到没有class属性的标签,这时可使用id定位,又或者定位其父标签,再往下找
#ul = data.xpath("//ul[@id='hotsearch-content-wrapper']/li")

#遍历
for li in ul:
# .//span表示当前节点下的任意span标签,我们再根据class值定位,使用text()获取文本信息
name = li.xpath(".//span[@class='title-content-title']/text()")
print(name[0])

在这里插入图片描述

==例==:定位百度热榜找它的父节点也就是a标签的href属性
在这里插入图片描述

1
2
3
4
5
python复制代码data = etree.HTML(response.text)

#..表示其父节点
url = data.xpath("//div[@class='title-text c-font-medium c-color-t']/../@href")
print(url[0])

在这里插入图片描述

Xpath语法其实不难的,大家需要多练习,进行实战,这样熟练掌握会很快的,可以下方的爬虫教程索引,里面有很多爬虫使用xpath写的,可以阅读看看。


💖最后

我是 Code皮皮虾,一个热爱分享知识的 皮皮虾爱好者,未来的日子里会不断更新出对大家有益的博文,期待大家的关注!!!

创作不易,如果这篇博文对各位有帮助,希望各位小伙伴可以一键三连哦!,感谢支持,我们下次再见~


一键三连.png

本文转载自: 掘金

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

使用 Python 和 OpenCV 对图像进行加水印

发表于 2021-10-16

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

在本文中,我将带着大家使用 Python 和 OpenCV 对图像进行加水印。水印有意在图像上留下文本/标志,很多博主会使用水印来保护图像的版权。使用水印我们可以确保图像的所有者是在图像上印制水印的人。

水印前的图片:
水印前的图片
logo.png:
logo.png

🌌 第 1 步:导入 OpenCV 并读取logo和要应用水印的图像

1
2
3
4
5
6
7
8
python复制代码# 使用 OpenCV 导入 cv2 的水印图像
import cv2

# 导入我们将要使用的logo
logo = cv2.imread("logo.png")

# 导入我们要应用水印的图像
img = cv2.imread("haiyong.png")

💨 第 2步:计算两个图像的高度和宽度

计算两个图像的高度和宽度,并将它们保存到其他变量中。我们需要计算宽度和高度,因为我们要将水印放置在图像上的某个位置,为此,我们只需要知道徽标和图像的正确宽度和高度。

1
2
3
4
5
python复制代码# 计算logo的尺寸高度和宽度
h_logo, w_logo, _ = logo.shape

# 图像的高度和宽度
h_img, w_img, _ = img.shape

在这里,我们使用了OpenCV 中的shape函数,它返回图像的高度和宽度的元组。

🚀 第 3 步:将水印放置在图像的中心

现在,我们将计算图像中心的坐标,因为我要将水印放置在图像的中心,你们也可以选择其他位置。

1
2
3
4
5
6
7
8
9
python复制代码# 计算中心计算中心的坐标,我们将在其中放置水印
center_y = int(h_img/2)
center_x = int(w_img/2)

# 从上、下、右、左计算
top_y = center_y - int(h_logo/2)
bottom_y = top_y + h_logo
right_x = left_x + w_logo
left_x = center_x - int(w_logo/2)

🍺 第 4 步:使用 OpenCV 中的 addWeighted 函数

要为图像添加水印,我们将使用 OpenCV 中的 addWeighted 函数。首先,我们将提供要放置水印的目的地,然后将该目的地传递给带有图像和徽标的 addWeighted 函数。

语法: cv2.addWeighted(source1, alpha, source2, beta, gamma)

在我们的例子中,source1 是我们想要放置logo的图像,alpha 是logo的不透明度,source2 是logo本身,我们将相应地设置 beta为1,不透明度的 alpha 和 gamma分别为 1 和 0。

1
2
3
python复制代码# 给图片添加水印
destination = img[top_y:bottom_y, left_x:right_x]
result = cv2.addWeighted(destination, 1, logo, 1, 0)

🎨 第 5 步:显示结果并保存输出

之后,我们只是显示结果并保存输出。为了显示我们使用imshow 函数的输出并写入/保存图像,我们在两个函数中都使用imwrite 函数,首先我们必须提供文件名作为参数,然后是文件本身。cv2.waitKey(0) 用于等待直到用户按下 Esc 键,之后 cv2.destroyAllWindows 函数将关闭窗口。

1
2
3
4
5
6
python复制代码# displaying and saving image
img[top_y:bottom_y, left_x:right_x] = result
cv2.imwrite("watermarked.jpg", img)
cv2.imshow("Watermarked Image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

🛹 下面是完整的实现:

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
python复制代码# 使用 OpenCV 导入 cv2 的水印图像
import cv2

# 导入我们将要使用的logo
logo = cv2.imread("logo.png")

# 导入我们要应用水印的图像
img = cv2.imread("haiyong.png")

# 计算logo的尺寸高度和宽度
h_logo, w_logo, _ = logo.shape

# 图像的高度和宽度
h_img, w_img, _ = img.shape

# 计算中心计算中心的坐标,我们将在其中放置水印
center_y = int(h_img/2)
center_x = int(w_img/2)

# 从上、下、右、左计算
top_y = center_y - int(h_logo/2)
left_x = center_x - int(w_logo/2)
bottom_y = top_y + h_logo
right_x = left_x + w_logo

# 给图片添加水印
destination = img[top_y:bottom_y, left_x:right_x]
result = cv2.addWeighted(destination, 1, logo, 1, 0)

# 显示和保存图像
img[top_y:bottom_y, left_x:right_x] = result
cv2.imwrite("watermarked.jpg", img)
cv2.imshow("Watermarked Image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

输出:
在这里插入图片描述

我已经写了很长一段时间的技术博客,并且主要通过掘金发表,这是我的一篇使用 Python 和 OpenCV 对图像进行加水印。我喜欢通过文章分享技术与快乐。您可以访问我的博客: juejin.cn/user/204034… 以了解更多信息。希望你们会喜欢!😊

💌 欢迎大家在评论区提出意见和建议!💌

掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边,抽奖详情见活动文章

本文转载自: 掘金

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

关于bootstrapsh相关分析

发表于 2021-10-16

为什么要分析安装脚本

对于初学者,很多文档都是直接运行该安装脚本,就可以自动化部署Fabric程序,虽然表面上针对小白比较友好,不需要自己下载源码进行编译,但是很多资源都是国外的,下载速度缓慢我也是饱受折磨。下面分析一下安装脚本,有助于我们更加深入理解Fabric架构,同时分析安装逻辑。修改脚本方便部署。

安装脚本在哪里

我们以fabric1.4.8为例进行分析。相关github上面的链接如下。我们发现官方脚本不好用,下载非常缓慢,
github.com/hyperledger…

安装逻辑是什么

下面是安装脚本的基本逻辑框架代码。接下来我们一点点分析。

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
sh复制代码#!/bin/bash
#
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
# if version not passed in, default to latest released version
export VERSION=1.4.8
# if ca version not passed in, default to latest released version
export CA_VERSION=1.4.7
# current version of thirdparty images (couchdb, kafka and zookeeper) released
export THIRDPARTY_IMAGE_VERSION=0.4.21
export ARCH=$(echo "$(uname -s|tr '[:upper:]' '[:lower:]'|sed 's/mingw64_nt.*/windows/')-$(uname -m | sed 's/x86_64/amd64/g')")
export MARCH=$(uname -m)

printHelp() {
}

dockerFabricPull() {
}

dockerThirdPartyImagesPull() {
}

dockerCaPull() {
}

samplesInstall() {
}

# Incrementally downloads the .tar.gz file locally first, only decompressing it
# after the download is complete. This is slower than binaryDownload() but
# allows the download to be resumed.
binaryIncrementalDownload() {
}

# This will attempt to download the .tar.gz all at once, but will trigger the
# binaryIncrementalDownload() function upon a failure, allowing for resume
# if there are network failures.
binaryDownload() {
}

dockerInstall() {
}

DOCKER=true
SAMPLES=true
BINARIES=true

# Parse commandline args pull out
# version and/or ca-version strings first
if [ ! -z "$1" -a ${1:0:1} != "-" ]; then
VERSION=$1;shift
if [ ! -z "$1" -a ${1:0:1} != "-" ]; then
CA_VERSION=$1;shift
if [ ! -z "$1" -a ${1:0:1} != "-" ]; then
THIRDPARTY_IMAGE_VERSION=$1;shift
fi
fi
fi

# prior to 1.2.0 architecture was determined by uname -m
if [[ $VERSION =~ ^1\.[0-1]\.* ]]; then
export FABRIC_TAG=${MARCH}-${VERSION}
export CA_TAG=${MARCH}-${CA_VERSION}
export THIRDPARTY_TAG=${MARCH}-${THIRDPARTY_IMAGE_VERSION}
else
# starting with 1.2.0, multi-arch images will be default
: ${CA_TAG:="$CA_VERSION"}
: ${FABRIC_TAG:="$VERSION"}
: ${THIRDPARTY_TAG:="$THIRDPARTY_IMAGE_VERSION"}
fi

BINARY_FILE=hyperledger-fabric-${ARCH}-${VERSION}.tar.gz
CA_BINARY_FILE=hyperledger-fabric-ca-${ARCH}-${CA_VERSION}.tar.gz

# then parse opts
while getopts "h?dsb" opt; do
case "$opt" in
h|\?)
printHelp
exit 0
;;
d) DOCKER=false
;;
s) SAMPLES=false
;;
b) BINARIES=false
;;
esac
done

if [ "$SAMPLES" == "true" ]; then
echo
echo "Installing hyperledger/fabric-samples repo"
echo
samplesInstall
fi
if [ "$BINARIES" == "true" ]; then
echo
echo "Installing Hyperledger Fabric binaries"
echo
binariesInstall
fi
if [ "$DOCKER" == "true" ]; then
echo
echo "Installing Hyperledger Fabric docker images"
echo
dockerInstall
fi

和传统编程语言一样,此脚本首先定义了一下全局变量,同时通过if判断逻辑,执行相关函数,进行环境的部署,想关源码的下载。

逐一进行分析

首先看的肯定是帮助命令。

1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码printHelp() {
echo "Usage: bootstrap.sh [version [ca_version [thirdparty_version]]] [options]"
echo
echo "options:"
echo "-h : this help"
echo "-d : bypass docker image download"
echo "-s : bypass fabric-samples repo clone"
echo "-b : bypass download of platform-specific binaries"
echo
echo "e.g. bootstrap.sh 1.4.8 -s"
echo "would download docker images and binaries for version 1.4.8"
}

我们可以看出,此脚本主要执行三部分功能,第一下载docker镜像,第二下载fabric-samples官方案例(为了更加方便的了解fabric构建项目),第三下载平台二进制文件。并且可以指定下载的版本。例如:1.4.8。同时可以参数选择不下载那一部分。代码最前面导入了相关变量export VERSION=1.4.8、export CA_VERSION=1.4.7、export THIRDPARTY_IMAGE_VERSION=0.4.21。默认使用的是这些参数。

首先是逻辑框架

前面默认给出的变量值都是true,通过判断参数值,执行相关功能。samplesInstall、binariesInstall,dockerInstall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bash复制代码if [ "$SAMPLES" == "true" ]; then
echo
echo "Installing hyperledger/fabric-samples repo"
echo
samplesInstall
fi
if [ "$BINARIES" == "true" ]; then
echo
echo "Installing Hyperledger Fabric binaries"
echo
binariesInstall
fi
if [ "$DOCKER" == "true" ]; then
echo
echo "Installing Hyperledger Fabric docker images"
echo
dockerInstall
fi

dockerInstall

我们可以看到下载docker镜像,主要执行dockerFabricPull ${FABRIC_TAG} dockerCaPull ${CA_TAG} dockerThirdPartyImagesPull ${THIRDPARTY_TAG}源文件无需更改,就可以执行。

这部分我们可以配置正确的docker国内加速源进行加速。不做过多的介绍。

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
bash复制代码dockerInstall() {
which docker >& /dev/null
NODOCKER=$?
if [ "${NODOCKER}" == 0 ]; then
echo "===> Pulling fabric Images"
dockerFabricPull ${FABRIC_TAG}
echo "===> Pulling fabric ca Image"
dockerCaPull ${CA_TAG}
echo "===> Pulling thirdparty docker images"
dockerThirdPartyImagesPull ${THIRDPARTY_TAG}
echo
echo "===> List out hyperledger docker images"
docker images | grep hyperledger*
else
echo "========================================================="
echo "Docker not installed, bypassing download of Fabric images"
echo "========================================================="
fi
}

dockerFabricPull() {
local FABRIC_TAG=$1
for IMAGES in peer orderer ccenv javaenv tools; do
echo "==> FABRIC IMAGE: $IMAGES"
echo
docker pull hyperledger/fabric-$IMAGES:$FABRIC_TAG
docker tag hyperledger/fabric-$IMAGES:$FABRIC_TAG hyperledger/fabric-$IMAGES
done
}
dockerCaPull() {
local CA_TAG=$1
echo "==> FABRIC CA IMAGE"
echo
docker pull hyperledger/fabric-ca:$CA_TAG
docker tag hyperledger/fabric-ca:$CA_TAG hyperledger/fabric-ca
}
dockerThirdPartyImagesPull() {
local THIRDPARTY_TAG=$1
for IMAGES in couchdb kafka zookeeper; do
echo "==> THIRDPARTY DOCKER IMAGE: $IMAGES"
echo
docker pull hyperledger/fabric-$IMAGES:$THIRDPARTY_TAG
docker tag hyperledger/fabric-$IMAGES:$THIRDPARTY_TAG hyperledger/fabric-$IMAGES
done
}

binariesInstall

这里面需要修改的地方是binaryDownload函数,可以看出官方给出的下载链接为国外地址并且已经不在维护,也就是执行此脚本最缓慢的原因之一。nexus.hyperledger.org/content/rep…

此链接已经不在维护,所以要更换。如果想更快,使用hub.fastgit.org 加速github网站。进行下载

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
bash复制代码binariesInstall() {
echo "===> Downloading version ${FABRIC_TAG} platform specific fabric binaries"
# wget https://hub.fastgit.org/hyperledger/fabric/releases/download/v1.4.8/hyperledger-fabric-linux-amd64-1.4.8.tar.gz
binaryDownload ${BINARY_FILE} https://hub.fastgit.org/hyperledger/fabric/releases/download/v${VERSION}/${BINARY_FILE}
if [ $? -eq 22 ]; then
echo
echo "------> ${FABRIC_TAG} platform specific fabric binary is not available to download <----"
echo
fi

echo "===> Downloading version ${CA_TAG} platform specific fabric-ca-client binary"
binaryDownload ${CA_BINARY_FILE} https://hub.fastgit.org/hyperledger/fabric-ca/releases/download/v${CA_VERSION}/${CA_BINARY_FILE}
if [ $? -eq 22 ]; then
echo
echo "------> ${CA_TAG} fabric-ca-client binary is not available to download (Available from 1.1.0-rc1) <----"
echo
fi
}

binaryDownload() {
local BINARY_FILE=$1
local URL=$2
echo "===> Downloading: " ${URL}
# Check if a previous failure occurred and the file was partially downloaded
if [ -e ${BINARY_FILE} ]; then
echo "==> Partial binary file found. Resuming download..."
binaryIncrementalDownload ${BINARY_FILE} ${URL}
else
curl -L --retry 5 --retry-delay 3 ${URL} | tar xz || rc=$?
if [ ! -z "$rc" ]; then
echo "==> There was an error downloading the binary file. Switching to incremental download."
echo "==> Downloading file..."
binaryIncrementalDownload ${BINARY_FILE} ${URL}
else
echo "==> Done."
fi
fi
}

samplesInstall

更换国内源,使用码云的镜像,进行加速下载fabric-samples。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bash复制代码samplesInstall() {
# clone (if needed) hyperledger/fabric-samples and checkout corresponding
# version to the binaries and docker images to be downloaded
if [ -d first-network ]; then
# if we are in the fabric-samples repo, checkout corresponding version
echo "===> Checking out v${VERSION} of hyperledger/fabric-samples"
git checkout v${VERSION}
elif [ -d fabric-samples ]; then
# if fabric-samples repo already cloned and in current directory,
# cd fabric-samples and checkout corresponding version
echo "===> Checking out v${VERSION} of hyperledger/fabric-samples"
cd fabric-samples && git checkout v${VERSION}
else
echo "===> Cloning hyperledger/fabric-samples repo and checkout v${VERSION}"
# git clone -b master https://github.com/hyperledger/fabric-samples.git && cd fabric-samples && git checkout v${VERSION}
git clone -b master https://gitee.com/hyperledger/fabric-samples.git && cd fabric-samples && git checkout v${VERSION}
fi
}

将上面修改完成之后完整的bootstrap.sh代码。

bootstrap.sh

通过替换和修改源,最终实现加速效果。如果不想了解上述过程,就直接复制粘贴下面代码即可。

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
234
235
236
237
238
bash复制代码#!/bin/bash
#
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#

# if version not passed in, default to latest released version
export VERSION=1.4.8
# if ca version not passed in, default to latest released version
export CA_VERSION=1.4.7
# current version of thirdparty images (couchdb, kafka and zookeeper) released
export THIRDPARTY_IMAGE_VERSION=0.4.21
export ARCH=$(echo "$(uname -s|tr '[:upper:]' '[:lower:]'|sed 's/mingw64_nt.*/windows/')-$(uname -m | sed 's/x86_64/amd64/g')")
export MARCH=$(uname -m)

printHelp() {
echo "Usage: bootstrap.sh [version [ca_version [thirdparty_version]]] [options]"
echo
echo "options:"
echo "-h : this help"
echo "-d : bypass docker image download"
echo "-s : bypass fabric-samples repo clone"
echo "-b : bypass download of platform-specific binaries"
echo
echo "e.g. bootstrap.sh 1.4.8 -s"
echo "would download docker images and binaries for version 1.4.8"
}

dockerFabricPull() {
local FABRIC_TAG=$1
for IMAGES in peer orderer ccenv javaenv tools; do
echo "==> FABRIC IMAGE: $IMAGES"
echo
docker pull hyperledger/fabric-$IMAGES:$FABRIC_TAG
docker tag hyperledger/fabric-$IMAGES:$FABRIC_TAG hyperledger/fabric-$IMAGES
done
}

dockerThirdPartyImagesPull() {
local THIRDPARTY_TAG=$1
for IMAGES in couchdb kafka zookeeper; do
echo "==> THIRDPARTY DOCKER IMAGE: $IMAGES"
echo
docker pull hyperledger/fabric-$IMAGES:$THIRDPARTY_TAG
docker tag hyperledger/fabric-$IMAGES:$THIRDPARTY_TAG hyperledger/fabric-$IMAGES
done
}

dockerCaPull() {
local CA_TAG=$1
echo "==> FABRIC CA IMAGE"
echo
docker pull hyperledger/fabric-ca:$CA_TAG
docker tag hyperledger/fabric-ca:$CA_TAG hyperledger/fabric-ca
}

samplesInstall() {
# clone (if needed) hyperledger/fabric-samples and checkout corresponding
# version to the binaries and docker images to be downloaded
if [ -d first-network ]; then
# if we are in the fabric-samples repo, checkout corresponding version
echo "===> Checking out v${VERSION} of hyperledger/fabric-samples"
git checkout v${VERSION}
elif [ -d fabric-samples ]; then
# if fabric-samples repo already cloned and in current directory,
# cd fabric-samples and checkout corresponding version
echo "===> Checking out v${VERSION} of hyperledger/fabric-samples"
cd fabric-samples && git checkout v${VERSION}
else
echo "===> Cloning hyperledger/fabric-samples repo and checkout v${VERSION}"
git clone -b master https://gitee.com/hyperledger/fabric-samples.git && cd fabric-samples && git checkout v${VERSION}
fi
}

# Incrementally downloads the .tar.gz file locally first, only decompressing it
# after the download is complete. This is slower than binaryDownload() but
# allows the download to be resumed.
binaryIncrementalDownload() {
local BINARY_FILE=$1
local URL=$2
curl -f -s -C - ${URL} -o ${BINARY_FILE} || rc=$?
# Due to limitations in the current Nexus repo:
# curl returns 33 when there's a resume attempt with no more bytes to download
# curl returns 2 after finishing a resumed download
# with -f curl returns 22 on a 404
if [ "$rc" = 22 ]; then
# looks like the requested file doesn't actually exist so stop here
return 22
fi
if [ -z "$rc" ] || [ $rc -eq 33 ] || [ $rc -eq 2 ]; then
# The checksum validates that RC 33 or 2 are not real failures
echo "==> File downloaded. Verifying the md5sum..."
localMd5sum=$(md5sum ${BINARY_FILE} | awk '{print $1}')
remoteMd5sum=$(curl -s ${URL}.md5)
if [ "$localMd5sum" == "$remoteMd5sum" ]; then
echo "==> Extracting ${BINARY_FILE}..."
tar xzf ./${BINARY_FILE} --overwrite
echo "==> Done."
rm -f ${BINARY_FILE} ${BINARY_FILE}.md5
else
echo "Download failed: the local md5sum is different from the remote md5sum. Please try again."
rm -f ${BINARY_FILE} ${BINARY_FILE}.md5
exit 1
fi
else
echo "Failure downloading binaries (curl RC=$rc). Please try again and the download will resume from where it stopped."
exit 1
fi
}

# This will attempt to download the .tar.gz all at once, but will trigger the
# binaryIncrementalDownload() function upon a failure, allowing for resume
# if there are network failures.
binaryDownload() {
local BINARY_FILE=$1
local URL=$2
echo "===> Downloading: " ${URL}
# Check if a previous failure occurred and the file was partially downloaded
if [ -e ${BINARY_FILE} ]; then
echo "==> Partial binary file found. Resuming download..."
binaryIncrementalDownload ${BINARY_FILE} ${URL}
else
curl -L --retry 5 --retry-delay 3 ${URL} | tar xz || rc=$?
if [ ! -z "$rc" ]; then
echo "==> There was an error downloading the binary file. Switching to incremental download."
echo "==> Downloading file..."
binaryIncrementalDownload ${BINARY_FILE} ${URL}
else
echo "==> Done."
fi
fi
}

binariesInstall() {
echo "===> Downloading version ${FABRIC_TAG} platform specific fabric binaries"
# wget https://hub.fastgit.org/hyperledger/fabric/releases/download/v1.4.8/hyperledger-fabric-linux-amd64-1.4.8.tar.gz
binaryDownload ${BINARY_FILE} https://hub.fastgit.org/hyperledger/fabric/releases/download/v${VERSION}/${BINARY_FILE}
if [ $? -eq 22 ]; then
echo
echo "------> ${FABRIC_TAG} platform specific fabric binary is not available to download <----"
echo
fi

echo "===> Downloading version ${CA_TAG} platform specific fabric-ca-client binary"
binaryDownload ${CA_BINARY_FILE} https://hub.fastgit.org/hyperledger/fabric-ca/releases/download/v${CA_VERSION}/${CA_BINARY_FILE}
if [ $? -eq 22 ]; then
echo
echo "------> ${CA_TAG} fabric-ca-client binary is not available to download (Available from 1.1.0-rc1) <----"
echo
fi
}

dockerInstall() {
which docker >& /dev/null
NODOCKER=$?
if [ "${NODOCKER}" == 0 ]; then
echo "===> Pulling fabric Images"
dockerFabricPull ${FABRIC_TAG}
echo "===> Pulling fabric ca Image"
dockerCaPull ${CA_TAG}
echo "===> Pulling thirdparty docker images"
dockerThirdPartyImagesPull ${THIRDPARTY_TAG}
echo
echo "===> List out hyperledger docker images"
docker images | grep hyperledger*
else
echo "========================================================="
echo "Docker not installed, bypassing download of Fabric images"
echo "========================================================="
fi
}

DOCKER=true
SAMPLES=true
BINARIES=true

# Parse commandline args pull out
# version and/or ca-version strings first
if [ ! -z "$1" -a ${1:0:1} != "-" ]; then
VERSION=$1;shift
if [ ! -z "$1" -a ${1:0:1} != "-" ]; then
CA_VERSION=$1;shift
if [ ! -z "$1" -a ${1:0:1} != "-" ]; then
THIRDPARTY_IMAGE_VERSION=$1;shift
fi
fi
fi

# prior to 1.2.0 architecture was determined by uname -m
if [[ $VERSION =~ ^1\.[0-1]\.* ]]; then
export FABRIC_TAG=${MARCH}-${VERSION}
export CA_TAG=${MARCH}-${CA_VERSION}
export THIRDPARTY_TAG=${MARCH}-${THIRDPARTY_IMAGE_VERSION}
else
# starting with 1.2.0, multi-arch images will be default
: ${CA_TAG:="$CA_VERSION"}
: ${FABRIC_TAG:="$VERSION"}
: ${THIRDPARTY_TAG:="$THIRDPARTY_IMAGE_VERSION"}
fi

BINARY_FILE=hyperledger-fabric-${ARCH}-${VERSION}.tar.gz
CA_BINARY_FILE=hyperledger-fabric-ca-${ARCH}-${CA_VERSION}.tar.gz

# then parse opts
while getopts "h?dsb" opt; do
case "$opt" in
h|\?)
printHelp
exit 0
;;
d) DOCKER=false
;;
s) SAMPLES=false
;;
b) BINARIES=false
;;
esac
done

if [ "$SAMPLES" == "true" ]; then
echo
echo "Installing hyperledger/fabric-samples repo"
echo
samplesInstall
fi
if [ "$BINARIES" == "true" ]; then
echo
echo "Installing Hyperledger Fabric binaries"
echo
binariesInstall
fi
if [ "$DOCKER" == "true" ]; then
echo
echo "Installing Hyperledger Fabric docker images"
echo
dockerInstall
fi

本文转载自: 掘金

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

MySQL 调优,程序员必备的4个效率工具

发表于 2021-10-16

对于正在运行的mysql,性能如何,参数设置的是否合理,账号设置的是否存在安全隐患,你是否了然于胸呢?

俗话说工欲善其事,必先利其器,定期对你的MYSQL数据库进行一个体检,是保证数据库安全运行的重要手段,因为,好的工具是使你的工作效率倍增!

今天和大家分享几个mysql 优化的工具,你可以使用它们对你的mysql进行一个体检,生成awr报告,让你从整体上把握你的数据库的性能情况。

)

mysqltuner.pl

是mysql一个常用的数据库性能诊断工具,主要检查参数设置的合理性包括日志文件、存储引擎、安全建议及性能分析。针对潜在的问题,给出改进的建议。是mysql优化的好帮手。

在上一版本中,MySQLTuner支持MySQL / MariaDB / Percona Server的约300个指标。

项目地址:github.com/major/MySQL…

1.1 下载

1
less复制代码[root@localhost ~]#wget https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl

1.2 使用

1
2
3
4
5
6
7
8
css复制代码[root@localhost ~]# ./mysqltuner.pl --socket /var/lib/mysql/mysql.sock
>> MySQLTuner 1.7.4 - Major Hayden <major@mhtx.net>
>> Bug reports, feature requests, and downloads at http://mysqltuner.com/
>> Run with '--help' for additional options and output filtering
[--] Skipped version check for MySQLTuner script
Please enter your MySQL administrative login: root
Please enter your MySQL administrative password: [OK] Currently running supported MySQL version 5.7.23
[OK] Operating on 64-bit architecture

1.3、报告分析

1)重要关注[!!](中括号有叹号的项)例如[!!] Maximum possible memory usage: 4.8G (244.13% of installed RAM),表示内存已经严重用超了。

)

2)关注最后给的建议“Recommendations ”。

)

tuning-primer.sh

mysql的另一个优化工具,针于mysql的整体进行一个体检,对潜在的问题,给出优化的建议。

项目地址:github.com/BMDan/tunin…

目前,支持检测和优化建议的内容如下:

)

2.1 下载

1
less复制代码[root@localhost ~]#wget https://launchpad.net/mysql-tuning-primer/trunk/1.6-r1/+download/tuning-primer.sh

2.2 使用

1
2
3
4
sql复制代码[root@localhost ~]# [root@localhost dba]# ./tuning-primer.sh 

-- MYSQL PERFORMANCE TUNING PRIMER --
- By: Matthew Montgomery -

2.3 报告分析

重点查看有红色告警的选项,根据建议结合自己系统的实际情况进行修改,例如:

)

pt-variable-advisor

pt-variable-advisor 可以分析MySQL变量并就可能出现的问题提出建议。

3.1 安装

www.percona.com/downloads/p…

1
2
less复制代码[root@localhost ~]#wget https://www.percona.com/downloads/percona-toolkit/3.0.13/binary/redhat/7/x86_64/percona-toolkit-3.0.13-re85ce15-el7-x86_64-bundle.tar
[root@localhost ~]#yum install percona-toolkit-3.0.13-1.el7.x86_64.rpm

3.2 使用

pt-variable-advisor是pt工具集的一个子工具,主要用来诊断你的参数设置是否合理。

1
css复制代码[root@localhost ~]# pt-variable-advisor localhost --socket /var/lib/mysql/mysql.sock

3.3 报告分析

重点关注有WARN的信息的条目,例如:

)

pt-qurey-digest

pt-query-digest 主要功能是从日志、进程列表和tcpdump分析MySQL查询。另外,关注公众号码猿技术专栏,回复关键词9527,送你一份Spring Cloud Alibaba实战视频教程!

4.1安装

具体参考3.1节

4.2使用

pt-query-digest主要用来分析mysql的慢日志,与mysqldumpshow工具相比,py-query_digest 工具的分析结果更具体,更完善。

1
csharp复制代码[root@localhost ~]# pt-query-digest /var/lib/mysql/slowtest-slow.log

4.3 常见用法分析

1)直接分析慢查询文件:

1
c复制代码pt-query-digest /var/lib/mysql/slowtest-slow.log > slow_report.log

2)分析最近12小时内的查询:

1
c复制代码pt-query-digest --since=12h /var/lib/mysql/slowtest-slow.log > slow_report2.log

3)分析指定时间范围内的查询:

1
c复制代码pt-query-digest /var/lib/mysql/slowtest-slow.log --since '2017-01-07 09:30:00' --until '2017-01-07 10:00:00'> > slow_report3.log

4)分析指含有select语句的慢查询

1
c复制代码pt-query-digest --filter '$event->{fingerprint} =~ m/^select/i' /var/lib/mysql/slowtest-slow.log> slow_report4.log

5)针对某个用户的慢查询

1
c复制代码pt-query-digest --filter '($event->{user} || "") =~ m/^root/i' /var/lib/mysql/slowtest-slow.log> slow_report5.log

6)查询所有所有的全表扫描或full join的慢查询

1
swift复制代码pt-query-digest --filter '(($event->{Full_scan} || "") eq "yes") ||(($event->{Full_join} || "") eq "yes")' /var/lib/mysql/slowtest-slow.log> slow_report6.log

4.4 报告分析

第一部分:总体统计结果

Overall:总共有多少条查询

Time range:查询执行的时间范围

unique:唯一查询数量,即对查询条件进行参数化以后,总共有多少个不同的查询

total:总计

min:最小

max:最大

avg:平均

95%:把所有值从小到大排列,位置位于95%的那个数,这个数一般最具有参考价值

median:中位数,把所有值从小到大排列,位置位于中间那个数

第二部分:查询分组统计结果

Rank:所有语句的排名,默认按查询时间降序排列,通过–order-by指定

Query ID:语句的ID,(去掉多余空格和文本字符,计算hash值)

Response:总的响应时间

time:该查询在本次分析中总的时间占比

calls:执行次数,即本次分析总共有多少条这种类型的查询语句

R/Call:平均每次执行的响应时间

V/M:响应时间Variance-to-mean的比率

Item:查询对象

第三部分:每一种查询的详细统计结果

ID:查询的ID号,和上图的Query ID对应

Databases:数据库名

Users:各个用户执行的次数(占比)

Query_time distribution :查询时间分布, 长短体现区间占比。

Tables:查询中涉及到的表

Explain:SQL语句

本文转载自: 掘金

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

Redis 性能测试与监控

发表于 2021-10-16

很多人在安装部署好Redis后,就没有对Rredis的配置和部署等有效性和高可用性进行性能测试,最终导致上线出现缓存穿透、雪崩等现象,导致性能还是有问题,其实做为技术运维人员在部署好Redis后可以使用Redis自带的压测工具进行简易型压测,如下命令:

redis-benchmark [option] [option value]

例如在本地搭建一个Redis服务,IP地址是10.100.81.171,这时需要模拟100用户并发链接请求,每个用户现场循环访问100次。

redis-benchmark -h 10.100.81.171 -p 6379 -c 100 -n 100000

参数详解:

1、100000 requests completed in 1.60 seconds //默认是100000,上面有,请求在1.6s内完成 2、3 bytes payload,每次写入3个字节的数据 3、keep alive: 1,保持一个连接,一台服务器来处理这些请求 4、100.00% <= 2 milliseconds,所有请求2毫秒完成 5、62656.64 requests per second 每次能处理请求数量

具体如下图:

)

Redis读写情况压测,如下:测试存取大小为500字节的数据包的性能

redis-benchmark -h 10.100.81.171 -p 6379 -q -d 500

)

这时可以通过监控命令或者其他工具看到Redis服务的服务器资源使用情况:

)

redis-benchmark 工具命令使用介绍:

)

本文转载自: 掘金

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

《算法日记-玩出新花样》- 两数求和的三种解法

发表于 2021-10-16

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

💨 一、前言

  • 大家好,我是小诚,终于,我还是将“魔爪”伸到了算法,在编写《算法日记》之前,我也考虑过许多问题,现在网上关于leetcode算法的案例这么多,我再重新”造轮子”有没有必要。
  • 最后和圈子中的朋友聊了聊(有能够聊天的朋友还是不错的),其实这些担心都是多余的,在编写的过程中,我自己不仅在算法和文笔上有了进步,还可能会帮助到一些”有缘”看到文章的朋友,这样即使重复”造轮子”又如何。
  • 《算法日记》系列特点:更加注重结合实际情况,将算法和实际开发中问题或面试举例相结合,让学习算法更有意义。(不信继续往下看!)
  • 如果文章对你有帮助,可以帮忙一键三连和专栏订阅哦!

🔮 二、两数求和

🔴 2.1、算法题目

  给定一个整数数组 nums 和一个整数目标值 target,请你 在该数组中找出和为目标值 target 的那个整数,并返回它们的数组下标。

  你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。返回答案顺序任意。

  示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码示例一:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例二:
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例三:
输入:nums = [3,3], target = 6
输出:[0,1]

🟠 2.2、分析题目

  题目的意思很清晰,输入一个目标值,然后在数组中找到两个元素值之和为这个目标值,并返回它们这两个元素的数组下标,需要注意的题目要求中提到:数组中同一个元素在答案中不能重复出现,这个是什么含义呢?

  其实看上面的示例三就知道,输入目标值为6,但是数组中元素都为3,这时候返回的结果必须为【0,1】或者【1,0】,而不能是【0,0】或者【1,1】即不能返回下标都是一样的结果。

🟡 2.3、解题方案一

  一、解题思路

  梳理完题目的含义后,相信很多人脑海里已经浮现了一个解题方案,那就是:使用for循环 + if条件判断来找出和目标值一致的两个元素,下面来看看如何实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码    /**
* 方案一:双重for循环
* @param nums
* @param target
* @return
*/
public static int[] twoSum2(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = 1; j < nums.length; j++) {
// 比较查询找到符合目标值的两个元素
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return null;
}

  二、解题方案评审

  如果是在面试中,使用上面的题目解决这个问题,面试官最多给你打60分,为什么? 我们需要先看看这个算法的时间复杂度是多少!(不知道时间复杂度和算法指标的一定要先看我上一篇文章。

  从算法知识讲解的文章中,我们可以知道 时间复杂度主要是跟:对运行时间有消耗的基本操作的执行次数成正比。 再继续看上面的程序,我们会发现,对运行时间消耗的基本操作实际上就是if条件判断的语句。

  随着问题规模n的增大,它的判断次数也会增多,使用问题规模函数来表达的话就是:f(n) = n2n^2n2,既它的时间复杂度为O(n2n^2n2), 那我们有没有更好的方式来进行优化呢? 答案是有滴,请继续往下看!

image.png

🟢 2.4、解题方案一优化

  一、优化思路

  上面的方案我们使用的是暴力破解的方式,最差情况是两个循环到最后一个元素才能够找到符合题目的答案,既然我们知道了运行时间都花费在if条件的比较逻辑上,是否能够通过减少比较逻辑达到减少运行时间呢,没错,确实存在可以优化的方法,下面先来看看优化后的代码吧。

  二、优化后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码/**
* 方案一优化:选择排序法
*
* @param nums
* @param target
* @return
*/
public static int[] twoSum2(int[] nums, int target) {
// length - 1的目的:每轮都会和集合其他元素比较筛选出一个最大/小值
// 意味着最后一个元素已经和之前所有的元素比较过了,无需再循环一次
for (int i = 0; i < nums.length - 1; i++) {
// j = i + 1表示:比较元素跳过与自身的比较,较少比较次数
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return null;
}

  特点: 对比第一种方案和优化后的方案,你会发现它们的区别主要是在第二层的 for (int j = i + 1; j < nums.length; j++) 中,这个的含义主要是:每轮都将最外层之前比较过的元素筛选出去,不再进行重复比较,具体可以通过下面的案例来清楚认识到。

  比如要循环一个数组【1、2、11、7】找出和为9的两个元素下标,我们会进行如下的操作:

  1、第一轮循环外层会使用【1】和内层中【2,、11、7】做运算,但没找有符合条件的两个元素,继续循环。

  2、第二次外层循环拿2和内层中的【11,7】做元素,得到符合条件的结果,如果没有,则以此类推继续进行第三轮、第四轮…的操作。

  通过这个操作,我们会发现,每次循环都会跳过之前已经比较过的元素(因为已经确定它们是不满足条件的),即内层循环都是从i+1开始比较的,这个就减少了不必要的比较,节省了运算时间,下面我们来推到下它的时间复杂度吧。

  三、推导优化后的代码的时间复杂度

  通过上面的案例步骤我们会发现,随着每次循环的进行(即i++),内层循环的次数(即j<nums.length)会逐渐减小(因为j=i+1),呈现如下规律:n-1次、n-2次,n-3次...等等,你是不是已经发现,内层循环的比较次数就是一个等差数列,公差为1(注:如果对等差数列不是很熟悉的同学,可以直接百度下,几分钟就能掌握)

  通过等差数列的求和方式:Sn=na1+n(n-1)d/2,我们可以得到耗费运行时间的函数f(n) = na1+n(n-1)d/2,再根据大O记法的推断,可以得出优化后的方法时间复杂度为O(n2n^2n2)。,不知道大O记法的请看我上一篇文章。

  四、这个优化方式有何意义

  上面优化后的方案后算法推算出来的时间复杂度还是O(n2n^2n2),肯定有同学会疑问,既然最终的时间复杂度和优化之前一样,这个方案有啥作用?诚然,这个方法如果当输入规模即n无限扩大的时候,和没有优化之前效率相差不大,但是在面试的时候,如果你能够给面试官讲解出来,面试官会在60分的基础给你加10分,为什么?

  这10分是对你遇到问题肯钻研、并且能够找到一些提高效率的方式的一个肯定,面试官看重的不仅是你当前的一个能力,还要考量的是你的一个学习力、成长力。

🔵 2.5、解题方案二

  既然使用上面的优化方式不能解决我们的问题,我们就需要另辟蹊径。相信开发的同学或多或少都听说过“使用时间换空间和使用空间换时间”这两个概念,那么在这个题目中能否采用这个方式呢?没错,解决这个问题采用空间换取时间的方式会更好,下面就来看看具体的实现方式吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码    public static int[] twoSum(int[] nums, int target) {
// 使用HashTable存储数组的值和下标
Hashtable<Integer, Integer> keyMap = new Hashtable<Integer, Integer>();
for (int i = 0; i < nums.length; i++) {
// 每次循环时判断是否在hashtable中存在与当前循环的值相加等于target,如果有则返回
if (keyMap.containsKey(target - nums[i])) {
return new int[]{keyMap.get(target - nums[i]), i};
}
// 没有找到符合条件的值则将元素值和下标存储起来
keyMap.put(nums[i], i);
}
return null;
}

  通过上面的代码,你会发现问题规模函数f(n) = n(即只存在一层循环,随着n的增到,循环次数也会增大),通过大O记法可推断出时间复杂度为O(n),上面的算法中引入了Hashtable,目的就是将数组中的值存储起来,减少内层循环,这个就是空间换时间的一种方式,下面通过具体的图片来看看时间复杂度O(n)和O(n2n^2n2)有多大的区别。

时间复杂度O(n)和O(n^2^的区别

🟣 2.6、实际业务中的运用

  在业务中,使用多层for查找符合条件的数据是很常见的业务,比如对某些数据进行排序,我们会使用到选择排序法、冒泡排序法等,它们都是通过for去解决排序问题。

  以控件换时间的方案,在开发中也非常常见,比如为了提高数据的检索速度,我们会给适当的字段创建对应的索引,以便快速定位到需要的数据。还有各种nosql数据库如redis,本质上也是通过控件换时间的方式来提高效率。

📑 三、写在最后

  通过上面三种不同解决方案,你会发现,寻找最优方式对大多数人来说是一个循序渐进的过程,很多人刚开始并不会立马就想到使用空间换取时间的方案来解决这个问题,就像你刚开始学习算法的时候,可能也觉得很难,又要编程、还可能涉及到数学知识。

  你可以有退却之心,但更要有试一试的勇气,算法看上去是很难,但是,你可以先开始第一步,尝试着了解算法的知识,尝试着去解决简单的算法问题,一步步坚持下来,你会发现,算法也没有这么难,数学也没有这么难。

  请记住!一个人可以走得很快,但是一群人才能走得更远! 欢迎加入技术人的圈子【IT学习日记】,共享资源,共同成长!

本文转载自: 掘金

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

mysql datetime类型的字段进行时间比较+myba

发表于 2021-10-15

mysql中datetime类型默认存的是精确到s,也就是存进去的时间是用这个格式”yyyy-MM-dd HH:ss:mm”。

如果使用在用这个字段作为过滤条件,使用mybatis或者plus直接把这个日期作为参数传递进去的话,mybatis会默认使用timestamp类型,也就是此时这个date会被转换成以毫秒为单位的long值。

在执行sql的时候,就会出现明明时间小,但是却查出来了的情况,因为mysql的datetime也转换成时间戳比较的话,是默认转换成秒为单位的。

总结:如果数据库字段类型为默认的datetime的话,使用mybatis传参日期进行比较的时候,不能直接传入Date类型。

解决方式:

1.修改字段类型变成datetime(4),也就是精确到毫秒

2.传入日期的字符串

3.对日期进行格式化处理

本文转载自: 掘金

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

手把手,带你从零封装Gin框架(五):静态资源处理 & 优雅

发表于 2021-10-15

项目源码

地址: github.com/jassue/jass…

前言

这一篇将对路由进行分组调整,把定义路由的文件集中到同一个目录下,并处理前端项目打包后的静态文件。在 Go 1.8 及以上版本中,内置的 http.Server 提供了 Shutdown() 方法,支持平滑重启服务器,本次将使用它调整项目启动代码,若 Go 版本低于 1.8 可以使用 fvbock/endless 来替代

路由分组调整

新建 routes/api.go 文件,用来存放 api 分组路由

1
2
3
4
5
6
7
8
9
10
11
12
13
go复制代码package routes

import (
"github.com/gin-gonic/gin"
"net/http"
)

// SetApiGroupRoutes 定义 api 分组路由
func SetApiGroupRoutes(router *gin.RouterGroup) {
router.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
}

新建 bootstrap/router.go 文件,编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
go复制代码package bootstrap

import (
"github.com/gin-gonic/gin"
"jassue-gin/global"
"jassue-gin/routes"
)

func setupRouter() *gin.Engine {
router := gin.Default()

// 注册 api 分组路由
apiGroup := router.Group("/api")
routes.SetApiGroupRoutes(apiGroup)

return router
}

// RunServer 启动服务器
func RunServer() {
r := setupRouter()
r.Run(":" + global.App.Config.App.Port)
}

若之后还有其它的分组路由,可以先在 routes 目录下新建一个文件,编写定义路由的方法,然后再到 bootstrap/router.go 调用注册

在 main.go 文件中调用 RunServer() 方法

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
go复制代码package main

import (
"jassue-gin/bootstrap"
"jassue-gin/global"
)

func main() {
// 初始化配置
bootstrap.InitializeConfig()

// 初始化日志
global.App.Log = bootstrap.InitializeLog()
global.App.Log.Info("log init success!")

// 初始化数据库
global.App.DB = bootstrap.InitializeDB()
// 程序关闭前,释放数据库连接
defer func() {
if global.App.DB != nil {
db, _ := global.App.DB.DB()
db.Close()
}
}()

// 启动服务器
bootstrap.RunServer()
}

静态资源处理

在 bootstrap/router.go 文件,setupRouter() 方法中编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go复制代码func setupRouter() *gin.Engine {
router := gin.Default()

// 前端项目静态资源
router.StaticFile("/", "./static/dist/index.html")
router.Static("/assets", "./static/dist/assets")
router.StaticFile("/favicon.ico", "./static/dist/favicon.ico")
// 其他静态资源
router.Static("/public", "./static")
router.Static("/storage", "./storage/app/public")

// 注册 api 分组路由
apiGroup := router.Group("/api")
routes.SetApiGroupRoutes(apiGroup)

return router
}

使用 docker 快速打包一份前端项目文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
shell复制代码# 创建 node环境 容器
docker run -idt --name vue-app jassue/node
# 进入容器
docker exec -it vue-app bash
# 初始化 vue 模板
npm init @vitejs/app . --template vue-ts
# 安装项目依赖
npm install
# 打包
npm run build
# 退出容器
exit
# 拷贝前端文件到 go 项目静态资源文件夹
docker cp vue-app:/app/dist ~/go/src/jassue-gin/static

启动 main.go ,访问 http://localhost:8888/ ,前端资源处理成功

image-20211015195022062.png

优雅重启/停止服务器

在 bootstrap/router.go 文件中,调整 RunServer() 方法

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
go复制代码package bootstrap

import (
"context"
"github.com/gin-gonic/gin"
"jassue-gin/global"
"jassue-gin/routes"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)

//...

func RunServer() {
r := setupRouter()

srv := &http.Server{
Addr: ":" + global.App.Config.App.Port,
Handler: r,
}

go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()

// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutdown Server ...")

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}

在 routes/api.go 中,添加一条测试路由:

1
2
3
4
go复制代码router.GET("/test", func(c *gin.Context) {
time.Sleep(5*time.Second)
c.String(http.StatusOK, "success")
})

启动 main.go,访问 http://localhost:8888/api/test ,使用 CTRL + C 停止服务器,如下图所示:

image-20211015200006388.png

服务器接收到中止命令后,依旧等待 /api/test 接口完成响应后才停止服务器

本文转载自: 掘金

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

1…488489490…956

开发者博客

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