Android 图片相识度比较(pHash)

概述

在 Android 中,要比对两张 Bitmap 图片的相似度,常见的方法有基于像素差异直方图比较、或者使用一些更高级的算法如 SSIM(结构相似性)感知哈希(pHash)

1. 基于像素的差异比较

可以逐像素比较两张 Bitmap,计算它们之间的差异。以下是一个简单的逐像素比较的例子:

public static double compareBitmaps(Bitmap bitmap1, Bitmap bitmap2) {
    if (bitmap1.getWidth() != bitmap2.getWidth() || bitmap1.getHeight() != bitmap2.getHeight()) {
        throw new IllegalArgumentException("Bitmap sizes are different!");
    }

    int width = bitmap1.getWidth();
    int height = bitmap1.getHeight();
    long diff = 0;

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int pixel1 = bitmap1.getPixel(x, y);
            int pixel2 = bitmap2.getPixel(x, y);

            int r1 = Color.red(pixel1);
            int g1 = Color.green(pixel1);
            int b1 = Color.blue(pixel1);

            int r2 = Color.red(pixel2);
            int g2 = Color.green(pixel2);
            int b2 = Color.blue(pixel2);

            // 计算 RGB 差异
            diff += Math.abs(r1 - r2);
            diff += Math.abs(g1 - g2);
            diff += Math.abs(b1 - b2);
        }
    }

    // 最大可能差异
    double maxDiff = 3L * 255 * width * height;

    // 返回 0 到 1 的值,越小表示相似度越高
    return (double) diff / maxDiff;
}

这段代码计算两张图片的 RGB 差异,返回的结果范围在 0-1 之间,数值越接近 0 表示图片越相似。

2. 基于直方图的比较

通过比较两张图片的颜色直方图来评估相似度。直方图可以捕捉图像的颜色分布,而不关心具体像素位置。

public static double compareHistograms(Bitmap bitmap1, Bitmap bitmap2) {
    int[] histogram1 = new int[256];
    int[] histogram2 = new int[256];

    // 计算两张图的灰度直方图
    for (int y = 0; y < bitmap1.getHeight(); y++) {
        for (int x = 0; x < bitmap1.getWidth(); x++) {
            int pixel1 = bitmap1.getPixel(x, y);
            int gray1 = (Color.red(pixel1) + Color.green(pixel1) + Color.blue(pixel1)) / 3;
            histogram1[gray1]++;
            
            int pixel2 = bitmap2.getPixel(x, y);
            int gray2 = (Color.red(pixel2) + Color.green(pixel2) + Color.blue(pixel2)) / 3;
            histogram2[gray2]++;
        }
    }

    // 计算直方图的差异
    double diff = 0;
    for (int i = 0; i < 256; i++) {
        diff += Math.abs(histogram1[i] - histogram2[i]);
    }

    return diff / (bitmap1.getWidth() * bitmap1.getHeight());
}

3. 使用 SSIM(结构相似性)

SSIM 是一种用来衡量两张图片结构相似性的算法,它比简单的像素差异或直方图比较更准确。Android SDK 没有内置的 SSIM 方法,但可以引入第三方库或者自己实现。SSIM 主要关注三方面:亮度、对比度和结构。

4. 感知哈希(pHash)

pHash 是一种图像哈希技术,它可以生成图片的“指纹”,然后比较两个哈希值的相似性。与传统哈希方法不同,pHash 对于图像的细微改变(例如缩放、旋转)不敏感。

可以通过第三方库实现 pHash,比如 ImageHash 库,或者自己实现基于 DCT(离散余弦变换)的算法。

// 引入第三方库 ImageHash 进行哈希比较
String hash1 = ImageHash.hash(bitmap1);
String hash2 = ImageHash.hash(bitmap2);

int similarity = ImageHash.compare(hash1, hash2);

一般来说:

  • 对于简单的图像比较,基于像素差异的方式即可。
  • 如果要忽略图片的细微变动,直方图或感知哈希是更合适的选择。
  • SSIM 适用于对图像结构有更高要求的场景。

实现

