2023年4月

LINQ对应的中文名称是:语言集成查询,是对一系列直接将查询功能集成到C#语言技术的统称,我们最常用的场景是,用于数据库的查询功能。

在.Net中,本身对Objects也支持LINQ的扩展,但是对于一些特定的功能,就需要我们自己去扩展。

所以,今天给大家推荐一个LINQ扩展库,主要是针对“LINQ to Objects”的扩展。

项目简介

该项目与LINQ规范保持一致,针对Objects进行扩展,总共扩展了100多个方法,功能非常齐全且强大。

项目结构

图片

扩展方法合集

图片

部分使用示例

引用

using static MoreLinq.Extensions.LagExtension;
using static MoreLinq.Extensions.LeadExtension;
using MoreEnumerable = MoreLinq.MoreEnumerable;

随机排序

图片

列表转换为DataTable

图片

判断结尾

图片

LeftJoin / RightJoin

图片

图片

项目地址

https://github.com/morelinq/MoreLINQ

更多开源项目请查看

一个专注推荐优秀.Net开源项目的榜单

- End -

文章首发于公众号【编程乐趣】,欢迎大家关注。
图片

介绍

在之前的文章中,写了一篇使用Spring @Profile实现开发环境,测试环境,生产环境的切换,之前的文章是使用SpringBoot项目搭建,实现了不同环境数据源的切换,在我们实际开发中,会分为dev,test,prod等环境,他们之间数独立的,今天进来详解介绍Spring @Profile的原理。

# Spring注解@Profile实现开发环境,测试环境,生产环境的切换

使用

带有@Profile的注解的bean的不会被注册进IOC容器,需要为其设置环境变量激活,才能注册进IOC容器,如下通过setActiveProfiles设置了dev值,那么这三个值所对应的Bean会被注册进IOC容器。当然,我们在实际使用中,不会这样去做,使用SpringBoot的话,我们一般是使用yml,在yml中配置
spring.profiles.active
,也可以通过配置jvm参数。

通过Environment设置profile

我们可以直接通过Environment来设置环境属性,这是比较原生的方法。

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("dev");

通过JVM参数设置

可以通过JVM参数来设置环境变量的值,在开发中,这种方式也是使用得比较普遍。

SpringBoot通过yml进行配置

在SpringBoot项目中,我们得配置项一般都是配置在yml文件中,这样就能和代码分开,并且也能进行动态配置。

从上面我们看出可以通过好几种方式进行配置,但是他们最终其实都是将环境变量设置进
Environment
中,这样,spring在后续得流程里面,就能从Environment中获取环境变量,然后进行相应的逻辑处理。

源码解析

BeanDefinition注册

首先,需要注册bean的元信息BeanDefinition,不过对于@Profile标注的方法,如果环境变量中有对应的变量值,那么就能注册,没有的话则不会进行注册,我们来看关键的代码,在ConfigurationClassBeanDefinitionReader中,有一个
shouldSkip
判断,它会筛选出符合的bean,不符合条件的bean则被加入skippedBeanMethods集合中,不会被注册。

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
	ConfigurationClass configClass = beanMethod.getConfigurationClass();
	MethodMetadata metadata = beanMethod.getMetadata();
	String methodName = metadata.getMethodName();
		// Do we need to mark the bean as skipped by its condition?
	if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
            configClass.skippedBeanMethods.add(methodName);
            return;
	}
            if (configClass.skippedBeanMethods.contains(methodName)) {
            return;
	}
}

shouldSkip源码

在shouldSkip中,会使用Condition接口,@Profile使用的是
ProfileCondition
,然后调用
matches
方法。

    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationCondition.ConfigurationPhase phase) {
        for (Condition condition : conditions) {
            ConfigurationCondition.ConfigurationPhase requiredPhase = null;
            if (condition instanceof ConfigurationCondition configurationCondition) {
                requiredPhase = configurationCondition.getConfigurationPhase();
            }
            if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
                return true;
            }
        }
        return false;
    }

ProfileCondition匹配

在ProfileCondition的matches方法中,主要就是去Environment中寻找环境变量,然后解析@Profile注解设置的value值,如果Environment中激活的配置中包含当前的配置,包含则能为true,不包含则为false,如上通过setActiveProfiles设置Environment中激活的配置为dev,当前传过来的配置为dev,那么就能匹配上,就能装配进IOC容器。

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }

从源码可以看出,其最核心的思想就是是否注册bean的元信息BeanDefinition,因为只有注册了BeanDefinition,后续才能为创建bean提供元数据支持,判断是否注册bean元信息,主要就是从Environment中取出profiles的值,然后和@Profile注解设置的值进行匹配,匹配得上就注册,bean不上就不注册。

总结

上面我们对@Profile的使用做了详细的介绍,并对它的核心源码进行解剖,无非就是判断是否要注册BeanDefinition,如果我们需要做一些环境隔离的工作,使用@Profile还是比较不错的。

今天的分享就到这里,感谢你的观看,下期见!

前言

看了网上许多关于Windows 本地安装mysql的很多教程,基本上大同小异。但是安装软件有时就可能因为一个细节安装失败。我也是综合了很多个教程才安装好的,

