wenmo8 发布的文章

前言

2024年是美好简单的一年,春节的时候去了一趟潮州 CityWalk,感受了一番热闹;春节之后入职了新公司,开启了人生新篇章;七月份续任微软最有价值专家,已经连续三年评上;十二月份在瑞的推荐下,成功加入华为云开发者专家;在坤坤的推荐下第一次看线下潮汕话脱口秀,氛围超好;还有今年我最爱的子华神新作《破·地狱》,太优秀了,忍不住二刷。

2024年回顾

  • 潮州 CityWalk
  • 就业
  • 公众号
  • 续任微软最有价值专家
  • 华为云开发者专家
  • 潮汕话脱口秀
  • 破·地狱
  • 猫猫狗狗

潮州 CityWalk

今年春节的时候在喜燃的邀请下,组织了一次潮州 CityWalk,他从澄海出发,到汕头接上我和黄老板,然后前往潮州找坤坤。

第一站是我家附近的金华肠粉,是我心目中汕头肠粉的天花板,只卖猪肉肠粉和牛肉肠粉,而且营业时间长得惊人,每天只休息一两个小时。肠粉店一大早就门庭若市,大排长龙。

早餐之后我们前往潮州找坤坤,他带我们去了一家官塘龙兴的牛肉火锅店,味道超赞,价格美丽,还遇到很多旅游大巴载着乘客直接到这里来吃饭。

午饭过后前往韩文公祠堂消消食,并合照留念,记录美好。

逛完韩文公祠堂之后,我们一路闲逛前往开元寺,感受节日的热闹。

晚餐是简简单单的潮汕白粥,朴实无华。

就业

春节之后入职了新公司,开启了人生新篇章。在刚入职的几个月,我参加了大量的公司活动:广州的早茶巴士、腰旗橄榄球、射箭、卡丁车等等,解锁了丰富的人生新体验。

由于我们公司工位有限,公司实行混合办公模式:每周有几天是居家办公。听起来很美妙,但是这对自律性的要求也很高,所以我也花了一些时间才适应了这种办公模式。

我目前入职了将近九个月,对工作逐渐熟悉。同事们都非常热心和耐心,领导们更是非常厉害,所以才能让大家保持生活和工作的平衡,超赞。

广州的早茶巴士

腰旗橄榄球

射箭

卡丁车

公众号

公众号年度累计发文 1793 篇,阅读量 24.7 万次,关注数量也在持续上升,感谢大家的支持,我会继续坚持日更。

续任微软最有价值专家

今年七月成功续任微软最有价值专家,目前已经连续三年评上,感谢大家的支持,让我们一起努力,继续把社区做大做强。

华为云开发者专家

今年五月份,我成功提名瑞成为微软最有价值专家。

十月份的时候,他问我有没有在华为云社区发过文章。我查了一下,曾发过两篇,于是他提名我成为华为云开发者专家。

在提交申请之后,我等待了一两周的时间。经过一轮面试,我最终在十二月成为华为云开发者专家。感谢华为云社区的认可,我会尽我所能为社区献出自己的微薄之力。

潮汕话脱口秀

十二月在坤坤的推荐下,第一次看了线下潮汕话脱口秀。之前在B站刷过水鸡兄姚远的演出,平时也有看脱口秀的爱好,所以这次参与线下活动的体验感超赞。作为观众也可以很好地融入其中,感受到脱口秀演员带来的欢乐。

这次脱口秀的主题是《柴房启示录》,灵感来自潮剧《柴房会》,结合潮剧的脱口秀多了一些深度。希望姚远作为潮汕话脱口秀第一人可以继续努力,不断成长,做大做强。

破·地狱

《破·地狱》是我心心念念的子华神的新作,影片一开始先在香港上映,不少人直接买票前往香港观看,口碑超赞,于是我就更加期待。

周六的时候听说广州已经可以买票看点映了,听罢我立刻买了第二天的票。观影的全程我都非常专注,同时内心不断发出赞叹,这作品真优秀!!!

由于电影过于优秀,让我第一次对电影产生了二刷的想法,又碰巧遇上了首映日活动,还有周边送,于是立刻加入活动群并抢到了门票。

