GDI+下字体大小自适应方案初探

news2024/11/19 14:30:43

在某个瞬间,我忽然发觉,三体或是AI,本质上是非常相近的事物,甚至在面对任何未知领域的时候,人类总会不自觉地划分为降临派、拯救派和幸存派。姑且不论马斯克等人叫停 GPT-5 的真实动机如何,当大语言模型(LLM)裹挟着 AIGC 的浪潮气势汹汹地袭来时,你是否会像很多人一样,担心有一天会被机器取代以致于失业呢?此前,我曾自嘲般地提到过,我是一名 YAML 工程师 、Markdown 工程师、Dockerfile 工程师……,甚至以后还会变成一名 Prompt 工程师,而这背后的因果关系,本质上我们对这个世界的编程方式,正在逐步地从 DSL 转向自然语言。我个人认为,任何低端、重复的工作最终都会被机器取代,而诸如情感、艺术、心理、创意……等非理性领域,则可能会成为人类最后的防线。两年前,柯洁以 0:3 的比分输给 AlphaGo,一度在棋盘前情绪失控,我想,那一刻他大概不会想到两年后还会出现 ChatGPT。在《蜘蛛侠:英雄无归》 电影里面,彼得·帕克对奇异博士说,“你知道比魔法更神奇的东西是什么吗?是数学”。我个人非常喜欢这句话,因为在绝对的理性面前,一切的技巧都是徒然,更重要的是,如此深刻的哲理,居然是来自生活中一个真实案例。

电子签章与数学

好的,虽然我们说那些低端、重复的工作最终都会被机器取代,但是真正残酷的现实是,我们并没有那么多需要创造力的工作,就像我们并不需要那么多架构师一样。毕竟,你想象不到,一个人在五年前和五年后做的工作毫无差别,特别是企业级应用中非常普遍的打印。过去这些年,企业数字化转型的口号一直在喊,可到头来我们并没有等来真正的无纸化,企业依然对打印单据这件事情乐此不疲,仿佛没有这一张纸业务就没法开展一样。在这个过程中,企业会希望你能在单据上加盖公司的印章,这就产生了所谓的“电子签章”的需求。当然,我们这里不考虑电子签章的申请、加/解密、防伪等实际的流程,我们只是考虑将其通过 GDI+ 绘制出来即可。考虑到印章有圆形和椭圆形两种形制,所以,我们下面来进行分类讨论。

圆形印章

可以注意到,圆形印章通常由四部分组成,分别是顶部文字、中心部分的五角星、中下部分文字和底部文字。

在这里插入图片描述

其中,顶部文字表示印章所属的公司/组织/机构,底部文字表示14位印章编号,这两部分文字均呈圆弧状分布。具体该如何实现呢?我们来一起看一下。首先,圆形印章的轮廓是一个标准的圆形,这个绘制非常容易:

// 从位图创建一个画布
var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
var g = Graphics.FromImage(bitmap);

// 绘制圆形边框 
var rect = new RectangleF(x, y, radius, radius);
var Pen pen = new Pen(Color.Red, 3.0f);
g.DrawEllipse(pen, rect);

而对于中心部分的五角星,我们使用一个路径填充即可。此时,问题的关键是在圆上找出五角星的五个顶点。显然,五角星的顶点满足下面的几何关系:

在这里插入图片描述

利用三角函数的知识,我们可以非常容易地写出对应代码,请注意,计算机中使用的坐标系 Y 轴正方向向下:

