`
jgsj
  • 浏览: 960603 次
文章分类
社区版块
存档分类
最新评论

Android图形显示之FramebufferNativeWindow本地窗口

 
阅读更多

Android窗口系统

我们知道Android系统采用OpenGL来绘制3D图形,OpenGL ES提供了本地窗口(NativeWindow)的概念,无论是在Android平台中还是其他平台中,只要实现OpenGL ES中的本地窗口定义的接口,就可以利用OpenGL ES来绘制图形。由于Android系统所有服务都建立在C/S模式下,因此Android系统在实现OpenGL ES的本地窗口时仍然包括两种本地窗口,服务进程端的本地窗口定义为FramebufferNativeWindow,该本地窗口直接由SurfaceFlinger管理。在应用程序进程端定义的本地创建为SurfaceTextureClient。在Android系统中,它们之间为一对多的关系,如下图所示:


每个应用程序App可以有多个窗口,即多个Surface,每个Surface所需的图形缓冲区由SurfaceFlinger进程中的BufferQueue对象负责管理,图形缓冲区个数最多可以有32个,每个图形缓冲区用GraphicBuffer来定义,应用程序在图形绘制前,请求SurfaceFlinger进程中的BufferQueue对象在内存中分配一块图形缓冲区,应用程序完成图形绘制后,由SurfaceFlinger将多个应用程序需要显示的Surface进行图形混合,混合后的图形窗口使用FramebufferNativeWindow来描述,同时将混合后的图形数据拷贝到Framebuffer的后台缓冲区中,等待渲染到显示屏上。以下就是Android的窗口系统设计模型:


FramebufferNativeWindow本地窗口所需的图形缓冲区直接从Framebuffer中分配,而Surface本地窗口所需的图形缓冲区则是从内存中分配,无论是从Framebuffer中分配还是从内存中分配,图形缓冲区的分配工作都是由Gralloc硬件抽象层完成,在Android图形显示之硬件抽象层Gralloc中详细分析了Gralloc模块,而Android图形缓冲区分配过程源码分析则分析了图形缓冲区的分配过程。SurfaceFlinger收集所有应用程序的显示需求,然后对应用程序所需显示的图形做图像混合操作,然后输出到自己的FramebufferNativeWindow本地窗口上。为了使用OpenGL ES绘制图形窗口,必须实现OpenGL ES定义的本地窗口协议NativeWindow。

EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config,
                                    NativeWindowType window,
                                    const EGLint *attrib_list)
函数eglCreateWindowSurface是OpenGL ES提供用于创建窗口的函数接口,参数window的类型为NativeWindowType,定义如下:

frameworks\native\opengl\include\EGL\eglplatform.h

typedef EGLNativeWindowType  NativeWindowType;

#if defined(_WIN32) || defined(__VC32__) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) /* Win32 and WinCE */
typedef HWND    EGLNativeWindowType;
#elif defined(__WINSCW__) || defined(__SYMBIAN32__)  /* Symbian */
typedef void *EGLNativeWindowType;
#elif defined(__ANDROID__) || defined(ANDROID)
typedef struct ANativeWindow*           EGLNativeWindowType;
#elif defined(__unix__)
typedef Window   EGLNativeWindowType;
#else
#error "Platform not recognized"
#endif
NativeWindowType定义为EGLNativeWindowType类型,而该类型在不同的平台中有不同的定义,这是因为OpenGL ES是一个跨平台的图形绘制库,对于Android系统来说,其定义为ANativeWindow指针类型,而ANativeWindow的定义如下:

struct ANativeWindow
{
#ifdef __cplusplus
    ANativeWindow(): flags(0), minSwapInterval(0), maxSwapInterval(0), xdpi(0), ydpi(0)
    {
        common.magic = ANDROID_NATIVE_WINDOW_MAGIC;
        common.version = sizeof(ANativeWindow);
        memset(common.reserved, 0, sizeof(common.reserved));
    }
    void incStrong(const void* id) const {
        common.incRef(const_cast<android_native_base_t*>(&common));
    }
    void decStrong(const void* id) const {
        common.decRef(const_cast<android_native_base_t*>(&common));
    }
#endif

    struct android_native_base_t common;
    const uint32_t flags;//用于描述该Surface的一些属性
    const int   minSwapInterval;//最小交换间隔时间
    const int   maxSwapInterval;//最大交换间隔时间
    const float xdpi;//水平方向的密度
    const float ydpi;//垂直方向的密度
    intptr_t    oem[4];//为OEM预留
	//设置交换间隔时间
    int     (*setSwapInterval)(struct ANativeWindow* window,int interval);
	//申请一个图形缓冲区buffer
    int     (*dequeueBuffer)(struct ANativeWindow* window,struct ANativeWindowBuffer** buffer);
	//锁定图形缓冲区
    int     (*lockBuffer)(struct ANativeWindow* window,struct ANativeWindowBuffer* buffer);
	//buffer渲染完成后,它调用这个接口来unlock和post buffer
    int     (*queueBuffer)(struct ANativeWindow* window,struct ANativeWindowBuffer* buffer);
	//向本地窗口查询相关信息
    int     (*query)(const struct ANativeWindow* window,int what, int* value);
	//执行本地窗口支持的各种操作
    int     (*perform)(struct ANativeWindow* window,int operation, ... );
	//取消一个已经dequeued的buffer
    int     (*cancelBuffer)(struct ANativeWindow* window,struct ANativeWindowBuffer* buffer);
	//预留
    void* reserved_proc[2];
}android_native_window_t;
当使用C++编译器是,为ANativeWindow定义了相应的构造函数,在OpenGL ES下的Android本地窗口系统的类关系图如下:


从上图可以看出,Surface和FramebufferNativeWindow都继承于ANativeWindow,因此也就继承了OpenGL ES下的本地窗口定义的相关协议:ANativeWindow中定义的相关接口。下面分别对这两种类型的本地窗口进行深入分析。

FramebufferNativeWindow

前面已经介绍了FramebufferNativeWindow是SurfaceFlinger服务进程维护的本地窗口,用于描述经过图形混合后的,即将渲染显示的图形窗口。FramebufferNativeWindow不仅实现了从ANativeWindow中继承下来的接口,自己还定义了一些额外属性:
class FramebufferNativeWindow 
    : public ANativeObjectBase<
        ANativeWindow, 
        FramebufferNativeWindow, 
        LightRefBase<FramebufferNativeWindow> >
{
    framebuffer_device_t* fbDev; //描述Framebuffer设备
    alloc_device_t* grDev; //描述gpu设备
    sp<NativeBuffer> buffers[NUM_FRAME_BUFFERS];//定义2个图形缓冲区
    sp<NativeBuffer> front; //前台图形缓冲区,即正在渲染的图形缓冲区
    mutable Mutex mutex; 
    Condition mCondition;
    int32_t mNumBuffers; //图形缓冲区个数
    int32_t mNumFreeBuffers; //可以使用的图形缓冲区个数
    int32_t mBufferHead; //
    int32_t mCurrentBufferIndex;//当前图形缓冲区的索引
    bool mUpdateOnDemand;
};
接下来看看FramebufferNativeWindow对象的构造过程:
FramebufferNativeWindow::FramebufferNativeWindow() : BASE(), fbDev(0), grDev(0), mUpdateOnDemand(false)
{
    hw_module_t const* module;
	//加载gralloc模块
    if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) == 0) {
        int stride;
        int err;
        int i;
		//打开fb设备
        err = framebuffer_open(module, &fbDev);
        ALOGE_IF(err, "couldn't open framebuffer HAL (%s)", strerror(-err));
        //打开gpu设备
        err = gralloc_open(module, &grDev);
        ALOGE_IF(err, "couldn't open gralloc HAL (%s)", strerror(-err));
        //设备打开失败,返回
        if (!fbDev || !grDev)
            return;
        mUpdateOnDemand = (fbDev->setUpdateRect != 0);
        // 初始化变量值
        mNumBuffers = NUM_FRAME_BUFFERS;//2
        mNumFreeBuffers = NUM_FRAME_BUFFERS;//2
        mBufferHead = mNumBuffers-1;//1
#ifdef FRAMEBUFFER_FORCE_FORMAT
        *((uint32_t *)&fbDev->format) = FRAMEBUFFER_FORCE_FORMAT;
#endif
		//创建2个NativeBuffer
        for (i = 0; i < mNumBuffers; i++)
        {
            buffers[i] = new NativeBuffer(fbDev->width, fbDev->height, fbDev->format, GRALLOC_USAGE_HW_FB);
        }
		//为NativeBuffer分配缓冲区
        for (i = 0; i < mNumBuffers; i++)
        {
                err = grDev->alloc(grDev,fbDev->width, fbDev->height, fbDev->format,GRALLOC_USAGE_HW_FB, &buffers[i]->handle, &buffers[i]->stride);
                ALOGE_IF(err, "fb buffer %d allocation failed w=%d, h=%d, err=%s",i, fbDev->width, fbDev->height, strerror(-err));
                if (err)
                {
                        mNumBuffers = i;
                        mNumFreeBuffers = i;
                        mBufferHead = mNumBuffers-1;
                        break;
                }
        }
		//使用Framebuffer的设备描述符来初始化本地窗口ANativeWindow的相关属性
        const_cast<uint32_t&>(ANativeWindow::flags) = fbDev->flags; 
        const_cast<float&>(ANativeWindow::xdpi) = fbDev->xdpi;
        const_cast<float&>(ANativeWindow::ydpi) = fbDev->ydpi;
        const_cast<int&>(ANativeWindow::minSwapInterval) = fbDev->minSwapInterval;
        const_cast<int&>(ANativeWindow::maxSwapInterval) = fbDev->maxSwapInterval;
    } else {
        ALOGE("Couldn't get gralloc module");
    }
	//为本地窗口ANativeWindow设置回调接口函数
    ANativeWindow::setSwapInterval = setSwapInterval;
    ANativeWindow::dequeueBuffer = dequeueBuffer;
    ANativeWindow::lockBuffer = lockBuffer;
    ANativeWindow::queueBuffer = queueBuffer;
    ANativeWindow::query = query;
    ANativeWindow::perform = perform;
}

函数首先加载Gralloc模块,关于硬件抽象层模块的加载过程,在Android硬件抽象Hardware库加载过程源码分析已经有详细的介绍了。当成功加载Gralloc模块后,依次打开Gralloc模块中定义的Framebuffer设备及gpu设备,我们知道Gralloc模块中定义的Framebuffer设备用于将已经准备好了的图形缓冲区渲染到帧缓冲区中,而定义的gpu设备用于分配一块图形缓冲区,并且将这块图形缓冲区映射到应用程序的地址空间关于Framebuffer设备和gpu设备的打开过程请参阅Android图形显示之硬件抽象层Gralloc。打开fb和gpu设备后,将这两种设备描述符分别保存到FramebufferNativeWindow的成员变量fbDev和grDev中。接着创建了两个NativeBuffer对象,并从Framebuffer帧缓冲区中分配了两块图形缓冲区。Android系统为定义的两种本地窗口分别定义了相应的图形缓冲区buffer的描述符,对于FramebufferNativeWindow本地窗口来说,为其定义的图形缓冲区描述符为NativeBuffer,而对于应用程序端的本地窗口Surface,为其定义的图形缓冲区描述符为GraphicBuffer,它们之间的类关系图如下:


从上面的类继承图中可以看出,无论是NativeBuffer还是GraphicBuffer,它们都继承于ANativeWindowBuffer,ANativeWindowBuffer用于描述一块图形缓冲区buffer的属性信息,比如图形的宽,高,图像格式及该buffer的句柄等等信息。

typedef struct ANativeWindowBuffer
{
//针对C++编译器定义构造函数
#ifdef __cplusplus
    ANativeWindowBuffer() {
        common.magic = ANDROID_NATIVE_BUFFER_MAGIC;
        common.version = sizeof(ANativeWindowBuffer);
        memset(common.reserved, 0, sizeof(common.reserved));
    }
    void incStrong(const void* id) const {
        common.incRef(const_cast<android_native_base_t*>(&common));
    }
    void decStrong(const void* id) const {
        common.decRef(const_cast<android_native_base_t*>(&common));
    }
#endif
    struct android_native_base_t common;//描述EGL版本信息
    int width; //图像宽度
    int height; //图像高度
    int stride; //
    int format; //图像格式
    int usage; //该buffer的用途
    void* reserved[2]; //保留
    buffer_handle_t handle; //该buffer的句柄信息
    void* reserved_proc[8];
} android_native_buffer_t;

接着为创建的2个NativeBuffer分配空间,使用Gralloc模块中的gpu来完成空间的分配过程,同时指定标志位为GRALLOC_USAGE_HW_FB,表示从系统帧缓冲区Framebuffer中分配。

err = grDev->alloc(grDev,fbDev->width, fbDev->height, fbDev->format,GRALLOC_USAGE_HW_FB, &buffers[i]->handle, &buffers[i]->stride);
关于图形缓冲区的完整分配过程请阅读Android图形缓冲区分配过程源码分析。FramebufferNativeWindow完成图形缓冲区的分配后,还需初始化从ANativeWindow中继承而来的本地窗口定义的相关接口,即FramebufferNativeWindow实现ANativeWindow本地窗口协议。从FramebufferNativeWindow的构造函数中,我们知道,FramebufferNativeWindow从Framebuffer中分配了2个缓冲区,说明FramebufferNativeWindow使用了双缓冲技术,使用双缓冲技术的优点是什么呢?假设我们需要绘制这样一个画面,包括两个三角形和三个圆形,最终结果如下图所示:

在只有一个buffer的情况下,我们是直接以屏幕为画板来实时做画的,假设图中的每一个三角形或圆形都需要0.5秒为例,那么总计耗时应该是0.5*5=2.5秒,图形绘制过程如下:

对于用户来说,他将看到一个不断刷新的画面。对于图像刷新很频繁的情况,用户的体验就会更差。出现这种现象的原因就是程序直接以屏幕为绘图板,把还没有准备就绪的图像直接呈现给了用户。换句话说,如果将整幅图绘制完成以后再刷新到屏幕上,那么对于用户来说,他在任何时候看到的都是完整的图像。采用两个缓冲区绘制图形的情况如下:

既然FramebufferNativeWindow创建了两块图形缓冲区,那它是如何维护这两块图形缓冲区的呢?接下来就介绍图形缓冲区的申请过程:

int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window, 
        ANativeWindowBuffer** buffer)
{
    FramebufferNativeWindow* self = getSelf(window);
    Mutex::Autolock _l(self->mutex);
	//从FramebufferNativeWindow对象中取出fb设备描述符,在构造FramebufferNativeWindow对象时,已经打开了fb设备
    framebuffer_device_t* fb = self->fbDev;
    //计算当前申请的图形缓冲区在buffers数组中的索引,同时将下一个申请的buffer的索引保存到mBufferHead中
    int index = self->mBufferHead++;
	//如果申请的下一个buffer的索引大于或等于buffer总数,则将下一个申请的buffer索引设置为0,这样就实现了对buffer数组的循环管理
    if (self->mBufferHead >= self->mNumBuffers)
        self->mBufferHead = 0;
    //如果当前没有空闲的buffer,即mNumFreeBuffers= 0,则线程睡眠等待buffer的释放
    while (!self->mNumFreeBuffers) {
        self->mCondition.wait(self->mutex);
    }
    //存在了空闲buffer,线程被唤醒继续执行,由于此时要申请一块buffer,因此空闲buffer的个数又需要减1
    self->mNumFreeBuffers--;
	//保存当前申请的buffer在缓冲区数组中的索引位置
    self->mCurrentBufferIndex = index;
    //得到buffer数组中的NativeBuffer对象指针
    *buffer = self->buffers[index].get();
    return 0;
}
dequeueBuffer函数就是从FramebufferNativeWindow创建的包含2个图形缓冲区的缓冲区队列buffers中取出一块空闲可用的图形buffer,如果当前缓冲区队列中没有空闲的buffer,则当前申请buffer线程阻塞等待,等待其他线程释放图形缓冲区。mNumFreeBuffers用来描述可用的空闲图形buffer个数,index记录当前申请buffer在图形缓冲区队列中的索引位置,mBufferHead指向下一次申请的图形buffer的位置,由于我们是循环利用两个缓冲区的,所以如果这个变量的值超过mNumBuffers,就需要置0。也就是说mBufferHead的值永远只能是0或者1。
上图描述了图形绘制的整个过程,SurfaceFlinger首先从FramebufferNativeWindow中申请出列一块图形buffer,然后将系统中的各个Surface的GraphicBuffer进行图形混合,将混合后的图形保存到申请所得的图形buffer中,接着将该buffer放回FramebufferNativeWindow的图形缓冲区队列中,最后将该buffer渲染到显示屏幕上。接下来介绍图形buffer如列过程:
int FramebufferNativeWindow::queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer)
{
    FramebufferNativeWindow* self = getSelf(window);
    Mutex::Autolock _l(self->mutex);
	//从FramebufferNativeWindow对象中取出fb设备描述符,在构造FramebufferNativeWindow对象时,已经打开了fb设备
    framebuffer_device_t* fb = self->fbDev;
	//从NativeBuffer对象中取出buffer_handle_t
    buffer_handle_t handle = static_cast<NativeBuffer*>(buffer)->handle;
    const int index = self->mCurrentBufferIndex;
	//调用framebuffer_device_t中注册的post函数将已绘制好的buffer渲染到Framebuffer中。
    int res = fb->post(fb, handle);
	//将当前NativeBuffer保存为前台buffer
    self->front = static_cast<NativeBuffer*>(buffer);
	//由于当前NativeBuffer已经渲染完成,因此将当前buffer入列,从而可以被申请
    self->mNumFreeBuffers++;
	//唤醒图形buffer申请出列线程,表示已有空闲buffer可以被申请
    self->mCondition.broadcast();
    return res;
}
这里将调用fb设备的post方法将buffer渲染到屏幕上,然后修改空闲buffer个数,最后唤醒正在申请图形buffer出列,却因无空闲buffer而睡眠的线程。
static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer)
{	
	//校验buffer_handle_t
    if (private_handle_t::validate(buffer) < 0)
        return -EINVAL;
    //将framebuffer_device_t强制转换为fb_context_t指针
    fb_context_t* ctx = (fb_context_t*)dev;
    //将buffer_handle_t强制转换为private_handle_t指针 
    private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(buffer);
	//通过fb_context_t设备描述符找到对应的硬件抽象设备hw_device_t,在根据hw_device_t找到对应的硬件抽象模块hw_moudle_t,最后强制转换为private_module_t指针
    private_module_t* m = reinterpret_cast<private_module_t*>(dev->common.module);
    //如果当前buffer是从Framebuffer中分配的缓冲区
    if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {
        const size_t offset = hnd->base - m->framebuffer->base;
        m->info.activate = FB_ACTIVATE_VBL;
        m->info.yoffset = offset / m->finfo.line_length;
        if (ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info) == -1) {
            ALOGE("FBIOPUT_VSCREENINFO failed");
            m->base.unlock(&m->base, buffer); 
            return -errno;
        }
        m->currentBuffer = buffer;
    } else {
        // If we can't do the page_flip, just copy the buffer to the front 
        // FIXME: use copybit HAL instead of memcpy
        void* fb_vaddr;
        void* buffer_vaddr;
        m->base.lock(&m->base, m->framebuffer, GRALLOC_USAGE_SW_WRITE_RARELY, 0, 0, m->info.xres, m->info.yres,&fb_vaddr);
        m->base.lock(&m->base, buffer, GRALLOC_USAGE_SW_READ_RARELY, 0, 0, m->info.xres, m->info.yres,&buffer_vaddr);
        memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres);
        m->base.unlock(&m->base, buffer); 
        m->base.unlock(&m->base, m->framebuffer); 
    }
    return 0;
}
最后通过FBIOPUT_VSCREENINFO命令进入Framebuffer驱动,将图形渲染显示。
分享到:
评论

相关推荐

    node-v16.13.1-darwin-arm64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    【前端素材】大数据-大数据可视化系统数据分析通用模版.zip

    大数据技术指的是用于处理和分析大规模数据集的技术和工具。以下是一些常见的大数据技术和工具: Hadoop:Apache Hadoop是一个用于分布式存储和处理大规模数据的开源框架。它包括Hadoop Distributed File System(HDFS)用于数据存储和MapReduce用于数据处理。 Spark:Apache Spark是一个快速、通用的集群计算系统,提供了比MapReduce更快的数据处理能力。它支持内存计算和更多复杂的数据处理流程。 NoSQL数据库:NoSQL数据库(如MongoDB、Cassandra等)则更适用于处理这类数据。 数据仓库:数据仓库是一个用于集成和分析大规模数据的存储系统,一些知名的数据仓库包括Snowflake、Amazon Redshift等。 数据湖:数据湖是一个存储结构化和非结构化数据的存储池,用于支持数据分析和机器学习应用。 机器学习:大数据技术也广泛应用于机器学习领域,支持大规模数据的模型训练和预测分析。 流式处理:针对实时数据处理需求,流式处理技术(如Apache Kafka、Apache Flink)可以实时。

    node-v8.3.0.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    云计算存储架构dr.pptx

    云计算存储架构dr.pptx

    【前端素材】大数据-兰州智慧消防大数据平台.zip

    大数据技术指的是用于处理和分析大规模数据集的技术和工具。以下是一些常见的大数据技术和工具: Hadoop:Apache Hadoop是一个用于分布式存储和处理大规模数据的开源框架。它包括Hadoop Distributed File System(HDFS)用于数据存储和MapReduce用于数据处理。 Spark:Apache Spark是一个快速、通用的集群计算系统,提供了比MapReduce更快的数据处理能力。它支持内存计算和更多复杂的数据处理流程。 NoSQL数据库:NoSQL数据库(如MongoDB、Cassandra等)则更适用于处理这类数据。 数据仓库:数据仓库是一个用于集成和分析大规模数据的存储系统,一些知名的数据仓库包括Snowflake、Amazon Redshift等。 数据湖:数据湖是一个存储结构化和非结构化数据的存储池,用于支持数据分析和机器学习应用。 机器学习:大数据技术也广泛应用于机器学习领域,支持大规模数据的模型训练和预测分析。 流式处理:针对实时数据处理需求,流式处理技术(如Apache Kafka、Apache Flink)可以实时。

    基于java的-152-springboot大学生体质测试管理系统--LW-源码.zip

    提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

    培训材料-云计算架构比较和关键技术讲解dr.ppt

    培训材料-云计算架构比较和关键技术讲解dr.ppt

    BP神经网络工具箱.doc

    数学模型算法

    基于python的-17-物业信息管理系统--LW-源码.zip

    提供的源码资源涵盖了python应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

    【前端素材】大数据-运营大数据.zip

    大数据技术指的是用于处理和分析大规模数据集的技术和工具。以下是一些常见的大数据技术和工具: Hadoop:Apache Hadoop是一个用于分布式存储和处理大规模数据的开源框架。它包括Hadoop Distributed File System(HDFS)用于数据存储和MapReduce用于数据处理。 Spark:Apache Spark是一个快速、通用的集群计算系统,提供了比MapReduce更快的数据处理能力。它支持内存计算和更多复杂的数据处理流程。 NoSQL数据库:NoSQL数据库(如MongoDB、Cassandra等)则更适用于处理这类数据。 数据仓库:数据仓库是一个用于集成和分析大规模数据的存储系统,一些知名的数据仓库包括Snowflake、Amazon Redshift等。 数据湖:数据湖是一个存储结构化和非结构化数据的存储池,用于支持数据分析和机器学习应用。 机器学习:大数据技术也广泛应用于机器学习领域,支持大规模数据的模型训练和预测分析。 流式处理:针对实时数据处理需求,流式处理技术(如Apache Kafka、Apache Flink)可以实时。

    node-v8.1.1.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v8.1.2.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    基于java的-160-springboot农机电招平台--LW-源码.zip

    提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

    小程序-58-童心党史小程序-源码.zip

    提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

    云计算基础课件—云安全dr.pptx

    云计算基础课件—云安全dr.pptx

    基于python开发的手写数字识别+源码(期末大作业&课程设计&项目开发)

    基于python开发的手写数字识别+源码,适合期末大作业、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于python开发的手写数字识别+源码,适合期末大作业、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于python开发的手写数字识别+源码,适合期末大作业、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于python开发的手写数字识别+源码,适合期末大作业、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~

    Zest是一款基于Spring的易于使用的单元测试工具(高分毕设).zip

    Java SSM项目是一种使用Java语言和SSM框架(Spring + Spring MVC + MyBatis)开发的Web应用程序。SSM是一种常用的Java开发框架组合,它结合了Spring框架、Spring MVC框架和MyBatis框架的优点,能够快速构建可靠、高效的企业级应用。 1. Spring框架:Spring是一个轻量级的Java开发框架,提供了丰富的功能和模块,用于开发企业级应用。它包括IoC(Inverse of Control,控制反转)容器、AOP(Aspect-Oriented Programming,面向切面编程)等特性,可以简化开发过程、提高代码的可维护性和可测试性。 2. Spring MVC框架:Spring MVC是基于Spring框架的Web框架,用于开发Web应用程序。它采用MVC(Model-View-Controller,模型-视图-控制器)的架构模式,将应用程序分为模型层、视图层和控制器层,提供了处理请求、渲染视图和管理流程的功能。 3. MyBatis框架:MyBatis是一个持久层框架,用于与数据库进行交互。它提供了一种将数据库操作与Java对象映射起来的方式,避免了手动编写繁琐的SQL语句,并提供了事务管理和缓存等功能,简化了数据库访问的过程

    数学建模模拟中的线性规划问题的数学模型.pptx

    数学模型算法

    基于python开发的口红色号识别程序+源码+开发文档+源码解析(毕业设计&课程设计&项目开发)

    基于python开发的口红色号识别程序+源码+开发文档+源码解析,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于python开发的口红色号识别程序+源码+开发文档+源码解析,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于python开发的口红色号识别程序+源码+开发文档+源码解析,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 项目简介: 口红色号识别器 选题: 基于图像处理、人脸识别和Flask构建的图片口红色号识别及商品推荐系统 功能: 根据上传的图片自动化图像处理后分析脸部特征,并判断其嘴部妆容状态,依托现有大数据分析,识别其口红色号,并推荐相关商品,通过Flask服务器框架返回到网页前端并动态呈现

    机械设计泵盖齿轮压装设备sw16可编辑非常好的设计图纸100%好用.zip

    机械设计泵盖齿轮压装设备sw16可编辑非常好的设计图纸100%好用.zip

Global site tag (gtag.js) - Google Analytics