由于已经看过一遍,对故事背景具备基本的了解,二刷的时候还可以观察到第一遍观看时没有留意到的细节,所以对电影主题有了更多更深刻的感悟,似乎明白了活人也需要破地狱的意义。

据说电影原版长达三小时,希望有机会可以了解更多背后的故事吧,如果有机会看到原版,那就更完美了。

下面是幸福周边展示(烫金签名海报 + 票根套装):

猫猫狗狗

接下来是一年一度的猫猫狗狗展示

2025 年计划

  • 好好工作
  • 坚持学习
  • 热爱生活
  • 感受美好
  • 追逐梦想

总结

岁月很长,美好简单,感恩

主要构件及其相互关系

主要构件:

主要的核心部件解释如下:

  • SqlSession: 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
  • Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  • StatementHandler: 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
  • ParameterHandler: 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
  • ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
  • TypeHandler: 负责java数据类型和jdbc数据类型之间的映射和转换
  • MappedStatement: MappedStatement维护了一条节点的封装,
  • SqlSource: 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  • BoundSql: 表示动态生成的SQL语句以及相应的参数信息
  • Configuration: MyBatis所有的配置信息都维持在Configuration对象之中。

MyBatis初始化的方式

MyBatis的初始化可以有两种方式:

  • 基于XML配置文件:基于XML配置文件的方式是将MyBatis的所有配置信息放在XML文件中,MyBatis通过加载XML配置文件,将配置文信息组装成内部的Configuration对象。
  • 基于Java API:这种方式不使用XML配置文件,需要MyBatis使用者在Java代码中,手动创建Configuration对象,然后将配置参数set 进入Configuration对象中。

基于XML配置

现在就从使用MyBatis的简单例子入手,深入分析一下MyBatis是怎样完成初始化的,都初始化了什么。看以下代码:

// mybatis初始化
String resource = "mybatis-config.xml";  
InputStream inputStream = Resources.getResourceAsStream(resource);  
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();  

// 执行SQL语句
List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");

上述语句的作用是执行com.foo.bean.BlogMapper.queryAllBlogInfo 定义的SQL语句,返回一个List结果集。总的来说,上述代码经历了三个阶段:

  • mybatis初始化
  • 创建SqlSession
  • 执行SQL语句

上述代码的功能是根据配置文件mybatis-config.xml 配置文件,创建SqlSessionFactory对象,然后产生SqlSession,执行SQL语句。而mybatis的初始化就发生在第三句:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

那么第三句到底发生了什么。

MyBatis初始化基本过程

SqlSessionFactoryBuilder根据传入的数据流生成Configuration对象,然后根据Configuration对象创建默认的SqlSessionFactory实例。

初始化的基本过程如下序列图所示:

由上图所示,mybatis初始化要经过简单的以下几步:

  1. 调用SqlSessionFactoryBuilder对象的build(inputStream)方法;
  2. SqlSessionFactoryBuilder会根据输入流inputStream等信息创建XMLConfigBuilder对象;
  3. SqlSessionFactoryBuilder调用XMLConfigBuilder对象的parse()方法;
  4. XMLConfigBuilder对象返回Configuration对象;
  5. SqlSessionFactoryBuilder根据Configuration对象创建一个DefaultSessionFactory对象;
  6. SqlSessionFactoryBuilder返回 DefaultSessionFactory对象给Client,供Client使用。

SqlSessionFactoryBuilder相关的代码如下所示:

public SqlSessionFactory build(InputStream inputStream)  {  
    return build(inputStream, null, null);  
}  

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)  {  
    try  {  
        //2. 创建XMLConfigBuilder对象用来解析XML配置文件,生成Configuration对象  
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);  
        //3. 将XML配置文件内的信息解析成Java对象Configuration对象  
        Configuration config = parser.parse();  
        //4. 根据Configuration对象创建出SqlSessionFactory对象  
        return build(config);  
    } catch (Exception e) {  
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);  
    } finally {  
        ErrorContext.instance().reset();  
        try {  
            inputStream.close();  
        } catch (IOException e) {  
            // Intentionally ignore. Prefer previous error.  
        }  
    }
}

