安卓内存优化-bitmap优化
Bitmap常用方法:
- public boolean compress
将位图的压缩到指定的OutputStream,可以理解成将Bitmap保存到文件中! format:格式,PNG,JPG等; quality:压缩质量,0-100,0表示最低画质压缩,100最大质量(PNG无损,会忽略品质设定) stream:输出流 返回值代表是否成功压缩到指定流!
- void recycle():
回收位图占用的内存空间,把位图标记为Dead
- boolean isRecycled():
判断位图内存是否已释放
- int getWidth():
获取位图的宽度
- int getHeight():
获取位图的高度
- boolean isMutable():
图片是否可修改
- int getScaledWidth(Canvas canvas):
获取指定密度转换后的图像的宽度
- int getScaledHeight(Canvas canvas):
获取指定密度转换后的图像的高度
- Bitmap createBitmap(Bitmap src):
以src为原图生成不可变得新图像
- Bitmap createScaledBitmap(Bitmap src, int dstWidth,int dstHeight, boolean filter):
以src为原图,创建新的图像,指定新图像的高宽以及是否变。
- Bitmap createBitmap(int width, int height, Config config):
创建指定格式、大小的位图,一般创建空的Bitmap
- Bitmap createBitmap(Bitmap source, int x, int y, int width, int height)
以source为原图,创建新的图片,指定起始坐标以及新图像的高宽。
BitmapFactory
options
- inJustDecodeBounds:
如果将这个值置为true,那么在解码的时候将不会返回bitmap,只会返回这个bitmap的尺寸。这个属性的目的是,如果你只想知道一个bitmap的尺寸,但又不想将其加载到内存时。这是一个非常有用的属性。
- inSampleSize:
这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4。
- inPreferredConfig:
这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes。
- inPremultiplied:
这个值和透明度通道有关,默认值是true,如果设置为true,则返回的bitmap的颜色通道上会预先附加上透明度通道。
- inDither:
这个值和抖动解码有关,默认值为false,表示不采用抖动解码。
- inDensity:
表示这个bitmap的像素密度(对应的是DisplayMetrics中的densityDpi,不是density)。
- inTargetDensity:
表示要被画出来时的目标像素密度(对应的是DisplayMetrics中的densityDpi,不是density)。
- inScreenDensity:
表示实际设备的像素密度(对应的是DisplayMetrics中的densityDpi,不是density)。
- inScaled:
设置这个Bitmap是否可以被缩放,默认值是true,表示可以被缩放。
- inPurgeable和inInputShareable:
这两个值一般是一起使用,设置为true时,前者表示空间不够是否可以被释放,后者表示是否可以共享引用。这两个值在Android5.0后被弃用。
- inPreferQualityOverSpeed:
这个值表示是否在解码时图片有更高的品质,仅用于JPEG格式。如果设置为true,则图片会有更高的品质,但是会解码速度会很慢。
- outWidth和outHeight:
表示这个Bitmap的宽和高,一般和inJustDecodeBounds一起使用来获得Bitmap的宽高,但是不加载到内存。
工厂方法:
- decodeByteArray(byte[] data, int offset,int length):从指定字节数组的offset位置开始,将长度为length的字节数据解析成Bitmap对象。
- decodeFIle(String pathName):从pathName指定的文件中解析、创建Bitmap对象。
- decodeFileDescriptor(FileDescriptor fd):用于从FileDescriptor对应的文件中解析、创建Bitmap对象。
- decodeResource(Resource res,int id):用于根据给定的资源ID从指定的资源文件中解析、创建Bitmap对象。
- decodeStream(InputStream is):用于从指定输入流中介解析、创建Bitmap对象。
图片占用内存
如1920*1080图片占用多少内存?
每个像素的字节大小
每个像素的字节大小由bitmap的可配置的参数Config来决定。
Bitmap中,存在一个枚举类Config,定义了Android中支持Bitmap配置:
Config | 占用字节大小(byte) | 说明 |
---|---|---|
ALPHA_8 | 1 | 单透明通道 |
RGB_565 | 2 | 简单RGB色调 |
ARGB_4444 | 2 | 已废弃 |
ARGB_8888 | 4 | 24位真彩色 |
RGBA_F16 | 8 | Android 8.0新增(更丰富色彩表现HDR) |
HARDWARE | Special | Android 8.0新增(Bitmap直接存储在graphicmemory) |
Bitmap加载方式
从获取方式分:
(1) 以文件流的方式
假设在sdcard下有 test.png图片
FileInputStream fis = new FileInputStream(“/sdcard/test.png”);
Bitmap bitmap=BitmapFactory.decodeStream(fis);
(2) 以R文件的方式
假设 res/drawable下有 test.jpg文件
Bitmap bitmap =BitmapFactory.decodeResource(getResources(), R.drawable.test);
或
BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(R.drawable. test );
Bitmap bitmap = bitmapDrawable.getBitmap();
(3) 以ResourceStream的方式,不用R文件
Bitmap bitmap=BitmapFactory.decodeStream(getClass().getResourceAsStream(“/res/drawable/test.png”));
(4) 以文件流+ R文件 的方式
InputStream in = getResources(). openRawResource(R.drawable. test );
Bitmap bitmap = BitmapFactory. decodeStream(in);
或
InputStream in = getResources(). openRawResource(R.drawable. test );
BitmapDrawable bitmapDrawable = new BitmapDrawable(in);
Bitmap bitmap = bitmapDrawable.getBitmap();
注意: openRawResource可以打开 drawable, sound, 和raw资源,但不能是string和color。
从资源存放路径分:
(1) 图片放在sdcard中
Bitmap imageBitmap = BitmapFactory.decodeFile(path);// (path 是图片的路径,跟目录是/sdcard)
(2)图片在项目的res文件夹下面
ApplicationInfo appInfo = getApplicationInfo();
//得到该图片的id(name 是该图片的名字,“drawable” 是该图片存放的目录,appInfo.packageName是应用程序的包)
int resID = getResources().getIdentifier(fileName, “drawable”, appInfo.packageName);
Bitmap imageBitmap2 = BitmapFactory. decodeResource(getResources(), resID);
(3) 图片放在src目录下
String path = “com/xiangmu/test.png”; //图片存放的路径
InputStream in = getClassLoader().getResourceAsStream(path); //得到图片流
Bitmap imageBitmap3 = BitmapFactory. decodeStream(in);
(4) 图片放在 Assets目录
InputStream in = getResources().getAssets().open(fileName);
Bitmap imageBitmap4 = BitmapFactory.decodeStream(in);
Bitmap Drawable byte[] InputStream 相互转换方法
// 将byte[]转换成InputStream public InputStream Byte2InputStream(byte[] b) { ByteArrayInputStream bais = new ByteArrayInputStream(b); return bais; } // 将InputStream转换成byte[] public byte[] InputStream2Bytes(InputStream is) { String str = ""; byte[] readByte = new byte[1024]; int readCount = -1; try { while ((readCount = is.read(readByte, 0, 1024)) != -1) { str += new String(readByte).trim(); } return str.getBytes(); } catch (Exception e) { e.printStackTrace(); } return null; } // 将Bitmap转换成InputStream public InputStream Bitmap2InputStream(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.JPEG, 100, baos); InputStream is = new ByteArrayInputStream(baos.toByteArray()); return is; } // 将Bitmap转换成InputStream public InputStream Bitmap2InputStream(Bitmap bm, int quality) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, quality, baos); InputStream is = new ByteArrayInputStream(baos.toByteArray()); return is; } // 将InputStream转换成Bitmap public Bitmap InputStream2Bitmap(InputStream is) { return BitmapFactory.decodeStream(is); } // Drawable转换成InputStream public InputStream Drawable2InputStream(Drawable d) { Bitmap bitmap = this.drawable2Bitmap(d); return this.Bitmap2InputStream(bitmap); } // InputStream转换成Drawable public Drawable InputStream2Drawable(InputStream is) { Bitmap bitmap = this.InputStream2Bitmap(is); return this.bitmap2Drawable(bitmap); } // Drawable转换成byte[] public byte[] Drawable2Bytes(Drawable d) { Bitmap bitmap = this.drawable2Bitmap(d); return this.Bitmap2Bytes(bitmap); } // byte[]转换成Drawable public Drawable Bytes2Drawable(byte[] b) { Bitmap bitmap = this.Bytes2Bitmap(b); return this.bitmap2Drawable(bitmap); } // Bitmap转换成byte[] public byte[] Bitmap2Bytes(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); } // byte[]转换成Bitmap public Bitmap Bytes2Bitmap(byte[] b) { if (b.length != 0) { return BitmapFactory.decodeByteArray(b, 0, b.length); } return null; } // Drawable转换成Bitmap public Bitmap drawable2Bitmap(Drawable drawable) { Bitmap bitmap = Bitmap .createBitmap( drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } // Bitmap转换成Drawable public Drawable bitmap2Drawable(Bitmap bitmap) { BitmapDrawable bd = new BitmapDrawable(bitmap); Drawable d = (Drawable) bd; return d; }
Bitmap常见操作
缩放
/*** 根据给定的宽和高进行拉伸** @param origin 原图* @param newWidth 新图的宽* @param newHeight 新图的高* @return new Bitmap*/private Bitmap scaleBitmap(Bitmap origin, int newWidth, int newHeight) {if (origin == null) {return null;}int height = origin.getHeight();int width = origin.getWidth();float scaleWidth = ((float) newWidth) / width;float scaleHeight = ((float) newHeight) / height;Matrix matrix = new Matrix();matrix.postScale(scaleWidth, scaleHeight);// 使用后乘Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);if (!origin.isRecycled()) {origin.recycle();}return newBM;}/*** 按比例缩放图片** @param origin 原图* @param ratio 比例* @return 新的bitmap*/private Bitmap scaleBitmap(Bitmap origin, float ratio) {if (origin == null) {return null;}int width = origin.getWidth();int height = origin.getHeight();Matrix matrix = new Matrix();matrix.preScale(ratio, ratio);Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);if (newBM.equals(origin)) {return newBM;}origin.recycle();return newBM;}
裁剪
/*** 裁剪** @param bitmap 原图* @return 裁剪后的图像*/
private Bitmap cropBitmap(Bitmap bitmap) {int w = bitmap.getWidth(); // 得到图片的宽,高int h = bitmap.getHeight();int cropWidth = w >= h ? h : w;// 裁切后所取的正方形区域边长cropWidth /= 2;int cropHeight = (int) (cropWidth / 1.2);return Bitmap.createBitmap(bitmap, w / 3, 0, cropWidth, cropHeight, null, false);
}
旋转
/*** 选择变换** @param origin 原图* @param alpha 旋转角度,可正可负* @return 旋转后的图片*/
private Bitmap rotateBitmap(Bitmap origin, float alpha) {if (origin == null) {return null;}int width = origin.getWidth();int height = origin.getHeight();Matrix matrix = new Matrix();matrix.setRotate(alpha);// 围绕原地进行旋转Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);if (newBM.equals(origin)) {return newBM;}origin.recycle();return newBM;
}
偏移
/*** 偏移效果* @param origin 原图* @return 偏移后的bitmap*/
private Bitmap skewBitmap(Bitmap origin) {if (origin == null) {return null;}int width = origin.getWidth();int height = origin.getHeight();Matrix matrix = new Matrix();matrix.postSkew(-0.6f, -0.3f);Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);if (newBM.equals(origin)) {return newBM;}origin.recycle();return newBM;
}
获得圆角图片
private Bitmap bimapRound(Bitmap mBitmap,float index){
Bitmap bitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Config.ARGB_4444);
Canvas canvas = new Canvas(bitmap);Paint paint = new Paint();paint.setAntiAlias(true);//设置矩形大小Rect rect = new Rect(0,0,mBitmap.getWidth(),mBitmap.getHeight());RectF rectf = new RectF(rect);// 相当于清屏canvas.drawARGB(0, 0, 0, 0);//画圆角canvas.drawRoundRect(rectf, index, index, paint);// 取两层绘制,显示上层paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));// 把原生的图片放到这个画布上,使之带有画布的效果canvas.drawBitmap(mBitmap, rect, rect, paint);return bitmap;}
Bitmap内存模型
Bitmap 在内存中的组成部分,在任何系统版本中都会存在以下 3 个部分:
- 1、Java Bitmap 对象: 位于 Java 堆,即我们熟悉的
android.graphics.Bitmap.java
; - 2、Native Bitmap 对象: 位于 Native 堆,以
Bitmap.cpp
为代表,除此之外还包括与 Skia 引擎相关的 SkBitmap、SkBitmapInfo 等一系列对象; - 3、图片像素数据: 图片解码后得到的像素数据。
其中,Java Bitmap 对象和 Native Bitmap 对象是分别存储在 Java 堆和 Native 堆的,毋庸置疑。唯一有操作性的是 3、图片像素数据,不同系统版本采用了不同的分配策略,分为 3 个历史时期:
- 时期 1 - Android 3.0 以前: 像素数据存放在 Native 堆(这部分系统版本的市场占有率已经非常低,后文我们不再考虑);
- 时期 2 - Android 8.0 以前: 从 Android 3.0 到 Android 7.1,像素数据存放在 Java 堆;
- 时期 3 - Android 8.0 以后: 从 Android 8.0 开始,像素数据重新存放在 Native 堆。另外还新增了 Hardware Bitmap 硬件位图,可以减少图片内存分配并提高绘制效率。
Bitmap的内存回收
- 2.3.3以前Bitmap的像素内存是分配在natvie上,而且不确定什么时候会被回收。根据官方文档的说法我们需要手动调用Bitmap.recycle()去回收:
- 3.0之后没有强调Bitmap.recycle();而是强调Bitmap的复用
如何复用
- 1.使用LruCache和DiskLruCache做内存和磁盘缓存;
- 2.使用Bitmap复用,同时针对版本进行兼容(inMutable和inBitmap)
- 3.使用inTempStorage
获取Bitmap的大小
/** * 得到bitmap的大小 */ public static int getBitmapSize(Bitmap bitmap) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //API 19 return bitmap.getAllocationByteCount(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {//API 12 return bitmap.getByteCount(); } // 在低版本中用一行的字节x高度 return bitmap.getRowBytes() * bitmap.getHeight();}
getByteCount()与getAllocationByteCount()的区别 一般情况下两者是相等的;如果通过复用Bitmap来解码图片,如果被复用的Bitmap的内存比待分配内存的Bitmap大,那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),getAllocationByteCount()表示被复用Bitmap真实占用的内存大小(getByteCount永远小于等于getAllocationByteCount)
Bitmap占用内存大小计算
在BitmapFactory.cpp中的 doDecode 方法
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {const int density = env->GetIntField(options, gOptions_densityFieldID);const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);if (density != 0 && targetDensity != 0 && density != screenDensity) {scale = (float) targetDensity / density;}
...
int scaledWidth = size.width();
int scaledHeight = size.height();
bool willScale = false;// Apply a fine scaling step if necessary.
if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {willScale = true;scaledWidth = codec->getInfo().width() / sampleSize;scaledHeight = codec->getInfo().height() / sampleSize;
}...
if (willScale) {// Set the allocator for the outputBitmap.SkBitmap::Allocator* outputAllocator;if (javaBitmap != nullptr) {outputAllocator = &recyclingAllocator;} else {outputAllocator = &defaultAllocator;}SkColorType scaledColorType = decodingBitmap.colorType();// FIXME: If the alphaType is kUnpremul and the image has alpha, the// colors may not be correct, since Skia does not yet support drawing// to/from unpremultiplied bitmaps.outputBitmap.setInfo(bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType));if (!outputBitmap.tryAllocPixels(outputAllocator)) {// This should only fail on OOM. The recyclingAllocator should have// enough memory since we check this before decoding using the// scaleCheckingAllocator.return nullObjectReturn("allocation failed for scaled bitmap");}SkPaint paint;// kSrc_Mode instructs us to overwrite the uninitialized pixels in// outputBitmap. Otherwise we would blend by default, which is not// what we want.paint.setBlendMode(SkBlendMode::kSrc);paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filteringSkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);canvas.scale(scaleX, scaleY);canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
}
从上述代码可以看出bitmap最终通过canvas绘制出来,而canvas在绘制之前,有一个scale的操作,scale的值由
scale = (float) targetDensity / density;
决定,即缩放的倍率和targetDensity和density相关,而这两个参数都是从传入的options中获取到的
- inDensity: Bitmap位图自身的密度,分辨率
- inTargetDensity: Bitmap最终绘制的目标位置的分辨率
- inScreenDensity: 设备屏幕分辨率
其中inDensit和图片存在的资源文件的目录有关,同一张图片放在不同目录下会有不同的值:
density | 0.75 | 1 | 1.5 | 2 | 3 | 3.5 | 4 |
---|---|---|---|---|---|---|---|
densityDpi | 120 | 160 | 240 | 320 | 480 | 560 | 640 |
DpiFolder | ldpi | mdpi | hdpi | xhdpi | xxhdpi | xxxdpi | xxxxhdpi |
总结:
- 图片放在drawable中,等同于放在drawable-mdpi中。原因为:drawable目录不与有屏幕密度特性,所以采用基准值,即mdpi
- 图片放在某个特性的drawable中,比如drawable-hdpi,如果设备的屏幕密度高于当前drawable目录所代表的密度,则图片会被放大,否则被缩小。
放大或缩小比例 = 设备屏幕密度 / drawable目录所代表的屏幕密度
因此,bitmap占用公式,从之前
Bitmap内存占用 ≈ 像素数据总大小 = 横向像素数量 x 纵向像素密度 x 每个像素的字节大小
细化为
Bitmap内存占用 ≈ 像素数据总大小 = 图片宽 x 图片高 x(设备分辨率/资源目录分辨率)² x 每个像素的字节大小
如要进行bitmap的解码
try{decode bitmap;
}catch(OutOfMemoryError e){//oom
质量压缩
再次解码
}
通过epic的hook框架可以对bitmap操作时进行优化处理
内存优化小结:
- 设备分级,不能脱离设备,针对不同机型做优化
- Bitmap优化 统一图片库 线上线下监控 hook
- 内存泄漏工具使用
- glide
安卓内存优化-bitmap优化相关推荐
- 内存优化二Bitmap优化
Bitmap基础知识 一张图片Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数 而Bitmap.Config,正是指定单位像素占用的字节数的重要参数. 其中,A代表透 ...
- 性能优化-Bitmap内存管理及优化
Bitmap作为重要Android应用之一,在很多时候如果应用不当,很容易造成内存溢出,那么这篇文章的目的就在于探讨Bitmap的有效运用及其优化 缓存介绍 当多次发送请求的时候,请求同一内容,为了使 ...
- 【Android 内存优化】Bitmap 内存缓存 ( Bitmap 内存复用 | 弱引用 | 引用队列 | 针对不同 Android 版本开发不同的 Bitmap 复用策略 | 工具类代码 )
文章目录 一.Bitmap 复用池 二.弱引用 Bitmap 内存释放 三.从 Bitmap 复用池中获取对应可以被复用的 Bitmap 对象 1.Android 2.3.3(API 级别 10)及以 ...
- 应用内存onLowMemory onTrimMemory优化
1.应用内存onLowMemory& onTrimMemory优化 onLowMemory& onTrimMemory简介: OnLowMemory是Android提供的API,在系统 ...
- Android性能优化:手把手教你如何让App更快、更稳、更省(含内存、布局优化等)...
2019独角兽企业重金招聘Python工程师标准>>> 前言 在 Android开发中,性能优化策略十分重要 因为其决定了应用程序的开发质量:可用性.流畅性.稳定性等,是提高用户留存 ...
- android包内存放视频,Android性能优化:手把手教你如何让App更快、更稳、更省(含内存、布局优化等)...
为其决定了应用程序的开发质量:可用性.流畅性.稳定性等,是提高用户留存率的关键 本文全面讲解性能优化中的所有知识,献上一份 Android性能优化的详细攻略, 含:优化方向.原因 & 具体优化 ...
- 实践App内存优化:如何有序地做内存分析与优化
由于项目里之前线上版本出现过一定比例的OOM,虽然比例并不大,但是还是暴露了一定的问题,所以打算对我们App分为几个步骤进行内存分析和优化,当然内存的优化是个长期的过程,不是一两个版本的事,每个版本都 ...
- Android 内存管理之优化建议
OOM(OutOfMemory)转:http://hukai.me/android-performance-oom/ 前面我们提到过使用getMemoryClass()的方法可以得到Dalvik He ...
- 来点干货 | Android 常见内存泄漏与优化(二)
作者 | 无名之辈FTER 责编 | 夕颜 出品 | CSDN(ID:CSDNnews) 在昨天的Android 内存泄漏问题多多,怎么优化?一文中,我们详细阐述了Java虚拟机工作原理和Androi ...
最新文章
- oracle 数据库对于多列求最大值
- Docker部署ELK 日志归集
- Apache Spark学习:利用Scala语言开发Spark应用程序
- plotly使用mapbox实现地图可视化
- 泛型--定制泛型接口、泛型类--介绍篇
- Vue中的Js动画与Velocity.js 的结合
- 将数据渲染到页面的几种方式
- apache arm 交叉编译_移植apache2 ARM版 – 交叉编译apache2 | 学步园
- java 求集合真子集_【每日打卡】新高一数学必修打卡第二天教学视频—集合的基本运算...
- python中!ls -r_光学现象的Python实现
- Vue Cli 3 搭建一个可按需引入组件的组件库架子
- 【JAVA】数据结构——二叉树 例题练习及代码详解
- 在计算机编程里pi是什么意思,编程中的术语“钩子”是什么意思?
- The program 'roscore' is currently not installed. You can install it by typing: sudo apt install pyt
- 类似苹果数据线的android,除了常见的安卓、苹果、Type-c,还有哪些你不知道的手机数据线?...
- 明明在内网,做种的人很多,为什么在transmission里bt下载速度仅10kb/s
- 使用重力感应传感器和Arduino的手机控制机器人车
- gps纠偏及大陆地图偏移原因
- MIMO均衡算法(CMA,LMS,RLS)原理介绍
- pandas--traning-how much sugar do we eat
热门文章
- 全球与中国投影幕布市场深度研究分析报告
- 树的应用 —— 树、森林与二叉树的转换
- 千万不要用Lightly去写代码!!!!踩大坑!!!!数据全没!!一个月都恢复不了!!!!
- 算法笔记CodeUp第一至第六章刷题记录
- USART_GetITStatus(USART3,USART_IT_IDLE) ==RESET进入中断问题
- 自己做的Google地图下载工具(一)
- SAP FI/CO 成本中心类型与功能范围
- Excel:固定表头,冻结窗格,悬挂标题
- 正则表达式 新手的歧途
- 调用支付宝第三方接口(沙箱环境) Spring Boot+Maven