我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android匿名内存怎么实现

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android匿名内存怎么实现

本篇内容主要讲解“Android匿名内存怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android匿名内存怎么实现”吧!

    Android 匿名内存解析

    有了binder机制为什么还需要匿名内存来实现IPC呢?我觉得很大的原因就是binder传输是有大小限制的,不说应用层的限制。在驱动中binder的传输大小被限制在了4M,分享一张图片可能就超过了这个限制。匿名内存的主要解决思路就是通过binder传输文件描述符,使得两个进程都能访问同一个地址来实现共享。

    MemoryFile使用

    在平常开发中android提供了MemoryFile来实现匿名内存。看下最简单的实现。

    Service端

    const val GET_ASH_MEMORY = 1000class MyService : Service() {    val ashData = "AshDemo".toByteArray()    override fun onBind(intent: Intent): IBinder {        return object : Binder() {            override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {                when(code){                    GET_ASH_MEMORY->{//收到客户端请求的时候会烦                        val descriptor = createMemoryFile()                        reply?.writeParcelable(descriptor, 0)                        reply?.writeInt(ashData.size)                        return true                    }                    else->{                        return super.onTransact(code, data, reply, flags)                    }                }            }        }    }    private fun createMemoryFile(): ParcelFileDescriptor? {        val file = MemoryFile("AshFile", 1024)//创建MemoryFile        val descriptorMethod = file.javaClass.getDeclaredMethod("getFileDescriptor")        val fd=descriptorMethod.invoke(file)//反射拿到fd        file.writeBytes(ashData, 0, 0,ashData.size)//写入字符串        return ParcelFileDescriptor.dup(fd as FileDescriptor?)//返回一个封装的fd    }}

    Server的功能很简单收到GET_ASH_MEMORY请求的时候创建一个MemoryFile,往里写入一个字符串的byte数组,然后将fd和字符长度写入reply中返回给客户端。

    Client端

    class MainActivity : AppCompatActivity() {    val connect = object :ServiceConnection{        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {            val reply = Parcel.obtain()            val sendData = Parcel.obtain()            service?.transact(GET_ASH_MEMORY, sendData, reply, 0)//传输信号GET_ASH_MEMORY            val pfd = reply.readParcelable<ParcelFileDescriptor>(javaClass.classLoader)            val descriptor = pfd?.fileDescriptor//拿到fd            val size = reply.readInt()//拿到长度            val input = FileInputStream(descriptor)            val bytes = input.readBytes()            val message = String(bytes, 0, size, Charsets.UTF_8)//生成string            Toast.makeText(this@MainActivity,message,Toast.LENGTH_SHORT).show()        }        override fun onServiceDisconnected(name: ComponentName?) {        }    }    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        findViewById<TextView>(R.id.intent).setOnClickListener {          //启动服务            bindService(Intent(this,MyService::class.java),connect, Context.BIND_AUTO_CREATE)        }    }}

    客户端也很简单,启动服务,发送一个获取MemoryFile的请求,然后通过reply拿到fd和长度,用FileInputStream读取fd中的内容,最后通过toast可以验证这个message已经拿到了。

    AshMemory 创建原理

        public MemoryFile(String name, int length) throws IOException {        try {            mSharedMemory = SharedMemory.create(name, length);            mMapping = mSharedMemory.mapReadWrite();        } catch (ErrnoException ex) {            ex.rethrowAsIOException();        }    }

    MemoryFile就是对SharedMemory的一层封装,具体的工能都是SharedMemory实现的。看SharedMemory的实现。

        public static @NonNull SharedMemory create(@Nullable String name, int size)            throws ErrnoException {        if (size <= 0) {            throw new IllegalArgumentException("Size must be greater than zero");        }        return new SharedMemory(nCreate(name, size));    }  private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;

    通过一个JNI获得fd,从这里可以推断出java层也只是一个封装,拿到的已经是创建好的fd。

    //frameworks/base/core/jni/android_os_SharedMemory.cppjobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) {    const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr;    int fd = ashmem_create_region(name, size);//创建匿名内存块    int err = fd < 0 ? errno : 0;    if (name) {        env->ReleaseStringUTFChars(jname, name);    }    if (fd < 0) {        jniThrowErrnoException(env, "SharedMemory_create", err);        return nullptr;    }    jobject jifd = jniCreateFileDescriptor(env, fd);//创建java fd返回    if (jifd == nullptr) {        close(fd);    }    return jifd;}

    通过cutils中的ashmem_create_region函数实现的创建

    //system/core/libcutils/ashmem-dev.cppint ashmem_create_region(const char *name, size_t size){    int ret, save_errno;    if (has_memfd_support()) {//老版本兼容用        return memfd_create_region(name ? name : "none", size);    }    int fd = __ashmem_open();//打开Ashmem驱动    if (fd < 0) {        return fd;    }    if (name) {        char buf[ASHMEM_NAME_LEN] = {0};        strlcpy(buf, name, sizeof(buf));        ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf));//通过ioctl设置名字        if (ret < 0) {            goto error;        }    }    ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));//通过ioctl设置大小    if (ret < 0) {        goto error;    }    return fd;error:    save_errno = errno;    close(fd);    errno = save_errno;    return ret;}

    标准的驱动交互操作

    open打开驱动

    通过ioctl与驱动进行交互

    下面看下open的流程

    static int __ashmem_open(){    int fd;    pthread_mutex_lock(&__ashmem_lock);    fd = __ashmem_open_locked();    pthread_mutex_unlock(&__ashmem_lock);    return fd;}static int __ashmem_open_locked(){    static const std::string ashmem_device_path = get_ashmem_device_path();//拿到Ashmem驱动路径    if (ashmem_device_path.empty()) {        return -1;    }    int fd = TEMP_FAILURE_RETRY(open(ashmem_device_path.c_str(), O_RDWR | O_CLOEXEC));    return fd;}

    回到MemoryFile的构造函数中,拿到了驱动的fd之后调用了mapReadWrite

        public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {        return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);    } public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {        checkOpen();        validateProt(prot);        if (offset < 0) {            throw new IllegalArgumentException("Offset must be >= 0");        }        if (length <= 0) {            throw new IllegalArgumentException("Length must be > 0");        }        if (offset + length > mSize) {            throw new IllegalArgumentException("offset + length must not exceed getSize()");        }        long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);//调用了系统的mmap        boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;        Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());        return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);    }

    到这里就有一个疑问,Linux就有共享内存,android为什么要自己搞一套,只能看下Ashmemory驱动的实现了。

    驱动第一步看init和file_operations

    static int __init ashmem_init(void){    int ret = -ENOMEM;    ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",                           sizeof(struct ashmem_area),                           0, 0, NULL);//创建    if (!ashmem_area_cachep) {        pr_err("failed to create slab cache\n");        goto out;    }    ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",                        sizeof(struct ashmem_range),                        0, SLAB_RECLAIM_ACCOUNT, NULL);//创建    if (!ashmem_range_cachep) {        pr_err("failed to create slab cache\n");        goto out_free1;    }    ret = misc_register(&ashmem_misc);//注册为了一个misc设备    ........    return ret;}

    创建了两个内存分配器ashmem_area_cachep和ashmem_range_cachep用于分配ashmem_area和ashmem_range

    //common/drivers/staging/android/ashmem.cstatic const struct file_operations ashmem_fops = {    .owner = THIS_MODULE,    .open = ashmem_open,    .release = ashmem_release,    .read_iter = ashmem_read_iter,    .llseek = ashmem_llseek,    .mmap = ashmem_mmap,    .unlocked_ioctl = ashmem_ioctl,#ifdef CONFIG_COMPAT    .compat_ioctl = compat_ashmem_ioctl,#endif#ifdef CONFIG_PROC_FS    .show_fdinfo = ashmem_show_fdinfo,#endif};

    open调用的就是ashmem_open

    static int ashmem_open(struct inode *inode, struct file *file){    struct ashmem_area *asma;    int ret;    ret = generic_file_open(inode, file);    if (ret)        return ret;    asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);//分配一个ashmem_area    if (!asma)        return -ENOMEM;    INIT_LIST_HEAD(&asma->unpinned_list);//初始化unpinned_list    memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);//初始化一个名字    asma->prot_mask = PROT_MASK;    file->private_data = asma;    return 0;}

    ioctl设置名字和长度调用的就是ashmem_ioctl

    static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg){    struct ashmem_area *asma = file->private_data;    long ret = -ENOTTY;    switch (cmd) {    case ASHMEM_SET_NAME:        ret = set_name(asma, (void __user *)arg);        break;    case ASHMEM_SET_SIZE:        ret = -EINVAL;        mutex_lock(&ashmem_mutex);        if (!asma->file) {            ret = 0;            asma->size = (size_t)arg;        }        mutex_unlock(&ashmem_mutex);        break;    }  ........  }

    实现也都很简单就是改变了一下asma里的值。接下来就是重点mmap了,具体是怎么分配内存的。

    static int ashmem_mmap(struct file *file, struct vm_area_struct *vma){    static struct file_operations vmfile_fops;    struct ashmem_area *asma = file->private_data;    int ret = 0;    mutex_lock(&ashmem_mutex);        if (!asma->size) {//判断设置了size        ret = -EINVAL;        goto out;    }        if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {//判断大小是否超过了虚拟内存        ret = -EINVAL;        goto out;    }        if ((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &        calc_vm_prot_bits(PROT_MASK, 0)) {//权限判断        ret = -EPERM;        goto out;    }    vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);    if (!asma->file) {//是否创建过临时文件,没创建过进入        char *name = ASHMEM_NAME_DEF;        struct file *vmfile;        struct inode *inode;        if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')            name = asma->name;                vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);//调用linux函数在tmpfs中创建临时文件        if (IS_ERR(vmfile)) {            ret = PTR_ERR(vmfile);            goto out;        }        vmfile->f_mode |= FMODE_LSEEK;        inode = file_inode(vmfile);        lockdep_set_class(&inode->i_rwsem, &backing_shmem_inode_class);        asma->file = vmfile;                if (!vmfile_fops.mmap) {//设置了临时文件的文件操作,防止有其他程序mmap这个临时文件            vmfile_fops = *vmfile->f_op;            vmfile_fops.mmap = ashmem_vmfile_mmap;            vmfile_fops.get_unmapped_area =                    ashmem_vmfile_get_unmapped_area;        }        vmfile->f_op = &vmfile_fops;    }    get_file(asma->file);        if (vma->vm_flags & VM_SHARED) {//这块内存是不是需要跨进程        ret = shmem_zero_setup(vma);//设置文件        if (ret) {            fput(asma->file);            goto out;        }    } else {            vma_set_anonymous(vma);    }    vma_set_file(vma, asma->file);        fput(asma->file);out:    mutex_unlock(&ashmem_mutex);    return ret;}

    函数很长,但是思路还是很清晰的。创建临时文件,设置文件操作。其中调用的都是linux的系统函数了,看真正设置的shmem_zero_setup函数

    int shmem_zero_setup(struct vm_area_struct *vma){    struct file *file;    loff_t size = vma->vm_end - vma->vm_start;        file = shmem_kernel_file_setup("dev/zero", size, vma->vm_flags);    if (IS_ERR(file))        return PTR_ERR(file);    if (vma->vm_file)        fput(vma->vm_file);    vma->vm_file = file;    vma->vm_ops = &shmem_vm_ops;//很重要的操作将这块虚拟内存的vm_ops设置为shmem_vm_ops    if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) &&            ((vma->vm_start + ~HPAGE_PMD_MASK) & HPAGE_PMD_MASK) <            (vma->vm_end & HPAGE_PMD_MASK)) {        khugepaged_enter(vma, vma->vm_flags);    }    return 0;}static const struct vm_operations_struct shmem_vm_ops = {    .fault      = shmem_fault,//Linux的共享内存实现的基础    .map_pages  = filemap_map_pages,#ifdef CONFIG_NUMA    .set_policy     = shmem_set_policy,    .get_policy     = shmem_get_policy,#endif};

    到这里共享内存的初始化就结束了。

    AshMemory 读写

    //frameworks/base/core/java/android/os/MemoryFile.javapublic void writeBytes(byte[] buffer, int class="lazy" data-srcOffset, int destOffset, int count)            throws IOException {        beginAccess();        try {            mMapping.position(destOffset);            mMapping.put(buffer, class="lazy" data-srcOffset, count);        } finally {            endAccess();        }    }    private void beginAccess() throws IOException {        checkActive();        if (mAllowPurging) {            if (native_pin(mSharedMemory.getFileDescriptor(), true)) {                throw new IOException("MemoryFile has been purged");            }        }    }    private void endAccess() throws IOException {        if (mAllowPurging) {            native_pin(mSharedMemory.getFileDescriptor(), false);        }    }

    其中beginAccess和endAccess是对应的。调用的都是native_pin是一个native函数,一个参数是true一个是false。pin的作用就是锁住这块内存不被系统回收,当不使用的时候就解锁。

    static jboolean android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor,        jboolean pin) {    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);    int result = (pin ? ashmem_pin_region(fd, 0, 0) : ashmem_unpin_region(fd, 0, 0));    if (result < 0) {        jniThrowException(env, "java/io/IOException", NULL);    }    return result == ASHMEM_WAS_PURGED;}

    调用的ashmem_pin_region和ashmem_unpin_region来实现解锁和解锁。实现还是在ashmem-dev.cpp

    //system/core/libcutils/ashmem-dev.cppint ashmem_pin_region(int fd, size_t offset, size_t len){    .......    ashmem_pin pin = { static_cast<uint32_t>(offset), static_cast<uint32_t>(len) };    return __ashmem_check_failure(fd, TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin)));}

    通过的也是ioclt通知的驱动。加锁的细节就不展开了。具体的写入就是利用linux的共享内存机制实现的共享。

    Linux共享机制简介

    共享简单的实现方式就是通过mmap同一个文件来实现。但是真实文件的读写速度实在是太慢了,所以利用tmpfs这个虚拟文件系统,创建了一个虚拟文件来读写。同时这块虚拟内存在上面也写到重写了vm_ops。当有进程操作这个虚拟内存的时候会触发缺页错误,接着会去查找Page缓存,由于是第一次所以没有缓存,读取物理内存,同时加入Page缓存,当第二个进程进来的时也触发缺页错误时就能找到Page缓存了,那么他们操作的就是同一块物理内存了。

    到此,相信大家对“Android匿名内存怎么实现”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    Android匿名内存怎么实现

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    Android匿名内存怎么实现

    本篇内容主要讲解“Android匿名内存怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android匿名内存怎么实现”吧!Android 匿名内存解析有了binder机制为什么还需要匿名
    2023-07-05

    Android匿名内存深入分析

    这篇文章主要为大家介绍了Android匿名内存深入分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-15

    怎么用匿名内部类实现Java 同步回调

    本篇内容主要讲解“怎么用匿名内部类实现Java 同步回调”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么用匿名内部类实现Java 同步回调”吧!在一个应用系统中,不论使用何种编程语言,模块之间
    2023-06-16

    android profiler内存分析怎么实现

    要在Android Profiler中进行内存分析,可以按照以下步骤进行操作:打开Android Studio,并打开要分析的项目。在Android Studio的工具栏中,点击"Android Profiler"按钮以打开Android
    2023-10-24

    JAVA匿名内部类怎么用

    这篇文章主要介绍了JAVA匿名内部类怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1.前言匿名内部类在我们JAVA程序员的日常工作中经常要用到,但是很多时候也只是照本宣
    2023-06-20

    Proftpf中怎么实现匿名登录

    Proftpf中怎么实现匿名登录,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。ProFTPD是继Wu-FTP之后最为流行的FTP服务器软件。我经常用Proftpd,想匿名登
    2023-06-16

    如何在Java2中实现匿名内部类

    这篇文章给大家分享的是有关如何在Java2中实现匿名内部类的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。/**知识点: 匿名内部类题目: 摩托车上装有警报器,当有人碰到摩托车时, 警报器发出报警响声思路:1. 抽
    2023-06-02

    Java匿名内部类怎么使用

    这篇文章主要介绍“Java匿名内部类怎么使用”,在日常操作中,相信很多人在Java匿名内部类怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java匿名内部类怎么使用”的疑惑有所帮助!接下来,请跟着小编
    2023-06-22

    C#实现匿名的方法是什么

    本篇内容介绍了“C#实现匿名的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!C#匿名方法允许我们定义委托对象可以接受的代码块。这个
    2023-06-17

    java匿名内部类怎么定义和使用

    在Java中,匿名内部类是一种没有显式定义类的方式,直接在代码中创建并使用类的一种方式。匿名内部类通常用于创建只需要使用一次的类对象。匿名内部类的定义和使用步骤如下:创建一个需要使用匿名内部类的接口或者抽象类。例如,创建一个接口Greet
    java匿名内部类怎么定义和使用
    2024-02-29

    JVM堆外内存怎么实现

    这篇“JVM堆外内存怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“JVM堆外内存怎么实现”文章吧。概述广义的堆外内存
    2023-06-29

    C++内存管理中简易内存池怎么实现

    这篇文章主要介绍“C++内存管理中简易内存池怎么实现”,在日常操作中,相信很多人在C++内存管理中简易内存池怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++内存管理中简易内存池怎么实现”的疑惑有所
    2023-06-22

    C++中怎么实现共享内存

    C++中怎么实现共享内存,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。初次使用C++标准库实现共享内存的管理时,Vector每次分配内存个数不固定,回收也不固定,这样的话,程序
    2023-06-17

    C++中怎么实现内存对齐

    这期内容当中小编将会给大家带来有关C++中怎么实现内存对齐,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一、为什么会有C++内存对齐以下内容节选自《Intel Architecture 32 Manual
    2023-06-17

    C++中怎么实现内存分配

    这篇文章给大家介绍C++中怎么实现内存分配,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。C++内存分配方式总共有三种:(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如
    2023-06-17

    Python中怎么实现内存管理

    这篇文章将为大家详细讲解有关Python中怎么实现内存管理,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。对象的内存使用赋值语句是语言最常见的功能了。但即使是最简单的赋值语句,也可以很有内涵。
    2023-06-15

    golang内存数据库怎么实现

    在Golang中实现一个内存数据库可以通过以下步骤:定义数据库结构:首先,你需要确定数据库的结构,即定义每个表的字段以及数据类型。可以使用结构体来表示每个表的行。type User struct {ID intName stringAg
    2023-10-23

    Linux中怎么实现共享内存

    Linux中怎么实现共享内存,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一、概念共享内存是被多个进程共享的一部分物理内存,是进程间共享数据的最快的一种方法。
    2023-06-12

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录