// 从此处可以看出,MyBatis内部通过Configuration对象来创建SqlSessionFactory,用户也可以自己通过API构造好Configuration对象,调用此方法创SqlSessionFactory  
public SqlSessionFactory build(Configuration config) {  
    return new DefaultSqlSessionFactory(config);  
}  

上述的初始化过程中,涉及到了以下几个对象:

  • SqlSessionFactoryBuilder : SqlSessionFactory的构造器,用于创建SqlSessionFactory,采用了Builder设计模式
  • Configuration :该对象是mybatis-config.xml文件中所有mybatis配置信息
  • SqlSessionFactory:SqlSession工厂类,以工厂形式创建SqlSession对象,采用了Factory工厂设计模式
  • XmlConfigParser :负责将mybatis-config.xml配置文件解析成Configuration对象,共SqlSessonFactoryBuilder使用,创建SqlSessionFactory

创建Configuration对象的过程

接着上述的 MyBatis初始化基本过程讨论,当SqlSessionFactoryBuilder执行build()方法,调用了XMLConfigBuilder的parse()方法,然后返回了Configuration对象。那么parse()方法是如何处理XML文件,生成Configuration对象的呢?

  • XMLConfigBuilder会将XML配置文件的信息转换为Document对象

而XML配置定义文件DTD转换成XMLMapperEntityResolver对象,然后将二者封装到XpathParser对象中,XpathParser的作用是提供根据Xpath表达式获取基本的DOM节点Node信息的操作。如下图所示:

  • 之后XMLConfigBuilder调用parse()方法

会从XPathParser中取出
<configuration>
节点对应的Node对象,然后解析此Node节点的子Node:properties, settings, typeAliases,typeHandlers, objectFactory, objectWrapperFactory, plugins, environments,databaseIdProvider, mappers:

public Configuration parse() {  
    if (parsed) {  
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");  
    }  
    parsed = true;  
    //源码中没有这一句,只有 parseConfiguration(parser.evalNode("/configuration"));  
    //为了让读者看得更明晰,源码拆分为以下两句  
    XNode configurationNode = parser.evalNode("/configuration");  
    parseConfiguration(configurationNode);  
    return configuration;  
}  
/** 
 * 解析 "/configuration"节点下的子节点信息,然后将解析的结果设置到Configuration对象中 
 */  
private void parseConfiguration(XNode root) {  
    try {  
        //1.首先处理properties 节点     
        propertiesElement(root.evalNode("properties")); //issue #117 read properties first  
        //2.处理typeAliases  
        typeAliasesElement(root.evalNode("typeAliases"));  
        //3.处理插件  
        pluginElement(root.evalNode("plugins"));  
        //4.处理objectFactory  
        objectFactoryElement(root.evalNode("objectFactory"));  
        //5.objectWrapperFactory  
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));  
        //6.settings  
        settingsElement(root.evalNode("settings"));  
        //7.处理environments  
        environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631  
        //8.database  
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));  
        //9.typeHandlers  
        typeHandlerElement(root.evalNode("typeHandlers"));  
        //10.mappers  
        mapperElement(root.evalNode("mappers"));  
    } catch (Exception e) {  
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);  
    }  
} 

注意:在上述代码中,还有一个非常重要的地方,就是解析XML配置文件子节点
<mappers>
的方法mapperElements(root.evalNode("mappers")), 它将解析我们配置的Mapper.xml配置文件,Mapper配置文件可以说是MyBatis的核心,MyBatis的特性和理念都体现在此Mapper的配置和设计上。

  • 然后将这些值解析出来设置到Configuration对象中

解析子节点的过程这里就不一一介绍了,用户可以参照MyBatis源码仔细揣摩,我们就看上述的environmentsElement(root.evalNode("environments")); 方法是如何将environments的信息解析出来,设置到Configuration对象中的:

