较大数据文件的读取优化过程

用CFile类读取大文件 随着Windows 2000和XP的普及,现在的大文件越来越多,而VC6中MFC的CFile类只支持不大于4GB的文件, 原因在于CFile类中使用了32位整型来处理文件,32位数的范围是2的32次方(4GB),超过这个范围的文件CFile就管不了,微软.Net中VC7的CFile类支持大于4GB的文件,而.Net还不普及,开发桌面应用VC6还是首选,所以我们可以参照VC7写一个CFile的继承类 阅读详情

问题由来

1.解析一个体积较大(50MB,约50w行)的数据文件时,遇到了读取、解析花费的时间较长(debug下20+s,release下3s)速度较慢的问题。
2.用UE或者everedit等编辑软件打开这种文件基本上只要0.5s不到的时间。
因此尝试优化代码,提高效率,这里记录下过程。

测试平台

环境

win10x64 + VS2015

文件格式

数据文件分为三段:

第一段 230000 250000
第二段开始 1 115.853843 22.534526 -47.04851444
………….
第二段结束 230000 ………………………
第三段开始 1 3 2 96 1 0
………………………..
第三段结束 250000 ………………….

处理过程

基本方法

最基本的MFC读数据方式:CStdioFile打开文件-Readstring读取每行数据-查找分隔符-分割出字符串-转化为浮点数-保存到数组里去。

