Android Camera capture

想了下还是挤挤时间,把相机这基础流程写完吧,前面每篇写的都还是挺耗时的(就是累了,想偷偷懒,哈哈哈哈),那接着前面的几篇文章,给这一些列写上一个中规中矩的结局吧~

APP层

以下是相机openCamera,configStream,startPreview,capture全过程的示例代码,不用再分段演示了,注释也写的比较详细了。

package com.example.simplecamera;import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceTexture;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.FrameLayout;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;public class MainActivity extends AppCompatActivity {// 权限请求码private static final int REQUEST_CAMERA_PERMISSION = 100;// 相机相关变量private String mCameraId;private CameraDevice mCameraDevice;private CameraCaptureSession mCaptureSession;private CaptureRequest.Builder mPreviewRequestBuilder;private Size mPreviewSize;private ImageReader mImageReader;// UI组件private TextureView mTextureView;private Button mCaptureButton;private TextView mStatusText;// 后台线程private HandlerThread mBackgroundThread;private Handler mBackgroundHandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化UImTextureView = new TextureView(this);FrameLayout previewContainer = findViewById(R.id.preview_container);previewContainer.addView(mTextureView);mCaptureButton = findViewById(R.id.capture_button);mStatusText = findViewById(R.id.status_text);// 设置拍照按钮点击事件mCaptureButton.setOnClickListener(v -> takePicture());mCaptureButton.setEnabled(false);// 设置TextureView监听mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);}// SurfaceTexture监听,用于处理预览准备就绪private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {// 检查权限if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CAMERA},REQUEST_CAMERA_PERMISSION);return;}// 权限已授予,打开相机openCamera(width, height);}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return true;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}};// 打开相机private void openCamera(int width, int height) {startBackgroundThread();// 获取相机管理器CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);try {// 遍历所有相机,选择后置摄像头for (String cameraId : manager.getCameraIdList()) {CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);// 只使用后置摄像头Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {continue;}// 获取可用的输出尺寸StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);if (map == null) {continue;}// 选择合适的预览尺寸mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),width, height);// 初始化ImageReader用于拍照mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);mCameraId = cameraId;break;}// 打开相机if (mCameraId != null) {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {return;}manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);} else {showStatus("未找到可用相机");}} catch (CameraAccessException e) {showStatus("打开相机失败: " + e.getMessage());e.printStackTrace();}}// 相机状态回调private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {mCameraDevice = camera;createCameraPreviewSession();runOnUiThread(() -> {mCaptureButton.setEnabled(true);showStatus("相机已就绪");});}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {camera.close();mCameraDevice = null;runOnUiThread(() -> {mCaptureButton.setEnabled(false);showStatus("相机已断开");});}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {camera.close();mCameraDevice = null;runOnUiThread(() -> {mCaptureButton.setEnabled(false);showStatus("相机错误: " + error);});}};// 创建相机预览会话private void createCameraPreviewSession() {try {SurfaceTexture texture = mTextureView.getSurfaceTexture();if (texture == null) {throw new NullPointerException("TextureView为null");}// 设置预览尺寸texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());// 创建预览的SurfaceSurface surface = new Surface(texture);// 创建预览请求mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewRequestBuilder.addTarget(surface);// 创建相机捕获会话mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCaptureSession = session;try {// 设置自动对焦和自动曝光mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 开始预览mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),null, mBackgroundHandler);} catch (CameraAccessException e) {showStatus("预览失败: " + e.getMessage());e.printStackTrace();}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {showStatus("预览配置失败");}}, null);} catch (CameraAccessException e) {showStatus("创建会话失败: " + e.getMessage());e.printStackTrace();}}// 拍照private void takePicture() {if (mCameraDevice == null || mCaptureSession == null) {Log.w(TAG, "Cannot take picture - camera not ready");return;}try {// 创建拍照请求final CaptureRequest.Builder captureBuilder =mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);captureBuilder.addTarget(mImageReader.getSurface());// 设置自动对焦和曝光captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CaptureRequest.CONTROL_AF_TRIGGER_START);captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 设置照片方向int rotation = getWindowManager().getDefaultDisplay().getRotation();captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));mCaptureSession.capture(captureBuilder.build(), captureCallback, mBackgroundHandler);} catch (CameraAccessException e) {showStatus("拍照失败: " + e.getMessage());e.printStackTrace();}}// 处理拍摄的图片private final ImageReader.OnImageAvailableListener mOnImageAvailableListener =new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {// 在后台处理图片mBackgroundHandler.post(() -> {Image image = reader.acquireNextImage();if (image != null) {File imageFile = saveImageToFile(image);String message = imageFile != null ? "照片已保存: " + imageFile.getAbsolutePath() : "保存照片失败";runOnUiThread(() -> showStatus(message));image.close();}});}};// 保存图片到文件private File saveImageToFile(Image image) {ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);// 创建图片文件String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());String fileName = "IMG_" + timeStamp + ".jpg";File storageDir = getExternalFilesDir(null);File imageFile = new File(storageDir, fileName);FileOutputStream output = null;try {output = new FileOutputStream(imageFile);output.write(bytes);return imageFile;} catch (IOException e) {e.printStackTrace();return null;} finally {if (output != null) {try {output.close();} catch (IOException e) {e.printStackTrace();}}}}// 选择最佳的预览尺寸private Size chooseOptimalSize(Size[] choices, int width, int height) {List<Size> bigEnough = new ArrayList<>();for (Size option : choices) {if (option.getHeight() == option.getWidth() * height / width &&option.getWidth() >= width && option.getHeight() >= height) {bigEnough.add(option);}}if (bigEnough.size() > 0) {return Collections.min(bigEnough, new CompareSizesByArea());} else {return choices[0];}}// 按面积比较尺寸的比较器private static class CompareSizesByArea implements Comparator<Size> {@Overridepublic int compare(Size lhs, Size rhs) {return Long.signum((long) lhs.getWidth() * lhs.getHeight() -(long) rhs.getWidth() * rhs.getHeight());}}// 获取照片方向private int getOrientation(int rotation) {switch (rotation) {case Surface.ROTATION_0: return 90;case Surface.ROTATION_90: return 0;case Surface.ROTATION_180: return 270;case Surface.ROTATION_270: return 180;default: return 90;}}// 启动后台线程private void startBackgroundThread() {mBackgroundThread = new HandlerThread("CameraBackground");mBackgroundThread.start();mBackgroundHandler = new Handler(mBackgroundThread.getLooper());}// 停止后台线程private void stopBackgroundThread() {mBackgroundThread.quitSafely();try {mBackgroundThread.join();mBackgroundThread = null;mBackgroundHandler = null;} catch (InterruptedException e) {e.printStackTrace();}}// 显示状态信息private void showStatus(String message) {runOnUiThread(() -> {mStatusText.setText(message);Toast.makeText(this, message, Toast.LENGTH_SHORT).show();});}// 权限请求结果处理@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == REQUEST_CAMERA_PERMISSION) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 权限已授予,重新尝试打开相机if (mTextureView.isAvailable()) {openCamera(mTextureView.getWidth(), mTextureView.getHeight());}} else {showStatus("需要相机权限才能使用应用");}}}@Overrideprotected void onResume() {super.onResume();startBackgroundThread();if (mTextureView.isAvailable()) {openCamera(mTextureView.getWidth(), mTextureView.getHeight());} else {mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);}}@Overrideprotected void onPause() {closeCamera();stopBackgroundThread();super.onPause();}// 关闭相机private void closeCamera() {if (mCaptureSession != null) {mCaptureSession.close();mCaptureSession = null;}if (mCameraDevice != null) {mCameraDevice.close();mCameraDevice = null;}if (mImageReader != null) {mImageReader.close();mImageReader = null;}}
}