/** 
 * 解析environments节点,并将结果设置到Configuration对象中 
 * 注意:创建envronment时,如果SqlSessionFactoryBuilder指定了特定的环境(即数据源); 
 *      则返回指定环境(数据源)的Environment对象,否则返回默认的Environment对象; 
 *      这种方式实现了MyBatis可以连接多数据源 
 */  
private void environmentsElement(XNode context) throws Exception {  
    if (context != null)  
    {  
        if (environment == null)  
        {  
            environment = context.getStringAttribute("default");  
        }  
        for (XNode child : context.getChildren())  
        {  
            String id = child.getStringAttribute("id");  
            if (isSpecifiedEnvironment(id))  
            {  
                //1.创建事务工厂 TransactionFactory  
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));  
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));  
                //2.创建数据源DataSource  
                DataSource dataSource = dsFactory.getDataSource();  
                //3. 构造Environment对象  
                Environment.Builder environmentBuilder = new Environment.Builder(id)  
                .transactionFactory(txFactory)  
                .dataSource(dataSource);  
                //4. 将创建的Envronment对象设置到configuration 对象中  
                configuration.setEnvironment(environmentBuilder.build());  
            }  
        }  
    }  
}

private boolean isSpecifiedEnvironment(String id)  
{  
    if (environment == null)  
    {  
        throw new BuilderException("No environment specified.");  
    }  
    else if (id == null)  
    {  
        throw new BuilderException("Environment requires an id attribute.");  
    }  
    else if (environment.equals(id))  
    {  
        return true;  
    }  
    return false;  
} 
  • 返回Configuration对象

将上述的MyBatis初始化基本过程的序列图细化:

基于Java API

当然可以使用XMLConfigBuilder手动解析XML配置文件来创建Configuration对象,代码如下:

String resource = "mybatis-config.xml";  
InputStream inputStream = Resources.getResourceAsStream(resource);  
// 手动创建XMLConfigBuilder,并解析创建Configuration对象  
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null,null); // 看这里 
Configuration configuration = parser.parse();  
// 使用Configuration对象创建SqlSessionFactory  
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);  
// 使用MyBatis  
SqlSession sqlSession = sqlSessionFactory.openSession();  
List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");  

由于上周出月刊「GitHub 热点速览」断更了一期,本期攒了两周的热门开源项目,真可谓是神仙打架!

这两天开源的大模型 DeepSeek-V3 凭借极低的训练成本和出色的评测表现,迅速出圈。开源仅 5 天便获得近 10k Star,并且还在以惊人的速度增长。对于一些人来说,DeepSeek 这个名字或许比较陌生,就是它凭一己之力,在中国大模型市场掀起了第一场价格战,因而被网友称为 AI 界的“拼多多”。

GitHub 地址→:
github.com/deepseek-ai/DeepSeek-V3

回到本期的热门开源项目,同样备受关注的还有高性能的 2D UI 布局库 Clay,它零依赖、轻量级,核心文件仅有 2KB。还有能够将电子书转换为高质量有声书的工具 ebook2audiobook,以及可以将 Office 文档转为 Markdown 的实用工具 MarkItDown,都是学习和办公中的好帮手。此外,开源的 LLM 应用评估框架 opik 也是评估 LLM 应用的利器。而生成式物理引擎和模拟平台 Genesis,专为机器人、嵌入式 AI 和物理 AI 应用而设计,在一周内更是涨了 2w Star,刷爆了整个 AI 圈。

作为 2024 年最后一期的「GitHub 热点速览」,我们由衷感谢大家这一年来的支持。在新的一年里,我们将继续分享更多优质内容回馈大家。最后,提前祝大家 2025 年快乐!我们来年再见~

  • 本文目录
    • 1. 热门开源项目
      • 1.1 高性能 C 语言 UI 布局库:Clay
      • 1.2 Office 文档转 Markdown 的工具:MarkItDown
      • 1.3 电子书转有声书的工具:ebook2audiobook
      • 1.4 开源的 LLM 应用评估框架:opik
      • 1.5 生成式物理引擎和模拟平台:Genesis
    • 2. HelloGitHub 热评
      • 2.1 开源的视频会议平台:jitsi-meet
      • 2.2 macOS 上轻松运行 Linux 虚拟机:Lima
    • 3. 结尾