void CGRDFile::OpenStdioFile(CString sFileName)
{
    CStdioFile m_file; 
    m_file.Open(sFileName, CFile::modeRead);

    Vertex v;
    MeshIndex mesh;
    CString smn, sm, sn,sVertex, sN, sX, sY, sZ, sLeft, sMesh, s1Index, s2Index, s3Index;

    m_file.ReadString(smn);
    smn.Trim();

    sm = smn.Left(smn.Find(' '));
    sn = smn.Mid(smn.Find(' '), smn.GetLength() - 1);
    sn.Trim();

    m_grdFile.mn.m = _wtoi(sm);
    m_grdFile.mn.n = _wtoi(sn);
    //预分配数组大小
    m_grdFile.vVertex.reserve(m_grdFile.mn.m);
    m_grdFile.vMeshIndex.reserve(m_grdFile.mn.n);
    //第二段处理
    for (int i = 0; i < m_grdFile.mn.m; i++)
    {
        m_file.ReadString(sVertex);

        sVertex.Trim();
        sN = sVertex.Left(sVertex.Find(' '));
        sLeft = sVertex.Right(sVertex.GetLength() - sN.GetLength());
        sLeft.Trim();

        sX = sLeft.Left(sLeft.Find(' '));
        sLeft = sLeft.Right(sLeft.GetLength() - sX.GetLength());
        sLeft.Trim();

        sY = sLeft.Left(sLeft.Find(' '));
        sLeft = sLeft.Right(sLeft.GetLength() - sY.GetLength());
        sLeft.Trim();

        sZ = sLeft;

        v.nIndex = _wtoi(sN);
        v.x = _wtof(sX);
        v.y = _wtof(sY);
        v.z = _wtof(sZ);

        m_grdFile.vVertex.push_back(v);
        //第三段处理类似,不贴了
    }

经过测试上面的代码在release下读取完数据耗时2779.03ms在debug下耗时21122ms。

内存文件映射法

内存文件映射是一个提高大文件读取速度的方法。

void CGRDFile::OpenFile(CString sFileName)
{
    HANDLE hFile = CreateFile(
        sFileName,
        FILE_SHARE_READ ,
        0,
        0,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {

        return ;
    }
    HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (hFileMap == NULL)
    {

        return;
    }
    // 得到系统分配粒度  
    SYSTEM_INFO SysInfo;
    GetSystemInfo(&SysInfo);
    DWORD dwGran = SysInfo.dwAllocationGranularity;

    // 得到文件尺寸  
    DWORD dwFileSizeHigh;
    __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
    qwFileSize |= (((__int64)dwFileSizeHigh) << 32);



    // 偏移地址   
    __int64 qwFileOffset = 0;

    // 块大小 
    DWORD dwBlockBytes = 0;
    for (int i = 1; ; ++i)
    {
        if (qwFileSize <= i * dwGran)
        {
            dwBlockBytes = i * dwGran;
            break;
        }
    }
    if (qwFileOffset >= 0)
    {
        // 映射视图
        //如果在调用MapViewOfFile()的时候,dwNumberOfBytesToMap如果大于文件的size,那么该call就会失败,
        //并且error code是access denied。所以MapViewOfFile的最后一个参数不能用dwBlockSize而只能用qwFileSize。
        //MapViewOfFile的返回值不能是TCHAR*,因为UNICODE时解释成宽字符,会出错,只能用char*
        char *lpbMapAddress = (char *)MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, qwFileSize);
        if (lpbMapAddress == NULL)
        {           
            return;
        }
        CString stemp(lpbMapAddress);
        CString smn,sm, sn;

        smn = stemp.Left(stemp.Find('\n'));
        smn.Trim();
        sm = smn.Left(smn.Find(' '));
        sn = smn.Mid(smn.Find(' '), smn.GetLength() - 1);
        sn.Trim();

        m_grdFile.mn.m = _wtoi(sm);
        m_grdFile.mn.n = _wtoi(sn);
        //预分配数组大小
        m_grdFile.vVertex.reserve(m_grdFile.mn.m);
        m_grdFile.vMeshIndex.reserve(m_grdFile.mn.n);
        //顶点数组
        Vertex v;
        CString sVertex, sN, sX, sY, sZ, sLeft;

        int nStart, nEnd;
        nStart = stemp.Find('\n');
        nEnd = stemp.Find('\n', nStart + 1);
        for (int i = 0; i < m_grdFile.mn.m; i++)
        {
            sVertex = stemp.Mid(nStart+ 1, nEnd - nStart + 1);

            sVertex.Trim();
            sN = sVertex.Left(sVertex.Find(' '));
            sLeft = sVertex.Right(sVertex.GetLength() - sN.GetLength());
            sLeft.Trim();

            sX = sLeft.Left(sLeft.Find(' '));
            sLeft = sLeft.Right(sLeft.GetLength() - sX.GetLength());
            sLeft.Trim();

            sY = sLeft.Left(sLeft.Find(' '));
            sLeft = sLeft.Right(sLeft.GetLength() - sY.GetLength());
            sLeft.Trim();

            sZ = sLeft;

            v.nIndex = _wtoi(sN);
            v.x = _wtof(sX);
            v.y = _wtof(sY);
            v.z = _wtof(sZ);

            m_grdFile.vVertex.push_back(v);

            nStart = stemp.Find('\n', nEnd);
            nEnd = stemp.Find('\n', nStart + 1);
        }
        //网格数组
        CString sMesh, s1Index, s2Index, s3Index;
        MeshIndex mesh;
        for (int i = 0; i < m_grdFile.mn.n; i++)
        {
            sMesh = stemp.Mid(nStart + 1, nEnd - nStart + 1);

            sMesh.Trim();
            sN = sMesh.Left(sMesh.Find(' '));
            sLeft = sMesh.Right(sMesh.GetLength() - sN.GetLength());
            sLeft.Trim();

            s1Index = sLeft.Left(sLeft.Find(' '));
            sLeft = sLeft.Right(sLeft.GetLength() - s1Index.GetLength());
            sLeft.Trim();
            s1Index = sLeft.Left(sLeft.Find(' '));
            sLeft = sLeft.Right(sLeft.GetLength() - s1Index.GetLength());
            sLeft.Trim();

            s2Index = sLeft.Left(sLeft.Find(' '));
            sLeft = sLeft.Right(sLeft.GetLength() - s2Index.GetLength());
            sLeft.Trim();

            s3Index = sLeft.Left(sLeft.Find(' '));

            mesh.nIndex = _wtoi(sN);
            mesh.nV1Index = _wtoi(s1Index);
            mesh.nV2Index = _wtoi(s2Index);
            mesh.nV3Index = _wtoi(s3Index);

            m_grdFile.vMeshIndex.push_back(mesh);

            nStart = stemp.Find('\n', nEnd);
            nEnd = stemp.Find('\n', nStart + 1);
        }


        // 撤销文件映像
        UnmapViewOfFile(lpbMapAddress);
        // 关闭文件对象  
        CloseHandle(hFile);
    }



}

经过测试上面的代码在release下读取完数据耗时1437.46ms在debug下耗时13738ms。

再优化下

仔细观察了数据文件,每行占的字节数、每个值占得字节数其实是都是确定的,利用这个特点,可以避免循环里非常复杂耗时的字符串解析过程。

//内存文件映射部分代码不贴了,和上面一样
        Vertex v;
        MeshIndex mesh;
        CString smn, sm, sn, sVertex, sN, sX, sY, sZ, sLeft, sMesh, s1Index, s2Index, s3Index,sblank;
        smn = stemp.Left(MN_LINE_SIZE);
        smn.Trim();

        sm = smn.Mid(0, MN_M_CHAR_SIZE);
        sn = smn.Mid(MN_M_CHAR_SIZE, MN_N_CHAR_SIZE);

        m_grdFile.mn.m = _wtoi(sm);
        m_grdFile.mn.n = _wtoi(sn);
        //预分配数组大小
        m_grdFile.vVertex.reserve(m_grdFile.mn.m);
        m_grdFile.vMeshIndex.reserve(m_grdFile.mn.n);
        //顶点数组
        for (int i = 0; i < m_grdFile.mn.m; i++)
        {
            sVertex = stemp.Mid(MN_LINE_SIZE + i * VERTEX_LINE_SIZE, VERTEX_LINE_SIZE);
            sN = sVertex.Mid(0, VERTEX_N_CHAR_SIZE);
            sX = sVertex.Mid(VERTEX_N_CHAR_SIZE , VERTEX_X_CHAR_SIZE);
            sY = sVertex.Mid(VERTEX_N_CHAR_SIZE + VERTEX_X_CHAR_SIZE , VERTEX_Y_CHAR_SIZE);
            sZ = sVertex.Mid(VERTEX_N_CHAR_SIZE + VERTEX_X_CHAR_SIZE + VERTEX_Y_CHAR_SIZE, VERTEX_Z_CHAR_SIZE);

            v.nIndex = _wtoi(sN);
            v.x = _wtof(sX);
            v.y = _wtof(sY);
            v.z = _wtof(sZ);

            m_grdFile.vVertex.push_back(v);
        }
        //网格数组
        for (int i = 0; i < m_grdFile.mn.n; i++)
        {
            sMesh = stemp.Mid(MN_LINE_SIZE + m_grdFile.mn.m * VERTEX_LINE_SIZE + MESH_LINE_SIZE * i , 
                MESH_LINE_SIZE);
            sN = sMesh.Mid(0, MESH_N_CHAR_SIZE);
            s1Index = sMesh.Mid(MESH_N_CHAR_SIZE + MESH_NODEFINE_CHAR_SIZE , MESH_V1_CHAR_SIZE);
            s2Index = sMesh.Mid(MESH_N_CHAR_SIZE + MESH_NODEFINE_CHAR_SIZE + MESH_V1_CHAR_SIZE , MESH_V2_CHAR_SIZE);
            s3Index = sMesh.Mid(MESH_N_CHAR_SIZE + MESH_NODEFINE_CHAR_SIZE + MESH_V1_CHAR_SIZE + MESH_V2_CHAR_SIZE ,
                MESH_V3_CHAR_SIZE);

            mesh.nIndex = _wtoi(sN);
            mesh.nV1Index = _wtoi(s1Index);
            mesh.nV2Index = _wtoi(s2Index);
            mesh.nV3Index = _wtoi(s3Index);

            m_grdFile.vMeshIndex.push_back(mesh);

        }

经过测试上面的代码在release下读取完数据耗时642ms在debug下耗时4763ms。

性能比较

性能比较
从图上可以看到使用了内存文件映射后,读取性能有了较大提高,在release下解析的速度可以和UE之类的软件比较了。

文件读取性能优化 相比于每次读取一个字节或一个字符,这两个类可以一次读取多个字符,从而减少了I/O操作的次数,提高了效率。三、使用缓存在文件读取时,将读取到的数据存储在内存中,再从内存中获取数据,可以大幅提高文件读取性能。相比于传统的I/O操作,NIO提供了更加高效的文件读取方式。// 将文件分割成4个块,每个线程负责读取一个块的数据for (int i = 0;四、控制文件读取的块大小在文件读取时,可以通过控制读取的块大小来提高性能。根据文件的大小和读取的需求,合理选择块大小,可以在一定程度上优化文件读取性能。 阅读详情

相关推荐

Python 文件读取操作详解

open()函数:用来打开文件,返回文件对象。常用读取方法read():读取整个文件内容。readline():按行读取文件。:读取文件的所有行,返回一个列表。with语句:自动关闭文件,避免忘记关闭文件而造成资源泄漏。文件模式:根据需要选择合适的文件打开模式,如'r''w''a'等。优化读取:对于大文件,使用逐行读取或缓冲区来优化内存使用。异常处理:处理文件操作中的常见错误,如文件不存在或权限问题。

m0_62599305的博客 1244

如何优化大文件读取时的性能

1、如果你的应用是多线程的,并且文件可以被独立地划分为多个部分,那么可以使用多个线程或进程同时读取和处理文件的不同部分。1、不要一次性将整个文件加载到内存中,而是将其分割成多个较小的块(例如,每块1MB或更大),然后逐块读取和处理。1、使用性能监视工具(如JProfiler、VisualVM等)来监视你的应用的性能,并找出可能的瓶颈。2、确保你的系统有足够的RAM和适当的磁盘I/O带宽来支持大文件的读取。1、根据你的应用的需求,调整JVM的堆大小和其他相关参数。2、确保JVM有足够的内存来处理大文件。

DKPT的博客 1608

用CStdioFile读写文件

文档讲述了使用CStdioFile对文件进行读写的方法与代码

超大文件读取性能最佳方案

超大文件读取

qq_41737501的博客 749

C#---第十一课:对列表的各种操作,添加、删除、排序、列表长度、获取元素的索引、FirstOrDefault()、SingleOrDefault()、last()

1.添加元素 2.删除元素 (1)删除列表中指定的元素 (2)删除列表A中所有与列表B相交的元素---常规 (3)删除列表A中所有与列表B相交的元素---lambda 3.排序(正序、倒序) 4.获取列表的最大容量(长度)、已用容量 6.获取元素在列表中的位置(index) 7.判断某元素是否在列表List中 8.获取列表中第一个元素First() & FirstOrDefault() 9.获取列表中唯一元素Single() & SingleOrDefault() 10.获取列表中最后一个元素

Alien-Hu 3627

CAN 比特率和采样点计算

架构根据输入频率和比特率自动计算。

ldinvicible的专栏 787

Spark 大/小文件读取优化

问题描述 使用Spark写复杂SQL时,我们经常会遇到两个尴尬的情况: 表里的单个文件都很大,而且因为record比较小,所以单个文件的record数量巨大。 表里每个分区都有成百上千个小文件,单个文件可能只有几条记录 对于第一种情况,会导致我们没办法充分利用我们已有的核,所以速度很慢。 对于第二种情况,则会大量浪费调度时间。比如你有100万个文件,假设只有100个核,那么需要调度一万轮,每轮调度除了调度自身的消耗,还有很多额外的消耗。在没有shuffle的情况下,会导致写太多文件,本身...

mojolang 3582

汽车测试工程师工作准则

希望这份综合的工作规范能帮助你更好地规划职业发展。如果你对某个特定领域,比如自动驾驶测试或智能座舱测试,有进一步的兴趣,我很乐意提供更具体的信息。

qq_40844444的博客 453

cfile read 最大读取限制_Read文件一个字节实际会发生多大的磁盘IO?

作者:yanfei出处:https://segmentfault.com/a/1190000037728927先讲一个作者大约7年前我在某当时很火的一个应用分发创业公司的面试小插曲,该公司安排了一个刚工作1年多的一个同学来面我,聊到我们项目中的配置文件里写的一个开关,这位同学就跳出来说,你这个读文件啦,每个用户请求来了还得多一次的磁盘IO,性能肯定差。借由这个故事其实我发现了一个问题,虽然我们中的...

weixin_39992072的博客 466

JS自动化生成大疆WPML航线文件实战(附完整代码与云台控制解析)

本文详细介绍了如何利用JavaScript自动化生成大疆WPML航线文件,以解决手动规划航线效率低、易出错的问题。通过解析WPML文件结构,特别是云台控制等核心参数,并封装可复用的JS模板函数,开发者能够快速批量生成复杂航线,如倾斜摄影五向飞行,实现航测与巡检任务的标准化与高效执行。

weixin_29007243的博客 299

保姆级教程:在ROS2 Humble上搞定Livox Mid-360雷达,从IP配置到Rviz点云显示

本文提供在ROS2 Humble上配置Livox Mid-360激光雷达的完整教程,涵盖从IP设置到Rviz点云显示的全流程。详细解析硬件连接、驱动编译、配置文件优化及常见问题排查,帮助开发者快速搭建开发环境并实现高效点云可视化。

weixin_30955617的博客 184

较大数据文件的读取优化过程续

由来在上一篇博客(见参考1)中写了从基本到优化的过程,但仍然有可以优化的余地,这里记录下。问题文章1中根据固定行字节数优化读取速度有很大的弊端,就是需要解析的文件可能是不规则行长度的。可以有通用的按行解析的方法。当然前提仍然是基于内存映射文件。这里操作一个dat文件,共分n段,数据以空格分隔,行以\r\n分隔,大部分段平均每行大约10个双精度浮点数,小部分整数。共180w行,300MB大小。char

小猿一枚的专栏 1993

深度学习笔记(3)——kernel(内核)与filter(滤波器)

本文仅仅简单介绍了kernel和filter的概念,介绍了确定filter和kernel个数的方法。

cs111211的博客 7139

fltk动态分配数据显示图像

fltk中在控件上显示图像可以使用生成静态数组的方式,用的时候直接加载显示即可,但是这种方式对于只是开始时显示以后就不用的图像,静态数组不能随后释放,特别是在图像比较大时会占用大量的内存。 可以使用fluid将图像转换的静态数组转换为图像数据,在使用的时候读入,用完之后释放,这样就可以节省一部分资源。 下面是一个将fluid生成的图像数据静态数组转换为数据文件然后在使用时加载的例子。 ...

Chinamming的专栏 282

Opencv项目实战:20 单手识别数字0到5

今天要做的是单手识别数字0到5,通过在窗口展示,实时的展示相应的图片以及文字。在这里,我提供一下识别更多数字的方法(0-10)。首先最简便的是双手识别,完全不用更改代码,把图片处理好就行了;其次,就是按照最上面的那张图片,参数figureup是一个长度为5的列表[0,0,0,0,0],你可以参照着手势将其打印出来,然后将其用if条件判断。当然,在我们这边最常见的还是华北手势表示数字,大家按照自己的习惯来就行。

m0_62919535的博客 2877

STM32F103C8T6驱动DHT11温湿度传感器程序

本程序基于 STM32F103C8T6 微控制器,通过 GPIO 模拟单总线通信时序实现对 DHT11 温湿度传感器的数据采集,主机采用拉低数据线的方式触发传感器启动,并通过检测高电平持续时间区分逻辑“0”和“1”,依次读取湿度和温度的整数数据及校验位,经过校验后将有效结果存入缓存数组,实现对环境温湿度的周期性测量与处理。

加斯顿工程师的博客 201

Oracle 删除数据后释放数据文件所占磁盘空间(表空间降高水位)

测试的时候向数据库中插入了大量的数据,测试完成后删除了测试用户以及其全部数据,但是数据文件却没有缩小。经查阅资料之后发现这是 Oracle “高水位”所致,那么怎么把这些数据文件的大小降下来呢?解决办法如下: 概念: 表空间的相关知识请见这里,详细的介绍了 Oracle 数据库的存储结构。 高水位:High Water Mark (HWM),是段(Segment)的一个指标,界定了段(Segment)曾经配置过的 block 水位。 据说,随着数据的 insert,所使用段(Segment)的数据块

liujinwei2005的专栏 2698

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

优化Pytorch框架的数据加载过程

queue模块 参考链接:https://www.cnblogs.com/skiler/p/6977727.html https://blog.youkuaiyun.com/qq_41185868/article/details/80502072 queue模块是python自带的模块。实现了多生产者,多消费者的队列。当要求信息必须在多线程间安全交换,这个模块在线程编程时非常有用。Queue模块实现了所有要求...

winycg的博客 5071

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

【Java难点攻克】「大文件处理系列」让我们一起去挑战一下如何读取一个较大或者超大的数据文件

Java在JDK 1.4引入了ByteBuffer等NIO相关的类,使得 Java 程序员可以抛弃基于 Stream ,从而使用基于 Block 的方式读写文件,java io操作中通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理大文件,不过java nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高,本文会介绍其性能如此高的内部实现原理,分析一下到底是 FileChannel 快还是 MappedByteBuffer 块。

世界上并没有完美的程序,但是我们并不因此而沮丧,因为写程序就是一个不断追求完美的过程。 1952

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

用Python对体积较大的CSV文件进行比较的经验

用Python对体积较大的CSV文件进行比较的经验 用Python对体积较大的CSV文件进行比较的经验 » 进化的测试 | 进化的测试用Python对体积较大的CSV文件进行比较的经验python Add comments八 032010 最近的工作总是跟数据...

438

关于 3.0 和 2.0 的数据文件差异以及性能优化思路

如果需要对数据库性能优化,了解数据文件的存储方式和工作原理是必要的。

涛思数据 691

Oracle 删除数据后释放数据文件所占磁盘空间

转载:http://www.cnblogs.com/gaoyuchuanIT/articles/4151231.html 测试的时候向数据库中插入了大量的数据,测试完成后删除了测试用户以及其全部数据,但是数据文件却没有缩小。经查阅资料之后发现这是 Oracle “高水位”所致,那么怎么把这些数据文件的大小降下来呢?解决办法如下: 概念: 表空间的相关知识请见这里,详细的介绍了 Ora

Quincylk的专栏 3664

Oracle 释放(恢复)数据文件大小

今天在测试数据量占用磁盘空间大小的时候遇到问题,当插入大量数据进入数据库后,数据文件变大,但是删除以后,数据文件大小不变,这里有解决办法。 经查阅资料之后发现这是 Oracle “高水位”所致,那么怎么把这些数据文件的大小降下来呢? 概念: 表空间的相关知识请见这里http://www.cnblogs.com/fnng/archive/2012/08/12/2634485.html,

java 847

读书笔记--python数据可视化--003_读取定宽数据文件

如果文件中的格式是以定宽的格式存储的,那么可以通过格式来匹配并提取数据。一种方法是逐行读取数据,然后利用字符串的操作来提取数据。这种方法比较直接,如果在性能不做要求的话是首选操作。另外一种是利用python的struct模块来提升性能,并能读取较大的数据(比如几百兆的数据),因为这个模块使用c语言而不是python实现的。struct模块是python标准库的一部分,因此不需安装就能够使用。#-*-

HH的专栏 1063
上一篇: MFC+Direct2D以屏幕中心为中心缩放的实现
下一篇: D2D画箭头的例子
洗洗睡去
博客等级 码龄19年 277粉丝 90原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值