Java日看一类(2)之IO包中的BufferedInputStream类

java中BufferedInputStream缓存为什么使IO效率高? 背景     最近在看socket编程,这里面需要用到stream进行数据的读取和写入。于是代码中用到了BufferedInputStream提高读的效率。所以就一直在想为什么加上buffer就能提高效率了,加上之前面试的时候曾有面试官问到过这个问题,当时没有回答上来,并且就上网搜索了一下,发现有个文章是为什么缓存能提高io效率,这个文章中说是因为缓存可以让DMA来处理io流,后面我一直都是这么认为的,今天在网上搜索发现即使你用的是inputstream来读也会用到D 阅读详情

类的继承和包的导入

BufferedInputStream类引用了一个其他类

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

同时BufferedInputStream类本身继承FilterInputStream

(以上类在后续文章中讲到后会贴链接,本篇文章不做过多解释)



理解一个类的作用最方便快捷的的方式就是查看其类开头的注释:

/**
 * A <code>BufferedInputStream</code> adds
 * functionality to another input stream-namely,
 * the ability to buffer the input and to
 * support the <code>mark</code> and <code>reset</code>
 * methods. When  the <code>BufferedInputStream</code>
 * is created, an internal buffer array is
 * created. As bytes  from the stream are read
 * or skipped, the internal buffer is refilled
 * as necessary  from the contained input stream,
 * many bytes at a time. The <code>mark</code>
 * operation  remembers a point in the input
 * stream and the <code>reset</code> operation
 * causes all the  bytes read since the most
 * recent <code>mark</code> operation to be
 * reread before new bytes are  taken from
 * the contained input stream.
 *
 * @author  Arthur van Hoff
 * @since   JDK1.0
 */

大意如下:

BufferedInputStream类是对其他输入流添加了一个缓冲读入的功能,并且拥有“标记”和“重置”方法。

当创建一个BufferedInputStream实例时,会创建一个内置的缓冲数组。

从输入流中读取字节或跳过字节时(字节可为复数),内部缓冲区从其所控制的输入流中再次读入字节是必要的,可一次性读取多个字节。

“标记”操作是在输入流(缓冲区)中设置记忆点,“重置”操作则是从最近的“标记”点开始重新读取(类似sql的回滚)。



在BufferedInputStream中声明了八个变量及对象:

默认的缓冲区大小

private static int DEFAULT_BUFFER_SIZE = 8192;

缓冲区最大值

private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

内置的缓冲数组

protected volatile byte buf[];

内置缓冲数组的原子更新器(所引入包的内容)

private static final
    AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
    AtomicReferenceFieldUpdater.newUpdater
    (BufferedInputStream.class,  byte[].class, "buf");

当前缓冲区的有效字节数

protected int count;

当前缓冲区的读取位置

protected int pos;

当前缓冲区的标记位置(-1为空)

protected int markpos = -1;

标记的最大值(实际为保存标记后所能容纳的最大数组长度)

protected int marklimit;

共有十四个方法:

获取输入流(流开启状态才可获取)

private InputStream getInIfOpen() throws IOException {
    InputStream input = in;
    if (input == null)
        throw new IOException("Stream closed");
    return input;
}

获取缓冲流,返回缓冲数组

private byte[] getBufIfOpen() throws IOException {
    byte[] buffer = buf;
    if (buffer == null)
        throw new IOException("Stream closed");
    return buffer;
}

构造函数(创建一个默认缓冲空间的实例)

public BufferedInputStream(InputStream in) {
    this(in, DEFAULT_BUFFER_SIZE);
}

构造函数(创建一个自定义大小缓冲空间的实例)

public BufferedInputStream(InputStream in, int size) {
    super(in);
    if (size <= 0) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}

从输入流装填入缓冲区,首先判断是否存在标记位,如果不存在标记位先将当前位置指向0,然后重置有效长度位(置为0),从输入流中读满数据,然后将有效长度位置于数组末尾。若不存在标记则分五种情况考虑,当当前位置大于超出数组范围时,有四种情况:标记位非数组头、数组长度大于标记最大值、数组长度大于最大缓冲区大小和其他情况。标记位非数组头时,将标记位移至数组头,当前位置相对标记位置同样移动,然后读满缓冲区(同上);当数组长度大于标记最大值时,将标记位置空,当前位置置0,然后读满缓冲区;当数组长度大于最大长度时,抛出内存溢出;其他情况时,设置变量nsz为当前位置的2倍和最大长度之中的较小值,当nsz大于标记最大值,nsz指向标记最大值,将当前的缓冲数组头到当前位置的数据拷贝至大小为标记最大值的新数组中,将buffer设为新数组的引用。最后一种情况是直接填充缓冲区即可。