1. 热门开源项目

1.1 高性能 C 语言 UI 布局库:Clay

主语言:C

Star:8.6k

周增长:2k

这是一个基于 C 语言实现的高性能 2D UI 布局库,能够提供微秒级的布局性能和灵活的响应式布局能力。它采用类似 flexbox 的布局模型,支持响应式设计、文本换行、滚动容器等功能。该库具有零依赖和轻量级的特性,核心文件仅为 2KB,且可编译为 wasm 文件(15KB)。适用于游戏开发、嵌入式设备以及轻量级 Web 应用等场景。

GitHub 地址→
github.com/nicbarker/clay

1.2 Office 文档转 Markdown 的工具:MarkItDown

主语言:Python

Star:8.3k

周增长:2k

这是一个由微软开源的 Python 工具,可以将非 Markdown 格式的文档转换为 Markdown 格式,支持 PDF、PPT、Word 和 Excel 等多种文件类型,为文档的索引和文本分析提供了极大的便利。

from markitdown import MarkItDown

md = MarkItDown()
result = md.convert("hellogithub.xlsx")
print(result.text_content)

GitHub 地址→
github.com/microsoft/markitdown

1.3 电子书转有声书的工具:ebook2audiobook

主语言:Python

Star:2.7k

周增长:1.4k

这款开源工具可以轻松将电子书转换为有声书,支持多种常见格式,如 EPUB、MOBI、PDF 等。它通过 calibre 提取电子书文本,并运用语音合成技术(Text-to-Speech),能够生成包含章节和元数据的有声书,支持包括中文在内的 1000 多种语言。

GitHub 地址→
github.com/DrewThomasson/ebook2audiobook

1.4 开源的 LLM 应用评估框架:opik

主语言:Python

Star:3.8k

这是一个用于构建评估、测试和监控 LLM 应用平台的框架,它提供直观的 Web 界面,能够记录开发和生产期间的所有 LLM 调用,支持反馈分数记录、测试用例存储和 CI/CD 集成等功能,适用于 RAG 聊天机器、代码助手和复杂的代理管道等应用场景。

GitHub 地址→
github.com/comet-ml/opik

1.5 生成式物理引擎和模拟平台:Genesis

主语言:Python

Star:20k

周增长:18k

这是一个专为机器人、嵌入式 AI 和物理 AI 应用打造的综合物理平台,旨在简化物理仿真过程。它整合了全新设计的物理引擎、高速的照片级真实感渲染系统,以及强大的数据生成引擎(暂未开源),能够模拟各种复杂的物理现象。

GitHub 地址→
github.com/Genesis-Embodied-AI/Genesis

2. HelloGitHub 热评

在此章节中,我们将为大家介绍本周 HelloGitHub 网站上的热门开源项目,我们不仅希望您能从中收获开源神器和编程知识,更渴望“听”到您的声音。欢迎您与我们分享使用这些
开源项目的亲身体验和评价
,用最真实反馈为开源项目的作者注入动力。

2.1 开源的视频会议平台:jitsi-meet

主语言:TypeScript

这是一个基于 WebRTC 构建的视频会议平台,提供高清音视频质量,支持私聊、举手、投票和虚拟背景等多种功能,兼容所有主流浏览器和移动平台。

项目详情→
hellogithub.com/repository/3365d2c6d8324d7ba089655e3c6d9738

2.2 macOS 上轻松运行 Linux 虚拟机:Lima

主语言:Go

这是一款在 macOS 上创建和管理 Linux 虚拟机的工具,它通过 QEMU 和 macOS 原生虚拟化技术,提供一个开箱即用、轻量级的 Linux 虚拟机环境,支持运行各种 Linux 应用和工具,以及 Docker、Podman、K8s 等容器。

项目详情→
hellogithub.com/repository/9a7e323baab74beba2f4b6d5f248c105

3. 结尾

以上就是本期「GitHub 热点速览」的全部内容,希望你能够在这里找到自己感兴趣的开源项目,如果你有其他好玩、有趣的 GitHub 开源项目想要分享,欢迎来
HelloGitHub
与我们交流和讨论。