图像比较的算法应用相当广泛, 本文基于感知哈希算法, 用于识别视频帧图像的左右两部分的相似度, 从而判断视频是否是一个左右眼的VR视频格式, 本文采用 感知哈希(pHash) 算法, 它非常适合处理具有细微变化的图像,如裁剪、缩放、亮度变化等。

感知哈希(pHash)是一种用于衡量图像相似度的算法,它通过将图像转换为频域信息,提取其视觉特征来生成一个哈希值。pHash 具有鲁棒性,能够忽略图像的小幅度变动、旋转和缩放等影响。下面是 pHash 算法的实现步骤及其原理。

pHash 算法的实现步骤

  1. 转换为灰度图:将图片转换为灰度图像,以便降低复杂度,并去除颜色信息的影响。

  2. 缩小尺寸:将图像缩小到一个固定的尺寸(例如 32x32),目的是去除高频细节,保留图片的整体特征。这一步骤在后续的离散余弦变换(DCT)中很重要。

  3. 离散余弦变换(DCT):对缩小后的图像执行离散余弦变换,将图像从空间域转换到频率域。这种转换能提取图像的低频信息,忽略高频噪声。

  4. 截取低频部分:只保留 DCT 结果的左上角部分(例如 8x8 的矩阵),因为这部分包含图像的主要信息。

  5. 计算均值:计算截取的低频部分的均值。

  6. 生成哈希值:将 DCT 中每个像素值与均值进行比较,生成一个二进制序列。如果某个像素值大于均值,置 1,否则置 0。最终的哈希值是由这个二进制序列构成。

参考pHash 算法实现

import android.graphics.Bitmap;
import android.graphics.Color;
import java.util.Arrays;

public class ImagePHash {

    // 默认使用 32x32 大小
    private static final int SIZE = 32;
    // DCT 截取的大小(例如 8x8)
    private static final int SMALLER_SIZE = 8;

    public String getHash(Bitmap img) {
        // 1. 转换为灰度图像
        Bitmap grayImg = toGrayscale(img);

        // 2. 缩小图片
        Bitmap smallImg = Bitmap.createScaledBitmap(grayImg, SIZE, SIZE, false);

        // 3. 转换为二维数组
        double[][] vals = new double[SIZE][SIZE];
        for (int x = 0; x < SIZE; x++) {
            for (int y = 0; y < SIZE; y++) {
                vals[x][y] = Color.red(smallImg.getPixel(x, y));
            }
        }

        // 4. 对图像执行离散余弦变换(DCT)
        double[][] dctVals = applyDCT(vals);

        // 5. 截取 DCT 左上角的 8x8 部分
        double[] dctLowFreq = new double[SMALLER_SIZE * SMALLER_SIZE];
        for (int x = 0; x < SMALLER_SIZE; x++) {
            for (int y = 0; y < SMALLER_SIZE; y++) {
                dctLowFreq[x * SMALLER_SIZE + y] = dctVals[x][y];
            }
        }

        // 6. 计算均值
        double avg = Arrays.stream(dctLowFreq).average().orElse(0.0);

        // 7. 生成哈希值
        StringBuilder hash = new StringBuilder();
        for (double value : dctLowFreq) {
            hash.append(value > avg ? "1" : "0");
        }

        return hash.toString();
    }