private void fill() throws IOException {
    byte[] buffer = getBufIfOpen();
    if (markpos < 0)
        pos = 0;            /* no mark: throw away the buffer */
    else if (pos >= buffer.length)  /* no room left in buffer */
        if (markpos > 0) {  /* can throw away early part of the buffer */
            int sz = pos - markpos;
            System.arraycopy(buffer, markpos, buffer, 0, sz);
            pos = sz;
            markpos = 0;
        } else if (buffer.length >= marklimit) {
            markpos = -1;   /* buffer got too big, invalidate mark */
            pos = 0;        /* drop buffer contents */
        } else if (buffer.length >= MAX_BUFFER_SIZE) {
            throw new OutOfMemoryError("Required array size too large");
        } else {            /* grow buffer */
            int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                    pos * 2 : MAX_BUFFER_SIZE;
            if (nsz > marklimit)
                nsz = marklimit;
            byte nbuf[] = new byte[nsz];
            System.arraycopy(buffer, 0, nbuf, 0, pos);
            if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                // Can't replace buf if there was an async close.
                // Note: This would need to be changed if fill()
                // is ever made accessible to multiple threads.
                // But for now, the only way CAS can fail is via close.
                // assert buf == null;
                throw new IOException("Stream closed");
            }
            buffer = nbuf;
        }
    count = pos;
    int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
    if (n > 0)
        count = n + pos;
}

读取缓冲区中数据,返回buf中第pos位的二进制码,并且pos位前移。如果pos位超过缓冲区的有效数据位,则进行缓冲区装填

public synchronized int read() throws IOException {
    if (pos >= count) {
        fill();
        if (pos >= count)
            return -1;
    }
    return getBufIfOpen()[pos++] & 0xff;
}

将缓冲数组复制到新的字节数组数组中(off为起始位置,len为写入长度),返回值为实际拷贝的长度。如果当前位置超出缓冲区有效位置,进一层if。在一层if下若需拷贝长度大于缓冲数组长度且不存在标记点时,直接从输入流中拷贝数据并返回,如果不满足条件则进行缓冲区装填,并重新判断当前位置与有效位置的位置关系,如果标记点仍在有效范围外,则程序错误,返回-1,否则出一层if。判断缓冲区的实际有效长度(有效标记点到当前位置的长度)和len的大小,写入较小值cnt的位数,pos前移cnt位并返回cnt。

private int read1(byte[] b, int off, int len) throws IOException {
    int avail = count - pos;
    if (avail <= 0) {
        /* If the requested length is at least as large as the buffer, and
           if there is no mark/reset activity, do not bother to copy the
           bytes into the local buffer.  In this way buffered streams will
           cascade harmlessly. */
        if (len >= getBufIfOpen().length && markpos < 0) {
            return getInIfOpen().read(b, off, len);
        }
        fill();
        avail = count - pos;
        if (avail <= 0) return -1;
    }
    int cnt = (avail < len) ? avail : len;
    System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
    pos += cnt;
    return cnt;
}

使用逻辑同上。工作流程:首先判定流是否关闭(buf是否被初始化),若off或len或off+len或目标数组长度与off+len的差值小于0则抛出数组越界错误,或者当需要拷贝的长度为0时直接返回。接下来设置一个判定位n,进循环。首先执行一次read1函数,判定read1的返回值是否小于等于0(即实际拷贝的长度是否为非正),如若小于等于0,进行三元运算,n为0是返回read1的返回值,n不为0则返回n,结束判定。修改n的数值,n为每次read1执行的累加和,判定若n大于需拷贝长度,直接返回n。开启新输入流(实际仍为本实例操控的输入流),若改输入流不为空且流中下个字节不可读则返回n。

public synchronized int read(byte b[], int off, int len)
    throws IOException
{
    getBufIfOpen(); // Check for closed stream
    if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return 0;
    }

    int n = 0;
    for (;;) {
        int nread = read1(b, off + n, len - n);
        if (nread <= 0)
            return (n == 0) ? nread : n;
        n += nread;
        if (n >= len)
            return n;
        // if not closed but no bytes available, return
        InputStream input = in;
        if (input != null && input.available() <= 0)
            return n;
    }
}

输入为预计跳过的字节数,返回值为实际跳过的字节数