往期回顾

在.Net中,System.Drawing有平台限制的问题,如果需要跨平台就需要使用第三方库。

今天推荐一个.NET开源图形库,不依赖任何库,支持跨平台的图形库。

01 项目简介

ImageSharp是一款完全托管的代码库,高性能、跨平台的图形库。它支持在任何支持.NET 6+的环境中安装和使用,可以运用在各种场景:设备端,云端以及嵌入式和物联网等。

ImageSharp功能非常丰富,使用简单,常见的操作只需几行代码就可以完成。它支持jpeg、bmp、gif、pbm、png、tga、tiff、webp和qoi等多种图像格式。

同时ImageSharp支持超过25种不同的像素格式,并支持对元数据编辑(如IPTC EXIF、XMP)和色彩空间转换(如RGB、CMYK、灰度、CIELab等)功能。

此外,它还提供了超过40种常见的图像处理操作,性能出色且内存友好,能够处理任何尺寸的图像,包括数百万像素的大图。

02 使用方法

1、安装依赖库

 Install-Package SixLabors.ImageSharp

2、调整图像大小

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

// 加载图像  
using (var image = Image.Load<Rgba32>("demo.jpg"))
{
    // 调整图像大小  
    image.Mutate(x => x.Resize(100, 100));

    // 保存调整大小后的图像  
    image.SaveAsJpeg("resized_image.jpg");
}

3、应用滤镜(例如灰度效果)

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

// 加载图像  
using (var image = Image.Load<Rgba32>("demo.jpg"))
{
    // 应用灰度滤镜  
    image.Mutate(x => x.Grayscale());

    // 保存应用滤镜后的图像  
    image.SaveAsJpeg("grayscale_image.jpg");
}

4、裁剪图像

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

// 加载图像  
using (var image = Image.Load<Rgba32>("demo.jpg"))
{
    // 裁剪图像  
    var rectangle = new Rectangle(100, 100, 200, 200); // x, y, width, height  
    image.Mutate(x => x.Crop(rectangle));

    // 保存裁剪后的图像  
    image.SaveAsJpeg("cropped_image.jpg");
}

5、旋转图像

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

// 加载图像  
using (var image = Image.Load<Rgba32>("demo.jpg"))
{
    // 旋转图像 90 度  
    image.Mutate(x => x.Rotate(90));

    // 保存旋转后的图像  
    image.SaveAsJpeg("rotated_image.jpg");
}

6、绘制文字图形

using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.Numerics;

// 加载字体文件  
var fontCollection = new FontCollection();
fontCollection.Add("C:\\Windows\\Fonts\\STZHONGS.TTF"); // 替换为您的字体文件路径  
var fontFamily = fontCollection.Get("STZhongsong"); // 替换为字体名称  
var font = fontFamily.CreateFont(size: 20); // 设置字体大小  

// 创建一个新的空白图像  
var image = new Image<Rgba32>(100, 100);

// 设置背景色为白色  
image.Mutate(x => x.Fill(Color.White));

// 使用ImageSharp的Drawing库来绘制文本  
image.Mutate(ctx => ctx
    .DrawText(
        "7788", // 要绘制的文本  
        font, // 使用的字体  
        Color.Black, // 文本颜色  
        new Vector2(10, 10)) // 文本开始绘制的位置  
);

// 保存图像  
image.SaveAsPng("image_with_text.png", new PngEncoder());

03 项目地址

https://github.com/SixLabors/ImageSharp

更多开源项目:
https://github.com/bianchenglequ/NetCodeTop

- End -

推荐阅读

Eval-Expression.NET:动态执行C#脚本,类似Javascript的Eval函数功能

一个C#开源工具库,集成了超过1000个扩展方法

Plotly.NET:一个强大的、漂亮的.NET开源交互式图表库

一个基于.Net的SVG图形开源操作库

OxyPlot:一个功能强大、漂亮.Net跨平台开源绘图库