所以本教程可能也不是普遍适合的。现我将自己本地安装的步骤总结如下,如有不对的地方,敬请大家批评指正!!!

安装环境:
win7/10

一、下载Mysql的安装包

我本地安装是Mysql8.0的版本,所以这里我就以我的这个版本为标准来作安装步骤说明

MySQL8.0 - Windows zip包下载地址:
https://dev.mysql.com/downloads/file/?id=476233
进入后不需要登录,直接选择底部 “No thanks, just start my download.”即可开始下载。

二、安装

1.解压zip包到安装目录

2.配置环境变量

将解压zip包到安装目录添加path中去

3.配置初始化的my.ini文件

解压后的目录没有my.ini文件,这里我们自己新建my.ini文件

my.ini文件内容如下:

[mysqld]
# 设置3306端口
port
=3306# 设置mysql的安装目录 切记此处一定要用双斜杠\\,单斜杠我这里会出错,不过看别人的教程,有的是单斜杠。自己尝试吧
basedir
=D:\\tools\\mysql-8.0.11-winx64
# 设置mysql数据库的数据的存放目录
datadir
=D:\\tools\\mysql-8.0.11-winx64\\Data # 此处同上
# 允许最大连接数
max_connections
=200# 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统
max_connect_errors
=10# 服务端使用的字符集默认为UTF8
character
-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
default_authentication_plugin
=mysql_native_password
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8
[client]
# 设置mysql客户端连接服务端时默认使用的端口
port
=3306 default-character-set=utf8

注意:其中的data目录不需要创建,下一步初始化工作中会自动创建。

4.安装mysql

在安装时,必须以管理员身份运行cmd,否则在安装时会报错,会导致安装失败的情况

5.初始化数据库

在MySQL安装目录的 bin 目录下执行命令:

注意!执行输出结果里面有一段: [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: RLVR+PGed0wl
其中root@localhost:后面的“RLVR+PGed0wl”就是初始密码(不含首位空格)。

在没有更改密码前,需要记住这个密码,后续登录需要用到。

6.安装服务

在MySQL安装目录的 bin 目录下执行命令:

mysqld
--install [服务名]
后面的服务名可以不写,默认的名字为 mysql。当然,如果你的电脑上需要安装多个MySQL服务,就可以用不同的名字区分了,比如 mysql5 和 mysql8。
安装完成之后,就可以通过命令net start mysql启动MySQL的服务了。通过命令net stop mysql停止服务。通过命令

7.连接mysql 更改密码

登录mysql:
mysql
-uroot -p(密码)
修改密码:
ALTER USER
'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'song@1234';

到此,安装部署就完成了。

三、数据库命令操作

1.命令查看一下默认安装的数据库

show databases;

2.查看Mysql用户信息

看到默认初始化了mysql数据库,其中user表里面存储MySQL用户信息。我们可以看一下默认MySQL用户:
select user,host,authentication_string from mysql.user;

4.连接IP设置

管理员root的host是localhost,代表仅限localhost登录访问。如果要允许开放其他ip登录,则需要添加新的host。如果要允许所有ip访问,可以直接修改成“%
CREATE USER
'sjsk'@'%' IDENTIFIED WITH mysql_native_password BY 'sjsk123!@#';

5.授权远程数据库

#授权所有权限 
GRANT ALL PRIVILEGES ON
*.* TO 'sjsk'@'%'
#授权基本的查询修改权限,按需求设置
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER ON
*.* TO 'sjsk'@'%';
#查看用户权限
show grants
for 'sjsk'@'%';
#查看密码加密方式:
select user, host, plugin, authentication_string from user;

四.利用Navicat测试安装的mysql

1.利用自己本地电脑IP连接:

2.利用localhost 连接:

中值滤波原理

中值滤波就是用一个奇数点的移动窗口(要求奇数主要是为了保证整个模板有唯一中心元素),将窗口中心点的值用窗口内个点的中值代替。假设窗口内有5点,其值为80、90、200、110和120,那么此窗口内各点的中值即为110。

设有一个一维序列
\(f_1,f_2,...,f_n\)
,取窗口长度(点数)为m(m为奇数),对其进行中值滤波,就是从输入序列中相机抽出m个数
\(f_{i-v},...,f_{i-1},f_i,f_{i+1},...,f_{i+v}\)
(其中
\(f_i\)
为窗口中心点值,
\(v=(m-1)/2\)
),再将这m个点按其数值大小排序,取其序号为中心点的那个数作为滤波输出。用数学公式表示为:

\[y_i=Median\{f_{i-v},...,f_{i-1},f_i,f_{i+1},...,f_{i+v}\},i\in N,v=\frac{m-1}{2}
\]

如:以3*3的领域为例求中值滤波中像素5的值。

image

  1. int pixel[9]中存储像素1,像素2...像素9的值;
  2. 对数组pixel进行排序操作;
  3. 像素5的值即为数组pixel的中值pixel[4]。

代码实现

void medianFilter(cv::Mat& src, cv::Mat& dst, cv::Size size) {
	/*step1:判断窗口size是否为奇数*/
	if (size.width % 2 == 0 || size.height % 2 == 0) {
		cout << "卷积核窗口大小应为奇数!\n";
		exit(-1);
	}

	/*step2:对原图进行边界扩充*/
	int h = (size.height - 1) / 2;
	int w = (size.width - 1) / 2;
	Mat src_border;
	copyMakeBorder(src, src_border, h, h, w, w, BORDER_REFLECT_101);

	/*step3:卷积操作*/
 	map<uchar, Point> mp; // 定义容器存储每个卷积窗口内各像素点的<像素值, 像素位置>
	for (int i = h; i < src.rows + h; i++) {
		for (int j = w; j < src.cols + w; j++) {
			mp.clear();
			for (int ii = i - h; ii <= i + h; ii++) {
				for (int jj = j - w; jj <= j + w; jj++) {
					Point point(jj, ii);
					uchar value;
					if (src.channels() == 1) {
						// 灰度图像,存储灰度值
						value = src_border.at<uchar>(ii, jj);
					}else {
						// 彩色图像,存储亮度值
						uchar value_b = src_border.at<cv::Vec3b>(ii, jj)[0];
						uchar value_g = src_border.at<cv::Vec3b>(ii, jj)[1];
						uchar value_r = src_border.at<cv::Vec3b>(ii, jj)[2];
						value = 0.114 * value_b + 0.587 * value_g + 0.299 * value_r;
					}
					mp[value] = point;
				}
			}
			// 将窗口中心点的值用窗口内个点的中值代替
			auto iter = mp.begin();
			int count = 0;
			Point pixel;
			int median_size = mp.size() / 2;
			while (iter != mp.end()) {
				if (count == median_size) {
					pixel = Point(iter->second.x, iter->second.y);
					break;
				}
				count++;
				iter++;
			}
			if (src.channels() == 1) {
				dst.at<uchar>(i - h, j - w) = src_border.at<uchar>(pixel.y, pixel.x);
			}
			else {
				dst.at<cv::Vec3b>(i - h, j - w)[0] = src_border.at<cv::Vec3b>(pixel.y, pixel.x)[0];
				dst.at<cv::Vec3b>(i - h, j - w)[1] = src_border.at<cv::Vec3b>(pixel.y, pixel.x)[1];
				dst.at<cv::Vec3b>(i - h, j - w)[2] = src_border.at<cv::Vec3b>(pixel.y, pixel.x)[2];
			}
		}
	}
}

代码讲解

copyMakeBorder(src,src_border,h,h,w,w,BORDER_REFLECT_101);

在模板或卷积的加权运算中的图像边界问题:当在图像上移动模板(卷积核)至图像边界时,在原图像中找不到与卷积核中的加权系数相对应的N个像素(N为卷积核元素个数),即卷积核悬挂在图像的边界上,这种现象在图像的上下左右四个边界上均会出现。例如,当模板为:

\[\frac{1}{9}
\begin{bmatrix} %该矩阵一共3列,每一列都居中放置
1 & 1 & 1\\ %第一行元素
1 & 1 & 1\\ %第二行元素
1 & 1 & 1\\ %第二行元素
\end{bmatrix}
\]

设原图像为:

\[\begin{bmatrix} %该矩阵一共3列,每一列都居中放置
1 & 1 & 1 & 1 & 1\\ %第1行元素
2 & 2 & 2 & 2 & 2\\ %第2行元素
3 & 3 & 3 & 3 & 3\\ %第3行元素
4 & 4 & 4 & 4 & 4\\ %第3行元素
\end{bmatrix}
\]

经过卷积操作之后图像为:

\[\begin{bmatrix} %该矩阵一共3列,每一列都居中放置
- & - & - & - & -\\ %第1行元素
- & 2 & 2 & 2 & -\\ %第2行元素
- & 3 & 3 & 3 & -\\ %第3行元素
- & - & - & - & -\\ %第3行元素
\end{bmatrix}
\]

"-"表示无法进行卷积操作的像素点。

解决方法有2种:①忽略图像边界数据(即不管边界,卷积操作的范围从整张图缩小为边界内缩N圈,N的值随卷积核尺寸变化)。②将原图像往外扩充像素,如在图像四周复制源图像边界的值,从而使得卷积核悬挂在原图像四周时也就而已正常进行正常的计算。

opencv边框处理copyMakeBorder:
https://zhuanlan.zhihu.com/p/108408180

value=0.114*value_b+0.587*value_g+0.299*value_r;

对于彩色图像,我们取图像亮度的中间值,亮度值的计算方法为:

\[luminance = 0.299R + 0.587G + 0.114B
\]

map<uchar, Point> mp;

map为C++的stl中的关联性容器,为了实现快速查找,map内部本身就是按序存储的(map底层实现是红黑二叉树)。在我们插入<key, value>键值对时,就会按照key的大小顺序进行存储,其中key的类型必须能够进行 < 运算,且唯一,默认排序是按照从小到大便利记忆联想到需要支持小于运算。

因此,将亮度值或灰度值作为键,map将自动进行按键排序,无需手动书写排序代码。

实现效果

卷积核size为(5, 5)。

image