    // 转换为灰度图像
    private Bitmap toGrayscale(Bitmap img) {
        int width = img.getWidth();
        int height = img.getHeight();
        Bitmap grayscaleImg = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int pixel = img.getPixel(x, y);
                int red = Color.red(pixel);
                int green = Color.green(pixel);
                int blue = Color.blue(pixel);
                int gray = (red + green + blue) / 3;
                int newPixel = Color.rgb(gray, gray, gray);
                grayscaleImg.setPixel(x, y, newPixel);
            }
        }
        return grayscaleImg;
    }

    // 执行离散余弦变换(DCT)
    private double[][] applyDCT(double[][] f) {
        int N = f.length;
        double[][] F = new double[N][N];
        for (int u = 0; u < N; u++) {
            for (int v = 0; v < N; v++) {
                double sum = 0.0;
                for (int i = 0; i < N; i++) {
                    for (int j = 0; j < N; j++) {
                        sum += f[i][j] *
                               Math.cos((2 * i + 1) * u * Math.PI / (2.0 * N)) *
                               Math.cos((2 * j + 1) * v * Math.PI / (2.0 * N));
                    }
                }
                double alphaU = (u == 0) ? Math.sqrt(1.0 / N) : Math.sqrt(2.0 / N);
                double alphaV = (v == 0) ? Math.sqrt(1.0 / N) : Math.sqrt(2.0 / N);
                F[u][v] = alphaU * alphaV * sum;
            }
        }
        return F;
    }

    // 比较两个哈希值,返回汉明距离(不同位数的个数)
    public int hammingDistance(String hash1, String hash2) {
        int distance = 0;
        for (int i = 0; i < hash1.length(); i++) {
            if (hash1.charAt(i) != hash2.charAt(i)) {
                distance++;
            }
        }
        return distance;
    }
}

对比效果如下(使用ListView 显示多张图片对比结果, 一帧视频图像从中间切割左右两部分, 分别显示在列表项的左右两侧, 中间的文字输出比较结果的汉明值, 值越小图像差异越小):
在这里插入图片描述
在这里插入图片描述

原始测试图片(从一个VR视频中截取出的视频帧):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码分享:

test_img_diff.xml 布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/rlRoot">
    <ListView android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

ListView 的item 布局: item_img_diff.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView android:id="@+id/ivLeft"
        android:layout_width="128dp"
        android:layout_height="72dp"/>

    <ImageView android:id="@+id/ivRight"
        android:layout_width="128dp"
        android:layout_height="72dp"
        android:layout_alignParentRight="true"/>

    <TextView android:id="@+id/tvRes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="18sp"
        android:textColor="#FFFFFFFF"/>
</RelativeLayout>

主界面Activity: ImgDiffTester.java