需要明确拍照过程是基于打开相机,配流之后的一个流程,所以点击拍照按钮进行拍照流程的时候需要判断相机连接,配流过程是否正常,即需要判断

    if (mCameraDevice == null || mCaptureSession == null) {Log.w(TAG, "Cannot take picture - camera not ready");return;}

如果都正常了,那就开始本文的关键流程分析,如下:

    mCaptureSession.capture(captureBuilder.build(), captureCallback, mBackgroundHandler);

Framework

由前面的文章介绍,我们就知道mCaptureSession其实就是CameraCaptureSessionImpl实例化对象
,所以继续看
frameworks/base/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java

    @Overridepublic int capture(CaptureRequest request, CaptureCallback callback,Handler handler) throws CameraAccessException {checkCaptureRequest(request);synchronized (mDeviceImpl.mInterfaceLock) {checkNotClosed();handler = checkHandler(handler, callback);if (DEBUG) {Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback +" handler " + handler);}//看到这里是不是很熟悉,是不是跟上一篇讲解setRepeatingRequest过程很像//即主要看参数的函数调用mDeviceImpl.capturereturn addPendingSequence(mDeviceImpl.capture(request,createCaptureCallbackProxy(handler, callback), mDeviceExecutor));}}

这个时候就接着跳转到CameraDeviceImpl类看capture()实现了
frameworks/base/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java

    public int capture(CaptureRequest request, CaptureCallback callback, Executor executor)throws CameraAccessException {if (DEBUG) {Log.d(TAG, "calling capture");}List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();requestList.add(request);return submitCaptureRequest(requestList, callback, executor, /*streaming*/false);}

然后直接看下一步,关键流程已加注释

    private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,Executor executor, boolean repeating) throws CameraAccessException {//这里带的参数是false,说明了拍照的时候是不需要停预览的,上一篇起预览的时候//这里是true,是必定要停止上一次预览连接的if (repeating) {stopRepeating();}//以下是关键函数requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);return requestInfo.getRequestId();}

通过binder调用到这里了
frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp

binder::Status CameraDeviceClient::submitRequestList(const std::vector<hardware::camera2::CaptureRequest>& requests,bool streaming,/*out*/hardware::camera2::utils::SubmitInfo *submitInfo) {if (streaming) {//预览环节,上一章跟踪的这个流程err = mDevice->setStreamingRequestList(metadataRequestList, surfaceMapList,&(submitInfo->mLastFrameNumber));} else {//拍照环节,这章跟踪这一个流程err = mDevice->captureList(metadataRequestList, surfaceMapList,&(submitInfo->mLastFrameNumber));}return res;
}

继续
frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp

status_t Camera3Device::captureList(const List<const PhysicalCameraSettingsList> &requestsList,const std::list<const SurfaceMap> &surfaceMaps,int64_t *lastFrameNumber) {ATRACE_CALL();return submitRequestsHelper(requestsList, surfaceMaps, /*repeating*/false, lastFrameNumber);
}

注意这个时候传的repeating是false了

status_t Camera3Device::submitRequestsHelper(const List<const PhysicalCameraSettingsList> &requests,const std::list<const SurfaceMap> &surfaceMaps,bool repeating,/*out*/int64_t *lastFrameNumber) {if (repeating) {//预览流程res = mRequestThread->setRepeatingRequests(requestList, lastFrameNumber);} else {//拍照流程res = mRequestThread->queueRequestList(requestList, lastFrameNumber);}return res;}
status_t Camera3Device::RequestThread::queueRequestList(List<sp<CaptureRequest> > &requests,/*out*/int64_t *lastFrameNumber) {ATRACE_CALL();Mutex::Autolock l(mRequestLock);for (List<sp<CaptureRequest> >::iterator it = requests.begin(); it != requests.end();++it) {//添加到请求队列mRequestQueue.push_back(*it);}if (lastFrameNumber != NULL) {*lastFrameNumber = mFrameNumber + mRequestQueue.size() - 1;ALOGV("%s: requestId %d, mFrameNumber %" PRId32 ", lastFrameNumber %" PRId64 ".",__FUNCTION__, (*(requests.begin()))->mResultExtras.requestId, mFrameNumber,*lastFrameNumber);}unpauseForNewRequests();return OK;
}

然后回到RequestThread

bool Camera3Device::RequestThread::threadLoop() //省略n行代码,以下流程跟预览流程一致,只提出来关键函数waitForNextRequestBatch();if (mNextRequests.size() == 0) {return true;}res = prepareHalRequests();submitRequestSuccess = sendRequestsBatch();return submitRequestSuccess;      
}

hardware/qcom/camera/msm8998/QCamera2/HAL3/QCamera3HWI.cpp

int QCamera3HardwareInterface::processCaptureRequest(camera3_capture_request_t *request,List<InternalRequest> &internallyRequestedStreams)
{
//省略内容
}

这咋跟预览一样一样的,很明显,我!跟!丢!了!
回去!

frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp

status_t Camera3Device::submitRequestsHelper(const List<const PhysicalCameraSettingsList> &requests,const std::list<const SurfaceMap> &surfaceMaps,bool repeating,/*out*/int64_t *lastFrameNumber) {if (repeating) {//预览流程res = mRequestThread->setRepeatingRequests(requestList, lastFrameNumber);} else {//拍照流程res = mRequestThread->queueRequestList(requestList, lastFrameNumber);}return res;}

仔细观察一下这里的实现有什么不一样呢
从上面代码可以发现capture流程会被放在mRequestQueue里;repeating 流程会放在mRepeatingRequests,在threadLoop() 的waitForNextRequestBatch() 可以知道,capture流程会先塞进请求队列里面

但好像只知道这些也不足以hal层区分拍照和预览啊,其实主要还有一个点漏了,就是APP请求拍照时候,还设置了请求的参数,其实这个就是区分的关键
拍照:

// 创建拍照请求
final CaptureRequest.Builder captureBuilder =mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)

预览:

// 创建预览请求
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

下发链路可概括为:
应用层设置意图 → Framework 层(CameraService)校验、打包为 camera3_capture_request_t → 通过 HIDL 接口跨进程传递到 HAL → HAL 层解析元数据识别预览,拍照意图。

整个过程中,Framework 层负责元数据的序列化和跨进程传递,HAL 层通过解析元数据中的具体值(1 对应 PREVIEW)来区分请求类型,最终路由到对应的处理逻辑,本章就说这么多了,其他的如驱动控制,数据编码,数据回传等后面有空再搞了,困了。

大概总结一下拍照全流程:
应用层(发起拍照请求)→ 框架层(校验/封装请求)→ HAL层(解析/配置硬件)→ 驱动层(控制传感器)→ 硬件(采集/处理数据)
→ 驱动层(传输数据)→ HAL层(写入缓冲区)→ 框架层(通知应用)→ 应用层(读取/保存数据)

简单看了下应该没啥大问题,睡觉!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

落霞归雁思维框架应用(十) ——在职考研 199 管综 + 英语二 30 周「顺水行舟」上岸指南

落霞归雁思维框架应用&#xff08;十&#xff09; ——在职考研 199 管综 英语二 30 周「顺水行舟」上岸指南 CSDN 首发 | 作者&#xff1a;落霞归雁 | 2025-08-01 开场&#xff1a;把 199英二 从“两座大山”变成“两条顺流” 在职党最怕两句话&#xff1a; “管综题量太大…

自动调优 vLLM 服务器参数(实战指南)

1. 操作步骤 该脚本能够自动搜索最优的vLLM服务器参数组合&#xff08;包括max-num-seqs和max-num-batched-tokens&#xff09;&#xff0c;在满足端到端延迟和前缀缓存命中率等要求的同时&#xff0c;实现吞吐量最大化。 1.1 前提条件 克隆 vLLM 并切到目标分支git clone http…

【腾讯云】EdgeOne免费版实现网站加速与安全防护

作为一名个人站长&#xff0c;我深知在有限的预算下如何为网站提供稳定、快速的服务是关键。腾讯云推出了 EdgeOne免费版 https://edgeone.ai/zh/redemption已经有一段时间。结合我的实际体验&#xff0c;我想从国内备案域名的角度&#xff0c;分享一下如何利用这个工具提升网站…

修改gitlab默认的语言

文章目录网上的方法1. 采用数据库触发器的方法2. 登录pg库2.1 查看表2.2 创建function2.3 创建触发器2.4 修改历史数据网上的方法 网上修改/opt/gitlab/embedded/service/gitlab-rails/config/application.rb的方法&#xff0c;我试了&#xff0c;没生效&#xff0c;没进一步研…

ndk { setAbiFilters([‘armeabi-v7a‘, “arm64-v8a“]) }

deepseek回答&#xff1a; 好的&#xff0c;我现在需要解释build.gradle中这段ndk配置代码的含义。首先&#xff0c;用户提供的代码片段是&#xff1a; ndk { setAbiFilters([‘armeabi-v7a’, “arm64-v8a”]) } 我的第一步是理解每个部分的作用。ndk块在Android构建文件中用于…

k8s 1.30 通过helm部署ingress-controller-4.12.1

一、基本概念k8s集群中&#xff0c;ingress作为集群内服务对外暴漏的访问接入点&#xff0c;几乎承载着集群内服务访问的所有流量。ingress是k8s中的一个资源对象&#xff0c;用来管理集群外部访问集群内部服务的方式。可以通过ingress资源来配置不同的转发规则&#xff0c;从而…

Gitee:本土开发者生态的崛起与数字化转型新引擎

Gitee&#xff1a;本土开发者生态的崛起与数字化转型新引擎 在数字化转型浪潮席卷全球的当下&#xff0c;代码托管平台已从单纯的代码存储工具演变为企业技术创新的基础设施。作为中国领先的一站式DevOps平台&#xff0c;Gitee以其本地化优势和创新功能矩阵&#xff0c;正重新定…

Servlet HTTP 状态码详解

Servlet HTTP 状态码详解 引言 在Web开发中,HTTP状态码是服务器响应客户端请求时返回的状态信息。这些状态码有助于开发者了解请求处理的结果,并针对不同的状态码进行相应的处理。Servlet作为Java Web开发的重要技术之一,理解HTTP状态码对于开发高质量的Web应用至关重要。…

ubuntu qt环境下出现No suitable kits found解决方案

1. 清理 Qt Creator 缓存Qt Creator 会缓存项目配置、索引等数据&#xff0c;可能导致某些异常。清理方法&#xff1a;(1) 删除 Qt Creator 配置目录bashrm -rf ~/.config/QtProject/&#xff08;Ubuntu/Linux&#xff09; 或 Windows&#xff1a;cmdrmdir /s /q "%APPDAT…

【保姆级喂饭教程】Python依赖管理工具大全:Virtualenv、venv、Pipenv、Poetry、pdm、Rye、UV、Conda、Pixi等

目录前言1前言2一、包管理工具1. pip&#xff08;Python官方&#xff0c;2008&#xff09;二、虚拟环境工具1. virtualenv&#xff08;Ian Bicking&#xff0c;2007&#xff09;2. venv&#xff08;Python3.3&#xff0c;2012&#xff09;三、版本管理工具1. pyenv&#xff08;…

Linux进程概念(五)进程地址空间

地址空间排布这段空间中自上而下&#xff0c;地址是增长的&#xff0c;栈是向地址减小方向增长&#xff0c;里面存放函数中的临时变量&#xff0c;而堆是向地址增长方向增长&#xff0c;malloc开辟的地址空间存放在堆区&#xff0c;堆栈之间的共享区域&#xff0c;主要用来加载…

Go语言实战案例-判断二叉树是否对称

给定一棵二叉树&#xff0c;判断这棵树是否是对称的。对称的含义是&#xff1a;这棵树的左子树和右子树在结构上是镜像对称的&#xff0c;且对应节点的值相等。示例 1&#xff1a;1/ \2 2/ \ / \ 3 4 4 3输出&#xff1a;true示例 2&#xff1a;1/ \2 2\ \3 3输出&a…

【机器学习深度学习】为什么需要分布式训练?

目录 前言 一、模型规模爆炸&#xff1a;单卡GPU已难以承载 1.1 问题描述 1.2 面临挑战 1.3 解决方案&#xff1a;模型并行 (Model Parallelism) 1.4 类比理解&#xff1a;模型并行 1.5 模型并行的关键点 1.6 模型并行&#xff08;Model Parallelism&#xff09;的流程…

二十八、【Linux系统域名解析】DNS安装、子域授权、缓存DNS、分离解析、多域名解析

DNS服务深度解析&#xff1a;缓存、分离与多域名管理一、DNS服务架构全景DNS核心组件关系DNS服务器类型对比二、基础DNS服务配置1. Bind9核心配置文件2. 区域文件结构解析区域文件记录类型表三、子域授权与分层解析子域授权原理子域配置流程1. 父域配置2. 子域配置递归与迭代查…

【LeetCode】前缀表相关算法

目录1、介绍2、核心概念【1】前缀和后缀【2】最长公共前后缀&#xff08;LPS&#xff09;3、相关算法题【1】找出字符串中第一个匹配项的下标【2】重复的子字符串1、介绍 前缀表是一种在字符串匹配算法&#xff08;特别是KMP算法&#xff09;中使用的数据结构&#xff0c;用于…

(六) Spring AI 1.0版本 + 千问大模型+RAG

上篇文章我们大概讲了一下向量模型的知识&#xff0c;本篇文章&#xff0c;我们将会通过RAG实战的形式&#xff0c;来感受一下RAG。 项目准备 pom.xml 这里我们需要引入向量库和pdf相关的包<dependency><groupId>org.springframework.ai</groupId><artifa…

Spring Boot与Mybatis-Plus集成SQLServer的完整指南

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;本项目旨在演示如何将SQLServer与Spring Boot以及Mybatis-Plus框架进行整合&#xff0c;打造一个高效稳定的后端服务。详细介绍涉及了数据库连接、实体类定义、Mapper接口创建、Service层业务逻辑编写、Control…

【工作笔记】判断一条方法需不需要事务/AOP

① 看注解方法/类上有 Transactional → 需要事务&#xff0c;必须走代理方法/类上有自定义 AOP 注解&#xff08;如 Log、Retry、Cacheable 等&#xff09;→ 需要代理什么都没有 → 几乎肯定不需要示例需求Transactional public void generateDailyTask(...)✅ 需要事务publi…

Unity 的UI动画调节

在游戏开发中&#xff0c;精美的UI动画能极大提升用户体验。Unity提供了强大的动画系统&#xff0c;让开发者可以轻松创建流畅的界面动效。本文将介绍UI动画的核心概念、制作流程和实用技巧。一、核心动画组件Animation窗口 - 可视化创建关键帧动画Animator组件 - 控制动画状态…

26考研11408数据结构

数据结构 1.绪论1.1.1数据结构的基本概念 数据数据元素&#xff1a;数据的基本单位&#xff0c;一个数据元素由多个数据项组成&#xff0c;数据项是组成数据元素不可分割的最小单位数据对象&#xff1a;具有相同性质的数据元素的集合&#xff0c;是数据的一个子集数据类型&…