当地时间12月29日上午9时,韩国济州航空编号7C2216航班坠毁于韩国务安机场,除救出的两人外,预计事故其余人员全部遇难。据了解,失事客机因起落架故障准备进行机腹着陆,在此过程中发生事故,最终与机场外围构筑物相撞后严重破损并起火。这起悲剧让我们深刻认识到,在航空领域,任何一个环节的故障都可能引发灾难性后果。而在Java编程世界里,异常处理机制就如同飞机上的安全防护系统,能够帮助我们在程序运行出现“故障”时,避免“坠机”,实现“安全着陆”。

异常处理机制:Java程序的“安全防护网”

在Java中,异常是在程序执行过程中出现的错误或意外情况。异常处理机制允许我们以一种结构化和可控的方式来处理这些异常,确保程序的稳定性和可靠性。

try-catch语句:捕获异常的“安全气囊”

try-catch语句是Java中处理异常的基本方式。它就像飞机上的安全气囊,在异常发生时提供保护,防止程序“坠毁”。以下是try-catch语句的基本语法:

try {
    // 可能会抛出异常的代码块
} catch (ExceptionType1 e1) {
    // 处理ExceptionType1类型异常的代码块
} catch (ExceptionType2 e2) {
    // 处理ExceptionType2类型异常的代码块
} finally {
    // 无论是否发生异常,都会执行的代码块
}

在try块中,我们放置可能会抛出异常的代码。如果在try块中发生了异常,程序会立即跳转到相应的catch块中进行异常处理。finally块中的代码则始终会被执行,无论是否发生异常,它常用于释放资源等操作。

例如,假设我们有一个简单的除法运算程序:

public class DivisionExample {
    public static void main(String[] args) {
        int dividend = 10;
        int divisor = 0;

        try {
            int result = dividend / divisor;
            System.out.println("结果: " + result);
        } catch (ArithmeticException e) {
            System.out.println("发生算术异常: " + e.getMessage());
        } finally {
            System.out.println("除法运算结束。");
        }
    }
}

在这个例子中,我们试图将10除以0,这会引发一个
ArithmeticException
异常。由于我们使用了try-catch语句,程序会捕获这个异常,并在catch块中输出错误信息。最后,finally块中的消息会被输出。

多个catch块:应对不同类型异常的“应急策略”

在实际编程中,可能会出现多种不同类型的异常。我们可以使用多个catch块来分别处理不同类型的异常,就像飞机针对不同故障有不同的应急策略一样。例如:

public class MultipleCatchExample {
    public static void main(String[] args) {
        try {
            int[] array = {1, 2, 3};
            System.out.println(array[5]); // 越界访问
            int result = 10 / 0; // 算术异常
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界异常: " + e.getMessage());
        } catch (ArithmeticException e) {
            System.out.println("算术异常: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("其他异常: " + e.getMessage());
        } finally {
            System.out.println("程序执行结束。");
        }
    }
}

在这个例子中,我们的代码可能会抛出
ArrayIndexOutOfBoundsException
(数组越界异常)和
ArithmeticException
(算术异常)。我们分别使用不同的catch块来处理这两种异常,并且还提供了一个通用的catch块来处理其他未预料到的异常。

异常类型体系:精准定位问题的“故障排查手册”

Java的异常类型体系非常丰富,它就像一本详细的故障排查手册,帮助我们精准定位程序中出现的问题。异常类型主要分为两大类:受检异常(Checked Exception)和非受检异常(Unchecked Exception)。

受检异常:必须处理的“严重故障”

受检异常是那些在编译时就必须处理的异常,它们通常表示一些外部条件导致的错误,例如文件不存在、网络连接失败等。如果方法可能抛出受检异常,那么在方法签名中必须声明该异常,或者在方法内部使用try-catch语句进行处理。例如,
FileInputStream
类的构造函数在打开文件时可能会抛出
FileNotFoundException
,这是一个受检异常:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        File file = new File("nonexistent.txt");
        try {
            FileInputStream fis = new FileInputStream(file);
        } catch (IOException e) {
            System.out.println("文件读取异常: " + e.getMessage());
        }
    }
}