public class ImgDiffTester extends Activity implements View.OnClickListener {
	final String TAG = "ImgDiffTester";
	ListView lv;
	ImgListAdapter adapter;
	@Override
	protected void onCreate(@Nullable Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.test_img_diff);
		findViewById(R.id.rlRoot).setOnClickListener(this);
		lv = (ListView) findViewById(R.id.lv);
		adapter = new ImgListAdapter();
		lv.setAdapter(adapter);
		startCompare();
	}

	void startCompare(){
		new Thread(){
			@Override
			public void run() {
				File[] fs = new File("/sdcard/Download/").listFiles(new FileFilter() {
					@Override
					public boolean accept(File file) {
						return file.getName().endsWith(".png");
					}
				});
				for(File f : fs){
					Bitmap bm = BitmapFactory.decodeFile(f.getAbsolutePath());
					compareBitmapAndShow(bm);
				}

				lv.post(new Runnable() {
					@Override
					public void run() {
						adapter.notifyDataSetChanged();
					}
				});
			}
		}.start();
	}
	void compareBitmapAndShow(Bitmap bm){
		if(bm != null && bm.getWidth() > 0 && bm.getHeight() > 0) {

			final Bitmap bm1 = BitmapUtils.clipBitmapWidthBounds(bm, new Rect(0, 0, bm.getWidth() / 2, bm.getHeight()));
			//bm1 = BitmapFactory.decodeFile("/sdcard/l.png");
			final Bitmap bm2 = BitmapUtils.clipBitmapWidthBounds(bm, new Rect(bm.getWidth() / 2, 0, bm.getWidth(), bm.getHeight()));
			//bm2 = BitmapFactory.decodeFile("/sdcard/r.png");
			try {
				Bitmap[] scaled = new Bitmap[2];
				//scaled[0] = Bitmap.createBitmap(pHash.DCT_LENGTH, pHash.DCT_LENGTH, Bitmap.Config.ARGB_8888);
				//scaled[1] = Bitmap.createBitmap(pHash.DCT_LENGTH, pHash.DCT_LENGTH, Bitmap.Config.ARGB_8888);
				//int cmp = pHash.compareBitmap(bm1, bm2, scaled, false);
				long st = SystemClock.uptimeMillis();
				final int cmp = ImagePHash.compareBitmap(bm1, bm2);
				long et = SystemClock.uptimeMillis();
				Log.d(TAG, "compare " + cmp + " spend " + (et - st) + " ms");
				Item item = new Item();
				item.l = bm1;
				item.r = bm2;
				item.res = "Result: " + cmp + ", spend " + (et - st) + " ms";
				adapter.items.add(item);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}
	public static class ImagePHash {

		// 默认使用 32x32 大小
		private static final int SIZE = 32;
		// DCT 截取的大小(例如 8x8)
		private static final int SMALLER_SIZE = 8;

		public static int compareBitmap(Bitmap bm1, Bitmap bm2){
			String h1 = getHash(bm1);
			String h2 = getHash(bm2);
			return hammingDistance(h1, h2);
		}
		@SuppressLint("NewApi")
		public static String getHash(Bitmap img) {
			long st = SystemClock.uptimeMillis();
			// 1. 转换为灰度图像
			Bitmap grayImg = toGrayscale(img);

			// 2. 缩小图片
			Bitmap smallImg = Bitmap.createScaledBitmap(grayImg, SIZE, SIZE, false);

			// 3. 转换为二维数组
			double[][] vals = new double[SIZE][SIZE];
			for (int x = 0; x < SIZE; x++) {
				for (int y = 0; y < SIZE; y++) {
					vals[x][y] = Color.red(smallImg.getPixel(x, y));
				}
			}

			long ct1 = SystemClock.uptimeMillis();
			// 4. 对图像执行离散余弦变换(DCT)
			double[][] dctVals = applyDCT(vals);
			long ct2 = SystemClock.uptimeMillis();

			// 5. 截取 DCT 左上角的 8x8 部分
			double[] dctLowFreq = new double[SMALLER_SIZE * SMALLER_SIZE];
			for (int x = 0; x < SMALLER_SIZE; x++) {
				for (int y = 0; y < SMALLER_SIZE; y++) {
					dctLowFreq[x * SMALLER_SIZE + y] = dctVals[x][y];
				}
			}

			// 6. 计算均值
			double avg = Arrays.stream(dctLowFreq).average().orElse(0.0);


			long ct3 = SystemClock.uptimeMillis();

			// 7. 生成哈希值
			StringBuilder hash = new StringBuilder();
			for (double value : dctLowFreq) {
				hash.append(value > avg ? "1" : "0");
			}
			Log.d("ImgDiff", (ct1 - st) + ", " + (ct2 - ct1));
			return hash.toString();
		}

		// 转换为灰度图像
		private static Bitmap toGrayscale(Bitmap img) {
			int width = img.getWidth();
			int height = img.getHeight();
			Bitmap grayscaleImg = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

			for (int y = 0; y < height; y++) {
				for (int x = 0; x < width; x++) {
					int pixel = img.getPixel(x, y);
					int red = Color.red(pixel);
					int green = Color.green(pixel);
					int blue = Color.blue(pixel);
					int gray = (red + green + blue) / 3;
					int newPixel = Color.rgb(gray, gray, gray);
					grayscaleImg.setPixel(x, y, newPixel);
				}
			}
			return grayscaleImg;
		}

		// 执行离散余弦变换(DCT)
		private static double[][] applyDCT(double[][] f) {
			int N = f.length;
			double[][] F = new double[N][N];
			for (int u = 0; u < N; u++) {
				for (int v = 0; v < N; v++) {
					double sum = 0.0;
					for (int i = 0; i < N; i++) {
						for (int j = 0; j < N; j++) {
							sum += f[i][j] *
									Math.cos((2 * i + 1) * u * Math.PI / (2.0 * N)) *
									Math.cos((2 * j + 1) * v * Math.PI / (2.0 * N));
						}
					}
					double alphaU = (u == 0) ? Math.sqrt(1.0 / N) : Math.sqrt(2.0 / N);
					double alphaV = (v == 0) ? Math.sqrt(1.0 / N) : Math.sqrt(2.0 / N);
					F[u][v] = alphaU * alphaV * sum;
				}
			}
			return F;
		}

		// 比较两个哈希值,返回汉明距离(不同位数的个数)
		public static int hammingDistance(String hash1, String hash2) {
			int distance = 0;
			for (int i = 0; i < hash1.length(); i++) {
				if (hash1.charAt(i) != hash2.charAt(i)) {
					distance++;
				}
			}
			return distance;
		}
	}

	class ImgListAdapter extends BaseAdapter{
		ArrayList<Item> items = new ArrayList<>();
		@Override
		public int getCount() {
			return items.size();
		}

		@Override
		public Object getItem(int i) {
			return items.get(i);
		}

		@Override
		public long getItemId(int i) {
			return i;
		}

		@Override
		public View getView(int pos, View view, ViewGroup viewGroup) {
			if(view == null){
				view = getLayoutInflater().inflate(R.layout.item_img_diff, null, false);
			}
			((ImageView)view.findViewById(R.id.ivLeft)).setImageBitmap(items.get(pos).l);
			((ImageView)view.findViewById(R.id.ivRight)).setImageBitmap(items.get(pos).r);
			((TextView)view.findViewById(R.id.tvRes)).setText(items.get(pos).res);
			return view;
		}
	}
	class Item{
		Bitmap l, r;
		String res;
	}
}

温馨提示
本文算法及用例仅供参考, 未经大量测试验证
请谨慎阅读参考

参考

Android Bitmap亮度调节、灰度化、二值化、相似距离实现

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java基础-注解机制详解

文章目录 注解基础Java内置注解内置注解- Override内置注解 - Deprecated内置注解 - SuppressWarnings 元注解元注解 - Target元注解 - Retention & RetentionTarget元注解 - Documented元注解 - Inherited 注解与反射接口自定义注解 深入理解注解Java8提供了哪些新的注解&…

【React系列三】—React学习历程的分享

一、组件实例核心—Refs 通过定义 ref 属性可以给标签添加标识 字符串形式的Refs 这种形式已经不再推荐使用&#xff0c;官方不建议使用 https://zh-hans.legacy.reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs 回调形式的Refs <script type"te…

nginx精讲

&#x1f939;‍♀️潜意识起点&#xff1a;个人主页 &#x1f399;座右铭&#xff1a;得之坦然&#xff0c;失之淡然。 &#x1f48e;擅长领域&#xff1a;前端 是的&#xff0c;我需要您的&#xff1a; &#x1f9e1;点赞❤️关注&#x1f499;收藏&#x1f49b; 是我持…

计算广告第三版pdf

需要该书pdf版本的同学点赞&#xff0c;私信我&#xff1a;

给哔哩哔哩bilibili电脑版做个手机遥控器

前言 bilibili电脑版可以在电脑屏幕上观看bilibili视频。然而&#xff0c;电脑版的bilibili不能通过手机控制视频翻页和调节音量&#xff0c;这意味着观看视频时需要一直坐在电脑旁边。那么&#xff0c;有没有办法制作一个手机遥控器来控制bilibili电脑版呢&#xff1f; 首先…

WPF MVVM模式实现DataGrid编辑

本文是一个MVVM模式开发的基础教程&#xff0c;完全手写实现&#xff0c;未借助三方框架&#xff0c;适用于初学者 要实现DataGrid的编辑&#xff0c;步骤如下&#xff1a; 1、创建两个窗口&#xff0c;第一个窗口用于显示DataGrid&#xff0c; 布局如下&#xff1a; 这个界…

(3) c++基本代码

main函数 main函数只有可执行程序才需要&#xff0c;如果是动态库等则无需main函数。 main函数标准的写法是 #include <iostream> using namspace std; int main(void) {// 业务代码return 0; } 当然以上代码只是最简单的案例&#xff0c;其中代表main函数值是int&#…

TypeScript中 元组、枚举enum、type

元组&#xff1a; let arr : [string, number] [hello, 3]; let arr2 : [number, boolean?] [44];//问号可选的let arr3 : [number, ...string[]] [34, a, b, c];//任意多个字符串&#xff0c;也可以没有 let arr4 : [number, ...string[]] [34]; 枚举&#xff1a; //e…

【C++进阶】之C++11的简单介绍(一)

&#x1f4c3;博客主页&#xff1a; 小镇敲码人 &#x1f49a;代码仓库&#xff0c;欢迎访问 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f30f; 任尔江湖满血骨&#xff0c;我自踏雪寻梅香。 万千浮云遮碧…

【CSS、JS】监听transitionend多次触发的原因

现有代码如下&#xff0c;移入红色内容区域触发动画&#xff0c;监听动画触发&#xff0c;但是每次触发控制台会打印4次 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content…

低功耗4G模组采集温湿度传感器数据~ 超全教程!不会的小伙伴看这篇!

在物联网&#xff08;IoT&#xff09;快速发展的今天&#xff0c;温湿度传感器作为环境监测的重要设备&#xff0c;被广泛应用于农业、工业、智慧城市等领域。本文将详细介绍如何使用低功耗4G模组Air780E采集温湿度传感器数据并实现网页查看&#xff0c;帮助初学者快速上手。 一…

springboot汉妆养生会馆网站-计算机毕业设计源码96229

目录 摘要 Abstract 1 绪论 1.1选题背景 1.2研究意义 1.3系统开发目标 2相关技术介绍 2.1 Java编程语言 2.2 B/S模式 2.3 MySQL简介 2.4 SpringBoot框架 3.汉妆养生会馆网站的设计与实现系统分析 3.1 可行性分析 3.1.1技术可行性分析 3.1.2经济可行性分析 3.1.3…

canvas小蜘蛛

一. 效果 二. 代码 <!--* Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git* Date: 2024-10-2…

【视频混剪Demo】FFmpeg的使用【Windows】

#1024程序员节 | 征文# 目录 一、简介 二、音频素材页 2.1 功能描述 &#x1f449; 搜索 &#x1f449; 添加 &#x1f449; 删除 2.2 效果展示 2.3 代码实现 &#x1f449; 前端 &#x1f449; 后端 三、视频素材页 3.1 功能描述 &#x1f449; 搜索 &#x1…

【2024CANN训练营第二季】使用华为云体验AscendC_Sample仓算子运行

环境介绍 NPU&#xff1a;Ascend910B2 环境准备 创建Notebook 华为云选择&#xff1a;【控制台】-【ModelArts】 ModelArts主页选择【开发生产】-【开发空间】-【Notebook】 页面右上角选择【创建Notebook】 选择资源 主要参数 规格&#xff1a;Ascend: 1*ascend-snt…

微搭低代码学习1:不同页面传递值

这个系列逐渐学习低代码平台&#xff0c;补足因为技术栈不足带来的问题&#xff0c;同时借助低代码平台快速搭建成型的系统。 这个博客用来记录一个非常常见的操作&#xff0c;在两个页面/多个页面之间传递值 文章目录 1. 创建页面2. 添加逻辑主动跳转页逻辑设置数据接收页逻辑…

【数据结构与算法】之栈详解

栈&#xff08;Stack&#xff09;是一种基本的线性数据结构&#xff0c;遵循后进先出、先进后出的原则。本文将更详细地介绍栈的概念、特点、Java 实现以及应用场景。 1. 栈概念概述 想象一摞叠放的盘子&#xff0c;你只能从最上面取盘子&#xff0c;放盘子也只能放在最上面。…

html和css实现页面

任务4 html文件 任务5 htm文件 css文件 任务6 html文件 css文件 任务7 html文件 css文件

工业交换机的电源类型

工业交换机的电源通常有以下几种类型和注意事项&#xff1a; 1. 电源类型&#xff1a; 交流电源&#xff08;AC&#xff09;&#xff1a;一些工业交换机使用标准的AC电源&#xff0c;通常是110V或220V。适用于有稳定电源环境的场合。 直流电源&#xff08;DC&#xff09;&#…

javaWeb项目-ssm+jsp大学生校园兼职系统功能介绍

本项目源码&#xff08;点击下方链接下载&#xff09;&#xff1a;java-ssmjsp大学生校园兼职系统实现源码(项目源码-说明文档)资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#x…