深入探索Android Bitmap:从原理到实战
一、Bitmap 是什么
在 Android 开发中,Bitmap 是极为重要的基石。简单来说,Bitmap 代表位图,是图片在内存里的具体呈现形式 ,任何诸如 JPEG、PNG、WEBP 等格式的图片,一旦被加载到内存中,就会以 Bitmap 对象的形式存在。从原理上看,Bitmap 本质是像素点的集合,若其宽度为 width,高度为 height,那么该 Bitmap 就由 width * height 个像素构成,其在内存中占用的内存大小为 width * height * 单个像素内存。
为了更直观地理解 Bitmap,我们可以将其类比为日常生活中的照片打印。假设你有一张美丽的风景照片,想要将它打印出来。在打印之前,照片的数据就如同 Bitmap,它包含了图像的所有像素信息,这些像素信息决定了照片上每一个点的颜色和亮度。而打印的过程就像是将 Bitmap 显示在设备屏幕上,打印机需要读取这些像素信息,然后通过墨水或 toner 将图像呈现在纸张上。同样,在 Android 应用中,当我们要在界面上展示一张图片时,就需要将图片文件加载为 Bitmap 对象,然后将其传递给 ImageView 等控件进行显示。
从功能角度来讲,Bitmap 在 Android 开发中就像是一个 “图像容器”,承载着图像的像素信息,凭借它,开发者能够在应用中轻松实现加载、显示和处理图像的操作。通过 Bitmap 类,开发者可以创建图像对象,在屏幕上展示或者对其进行更深入的处理,诸如缩放、裁剪、旋转等常见的图像操作,都可以借助 Bitmap 来完成。例如,在一个图片编辑应用中,用户可以通过 Bitmap 对图片进行裁剪,选择自己喜欢的部分进行保留;也可以对图片进行旋转,调整到合适的角度;还能对图片进行缩放,使其适应不同的屏幕尺寸。在一个社交应用中,用户上传的照片可能需要进行压缩和裁剪,以适应服务器的存储和传输要求,这时候就可以使用 Bitmap 来实现这些操作。
二、Bitmap 的内部原理
(一)颜色通道
在 Android 的图像世界里,Bitmap 以 ARGB 的独特顺序存储颜色通道,这里的 ARGB 分别代表 Alpha(透明度)、Red(红色)、Green(绿色)和 Blue(蓝色) 。
Alpha 通道用于掌控像素的透明度,取值范围从 0 到 255 。当 Alpha 为 0 时,像素处于完全透明状态,就如同空气一般不可见;而当 Alpha 达到 255 时,像素则是完全不透明的,稳稳地呈现出自身的颜色。Red、Green 和 Blue 通道则分别表示红色、绿色和蓝色分量的强度,它们的取值范围同样是 0 到 255 。通过这三个通道不同强度的组合,便能调配出丰富多彩的颜色。比如,当 Red 通道取值为 255,Green 和 Blue 通道取值为 0 时,呈现出的就是鲜艳的纯红色;当 Red 和 Green 通道取值均为 255,Blue 通道取值为 0 时,展现的便是充满活力的黄色。
在内存的舞台上,Bitmap 的像素通常是按行有序存储的。以常见的 ARGB_8888 格式为例,每个像素占用 4 个字节的空间,这 4 个字节依次对应 ARGB 四个通道 ,即每个像素占据 32 位。假设我们有一个简单的 2x2 的 Bitmap,其像素数据按行存储,第一个像素的 ARGB 值为 (255, 255, 0, 255),第二个像素的 ARGB 值为 (0, 255, 255, 255),那么在内存中,它们的存储顺序就是第一个像素的 A 通道字节、R 通道字节、G 通道字节、B 通道字节,接着是第二个像素的 A 通道字节、R 通道字节、G 通道字节、B 通道字节。
除了 ARGB_8888 格式,Android 中还有其他常见的颜色格式,如 RGB_565 。在 RGB_565 格式中,没有 Alpha 通道,只专注于颜色的呈现。其中,红色占用 5 位,绿色占用 6 位,蓝色占用 5 位,总共 16 位,即每个像素占用 2 个字节 。这种格式在一些对透明度要求不高、追求节省内存空间的场景中应用广泛。例如,在一些简单的游戏界面中,背景图片可能不需要透明度效果,使用 RGB_565 格式就能在保证一定图像质量的同时,有效减少内存占用,提升游戏的运行性能。 而 ARGB_4444 格式,虽然曾经也被使用,但由于每个通道仅用 4 位存储,导致画质质量较差,如今已不被推荐使用。ALPHA_8 格式则比较特殊,它只保存透明度,不保存颜色,每个像素仅占用 1 个字节,适用于一些只需要透明度信息的特殊场景。
(二)内存占用计算
Bitmap 占用内存大小的计算公式为:大小(字节) = 宽度 × 高度 × 每个像素占用的字节数 。其中,每个像素占用的字节数取决于 Bitmap 的配置 。
以常见的 ARGB_8888 格式为例,每个像素占用 4 个字节。假设有一张宽度为 800 像素,高度为 600 像素的图片,若采用 ARGB_8888 格式,那么它占用的内存大小为 800 × 600 × 4 = 1920000 字节,换算后约为 1.83MB 。而如果采用 RGB_565 格式,每个像素占用 2 个字节,同样尺寸的图片占用的内存大小则为 800 × 600 × 2 = 960000 字节,约为 0.92MB 。通过这两个例子可以明显看出,颜色格式对 Bitmap 内存占用有着显著的影响,在对图像质量要求不是特别高的情况下,选择 RGB_565 格式可以大幅减少内存占用。
图片尺寸对内存占用的影响也十分直观。当图片的宽度和高度增加时,像素点的数量会呈指数级增长,从而导致内存占用急剧上升。比如,将上述图片的宽度和高度都翻倍,变为 1600 像素和 1200 像素,采用 ARGB_8888 格式时,内存占用就会变为 1600 × 1200 × 4 = 7680000 字节,约为 7.33MB,是原来的四倍 。这也解释了为什么在加载高清大图时,容易出现内存溢出的问题。
此外,还需要注意的是,在实际的 Android 开发中,Bitmap 除了自身像素数据占用的内存外,还会占用一定的额外内存,用于存储 Bitmap 的配置信息、像素数据等 。并且,当从资源文件中加载 Bitmap 时,其内存占用还可能受到设备密度和资源文件夹密度的影响,这涉及到图片的缩放问题,会进一步影响内存的占用情况。
三、Bitmap 的创建与加载
(一)创建 Bitmap 对象
在 Android 开发中,创建 Bitmap 对象有多种方式,每种方式都适用于不同的场景。
- 从资源文件创建:通过BitmapFactory.decodeResource方法可以从资源文件中创建 Bitmap 对象 。例如:
Resources resources = getResources();
Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.example_image);
在这段代码中,getResources()用于获取当前上下文的资源,R.drawable.example_image是图片资源的 ID,decodeResource方法会根据这个 ID 从资源文件中读取图片数据,并创建对应的 Bitmap 对象 。这种方式适用于应用中预先打包好的图片资源,比如应用的图标、默认背景图片等。
- 从本地文件创建:利用BitmapFactory.decodeFile方法可以从本地文件系统中创建 Bitmap 对象 。示例代码如下:
String filePath = "/sdcard/images/example.jpg";
Bitmap bitmap = BitmapFactory.decodeFile(filePath);
这里的filePath是本地图片文件的路径,decodeFile方法会读取该路径下的图片文件,并将其转换为 Bitmap 对象 。这种方式常用于加载用户本地存储的图片,比如用户拍摄的照片、下载的图片等。
- 从输入流创建:通过BitmapFactory.decodeStream方法可以从输入流中创建 Bitmap 对象 。代码示例:
try {InputStream inputStream = new FileInputStream("/sdcard/images/example.jpg");Bitmap bitmap = BitmapFactory.decodeStream(inputStream);inputStream.close();
} catch (IOException e) {e.printStackTrace();
}
在这个例子中,首先创建一个FileInputStream输入流来读取本地图片文件,然后将输入流传递给decodeStream方法,该方法会从输入流中读取图片数据并创建 Bitmap 对象 。最后,别忘了关闭输入流以释放资源。这种方式灵活性较高,不仅可以从文件输入流创建 Bitmap,还可以从网络输入流等其他类型的输入流创建,比如从网络加载图片时就可以使用这种方式。
- 从字节数组创建:使用BitmapFactory.decodeByteArray方法可以从字节数组创建 Bitmap 对象 。示例如下:
byte[] byteArray = getImageBytes();// 假设这个方法用于获取图片的字节数组
Bitmap bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
这里的byteArray是包含图片数据的字节数组,0表示从字节数组的起始位置开始读取,byteArray.length表示读取的字节数,即整个字节数组 。这种方式适用于图片数据以字节数组形式存在的情况,比如从网络传输过来的图片数据或者从本地文件读取到的字节数组。
- 从 Uri 创建:通过ContentResolver获取输入流,再利用BitmapFactory.decodeStream方法从 Uri 创建 Bitmap 对象 。代码如下:
Uri uri = Uri.parse("content://media/external/images/media/123");// 假设这是图片的Uri
ContentResolver resolver = getContentResolver();
try {InputStream inputStream = resolver.openInputStream(uri);Bitmap bitmap = BitmapFactory.decodeStream(inputStream);inputStream.close();
} catch (IOException e) {e.printStackTrace();
}
在这段代码中,首先通过Uri.parse方法将图片的 Uri 解析为Uri对象,然后使用getContentResolver获取ContentResolver对象,再通过openInputStream方法从Uri中获取输入流,最后利用decodeStream方法从输入流中创建 Bitmap 对象 。这种方式常用于获取系统相册中的图片或者其他通过Uri标识的图片资源。
(二)加载图片资源
在加载图片资源时,由于图片的大小和分辨率各不相同,若处理不当,很容易出现内存溢出的问题 。比如,当加载一张分辨率很高的大图片时,其占用的内存可能会超过应用的可用内存,从而导致OutOfMemoryError异常。为了避免这种情况,我们可以通过BitmapFactory.Options来设置参数,优化加载过程。
- inJustDecodeBounds 参数:将inJustDecodeBounds设置为true,可以在不实际加载图片到内存的情况下,获取图片的宽高和其他信息 。示例代码如下:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
在这段代码中,首先创建一个BitmapFactory.Options对象,并将其inJustDecodeBounds属性设置为true。然后调用decodeResource方法,此时并不会真正加载图片到内存,而是仅仅解析图片的头部信息,将图片的宽度和高度分别存储在options.outWidth和options.outHeight中 。通过这种方式,我们可以在加载图片之前就了解图片的大小,从而根据需要决定是否加载或者如何加载图片。
- inSampleSize 参数:inSampleSize用于设置图片的采样率,通过设置合适的采样率,可以缩小图片的尺寸,减少内存占用 。例如,当inSampleSize = 2时,图片的宽高都会被缩小为原来的一半,像素数变为原来的四分之一,内存占用也相应减少 。计算合适的inSampleSize的方法如下:
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {final int height = options.outHeight;final int width = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {final int heightRatio = Math.round((float) height / (float) reqHeight);final int widthRatio = Math.round((float) width / (float) reqWidth);inSampleSize = heightRatio < widthRatio? heightRatio : widthRatio;}return inSampleSize;
}
使用这个方法时,首先需要将BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片获取其原始宽高。然后将BitmapFactory.Options连同期望的宽度和高度一起传递到calculateInSampleSize方法中,得到合适的inSampleSize值 。最后再将inJustDecodeBounds设置为false,重新加载图片,此时加载的就是缩小后的图片 。示例代码如下:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
int inSampleSize = calculateInSampleSize(options, 200, 200);
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
在这个例子中,首先通过inJustDecodeBounds = true获取图片的原始宽高,然后计算出合适的inSampleSize值,最后将inJustDecodeBounds设置为false,并设置inSampleSize,重新加载图片,这样加载的图片就会根据inSampleSize进行缩放,从而减少内存占用 。通过合理设置inJustDecodeBounds和inSampleSize参数,可以有效地优化图片加载过程,避免内存溢出问题,提高应用的性能和稳定性 。
四、Bitmap 的常见操作
(一)缩放
在 Android 开发中,对 Bitmap 进行缩放是常见的操作之一,主要有根据给定宽高拉伸和按比例缩放两种方式 。
- 根据给定宽高拉伸:通过Bitmap.createScaledBitmap方法可以实现根据给定宽高对 Bitmap 进行拉伸 。示例代码如下:
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.example_image);
int newWidth = 200;
int newHeight = 300;
Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true);
在这段代码中,originalBitmap是原始的 Bitmap 对象,newWidth和newHeight分别是期望的新宽度和新高度 。createScaledBitmap方法会根据指定的宽高对原始 Bitmap 进行拉伸或压缩,返回一个新的 Bitmap 对象 。
- 按比例缩放:利用Matrix类可以实现按比例缩放 。示例代码如下:
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.example_image);
float scaleRatio = 0.5f;
Matrix matrix = new Matrix();
matrix.postScale(scaleRatio, scaleRatio);
Bitmap scaledBitmap = Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), originalBitmap.getHeight(), matrix, true);
这里的scaleRatio是缩放比例,Matrix类的postScale方法用于设置缩放比例 。通过postScale方法设置好缩放比例后,再使用Bitmap.createBitmap方法创建一个新的按比例缩放后的 Bitmap 对象 。
Matrix类在缩放操作中起着关键作用,它通过一个 3x3 的矩阵来处理位图 。在缩放时,Matrix会根据设置的缩放比例,重新计算位图中各个像素点的位置 。例如,当设置scaleRatio = 0.5f时,位图在 x 和 y 方向上的像素点位置都会变为原来的 0.5 倍,从而实现图像的缩小 。从数学原理上来说,Matrix中的缩放操作是通过矩阵乘法来实现的,它将原始位图的坐标矩阵与缩放矩阵相乘,得到新的坐标矩阵,进而确定缩放后位图中像素点的位置 。 缩放操作在实际应用中用途广泛,比如在适配不同屏幕尺寸时,需要将图片缩放到合适的大小以适应屏幕;在图片浏览应用中,用户可能会对图片进行放大或缩小操作,这都需要使用到 Bitmap 的缩放功能 。
(二)裁剪
裁剪 Bitmap 是指根据指定区域从原始 Bitmap 中提取出一部分图像,创建一个新的 Bitmap 。在 Android 中,可以使用Bitmap.createBitmap方法来实现裁剪 。示例代码如下:
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.example_image);
int x = 50;
int y = 50;
int width = 100;
int height = 100;
Bitmap croppedBitmap = Bitmap.createBitmap(originalBitmap, x, y, width, height);
在这段代码中,x和y是裁剪区域的起始坐标,width和height是裁剪区域的宽度和高度 。createBitmap方法会从原始 Bitmap 的指定位置(x, y)开始,截取指定宽度和高度的图像,创建一个新的 Bitmap 对象 。
裁剪操作在实际应用中有很多用途,比如在头像裁剪功能中,用户上传的头像可能是一张较大的图片,需要裁剪出合适的部分作为头像显示 。假设用户上传了一张全身照,而头像显示区域只需要脸部部分,这时就可以通过裁剪操作,从全身照中提取出脸部区域的图像作为头像 。在图片编辑应用中,用户也可以通过裁剪操作,去除图片中不需要的部分,突出图片的主体内容 。 裁剪操作还可以用于创建图像的缩略图,通过裁剪原始图像的中心部分,并进行适当的缩放,可以得到一个简洁的缩略图 。
(三)旋转
旋转 Bitmap 是将 Bitmap 按照指定的角度进行旋转,在 Android 中可以借助Matrix类来实现 。示例代码如下:
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.example_image);
float rotationAngle = 90;
Matrix matrix = new Matrix();
matrix.setRotate(rotationAngle);
Bitmap rotatedBitmap = Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), originalBitmap.getHeight(), matrix, true);
在这段代码中,rotationAngle是旋转角度,Matrix类的setRotate方法用于设置旋转角度 。设置好旋转角度后,通过Bitmap.createBitmap方法创建一个新的旋转后的 Bitmap 对象 。
Matrix类的旋转操作原理是通过矩阵变换来实现的 。在数学上,旋转操作可以用一个旋转矩阵来表示,Matrix类会根据设置的旋转角度生成相应的旋转矩阵,然后将原始 Bitmap 的坐标矩阵与旋转矩阵相乘,得到旋转后 Bitmap 的坐标矩阵,从而确定旋转后 Bitmap 中各个像素点的位置 。
旋转操作在很多场景中都有应用,比如在图片查看器中,当用户发现图片方向不正确时,可以通过旋转操作将图片调整到正确的方向 。在图像识别应用中,有时为了提高识别准确率,需要对图片进行不同角度的旋转,以获取更多的图像特征 。在一些艺术创作应用中,旋转操作可以为图片增添独特的视觉效果,满足用户的创意需求 。
(四)偏移
偏移 Bitmap 是指将 Bitmap 在 x 和 y 方向上进行平移,在 Android 中同样可以使用Matrix类来实现 。示例代码如下:
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.example_image);
float dx = 50;
float dy = 30;
Matrix matrix = new Matrix();
matrix.postTranslate(dx, dy);
Bitmap skewedBitmap = Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), originalBitmap.getHeight(), matrix, true);
在这段代码中,dx和dy分别是在 x 方向和 y 方向上的偏移量 。Matrix类的postTranslate方法用于设置偏移量,通过设置偏移量后,再使用Bitmap.createBitmap方法创建一个新的偏移后的 Bitmap 对象 。
Matrix类的偏移操作原理是通过矩阵变换来实现的 。在数学上,偏移操作可以用一个平移矩阵来表示,Matrix类会根据设置的偏移量生成相应的平移矩阵,然后将原始 Bitmap 的坐标矩阵与平移矩阵相乘,得到偏移后 Bitmap 的坐标矩阵,从而确定偏移后 Bitmap 中各个像素点的新位置 。
偏移操作在特殊效果制作中有着广泛的应用,比如在实现图片视差效果时,可以通过对不同图层的 Bitmap 进行不同程度的偏移,营造出深度感 。在一些动画效果中,通过对图片进行动态的偏移,可以实现图片的移动效果,增强动画的趣味性 。在游戏开发中,偏移操作可以用于实现游戏角色的移动、场景的切换等功能 。
五、Bitmap 的性能优化
(一)内存优化策略
1. 采样率压缩
采样率压缩是通过设置inSampleSize来实现的 。inSampleSize表示采样率,它是一个整数值,用于指定加载图片时对图片进行压缩的程度 。当inSampleSize = 2时,图片在宽高方向上都会缩小为原来的一半,像素数变为原来的四分之一,内存占用也相应减少 。这是因为图片占用的内存大小与像素数成正比,而像素数又与宽高的乘积成正比,当宽高都缩小为原来的一半时,像素数就变为原来的四分之一,从而内存占用也变为原来的四分之一 。
下面通过一个实际案例来展示采样率压缩前后内存占用和图片质量的变化 。假设我们有一张分辨率为 2000x1500 像素的图片,原始格式为 ARGB_8888,每个像素占用 4 个字节 。
// 计算原始图片内存占用
int originalWidth = 2000;
int originalHeight = 1500;
int originalMemory = originalWidth * originalHeight * 4;
在上述代码中,通过图片的宽度originalWidth、高度originalHeight以及每个像素占用的字节数 4,计算出原始图片的内存占用originalMemory 。
接下来进行采样率压缩,设置inSampleSize = 4:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap sampledBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);
int sampledWidth = sampledBitmap.getWidth();
int sampledHeight = sampledBitmap.getHeight();
int sampledMemory = sampledWidth * sampledHeight * 4;
在这段代码中,首先创建一个BitmapFactory.Options对象,并设置其inSampleSize为 4 。然后使用BitmapFactory.decodeResource方法根据设置的选项加载图片,得到采样后的Bitmap对象 。接着获取采样后图片的宽度sampledWidth和高度sampledHeight,并计算其内存占用sampledMemory 。
经过计算,原始图片内存占用约为 12MB,而采样率为 4 时,图片宽高变为 500x375 像素,内存占用约为 0.75MB 。从图片质量上看,由于像素数减少,图片会变得模糊一些,尤其是在放大查看时,细节会有所丢失 。但在一些对图片质量要求不是特别高,而更注重内存占用和加载速度的场景中,这种程度的质量损失是可以接受的 。例如,在加载列表中的图片时,用户可能更关注图片的大致内容,而对细节要求不高,此时采用采样率压缩可以有效减少内存占用,提高列表的加载速度和滑动流畅性 。
2. 更改 Bitmap.Config 格式
更改 Bitmap.Config 格式是减少内存占用的另一种有效方法 。常见的 Bitmap.Config 格式有 ARGB_8888、RGB_565、ARGB_4444 和 ALPHA_8 。其中,ARGB_8888 每个像素占用 4 个字节,能表示丰富的颜色和透明度,是默认的格式 ;RGB_565 每个像素占用 2 个字节,不支持透明度,主要用于显示颜色 ;ARGB_4444 每个像素占用 2 个字节,但由于每个通道仅用 4 位存储,画质较差,已不被推荐使用 ;ALPHA_8 每个像素仅占用 1 个字节,只保存透明度,不保存颜色 。
将 Bitmap.Config 格式从 ARGB_8888 改为 RGB_565 时,内存占用会减少一半 。例如,有一张 1000x800 像素的图片,采用 ARGB_8888 格式时,内存占用为 1000 × 800 × 4 = 3200000 字节 。若改为 RGB_565 格式,内存占用则变为 1000 × 800 × 2 = 1600000 字节 。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.example_image, options);
在这段代码中,创建BitmapFactory.Options对象后,将inPreferredConfig属性设置为Bitmap.Config.RGB_565,然后使用该选项从资源文件中解码图片,得到的Bitmap对象就是采用 RGB_565 格式存储的 。
RGB_565 格式适用于对透明度要求不高的场景,比如游戏中的背景图片、一些简单的图标等 。在这些场景中,使用 RGB_565 格式可以在保证一定图像质量的前提下,有效减少内存占用,提升应用的性能 。但需要注意的是,由于 RGB_565 不支持透明度,对于需要透明度效果的图片,如带有透明背景的图标、半透明的遮罩层等,就不能使用这种格式,否则会导致透明度信息丢失,影响图片的显示效果 。
3. Bitmap 复用
Bitmap 复用是指使用Options.inBitmap参数来复用已有的 Bitmap 内存,从而避免重复创建 Bitmap 导致的内存开销 。在 Android 3.0 开始引入了inBitmap设置,通过设置这个参数,在图片加载的时候可以使用之前已经创建了的 Bitmap,以便节省内存,避免再次创建一个 Bitmap 。在 Android 4.4,新增了允许inBitmap设置的图片与需要加载的图片的大小不同的情况,只要inBitmap的图片比当前需要加载的图片大就好了 。
以下是使用Options.inBitmap复用 Bitmap 内存的代码示例:
// 创建一个可以复用的Bitmap
Bitmap reusableBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
options.inBitmap = reusableBitmap;
Bitmap newBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.example_image, options);
在这段代码中,首先创建一个reusableBitmap,然后创建BitmapFactory.Options对象,并将inMutable设置为true,表示该 Bitmap 是可变的,这是复用的必要条件 。接着将inBitmap设置为reusableBitmap,最后使用该选项从资源文件中解码图片,得到的newBitmap就复用了reusableBitmap的内存 。
复用 Bitmap 内存的条件如下:
- 在 Android 4.4 版本之前,只能重用相同大小的 Bitmap 内存区域 。
- 在 Android 4.4 及之后,只要inBitmap的图片比将要分配内存的 Bitmap 大就可以复用 。
在使用 Bitmap 复用时,还需要注意以下事项:
- 复用的 Bitmap 必须是可变的,即inMutable需设置为true 。
- 复用的 Bitmap 在不再使用时,需要谨慎处理,避免内存泄漏 。
- 复用的 Bitmap 可能会影响图片的显示效果,比如当复用的 Bitmap 与新图片的格式不一致时,可能会导致颜色偏差等问题 。
为了对比复用前后的内存使用情况,可以使用 Android Profiler 工具进行观察 。在未复用 Bitmap 内存时,每次加载新图片都会分配新的内存空间,内存占用会随着图片的加载不断增加 。而使用 Bitmap 复用后,当新图片的大小符合复用条件时,会复用已有的 Bitmap 内存,内存占用相对稳定,不会因为频繁加载图片而大幅增加 。这在处理大量图片加载的场景中,如图片浏览器、相册应用等,可以显著减少内存的使用,提高应用的性能和稳定性 。
(二)缓存策略
1. LruCache 工具类
LruCache 是一种基于最近最少使用(Least Recently Used,LRU)算法的缓存机制,它适用于有限的缓存空间,如在 Android 开发中的内存缓存 。LruCache 通过维护一个最近使用的缓存项的列表,根据访问顺序淘汰最久未使用的数据 。在 Android 系统中,LruCache 通过以下步骤来实现缓存机制:
- 创建一个固定大小的缓存容器,使用LinkedHashMap来存储缓存对象 。LinkedHashMap是一种有序的哈希表,它可以根据访问顺序或插入顺序来维护元素的顺序,在 LruCache 中,我们使用它的访问顺序特性,即每次访问一个元素时,该元素会被移动到链表的头部 。
- 每当有缓存项被访问时,它就会被移动到LinkedHashMap的链表头部 。这是通过LinkedHashMap的afterNodeAccess方法实现的,当调用get方法获取缓存项时,该缓存项会被移动到链表头部,表明它是最近被访问的 。
- 当缓存容器达到最大容量限制时,最久未被访问的缓存项将位于链表的尾部,LruCache 会将这个尾部元素从缓存中移除 。在put方法中,当缓存大小超过最大容量时,会调用trimToSize方法,该方法会不断移除链表头部(即最久未使用)的元素,直到缓存大小小于等于最大容量 。
下面给出使用 LruCache 管理 Bitmap 缓存的代码示例:
// 设置缓存大小为10MB
int cacheSize = 10 * 1024 * 1024;
LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {// 重写此方法来定义每个缓存项的大小,这里以图片的字节数组大小为准return value.getByteCount();}
};
在上述代码中,首先创建一个LruCache对象,设置其缓存大小为 10MB 。然后重写sizeOf方法,用于计算每个缓存项(即 Bitmap)的大小,这里以 Bitmap 的字节数作为大小 。
在使用 LruCache 时,设置缓存大小是一个关键步骤 。缓存大小设置得过小,可能导致缓存命中率低,频繁地从磁盘或网络加载图片,影响性能;而设置得过大,则可能占用过多内存,导致应用出现内存溢出的风险 。一般来说,可以根据应用的需求和设备的内存状况来定制缓存大小 。例如,可以使用Runtime.getRuntime().maxMemory()方法来获取应用能够使用的最大内存,并据此计算 LruCache 的大小 。比如,将缓存大小设置为可用内存的 1/8:
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount() / 1024; }
};
在处理缓存命中与未命中的情况时,代码如下:
public Bitmap loadBitmap(String url) {Bitmap bitmap = mMemoryCache.get(url);if (bitmap == null) {// 缓存未命中,从网络或磁盘加载图片bitmap = downloadBitmap(url); if (bitmap != null) {mMemoryCache.put(url, bitmap);}}return bitmap;
}
在loadBitmap方法中,首先尝试从 LruCache 中获取指定 URL 对应的 Bitmap 。如果获取到了(即缓存命中),则直接返回该 Bitmap 。如果未获取到(即缓存未命中),则从网络或磁盘加载图片(这里假设downloadBitmap方法用于从网络或磁盘加载图片) 。加载成功后,将图片存入 LruCache,并返回该 Bitmap 。通过这样的方式,有效地利用了 LruCache 的缓存机制,减少了重复加载图片的开销,提高了应用的性能和响应速度 。
2. 内存缓存与磁盘缓存
内存缓存和磁盘缓存各有优缺点 。内存缓存的优点是速度快,因为数据存储在内存中,读取速度非常快,可以快速地获取图片并显示在界面上,极大地提升了用户体验 。而且内存缓存与应用的生命周期紧密相关,当应用关闭时,内存缓存中的数据会自动被清除,不需要额外的清理操作 。但是,内存缓存的缺点也很明显,它的容量有限,因为手机的内存资源是有限的,不能无限制地存储图片 。而且内存缓存的数据容易丢失,当系统内存不足时,可能会回收内存缓存中的数据,导致缓存失效 。
磁盘缓存的优点是容量大,手机的磁盘空间相对较大,可以存储更多的图片 。并且磁盘缓存的数据相对持久,即使应用关闭后重新打开,磁盘缓存中的数据仍然存在,不需要重新加载 。然而,磁盘缓存的缺点是读取速度相对较慢,因为磁盘的读写速度比内存慢很多,从磁盘读取图片会增加加载时间,影响用户体验 。而且磁盘 I/O 操作会消耗一定的系统资源,频繁的磁盘读写可能会影响系统性能 。
在实际应用中,通常会结合使用内存缓存和磁盘缓存来提高图片加载效率 。结合使用的原理是,首先从内存缓存中查找图片,如果找到,则直接使用,这样可以快速加载图片,提高用户体验 。如果内存缓存中未找到图片,则从磁盘缓存中查找 。如果磁盘缓存中存在图片,则将其加载到内存中,并同时存入内存缓存,以便下次可以直接从内存缓存中获取 。如果磁盘缓存中也没有图片,则从网络加载图片,加载成功后,将图片存入内存缓存和磁盘缓存,以备后续使用 。
下面给出磁盘缓存的实现思路和代码示例 。实现磁盘缓存可以使用DiskLruCache类,它是一个专门用于磁盘缓存的工具类 。
public class DiskLruCacheHelper {private static final String CACHE_DIR = "image_cache"; private static final int APP_VERSION = 1; private static final int VALUE_COUNT = 1; private static final int MAX_SIZE = 10 * 1024 * 1024; private DiskLruCache mDiskLruCache;public DiskLruCacheHelper(Context context) {try {File cacheDir = new File(context.getCacheDir(), CACHE_DIR);mDiskLruCache = DiskLruCache.open(cacheDir, APP_VERSION, VALUE_COUNT, MAX_SIZE);} catch (IOException e) {e.printStackTrace();}}public void put(String key, Bitmap bitmap) {try {DiskLruCache.Editor editor = mDiskLruCache.edit(key);if (editor != null) {OutputStream outputStream = editor.newOutputStream(0);bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);editor.commit();}} catch (IOException e) {e.printStackTrace();}}public Bitmap get(String key) {try {DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);if (snapshot != null) {InputStream inputStream = snapshot.getInputStream(0);return BitmapFactory.decodeStream(inputStream);}} catch (IOException e) {e.printStackTrace();}return null;}public void close() {try {mDiskLruCache.close();} catch (IOException e) {e.printStackTrace();}}
}
在上述代码中,DiskLruCacheHelper类封装了磁盘缓存的操作 。在构造函数中,通过DiskLruCache.open方法打开磁盘缓存,指定缓存目录CACHE_DIR、应用版本APP_VERSION、每个键对应的值的数量VALUE_COUNT以及缓存的最大大小MAX_SIZE 。put方法用于将 Bitmap 存入磁盘缓存,首先获取DiskLruCache.Editor,然后通过editor.newOutputStream获取输出流,将 Bitmap 压缩后写入输出流,最后提交编辑 。get方法用于从磁盘缓存中获取 Bitmap,首先获取DiskLruCache.Snapshot,如果获取到,则通过Snapshot.getInputStream获取输入流,再使用BitmapFactory.decodeStream方法将输入流解码为 Bitmap 。close方法用于关闭磁盘缓存 。通过这样的实现,有效地利用了磁盘缓存,结合内存缓存,可以大大提高图片加载的效率和稳定性 。
六、Bitmap 在实际项目中的应用场景
(一)图片展示
在 Android 开发中,ImageView、RecyclerView 和 ListView 是展示图片的常用控件 。在 ImageView 中展示 Bitmap 较为简单,通过setImageBitmap方法即可实现 。例如:
ImageView imageView = findViewById(R.id.imageView);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.example_image);
imageView.setImageBitmap(bitmap);
在 RecyclerView 和 ListView 中展示 Bitmap 时,通常需要结合适配器来实现 。以 RecyclerView 为例,首先需要创建一个适配器,在适配器的onBindViewHolder方法中设置 ImageView 的 Bitmap 。示例代码如下:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {private List<Bitmap> bitmapList;public MyAdapter(List<Bitmap> bitmapList) {this.bitmapList = bitmapList;}@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);return new ViewHolder(view);}@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {holder.imageView.setImageBitmap(bitmapList.get(position));}@Overridepublic int getItemCount() {return bitmapList.size();}public static class ViewHolder extends RecyclerView.ViewHolder {public ImageView imageView;public ViewHolder(View itemView) {super(itemView);imageView = itemView.findViewById(R.id.imageView);}}
}
在使用这些控件展示 Bitmap 时,防止图片闪烁和错位是非常重要的 。图片闪烁和错位的主要原因是异步加载和 View 复用 。当 ListView 或 RecyclerView 滑动时,由于 View 的复用机制,可能会出现图片加载完成后显示到错误的位置,或者图片在加载过程中频繁闪烁的情况 。
为了防止图片闪烁,我们可以在图片加载完成前设置一个默认的占位图 ,这样可以避免图片在加载过程中出现空白闪烁的情况 。同时,在图片加载完成后,通过判断 ImageView 的 Tag 来确保图片显示在正确的位置 。例如:
class ImageLoader {private LruCache<String, Bitmap> mMemoryCache;private Map<ImageView, String> imageViewMap;public ImageLoader() {int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);int cacheSize = maxMemory / 8;mMemoryCache = new LruCache<String, Bitmap>(cacheSize);imageViewMap = new HashMap<>();}public void addBitmapToMemoryCache(String key, Bitmap bitmap) {if (getBitmapFromMemoryCache(key) == null) {mMemoryCache.put(key, bitmap);}}public Bitmap getBitmapFromMemoryCache(String key) {return mMemoryCache.get(key);}public void loadImage(final String url, final ImageView imageView) {imageViewMap.put(imageView, url);imageView.setImageResource(R.drawable.default_image); Bitmap bitmap = getBitmapFromMemoryCache(url);if (bitmap != null) {imageView.setImageBitmap(bitmap);} else {new Thread(new Runnable() {@Overridepublic void run() {Bitmap downloadedBitmap = downloadBitmap(url); if (downloadedBitmap != null) {addBitmapToMemoryCache(url, downloadedBitmap);final Bitmap finalBitmap = downloadedBitmap;imageView.post(new Runnable() {@Overridepublic void run() {if (imageViewMap.get(imageView) != null && imageViewMap.get(imageView).equals(url)) {imageView.setImageBitmap(finalBitmap);}}});}}}).start();}}private Bitmap downloadBitmap(String url) {// 这里实现从网络下载图片的逻辑// 例如使用HttpURLConnection或OkHttpreturn null;}
}
在上述代码中,ImageLoader类实现了图片的加载和缓存功能 。在loadImage方法中,首先将 ImageView 和对应的图片 URL 存入imageViewMap中,然后设置默认的占位图 。接着从内存缓存中获取图片,如果存在则直接显示;如果不存在,则开启一个线程从网络下载图片 。下载完成后,将图片存入内存缓存,并通过post方法在主线程中更新 ImageView 的显示,同时通过判断imageViewMap中 ImageView 对应的 URL 来确保图片显示在正确的位置 。通过这种方式,可以有效地防止图片闪烁和错位,提升用户体验 。
(二)图像处理
在滤镜、图像合成、图像识别等图像处理场景中,Bitmap 都有着广泛的应用 。
在滤镜处理中,我们可以通过操作 Bitmap 的像素来实现各种滤镜效果 。以灰度化为例,灰度化是将彩色图像转换为黑白图像的过程,其原理是根据一定的权重将 RGB 三个通道的值转换为一个灰度值 。常见的灰度化算法有加权平均法,计算公式为:Gray = 0.299 * R + 0.587 * G + 0.114 * B 。下面是使用 Java 实现灰度化的代码示例:
public static Bitmap grayScale(Bitmap bitmap) {int width = bitmap.getWidth();int height = bitmap.getHeight();Bitmap grayBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {int pixel = bitmap.getPixel(x, y);int alpha = Color.alpha(pixel);int red = Color.red(pixel);int green = Color.green(pixel);int blue = Color.blue(pixel);int gray = (int) (0.299 * red + 0.587 * green + 0.114 * blue);grayBitmap.setPixel(x, y, Color.argb(alpha, gray, gray, gray));}}return grayBitmap;
}
在这段代码中,首先创建一个与原始 Bitmap 大小相同的新 Bitmap,用于存储灰度化后的图像 。然后通过双重循环遍历原始 Bitmap 的每一个像素,获取其 ARGB 值,根据灰度化公式计算出灰度值,最后将灰度值设置到新的 Bitmap 中 。通过这种方式,实现了将彩色 Bitmap 转换为灰度 Bitmap 的功能 。
图像合成是将多个 Bitmap 合并成一个 Bitmap 的过程 。例如,将一个水印图片合成到另一个图片上 。实现思路是创建一个新的 Bitmap,其大小为两个图片中较大的尺寸 ,然后分别将两个图片绘制到新 Bitmap 的指定位置 。示例代码如下:
public static Bitmap mergeBitmaps(Bitmap background, Bitmap watermark) {int width = Math.max(background.getWidth(), watermark.getWidth());int height = Math.max(background.getHeight(), watermark.getHeight());Bitmap mergedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(mergedBitmap);canvas.drawBitmap(background, 0, 0, null);canvas.drawBitmap(watermark, width - watermark.getWidth(), height - watermark.getHeight(), null);return mergedBitmap;
}
在这段代码中,首先计算出合并后 Bitmap 的宽度和高度,然后创建一个新的 Bitmap 。接着创建一个Canvas对象,将背景图片绘制到Canvas的 (0, 0) 位置,再将水印图片绘制到Canvas的右下角位置 。最后返回合成后的 Bitmap 。
在图像识别领域,Bitmap 也发挥着重要作用 。例如,在简单的字符识别中,首先将图像转换为 Bitmap,然后对 Bitmap 进行预处理,如灰度化、二值化等操作,以突出图像的特征 。二值化是将图像转换为只有黑白两种颜色的图像,其原理是根据一个阈值将像素值分为两类 。示例代码如下:
public static Bitmap binaryzation(Bitmap bitmap, int threshold) {int width = bitmap.getWidth();int height = bitmap.getHeight();Bitmap binaryBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {int pixel = bitmap.getPixel(x, y);int alpha = Color.alpha(pixel);int red = Color.red(pixel);int green = Color.green(pixel);int blue = Color.blue(pixel);int gray = (int) (0.299 * red + 0.587 * green + 0.114 * blue);int newPixel = gray > threshold? Color.WHITE : Color.BLACK;binaryBitmap.setPixel(x, y, Color.argb(alpha, newPixel, newPixel, newPixel));}}return binaryBitmap;
}
在这段代码中,首先创建一个与原始 Bitmap 大小相同的新 Bitmap,用于存储二值化后的图像 。然后通过双重循环遍历原始 Bitmap 的每一个像素,获取其 ARGB 值,计算出灰度值 。根据设定的阈值,将灰度值与阈值进行比较,如果灰度值大于阈值,则将该像素设置为白色;否则设置为黑色 。最后将处理后的像素值设置到新的 Bitmap 中,完成二值化操作 。经过预处理后的 Bitmap 可以进一步用于特征提取和模式匹配,以实现图像识别的功能 。
七、总结
Bitmap 在 Android 开发中占据着举足轻重的地位,作为图片在内存中的呈现形式,它为开发者提供了强大的图像操作能力。从原理上看,Bitmap 通过像素点集合存储图像信息,其颜色通道和内存占用的计算方式决定了图像的显示效果和内存开销 。在实际应用中,我们需要根据不同的需求选择合适的创建和加载方式,并且要注意优化性能,避免内存溢出等问题 。
通过本文的介绍,我们深入了解了 Bitmap 的内部原理,包括颜色通道的存储方式和内存占用的计算方法 。掌握了 Bitmap 的创建与加载方式,学会了如何通过BitmapFactory.Options参数来优化加载过程,避免内存溢出 。还熟悉了 Bitmap 的常见操作,如缩放、裁剪、旋转和偏移,以及这些操作在实际应用中的实现方法 。在性能优化方面,我们探讨了内存优化策略和缓存策略,包括采样率压缩、更改 Bitmap.Config 格式、Bitmap 复用以及 LruCache 和磁盘缓存的使用 。最后,我们分析了 Bitmap 在实际项目中的应用场景,如图片展示和图像处理,并且给出了相应的代码示例和解决方案 。
相关文章:
深入探索Android Bitmap:从原理到实战
一、Bitmap 是什么 在 Android 开发中,Bitmap 是极为重要的基石。简单来说,Bitmap 代表位图,是图片在内存里的具体呈现形式 ,任何诸如 JPEG、PNG、WEBP 等格式的图片,一旦被加载到内存中,就会以 Bitmap 对…...
软考计算机知识-流水线
计算机流水线类似工业生产过程的流水线,在同一时间,m个部件进行不同的操作,完成对不同对象的处理。 理解重叠:让不同的指令在时间上重叠地解释。在解释第k条指令的操作完成之前,就可以开始解释第k1条指令。 题1&#…...
【Linux】从互斥原理到C++ RAII封装实践
📢博客主页:https://blog.csdn.net/2301_779549673 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📢本文由 JohnKi 原创,首发于 CSDN🙉 📢未来很长&#…...
【哈希表与字符串的算法之路:思路与实现】—— LeetCode
文章目录 两数之和面试题01.02.判定是否为字符重排存在重复元素存在重复元素||字母异位词分组最长公共前缀和最长回文子串二进制求和字符串相乘 两数之和 这题的思路很简单,在读完题目之后,便可以想到暴力枚举,直接遍历整个数组两遍即可&…...
rdiff-backup备份
目录 1. 服务器备份知识点 1.1 备份策略 1.2 备份步骤和宝塔面板简介 1.3 CentOS7重要目录 2. 备份工具 2.1 tar -g 备份演示 2. rsync 备份演示 3. rdiff-backup 备份演示 4. 差异和优缺点 3. rdiff-backup安装和使用 3.1 备份命令rdiff-backup 3.2 恢复命令--…...
Netty基础—4.NIO的使用简介一
大纲 1.Buffer缓冲区 2.Channel通道 3.BIO编程 4.伪异步IO编程 5.改造程序以支持长连接 6.NIO三大核心组件 7.NIO服务端的创建流程 8.NIO客户端的创建流程 9.NIO优点总结 10.NIO问题总结 1.Buffer缓冲区 (1)Buffer缓冲区的作用 (2)Buffer缓冲区的4个核心概念 (3)使…...
贪心算法简介(greed)
前言: 贪心算法(Greedy Algorithm)是一种在每个决策阶段都选择当前最优解的算法策略,通过局部最优的累积来寻求全局最优解。其本质是"短视"策略,不回溯已做选择。 什么是贪心、如何来理解贪心(个人对贪心的…...
驻场运维服务方案书(Word文件)
目 录 第一章 背景分析 1.1. 项目背景 1.2. 项目目标 1.3. 系统现状 1.3.1. 网络系统 1.3.2. 设备清单梳理 1.3.3. 应用系统 第二章 需求分析及理解 2.1. 在重要日期能保障信息系统安全 2.2. 信息系统可长期安全、持续、稳定的运行 2.3. 提升发现安全问题、解决安全…...
嵌入式硬件--开发工具-AD使用常用操作
ad16.1.12 1.如何显示/隐藏其他图层 在pcb界面点击L--试图界面中找到“视图选项”--单层模式选择 not in single layer mode 在pcb界面点击L--试图界面中找到“视图选项”--单层模式选择 gray scale other layers 【Altium】AD如何只显示一层,隐藏其他层显示&…...
在 Ubuntu 上安装和配置 Docker 的完整指南
Docker 是一个开源的平台,旨在简化应用程序的开发、部署和运行。通过将应用程序及其依赖项打包到容器中,Docker 确保应用程序可以在任何环境中一致地运行。 目录 前言安装前的准备安装 Docker 步骤 1:更新包索引步骤 2:安装必要…...
微服务全局ID方案汇总
自增id 对于大多数系统来说,使用mysql的自增id当作主键再最合适不过了。在数据库层面就可以获取一个顺序的、唯一的、空间占用少的id。 自增id需要是 int、bigint这些整数类型,uint 支持 40 亿的数据量,bigint unsign(0 &#x…...
实验5 逻辑回归
实验5 逻辑回归 【实验目的】掌握逻辑回归算法 【实验内容】处理样本,使用逻辑回归算法进行参数估计,并画出分类边界 【实验要求】写明实验步骤,必要时补充截图 1、参照“2.1梯度下降法实现线性逻辑回归.ipynb”和“2.2 sklearn实现线性逻辑…...
【原创】在高性能服务器上,使用受限用户运行Nginx,充当反向代理服务器[未完待续]
1 起因 在公共高性能服务器上运行OllamaDeepSeek,如果按照默认配置启动Ollama程序,则自己在远程无法连接你启动的Ollama服务。 如果修改掉默认的配置,则会遇到你的Ollama被他人完全控制的安全风险。 不过,我们可以使用一个方向…...
Linux 下 MySQL 8 搭建教程
一、下载 你可以从 MySQL 官方下载地址 下载所需的 MySQL 安装包。 二、环境准备 1. 查看 MySQL 是否存在 使用以下命令查看系统中是否已经安装了 MySQL: rpm -qa|grep -i mysql2. 清空 /etc/ 目录下的 my.cnf 执行以下命令删除 my.cnf 文件: [roo…...
vue 仿deepseek前端开发一个对话界面
后端:调用deepseek的api,所以返回数据格式和deepseek相同 {"model": "DeepSeek-R1-Distill-Qwen-1.5B", "choices": [{"index": 0, "delta": {"role": "assistant", "cont…...
MinIO问题总结(持续更新)
目录 Q: 之前使用正常,突然使用空间为0B,上传文件也是0B(部署在k8s中)Q: 无法上传大文件参考yaml Q: 之前使用正常,突然使用空间为0B,上传文件也是0B(部署在k8s中) A: 1、检查pod状态…...
STM32配套程序接线图
1 工程模板 2 LED闪烁 3LED流水灯 4蜂鸣器 5按键控制LED 6光敏传感器控制蜂鸣器 7OLED显示屏 8对射式红外传感器计次 9旋转编码器计次 10 定时器定时中断 11定时器外部时钟 12PWM驱动LED呼吸灯 13 PWM驱动舵机 14 PWM驱动直流电机 15输入捕获模式测频率 16PWMI模式测频率占空…...
深入理解Linux网络随笔(七):容器网络虚拟化--Veth设备对
深入理解Linux网络随笔(七):容器网络虚拟化 微服务架构中服务被拆分成多个独立的容器,docker网络虚拟化的核心技术为:Veth设备对、Network Namespace、Bridg。 Veth设备对 veth设备是一种 成对 出现的虚拟网络接口&…...
实战指南:鸿蒙ArkTS中实现列表下拉刷新与触底加载的完整解析
前言: 在移动应用开发中,下拉刷新和触底加载更多是提升用户体验的核心功能。鸿蒙ArkUI框架通过Refresh组件和List组件的onReachEnd事件,为开发者提供了简洁高效的实现方案。本文将通过代码示例,详解如何利用ArkTS实现这两个功能。…...
【栈数据结构应用解析:常见算法题详细解答】—— Leetcode
文章目录 栈的模拟实现删除字符串中的所有相邻重复项比较含退格的字符串基本计算器||字符串解码验证栈序列 栈的模拟实现 #include <iostream>using namespace std;const int N 1e5 10;// 创建栈 int stk[N], n;// 进栈 - 本质就是顺序表里面的尾插 void push(int x) …...
Git常用操作之GitLab
Git常用操作之GitLab 小薛博客官网:小薛博客Git常用操作之GitLab官方地址 1、GitLab安装 https://gitlab.cn/install/ 1、Docker安装GitLab https://docs.gitlab.cn/jh/install/docker.html 1、设置卷位置 在设置其他所有内容之前,请配置一个新的…...
2025探索短剧行业新可能报告40+份汇总解读|附PDF下载
原文链接:https://tecdat.cn/?p41043 近年来,短剧以其紧凑的剧情、碎片化的观看体验,迅速吸引了大量用户。百度作为互联网巨头,在短剧领域积极布局。从早期建立行业专属模型冷启动,到如今构建完整的商业生态…...
各省水资源平台 水资源遥测终端机都用什么协议
各个省水资源平台 水资源遥测终端机 的建设大部分从2012年开始启动,经过多年建设,基本都已经形成了稳定的通讯要求;河北瑾航科技 遥测终端机,兼容了大部分省市的通讯协议,如果需要,可以咨询和互相学习&…...
C#+EF+SqlServer性能优化笔记
文章目录 前言一、C#EF 代码优化1.接口代码改异步2.查询异步,只查询需要的数据3.查询数据判断时4.直接使用sql查询 二、数据库优化1.减少关联表,一些基础数据,字典表可以考虑放到redis中,在代码中映射2.增加索引,删除无…...
列表动态列处理
1、在initialize()方法里,获取列表控件,添加CreateListColumnsListener监听 public void initialize(){ BillList billlist(BillList)this.getControl("billlistap"); billlist.addCreateListColumnsListener(this::beforeCreateListColumns)…...
电机控制常见面试问题(十二)
文章目录 一.电机锁相环1.理解锁相环2.电机控制中的锁相环应用3.数字锁相环(DPLL) vs 模拟锁相环(APLL)4.锁相环设计的关键技术挑战5.总结 二、磁链观测1.什么是磁链?2.为什么要观测磁链?3.怎么观测磁链&am…...
芯驿电子 ALINX 亮相德国纽伦堡,Embedded World 2025 精彩回顾
2025年3月13日,全球规模最大的嵌入式行业盛会——德国纽伦堡国际嵌入式展(embedded world 2025)圆满落幕。 在这场汇聚全球 950 家展商、3 万余专业观众的科技盛宴中,芯驿电子 ALINX 展位人头攒动,多款尖端产品吸引客户…...
西门子S7-1200 PLC远程上下载程序方案
西门子S7-1200 PLC远程上下载程序方案(巨控GRM552YW-C模块) 三步完成配置 | 全球适用 | 稳定高效 三步快速完成远程配置 硬件部署 准备巨控GRM552YW-CHE模块1台,通过网口连接西门子S7-1200 PLC以太网口。 模块支持4G/5G/Wi-Fi/网线接入外网…...
MFC窗口的创建/消息映射机制
mfc.h #include<afxwin.h>//mfc头文件//应用程序类 class MyApp:public CWinApp //继承于应用程序类 { public://程序入口virtual BOOL InitInstance(); };//框架类 class MyFrame:public CFrameWnd { public:MyFrame();//声明宏 提供消息映射机制DECLARE_MESSAGE_MAP()…...
【每日学点HarmonyOS Next知识】tab对齐、相对布局、自定义弹窗全屏、动画集合、回到桌面
1、HarmonyOS Tabs 是否能支持 tabbar 居左对齐? 当前方案为自定义tabbar实现,示例demo: Entry Component struct TabsExample {State tabArray: Array<number> [0, 1,2]State focusIndex: number 0State pre: number 0State inde…...
如何在TikTok网页版切换地区设置
今天我们来聊聊如何在TikTok网页版上更改地区设置。TikTok作为全球知名的短视频社交应用,不仅仅局限于某个国家或地区。修改地区设置可以让你探索来自不同地方的内容,享受更为丰富的社交互动体验。那么,具体该如何操作呢?让我一步…...
redis工具类
前言 Redis 是一个高性能的键值存储系统,广泛应用于缓存、消息队列、实时分析等场景。为了更高效地操作 Redis,许多开发者会选择使用 Redisson 客户端库。 依赖配置 首先确保您的项目中已经包含了 Redisson 的最新版本(如 3.44.0ÿ…...
【Python办公】Excel通用匹配工具(双表互匹)
目录 专栏导读1、背景介绍2、库的安装3、核心代码4、完整代码总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文章专栏:请点击——>Python办公自动化专…...
安徽省青少年信息学奥林匹克竞赛初中组第1题LuoguP762
先放题目: 【题目背景】.你 .可 .以 .选 .择 .跳 .过 .背 .景 .部 .分。初春的一天,正是乍暖还寒时候,狂风乍起。小可可裹紧了单薄的外衣,往小雪家中赶去。“今天真不是个出门的时候啊!”小可可感叹道。“但是我还有东西要买………...
AVL树的平衡算法的简化问题
AVL树是一种紧凑的二叉查找树。它的每个结点,都有左右子树高度相等,或者只相差1这样的特性。文章https://blog.csdn.net/aaasssdddd96/article/details/106291144给出了一个例子。 为了便于讨论,这里对AVL树的结点平衡情况定义2个名称&#…...
NFS实验配置笔记
NFS NFS服务 nfs,最早是Sun这家公司所发展出来的,它最大的功能就是可以透过网络,让不同的机器,不同的操作系统,进行实现文档的共享。所以你可以简单的将他看做是文件服务器。 实验准备 ①先准备一个服务器端的操作…...
C盘清理技巧分享:释放空间,提升电脑性能
目录 1. 引言 2. C盘空间不足的影响 3. C盘清理的必要性 4. C盘清理的具体技巧 4.1 删除临时文件 4.2 清理系统还原点 4.3 卸载不必要的程序 4.4 清理下载文件夹 4.5 移动大文件到其他盘 4.6 清理系统缓存 4.7 使用磁盘清理工具 4.8 清理Windows更新文件 4.9 禁用…...
【云馨AI-大模型】RAGFlow功能预览:Dify接入外部知识库RAGFlow指南
介绍 Dify介绍 开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力,轻松构建和运营生成式 AI 原生应用。比 LangChain 更易用。官网:https://dify.ai/zh RAGFlow介绍 RAGFlow 是一款基于深度文档理解构建的…...
大模型学习笔记------Llama 3模型架构之旋转编码(RoPE)
大模型学习笔记------Llama 3模型架构之旋转编码(RoPE) 1、位置编码简介1.1 绝对位置编码1.2 相对位置编码 2、旋转编码(RoPE)2.1 基本概念---旋转矩阵2.2 RoPE计算原理2.2.1 绝对位置编码2.2.2 相对位置编码 3、旋转编码…...
Anthropic 的模型
Anthropic 的模型(特别是 Claude 系列)之所以在性能和推理能力上表现强劲,可以从技术设计、研究理念、训练方法以及应用优化等多个方面进行详细分析。以下是基于当前信息(截至 2025 年 3 月 13 日)和行业趋势的深入剖析…...
初探大模型开发:使用 LangChain 和 DeepSeek 构建简单 Demo
最近,我开始接触大模型开发,并尝试使用 LangChain 和 DeepSeek 构建了一个简单的 Demo。通过这个 Demo,我不仅加深了对大模型的理解,还体验到了 LangChain 和 DeepSeek 的强大功能。下面,我将分享我的开发过程以及一些…...
FPGA初级项目10——基于SPI的DAC芯片进行数模转换
FPGA初级项目10——基于SPI的DAC芯片进行数模转换 DAC芯片介绍 DAC 芯片(数字模拟转换器)是一种将数字信号转换为连续模拟信号(如电压或电流)的集成电路,广泛应用于电子系统中,连接数字世界与模拟世界。 …...
【论文解读】Contrastive Learning for Compact Single Image Dehazing(AECR-Net)
文章目录 问题创新网络主要贡献Autoencoder-like Dehazing NetworkAdaptive Mixup for Feature PreservingDynamic Feature Enhancement1. 可变形卷积的使用2. 扩展感受野3. 减少网格伪影4. 融合空间结构信息 Contrastive Regularization1. 核心思想2. 正样本对和负样本对的构建…...
unity基础——线段与拖尾
1、LineRenderer(线段渲染器) 为空物体加上组件添加材质 选择默认线段的材质 Default—Line Color:可以修改颜色Corner Vertices:角顶点 圆滑度 End Cap Vertices:边缘顶点 线段编辑 1、可以移动线段点的位置…...
【服务器知识】Nginx路由匹配规则说明
Nginx路由匹配规则说明 **一、Nginx路由匹配核心机制****二、匹配规则语法详解**1. **精确匹配 ()**2. **前缀匹配 (^~ 或 /)**3. **正则匹配 (~ 或 ~*)**4. **通配符匹配 (*)** **三、路由匹配优先级顺序****四、高级路由技巧**1. **条件判断 (if语句)**2. **路径重写 (rewrit…...
Python----数据可视化(Pyecharts三:绘图二:涟漪散点图,K线图,漏斗图,雷达图,词云图,地图,柱状图折线图组合,时间线轮廓图)
1、涟漪特效散点图 from pyecharts.globals import SymbolType from pyecharts.charts import EffectScatter from pyecharts.faker import Faker from pyecharts import options as opts from pyecharts.globals import ThemeType # 绘制图表 es (EffectScatter(init_optsop…...
机器学习中的梯度下降是什么意思?
梯度下降(Gradient Descent)是机器学习中一种常用的优化算法,用于最小化损失函数(Loss Function)。通过迭代调整模型参数,梯度下降帮助模型逐步逼近最优解,从而提升模型的性能。 1.核心思想 梯…...
C语言中的字符串与数组的关系
在C语言中,字符串和数组之间有着紧密的关系。理解它们的区别和联系对于编写高效且可靠的代码至关重要。在本篇博文中,我们将详细分析字符串和数组在C语言中的概念、它们的关系以及如何在编程中应用它们。 一、字符串与数组的基础知识 1.1 数组概念 在C语言中,数组是一组相…...
Ubuntu 18,04 LTS 通过APT安装mips64el的交叉编译器。
安装 g-5v的版本: sudo apt update sudo apt install g-5-mips64el-linux-gnuabi64 How to Install g-5-mips64el-linux-gnuabi64 in Ubuntu 18.04 安装 gcc/g-7v的版本: sudo apt-get install gcc-mips64el-linux-gnu* g-mips64el-linux-gnu* -y 安装…...
MySQL 衍生表(Derived Tables)
在SQL的查询语句select …. from …中,跟在from子句后面的通常是一张拥有定义的实体表,而有的时候我们会用子查询来扮演实体表的角色,这个在from子句中的子查询会返回一个结果集,这个结果集可以像普通的实体表一样查询、连接&…...