在这个例子中,我们试图打开一个不存在的文件,
FileInputStream
构造函数会抛出
IOException
,由于这是一个受检异常,我们必须在try-catch块中处理它。

非受检异常:运行时的“意外情况”

非受检异常是那些在运行时可能出现的异常,它们通常表示程序中的逻辑错误,例如空指针引用、数组越界等。这些异常不需要在方法签名中声明,但如果不处理,可能会导致程序崩溃。例如,
NullPointerException
就是一个非受检异常:

public class UncheckedExceptionExample {
    public static void main(String[] args) {
        String str = null;
        System.out.println(str.length()); // 空指针引用
    }
}

在这个例子中,我们试图调用一个空对象的
length
方法,这会引发一个
NullPointerException
。由于这是非受检异常,我们可以选择处理它,也可以让程序终止并打印异常堆栈信息。

自定义异常:打造专属的“故障预警系统”

除了使用Java内置的异常类型,我们还可以根据程序的需求自定义异常。自定义异常就像为我们的程序打造了一个专属的故障预警系统,能够更准确地传达程序中的错误信息。

创建自定义异常类

要创建自定义异常类,只需继承
Exception
类或其子类即可。例如,假设我们正在开发一个学生成绩管理系统,我们可以定义一个
InvalidGradeException
来表示无效的成绩:

class InvalidGradeException extends Exception {
    public InvalidGradeException(String message) {
        super(message);
    }
}

抛出和处理自定义异常

在程序中,当满足特定条件时,我们可以使用
throw
关键字抛出自定义异常。例如:

public class GradeManager {
    public static void checkGrade(int grade) throws InvalidGradeException {
        if (grade < 0 || grade > 100) {
            throw new InvalidGradeException("成绩必须在0到100之间。");
        }
        System.out.println("成绩有效。");
    }

    public static void main(String[] args) {
        try {
            checkGrade(120);
        } catch (InvalidGradeException e) {
            System.out.println("错误: " + e.getMessage());
        }
    }
}

在这个例子中,如果传入的成绩不在0到100之间,我们就抛出
InvalidGradeException
异常。在
main
方法中,我们使用try-catch语句来捕获并处理这个自定义异常。

异常处理最佳实践:构建稳健程序的“飞行准则”

在Java编程中,合理使用异常处理机制是构建稳健程序的关键。以下是一些异常处理的最佳实践,就像飞机驾驶员遵循的飞行准则一样,帮助我们确保程序的安全和稳定。

具体异常优先处理

在使用多个catch块时,应该将更具体的异常类型放在前面,更通用的异常类型放在后面。这样可以确保异常被正确地捕获和处理,避免被通用的catch块过早捕获。

避免过度使用异常

异常处理机制虽然强大,但不应该被过度使用。对于一些可以通过简单的条件判断来避免的错误,不建议使用异常处理。例如,在进行数组访问时,先检查数组下标是否合法,而不是依赖捕获
ArrayIndexOutOfBoundsException

提供有意义的异常信息

在抛出异常时,应该提供有意义的错误信息,以便于调试和定位问题。异常信息应该清晰地描述发生了什么错误以及错误发生的位置。

在合适的层级处理异常

异常应该在合适的层级进行处理,避免在底层方法中捕获并忽略异常,导致问题被隐藏。通常,应该在接近异常发生的地方进行处理,如果无法处理,可以将异常向上抛出,让上层调用者决定如何处理。

总结

韩国客机起火坠毁事故给我们敲响了警钟,在航空领域,安全是至关重要的,任何一个细节都不容忽视。同样,在Java编程中,异常处理机制是保障程序稳定运行的关键。通过合理使用try-catch语句、理解异常类型体系、创建自定义异常以及遵循异常处理最佳实践,我们能够构建出更加稳健、可靠的Java程序,避免程序在运行过程中“失控坠毁”。希望每一位Java开发者都能重视异常处理,让我们的程序在“编程天空”中安全、平稳地翱翔。
作者:
代老师的编程课
出处:
https://zthinker.com/
如果你喜欢本文,请长按二维码,关注
Java码界探秘
.
代老师的编程课