var Radius = rect.Width / 2 * 0.45;
var Center = new PointF(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
PointF[] points = new PointF[]
{
    // P0
    new PointF(Center.X, (float)(Center.Y - Radius)),
    // P1
    new PointF(
        (float)(Center.X + Radius * Math.Sin(72 * Math.PI / 180)), 
        (float)(Center.Y - Radius * Math.Cos(72 * Math.PI / 180))
    ),
    // P2
    new PointF(
        (float)(Center.X + Radius * Math.Sin(36 * Math.PI / 180)), 
        (float)(Center.Y + Radius * Math.Cos(36* Math.PI / 180))
    ),
    // P3
    new PointF(
        (float)(Center.X - Radius * Math.Sin(36 * Math.PI / 180)),
        (float)( Center.Y + Radius * Math.Cos(36 * Math.PI / 180))
    ),
    // P4
    new PointF(
        (float)(Center.X - Radius * Math.Sin(72 * Math.PI / 180)), 
        (float)(Center.Y - Radius * Math.Cos(72 * Math.PI / 180))
    ),
};

// 根据五个点生成一个封闭路径
var path = new GraphicsPath(FillMode.Winding);
path.AddLine(points[0], points[2]);
path.AddLine(points[2], points[4]);
path.AddLine(points[4], points[1]);
path.AddLine(points[1], points[3]);
path.AddLine(points[3], points[0]);
path.CloseFigure();

// 填充路径
g.RotateTransform(0);
g.FillPath(new SolidBrush(Color.Red), path);

接下来,我们来考虑如何绘制这两段呈圆弧状分布的文字,这里需要用到的数学知识是圆的参数方程以及三角函数。其基本思路是:选定一个起始角度,再根据总角度和文字数目计算一个步长,进而确定每一个文字对应的角度。譬如,这里假定上半部分圆弧总角度为300 度,下半部分圆弧总角度为 60 度:

var center = new PointF(rect.X + rect.Width / 2.0f, rect.Y + rect.Height / 2.0f);
var fontToFit = new Font("宋体", 13, FontStyle.Bold, GraphicsUnit.Pixel);
var totalAngle = Math.PI * 5 / 3;
var stepAngle = totalAngle / (text1.Length + 1);
var startAngle = Math.PI * 4 / 3;
for (int i = 0; i < text.Length; i++)
{
    float angle = (float)(startAngle - (i + 1) * stepAngle);
    if (angle < 0) angle += (float)Math.PI * 2;
    var point = new PointF(
        center.X + radius * (float)Math.Cos(angle), 
        center.Y - radius * (float)Math.Sin(angle)
    );

    g.DrawString(text1[i].ToString(), fontToFit1, brush, point.x, point.y);
}

我们知道,在三角函数定义中,逆时针方向为正方向,所以,对于上半部分的弧形文字,只需要用起始角度依次减去对应的步长数。为了让后续计算角度的时候更方便一点,这里会将负角统一转换为正角。接下来的事情就顺理成章啦,因为你可以利用三角函数计算出对应的坐标,此时,我们只需要在指定的位置调用 DrawString 函数将每个字符绘制出来即可:

在这里插入图片描述

不过,你很快会发现一个问题,那就是这些文字的方向并没有像一般印章那样,始终“正对”着你的实现。此时,你会用到第三个数学知识,即:当一个点变为原点以后,它与 X 轴正方向的夹角会如何变化?你为什么需要这个知识呢?因为我们需要对每个字符做一次平移变换、一次旋转变换,这样才能达到我们的目的,即:无论你从哪一个方向去看这些文字,它对你来说都是“正”的,其代码实现如下:

var center = new PointF(rect.X + rect.Width / 2.0f, rect.Y + rect.Height / 2.0f);
var fontToFit = new Font("宋体", 13, FontStyle.Bold, GraphicsUnit.Pixel);
var totalAngle = Math.PI * 5 / 3;
var stepAngle = totalAngle / (text1.Length + 1);
var startAngle = Math.PI * 4 / 3;
for (int i = 0; i < text.Length; i++)
{
    float angle = (float)(startAngle - (i + 1) * stepAngle);
    if (angle < 0) angle += (float)Math.PI * 2;
    var point = new PointF(
        center.X + radius * (float)Math.Cos(angle), 
        center.Y - radius * (float)Math.Sin(angle)
    );

    g.TranslateTransform(point.X, point.Y);
    var transformAngle = (float)(angle * 180 / Math.PI + 90);
    if (transformAngle > 360) transformAngle -= 360;

    // 注意:RotateTransform() 方法旋转方向是顺时针方向,所以,要用 360 度减去当前角度
    // 印章上方的文字需要正对着外侧,所以,要再加上 180 度
    transformAngle = 360 - transformAngle + 180;
    g.RotateTransform(transformAngle);
    g.DrawString(text[i].ToString(), fontToFit, brush, 0, 0, format);
    g.ResetTransform();
}

这里的关键是 TranslateTransform()RotateTransform() 两个方法,这就是我们上文中提到的平移变换和旋转变换。为什么要这样处理呢?因为我们希望的看到是,文字旋转到“正确”的方向且保持位置不变,如果没有平移转换的话,它会就变成点 A 围绕 点 B 旋转,这样显示不符合我们的预期。总之,当一切都在向着预期的方向发展的时候,我们就可以利用这个技巧“照葫芦画瓢”,需要注意的是,底部弧形文字的“正方向”是向下的,因此,在做旋转变换的时候,两者会相差 180 度。对此,我想说,数学真的好用:

在这里插入图片描述

相比之下,绘制中下部分文字就非常简单啦,因为它是在一个矩形范围上绘制,你唯一需要的就只有一个勾股定理。
理论上,你只要将以上代码片段整合起来,就可以绘制出一个相对完美的印章,我个人踩坑的体会是:真正的困难常常是印章的实际尺寸、打印尺寸、DPI、分辨率这些客观因素。

椭圆形印章

好了,接下来再说椭圆形印章的绘制,原理基本相同,对应的数学知识是椭圆的参数方程。不知道大家是否还记得圆锥曲线、离心率、焦点等概念,博主在写这篇博客的时候,的确是回过头再次重温了这些知识。作为一名程序员,平时没少被别人“教育”业务的重要性,可对我来说,业务大概就是朝三暮四、朝秦暮楚的代名词,相比这些人为想象和构筑的东西,我更喜欢风、沙、星辰这些接近自然和宇宙的东西,对我来说,数学便是如此。好了,下面是绘制弧形文字的代码片段,供大家参考:

var a = rect.Width / 2 * 0.8f;
var b = rect.Height / 2 * 0.8f;
var center = new PointF(rect.X + rect.Width / 2.0f, rect.Y + rect.Height / 2.0f);
var fontToFit = new Font("宋体", 13, FontStyle.Bold, GraphicsUnit.Pixel);
var totalAngle = Math.PI * 5 / 3
var stepAngle = totalAngle / (text1.Length + 1);
var startAngle = Math.PI * 4 / 3
for (int i = 0; i < text.Length; i++)
{
    float angle = (float)(startAngle - (i + 1) * stepAngle);
    if (angle < 0) angle += (float)Math.PI * 2;

    // 利用椭圆参数方程计算坐标
    var point = new PointF(
        center.X + a * (float)Math.Cos(angle), 
        center.Y - b * (float)Math.Sin(angle)
    );

    g.TranslateTransform(point.X, point.Y);
    var transformAngle = (float)(angle * 180 / Math.PI + 90);
    if (transformAngle > 360) transformAngle -= 360;

    // 注意:RotateTransform() 方法旋转方向是顺时针方向,所以,要用 360 度减去当前角度
    // 印章上方的文字需要正对着外侧,所以,要再加上 180 度
    transformAngle = 360 - transformAngle + 180;
    g.RotateTransform(transformAngle);
    g.DrawString(text[i].ToString(), fontToFit1, brush, 0, 0, format);
    g.ResetTransform();
}

可以看出,本质上只需要用半长轴 a 和半短轴 b 替换半径即可。考虑到圆是椭圆的特殊形式,这种从特殊到一般的认知方式,难道不显得有趣吗?同样地,这里提供一个椭圆形印章的效果图:

在这里插入图片描述

至此,关于如何通过 GDI+ 绘制印章,笔者可谓是倾囊相授啦,在这个过程中,我最享受的环节,恰恰是那些再寻常不过的数学知识,当你觉得 AI 有一天一定会取代人类的时候,我以为,这代表着绝对理性的胜利,就像令柯洁落泪的 AlphaGo 一样,没有任何技巧,它只是在经历无数次计算以后得出的必然结果,从某种意义上来讲,数学反而是宇宙间最简单的学问,不是吗?如果说人工智能里还有哪些更接近“玄学”的东西,我以为,大概是我们自始至终都没能解释清楚,模仿神经元的神经网络到底是怎样一步步地产生了“意识”?而这一切就好比,你的确知道 ChatGPT 给了你一个满意的答案,但你始终不知道的是,它到底是在经过了一个什么样的思考过程以后,才能给出一个如此契合你心理预期的答案?恍惚之间,我会觉得它符合人类眼中的“高情商”标准,即使它对你一无所知,可它还是能讲出令你“舒服”的话语,如果语言本身就充满了这种迷惑性,那么人类最看重的情感到底又算什么?

字体自适应方案

好了,现在让我们来考虑得更长远一点,当我们实现了一个相对通用的印章绘制算法以后,我们会希望通过配置这些文字来生成不同的印章。此时,一个新的问题产生了:在印章尺寸固定(有相关标准)的情况下,如何能兼容不同长度的文字?一个容易想到的方案是修改字体大小。譬如,当文字较多的时候就缩小字体,当文字较少时就放大字体。所以,下面我想分享的是动态调整字体大小实现字体自适应。

基于宽高

第一种方案基于宽高,即字体是绘制在一个矩形区域内,印章的中下部分通常用来表示印章的用途,此时,我们可以利用 MeasureString 这个方法来测量整个字符串的宽度,并将其和当前矩形的宽度进行比较。如果实际宽度大于当前矩形的宽度,则需要减小字号;如果实际宽度小于当前矩形的宽度,则需要增加字号。当然,缩小的时候可以给一个最小字号,因为你要确保别人能看清印章上的文字;放大的时候需要考虑矩形的高度,因为你要确保印章上的元素不会相互重叠。下面是一个基本实现:

float ScaleFontSizeByContainerSize(Graphics g, string text, Font font, SizeF size) {
    var fontSize = font.Size;

    // 对字体缩小时需要考虑最小的字体大小
    var measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    while (measuredSize.Width > size.Width) {
        fontSize -= 0.5f;
        if (fontSize <= MIN_FONT_SIZE) break;
        measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    }

    // 对字体放大时需要考虑高度的问题
    measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    while (measuredSize.Width < size.Width && measuredSize.Height < size.Height) {
        fontSize += 0.5f;
        measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    }

    return fontSize;
}

如图所示,下图展示了相同尺寸下,文字根据字数的多少按不同的字号动态进行缩放:

在这里插入图片描述

基于周长

第二种方案基于周长,主要是针对印章中呈圆弧状分布的这些文字,此时,宽度和高度不足以评估文字能否“恰当”地分布在印章上,所以,我们就可以尝试用周长来进行比较。按照微积分的思想,我们可以粗略地认为,每个字符的宽度累加起来,其总和就约等于对应的这段孤线的长度。在这种情况下,我们可以考虑使用弧长公式和椭圆的周长公式。当文字宽度小于周长时,表明字体还可以放大一点;当文字宽度大于周长时,表明字体还可以缩小一点。类似地,这里给出一个基本实现:

float ScaleFontSizeByPerimeter(Graphics g, string text, Font font, float radius, float angle) {
    var fontSize = font.Size;
    
    // 圆形周长公式
    var perimeter = angle * Math.PI * radius / 180;

    // // 椭圆周长公式
    // var h = Math.Pow((a - b) / (a + b), 2);
    // var c = Math.PI * (a + b) * (1 + (3 * h / (10 + (4 - 3 * h))));
    // var perimeter = c * angle / 360;

    // 对字体缩小时需要考虑最小的字体大小
    var measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    while (measuredSize.Width > perimeter) {
        fontSize -= 0.1f;
        if (fontSize <= MIN_FONT_SIZE) break;
        measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    }

    // 对字体放大时需要考虑高度的问题
    measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    while (measuredSize.Width < perimeter) {
        fontSize += 0.1f;
        if (fontSize >= MAX_FONT_SIZE) break;
        measuredSize = g.MeasureString(text, new Font(font.FontFamily, fontSize));
    }

    return fontSize;
}

如图所示,下图展示了相同尺寸下,文字根据字数的多少按不同的字号动态进行缩放:

在这里插入图片描述

本文小结

写这篇博客时,其实是在一个多事之秋。如果是指新海诚的新电影《铃芽之旅》,我大概会写遗憾以及自我和解;如果是指 ChatGPT,我大概会写我对于人工智能的看法;如果是指景甜和张继科这对分手了的男女朋友,我大概会写我对于人类情感的理解;如果是指中国电科的离职事件,我大概会写我对生产力/生产关系的想法……可当很多东西纠缠在一起的时候,任何一种情绪注定都无法独自存活下去,所以,我还是决定写一点可以掌控的事物。如彼得·帕克所言,“比魔法更神奇的东西是数学”,在一个不确定性远大于确定性的时代,能够再次感受到数学世界的美好实在是一种幸运,因为一切的公式/定理都会引导你通往某个确定的地方,正如当我画完圆形印章以后,我可以快速地画出椭圆形印章,它不会增加一丝一毫的心智上的负担,而人类热衷于构建的业务流程,则永远都存在着这样那样的缺陷。也许,直到这一刻,我才能够明白科学家们渴望用一个公式描述这个世界的偏执。因为,在一个不确定的世界里寻找确定,这件事情本身就足够迷人,就像你输入到 AI 模型里的一个个提示词,它们本身或许是随机的、不确定的,可你需要的可能是一个确定的结果。毕竟,这个世界早就复杂到连选择本身都是一件困难重重的事情,对吧?
再次感受到数学世界的美好实在是一种幸运,因为一切的公式/定理都会引导你通往某个确定的地方,正如当我画完圆形印章以后,我可以快速地画出椭圆形印章,它不会增加一丝一毫的心智上的负担,而人类热衷于构建的业务流程,则永远都存在着这样那样的缺陷。也许,直到这一刻,我才能够明白科学家们渴望用一个公式描述这个世界的偏执。因为,在一个不确定的世界里寻找确定,这件事情本身就足够迷人,就像你输入到 AI 模型里的一个个提示词,它们本身或许是随机的、不确定的,可你需要的可能是一个确定的结果。毕竟,这个世界早就复杂到连选择本身都是一件困难重重的事情,对吧?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/419003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JMU Oracle实验四

用来记录实验操作的 spool E:\oracle_record\record20230406.txt ... spool off老师问的问题 让我展示了一下open_cursor的alter操作问我怎么查看spfile文件&#xff0c;实例&#xff0c;会话的参数内容就这两个 1. 采用不同的方法查询Oracle数据库当前使用的初始化参数文件…

仅三行就能学会数据分析——Sweetviz详解

文章目录前言一、准备二、sweetviz 基本用法1.引入库2.读入数据3.调整报告布局总结前言 Sweetviz是一个开源Python库&#xff0c;它只需三行代码就可以生成漂亮的高精度可视化效果来启动EDA(探索性数据分析)。输出一个HTML。 如上图所示&#xff0c;它不仅能根据性别、年龄等…

PHP 调用百度人脸对比

本文章主要介绍人脸对比API能力、应用场景、请求实例、参数说明。 接口能力 两张人脸图片相似度对比&#xff1a;比对两张图片中人脸的相似度&#xff0c;并返回相似度分值。 多种图片类型&#xff1a;支持生活照、证件照、身份证芯片照、带网纹照四种类型的人脸对比。 活体检测…

redis双写一致问题场景及方案

产生问题的场景 写入数据库后立即更新缓存&#xff08;较常见&#xff09; 这种场景下 问题产生的主要原因是写入数据库与更新缓存非原子性 有延迟 所以这样会导致谁更新缓存慢 谁会真正的更新缓存 更新数据库后立即删除缓存 查询时再插入缓存 与上一场景类似 虽然写入数据库…

C#,码海拾贝(18)——矩阵的(一般)三角分解法(Triangular Decomposition)之C#源代码,《C#数值计算算法编程》源代码升级改进版

1 三角分解法 Triangular Decomposition 三角分解法亦称因子分解法&#xff0c;由消元法演变而来的解线性方程组的一类方法。设方程组的矩阵形式为Axb&#xff0c;三角分解法就是将系数矩阵A分解为一个下三角矩阵L和一个上三角矩阵U之积&#xff1a;ALU&#xff0c;然后依次解…

Vue——模板引用

目录 访问模板引用​ v-for 中的模板引用​ 函数模板引用​ 组件上的 ref​ 虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作&#xff0c;但在某些情况下&#xff0c;我们仍然需要直接访问底层 DOM 元素。要实现这一点&#xff0c;我们可以使用特殊的 ref att…

Vue3技术3之setup的两个注意点、computed计算属性

Vue3技术3setup的两个注意点Vue2中的一些知识点App.vueDemo.vuesetup的两个注意点第一个注意点App.vueDemo.vue第二个注意点App.vueDemoTwo.vue总结computed计算属性App.vueDemo.vue总结setup的两个注意点 Vue2中的一些知识点 App.vue <template><div><h1>…

CnOpenData制造业单项冠军企业工商注册基本信息数据

一、数据简介 2016年3月&#xff0c;工信部印发《制造业单项冠军企业培育提升专项行动实施方案》&#xff0c;方案指出&#xff1a;“到2025年&#xff0c;总结提升200家制造业单项冠军示范企业&#xff0c;发现和培育600家有潜力成长为单项冠军的企业”。截至2022年&#xff0…

工程行业管理系统-专业的工程管理软件-提供一站式服务

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…

基于Python的简单40例和爬虫详细讲解(文末赠书)

目录 先来看看Python40例 学习Python容易坐牢&#xff1f; 介绍一下什么是爬虫 1、收集数据 2、爬虫调研 3、刷流量和秒杀 二、爬虫是如何工作的&#xff1f; 三、爬虫与SEO优化 什么是python爬虫 Python爬虫架构 最担心的问题 本期送书 随着人工智能以及大数据的兴起…

《JavaEE》HashTable、HashMap、ConcurrentHashMap

目录 HashTable HashMap ConcurrentHashMap ​编辑 HashTable与ConcurrentHashMap的区别 &#x1f451;作者主页&#xff1a;Java冰激凌 &#x1f4d6;专栏链接&#xff1a;JavaEE 进入到线程模块 必不可少的就是接触到线程安全的数据结构 例如StringBuffer、BlockingQueu…

计网第五章.运输层—TCP的拥塞控制

以下来自湖科大计算机网络公开课笔记及个人所搜集资料 目录一、拥塞控制与流量控制1.1 拥塞控制的目的1.2 区分拥塞控制与流量控制二、四种拥塞控制算法2.1 慢开始和拥塞避免2.2 快重传2.3 快恢复一、拥塞控制与流量控制 1.1 拥塞控制的目的 先看一下什么是拥塞&#xff1a; …

AVL树介绍

AVL树AVL树的概念AVL树结点的定义AVL树的插入AVL树的旋转&#xff08;1&#xff09;左单旋&#xff08;2&#xff09;右单旋&#xff08;3&#xff09;左右双旋&#xff08;4&#xff09;右左双旋AVL树的验证AVL树的性能AVL树的概念 二叉搜索树虽然可以提高我们查找数据的效率…

第三章 Linux实际操作——vi和vim编辑器

第三章 Linux实际操作——vi和vim编辑器3.1 vi和vim的基本介绍3.2 vi和vim常用的三种3.2.1 正常模式3.2.2 插入模式3.2.3 命令行模式3.3 vi和vim基本使用3.4 各种模式的相互切换3.5 vi和vim的快捷键3.1 vi和vim的基本介绍 Linux系统会内置 vi文本编辑器Vim具有程序编辑的能力&…

hadoop单机版安装

文章目录1. 将安装包hadoop-3.1.3.tar.gz上次至linux中2. 进行解压操作3. 修改目录名称4. 配置环境变量5. 使用官方提供的jar包实现wordcount案例1. 将安装包hadoop-3.1.3.tar.gz上次至linux中 2. 进行解压操作 tar -zxvf hadoop-3.1.3.tar.gz -C /opt/softs/##tar: 解压打包的…

TikTok听证会后:走不出的隐私风暴和出不了的海

TikTok听证会结束一周。 这场来自大洋彼岸的漩涡紧扣着中国科技圈的心弦&#xff0c;这不是一场稀松平常的热点&#xff0c;而是一次样本实例的演绎&#xff0c;每一个企图出海&#xff0c;扩展生意版图的中国企业们&#xff0c;都在密切关注&#xff0c;希望在TikTok身上找到…

快排非递归/归并排序/排序总结

一、非递归实现快排 在某些情景下&#xff0c;递归可以利用分治思想&#xff0c;将一个问题转化为多个子问题&#xff0c;再转化为更多个最小规模的子问题。从而帮助我们解决问题。 但是&#xff0c;递归可能在效率和内存上产生问题。现如今&#xff0c;由于编译器的进一步优化…

图像分割中的混淆矩阵和利用混淆矩阵计算指标

目录 1. 介绍 2. 创建混淆矩阵 2.1 update 方法 2.2 compute 方法 2.3 str 方法 3. 测试 4. 完整代码 1. 介绍 语义分割中&#xff0c;性能指标可以利用混淆矩阵进行计算 这里实现的方法和图像分类中不一样&#xff0c;需要的可以参考&#xff1a;混淆矩阵Confusion M…

大数据技术——spark集群搭建

目录 spark概述 spark集群搭建 1.Spark安装 2.环境变量配置 3.Spark集群配置 4.启动Spark集群 存在问题及解决方案 请参考以下文章 spark概述 Spark是一个开源的大数据处理框架&#xff0c;它可以在分布式计算集群上进行高效的数据处理和分析。Spark的特点是速度快、易…

多路I/O转接 poll(了解)

poll() 的机制与 select() 类似&#xff0c;与 select() 在本质上没有多大差别&#xff0c;管理多个描述符也是进行轮询&#xff0c;根据描述符的状态进行处理&#xff0c;但是 poll() 没有最大文件描述符数量的限制&#xff08;但是数量过大后性能也是会下降&#xff09;。 p…