public synchronized long skip(long n) throws IOException {
    getBufIfOpen(); // Check for closed stream
    if (n <= 0) {
        return 0;
    }
    long avail = count - pos;

    if (avail <= 0) {
        // If no mark position set then don't keep in buffer
        if (markpos <0)
            return getInIfOpen().skip(n);

        // Fill in buffer to save bytes for reset
        fill();
        avail = count - pos;
        if (avail <= 0)
            return 0;
    }

    long skipped = (avail < n) ? avail : n;
    pos += skipped;
    return skipped;
}

返回下次最多可以读取的字节数

public synchronized int available() throws IOException {
    int n = count - pos;
    int avail = getInIfOpen().available();
    return n > (Integer.MAX_VALUE - avail)
                ? Integer.MAX_VALUE
                : n + avail;
}

设置标记,记录当前位置

public synchronized void mark(int readlimit) {
    marklimit = readlimit;
    markpos = pos;
}

数据回滚,回滚至上个mark点开始读取

public synchronized void reset() throws IOException {
    getBufIfOpen(); // Cause exception if closed
    if (markpos < 0)
        throw new IOException("Resetting to invalid mark");
    pos = markpos;
}

恒真判定,表示一种属性:该类对象支持mark操作

public boolean markSupported() {
    return true;
}

关闭输入流(当缓冲区不再更新,数据流全部读取入缓冲区后)

public void close() throws IOException {
    byte[] buffer;
    while ( (buffer = buf) != null) {
        if (bufUpdater.compareAndSet(this, buffer, null)) {
            InputStream input = in;
            in = null;
            if (input != null)
                input.close();
            return;
        }
        // Else retry in case a new buf was CASed in fill()
    }
}


BufferedInputStream类大体结构如下:上接InputStream,中间为缓冲区,下部对接其他对象或方法使其可以从缓冲区中读取数据。

作为较常使用的流读入类,熟悉BufferedInputStream是必要的。

需要注意java在函数返回值时返回的是引用,在数组直接‘=’时也是引用

深入理解Java I/O流之BufferedInputStream类详解 在 Java 开发中,IO 操作是十分常见且重要的一个内容。在 IO 操作中,类是一个十分重要的类,它可以提供缓冲和流的级联两个功能,使得读取操作变得更加高效,提供了一定的性能优化。本文将对类进行详细介绍。本文将从类的定义入手,介绍其主要的功能、使用场景和优缺点,然后从源代码解析、应用场景案例和类代码方法介绍等方面深入探讨类的内部机制和使用方法,并提供测试用例对类进行实际测试。最后对全文内容进行总结,帮助读者更好地理解类。类继承自。 阅读详情

相关推荐

揭秘个人开发者接支付宝的‘野路子’:不用企业资质,用Python模拟扫码转账与回调监听

本文探讨了个人开发者如何通过Python模拟支付宝扫码转账与回调监听的技术实现,解析了支付宝URL协议格式,并设计了本地化的支付状态监听方案。文章强调该方法仅用于技术研究,实际应用需遵守支付宝官方接口和金融合规要求。

weixin_30736301的博客 409

BufferedInputStream类详解

当创建BufferedInputStream时,将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节。 mark操作会记住输入流中的一点,并且reset操作会导致从最近的mark操作之后读取的所有字节在从包含的输入流中取出新的字节之前重新读取。 BufferedInputStream 的父类是FilterInputStream FilterInputStream的父类是InputStream(字节输入流的超类) 相对于 FileInputS.

布懂同学开始提问题 1万+

从CD4051到现代模拟开关:40年芯片演进史与选型避坑指南

本文回顾了从CD4051到现代模拟开关的40年技术演进,详细对比了导通电阻、带宽等关键参数的显著提升。通过实际案例和选型矩阵,揭示现代模拟开关在精度、功耗和集成度方面的优势,并提供高精度测量、电池供电等场景的选型策略与避坑指南,帮助工程师优化设计。

weixin_42543046的博客 154

BufferedInputStream类的详解(画图分析)

BufferedInputStream 缓冲字节输入流 类的关系图: 缓冲流,那何为缓冲呢? 其实就是在内存中开辟一块了缓冲区,因为内存的读写速度比硬盘要高得多,因此它可以做到提升读取速率的功能 原理图 8 ...

qq_44828283的博客 1134

手把手教你用开源EtherCAT协议栈SOEM开发从站设备(附避坑指南)

本文详细介绍了如何使用开源EtherCAT从站协议栈SOES进行嵌入式设备开发。内容涵盖从环境搭建、硬件准备到核心的对象字典与PDO映射配置,并重点分享了主循环集成、状态机处理以及实战调试中的常见问题与避坑指南,旨在帮助开发者高效实现设备接入工业现场总线网络。

linux的博客 631

仿人型机械臂逆运动学避坑指南:当末端三关节平行时如何处理奇异点

本文深入探讨了6R仿人型机械臂在末三轴平行结构下的逆运动学求解策略,重点分析了运动学逆解中的奇异点问题及其处理方法。通过MATLAB实现展示了D-H参数建模、几何解析法求解流程以及奇异点规避策略,为工业机器人开发提供了实用的数值算法方案和工程优化技巧。

sss66的博客 884

BufferedOutputStream与BufferedInputStream

喜欢在下面评论区扣1支持下呗 字节缓冲输出流: BufferedOutputStream (OutputStream out) public class BufferStreamDemo { public static void main(String[] args) throws IOException { //字节缓冲输出流: BufferedOutputStream (Output...

程序员小哲的博客 4125

关于三维全波电磁场仿真软件CST的安装和问题解决

百度网盘链接:https://pan.baidu.com/s/1RgWr8Qau1EzSOlh29SewFQ提取码:fzza这里是2023版本的CST,需要其他版本的uu们可以去官网或者其他人的网盘下载。

weixin_60728441的博客 3620

JDK1.8源码学习--io包(BufferedInputStream)

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同样希望大家最终都能从零到零,把知识从薄变厚,再由厚变薄! 一.BufferedInputStream的作用: 直接看源码注释(我的翻译可能不太准,如果道友们有更棒的理解,可以留言或者私信)...

w5_Silence的博客 639

华为OD机试_2025 B卷_模拟消息队列(Python,100分)(附详细解题思路)

时间排序:确保事件处理顺序正确消费者管理:集合操作高效维护状态优先级实现:利用消费者ID自然排序边界处理:正确处理空消费者情况。

测试工程师的成长之路 1122

智能车竞赛环岛处理全解析:从图像识别到状态机控制

在嵌入式系统与自动控制领域,状态机是管理复杂系统行为、实现清晰逻辑流的核心设计模式。其原理在于定义一组有限的状态,以及触发状态迁移的事件和条件,从而将连续、模糊的过程分解为离散、确定的阶段。这种技术在机器人导航、工业自动化等场景中价值显著,尤其适用于需要应对多种场景、做出序列化决策的场合。在智能车竞赛中,赛道元素识别与路径跟踪是典型应用,其中环岛作为连续闭环的复杂场景,对系统的鲁棒性和决策连贯性提出了更高要求。通过图像处理提取赛道边线、宽度和曲率等特征,结合陀螺仪等传感器数据,可以构建一个专门处理环岛的状态

weixin_34033624的博客 565

Java IO 学习总结(三)BufferedInputStream 缓冲流

学习IO流,记录并分享。文章如有错误,恳请指正。BufferedInputStream 是缓冲输入流。它继承于 FilterInputStream。BufferedInputStream 的作用是为一个输入流添加一些功能,例如,提供“缓冲功能”以及支持 “mark() 标记” 和 “reset() 重置” 方法。

uziuzi669的博客 4441

TMS320C6678开发笔记1-6---C6678单核烧写程序

第6节 单核烧写加载程序 6.1节 TI参考文档 参考文档:《KeystoneI Bootloader Resources and FAQ》讲的非常好,建议一定要看 https://processors.wiki.ti.com/index.php/KeystoneI_Bootloader_Resources_and_FAQ#Direct_Boot_Examples_.28without_IBL.29 里面有三个工程: C6678 EVM SPI boot example C6657 EVM ..

kunkliu的博客 5699

Java 日看一类(6)之IO包中的ByteArrayInputStream类

引入包和继承关系:无引入包继承InputStream类类作用注释如下:/** * A &lt;code&gt;ByteArrayInputStream&lt;/code&gt; contains * an internal buffer that contains bytes that * may be read from the stream. An internal * counter...

github_37666068的博客 257

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

java io里的BufferedInputStream为什么性能高

java io流的分类 从读写角度拆分 读取流(InputStream、Reader的子类) 写入流(OutputStream、Writer的子类) 按操作数据类型可分为 字节流:所有InputStream、OutputStream的子类都是字节进行操作。 字符流:所有Reader、Writer的子类都是针对字符进行操作。 按处理类型可分为 终端流(节点流):个人理解,觉得终端流更形象贴切一些。如果某个流是直接和磁盘、内存、网络等物理终端联通来进行读写操作的,这种流就可以称为终端流或者

老艮头的后知后觉 2415

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Java IO常见类总结

JDK帮助文档真TMD是个好东西! 我们首先来看一幅图,该图清楚地展示了基本常用到的IO流:

快乐很伟大 4074

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Java BufferedInputStream reset()方法及示例

BufferedInputStream类reset()方法 (BufferedInputStream Class reset() method) reset() method is available in java.io package. reset()方法在java.io包中可用。 reset() method is used to reset this BufferedInputStrea...

cumtv80668的专栏 1146

Java IO流操作(PipeInputStream、SequenceInputStream、BufferedInputStream)

一、PipeInputStream 1、介绍。 管道流的主要作用是可以进行两个线程间的通讯,分为管道输入流(PipeOutputStream)和管道输出流(PipeInputStream)。如果要想进行管道输出,则必须把输出流连在输入流之上 2、代码。 private static final String Pre_Path = "G:\\项目测试\\io流文件测试\\"...

qq_36511401的博客 1万+

java.io.BufferedInputStream 详解

A[程序] -->|read请求| B[BufferedInputStream]// 标记当前位置(允许后续回退100字节)读取图片/压缩包等二进制文件时,必须用字节流(不能用字符流)// 1. 包装现有输入流(默认缓冲区大小8192字节):一次性从底层流读取8192字节(默认)到缓冲区。C -->|否| E[填充缓冲区8KB]// 处理buffer中的数据。C -->|是| D[从缓冲区返回]"字节输入加个盾,缓冲八千默认存,// ...读取部分数据...// 2. 自定义缓冲区大小。

weixin_51288065的博客 1370

Java-BufferedInputStream介绍

一个添加功能到另一个输入流,即缓冲输入和支持mark和reset方法的能力。当创建,内部缓冲区创建数组。从流中读取字节或跳过,内部缓冲区填充为必要从包含输入流,同时许多字节。的mark操作记得输入流中的一个点和reset操作使所有字节读因为最近的mark操作要重读前新的字节是从包含输入流。BufferedInputStream是InputStream的子类,它提供了一种缓冲机制来提高读取数据的性能。

架构师之路。。 1918

Java.io.BufferedInputStream类实例

本文章向大家介绍Java.io.BufferedInputStream类实例,主要包括Java.io.BufferedInputStream类实例使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。 Java.io.BufferedInputStream类添加功能到另一个输入流,缓冲输入以及支持mark和reset methods.Following是关于缓冲输入流的要点: 当创建缓冲输入,创建一个内部缓冲区数组。 如从该流的字节被读出或跳过,内.

wuxiaopengnihao1的博客 369

Java IO 4个Buffed的缓冲顶级类(比如bufferedInputStream)的 总结教程

原理:Buffered的原理和之前数组增加复制图片速度的原理一致点击查看 一、bufferedInputStream,bufferedOutputStream, 1.总结 一、原理,读取单个字节太慢,5000毫秒 我们用一个数组来代替很快 0毫秒就可以,Buffer就是一个默认长度的数组. 二、BufferInputStream extends InputStream.继承所有方法使用方法一致...

半亩方糖 608

java中字节IO流BufferedInputStream和BufferOutputStream的使用

java中IO流(BufferedInputStream和BufferOutputStream拷贝) * A:缓冲思想 * 字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多, * 这是加入了数组这样的缓冲区效果,java本身在设计的时候, * 也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲区流 * B.BufferedInputStrea...

可爱+小可爱的博客 1340

Java BufferedInputStream mark()方法与示例

BufferedInputStream类mark()方法 (BufferedInputStream Class mark() method) mark() method is available in java.io package. mark()方法在java.io包中可用。 mark() method is used to set the limit of bytes to be read ...

947

BufferedInputStream缓冲IO以及InputStream中的read(byte[] b)

InputStream InputStream是所有输入流的顶级类,当然只定义,不实现,具体的由子类去实现,如AudioInputStream, ByteArrayInputStream, FileInputStream等。 指明了InputStream的read(byte[],int off,int len)的实现方式,就是简单的调用read()方法而已,而read()方法是一次只读取一个字节,...

weixin_45830683的博客 1125

Java IO流学习总结三:缓冲流-BufferedInputStream、BufferedOutputStream

原文 Java IO流学习总结三:缓冲流-BufferedInputStream、BufferedOutputStream 继承关系图 InputStream |__FilterInputStream |__BufferedInputStream 首先抛出一个问题,有了InputStream为什么还要有BufferedInputStream? BufferedInputStream和BufferedOutputStream这两个类分别是FilterInputStream和FilterOu

ss810540895的博客 1296
上一篇: Java 日看一类(1)之IO包中的Bits
下一篇: Java 日看一类(3)之IO包中的BufferedOutputStream类
wyf95278
博客等级 码龄10年 38粉丝 71原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值