前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Jackson] 二、jackson-core之流式API与JsonFactory、JsonGenerator、JsonParser

[享学Jackson] 二、jackson-core之流式API与JsonFactory、JsonGenerator、JsonParser

作者头像
YourBatman
发布2022-05-11 16:00:55
1.1K0
发布2022-05-11 16:00:55
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

目录

前言

jackson-core是三大核心模块之一,并且它是核心中的核心,它提供了对JSON数据的完整支持。 此模块提供了最具底层的Streaming JSON解析器/生成器,这组流式API属于Low-Level API,具有非常显著的特点:

  • 开销小,损耗小,性能极高
  • 因为是Low-Level API,所以灵活度极高
  • 又因为是Low-Level API,所以易错性高,可读性差

jackson-core模块提供了两种处理JSON的方式(整个Jackson一共3种):

  1. 流式API:读取并将JSON内容写入作为离散事件 -> JsonParser读取数据,而JsonGenerator负责写入数据
  2. 树模型:JSON文件在内存里以树形式表示。此种方式也很灵活,它类似于XML的DOM解析

本文将重点讲解流式API的使用:它是所有的三种方式中效率上最高的,当然也是最易出错、且最难使用的方式。

说明:树模型方式,将会放在和第三种处理方式:数据绑定 中一起讲述(数据绑定在另外一个核心模块jackson-databind中)

正文

我们知道,Jackson提供了一种对性能有极致要求的方式:流式API。它用于对性能有一定要求的场景,这个时候就可以使用此种方式来对JSON进行读写。

对于一般的读写(99.99%情况),我们使用最简单的databind方式即可,这部分在专栏对应章节会作为重中之重进行讲解

概念解释:流式、增量模式、JsonToken

  • 流式(Streaming):此概念和Java8中的Stream流是不同的。这里指的是IO流,因此在读/写的最后都是需要close的哦
  • 增量模式(incremental mode):它表示每个部分一个一个地往上增加,类似于垒砖。使用此流式API读写JSON的方式使用的均是增量模式,
  • JsonToken:每一部分都是一个独立的Token(有不同类型的Token),最终被“拼凑”起来就是一个JSON。这是流式API里很重要的一个抽象概念。

Demo示例

下面将通过一个示例先看看效果,再基于此做更深入的探讨。

JsonGenerator 写
代码语言:javascript
复制
public static void main(String[] args) throws IOException {
    JsonFactory factory = new JsonFactory();

    // 此处最终输输出到OutputStreams输出流(此处输出到文件)
    JsonGenerator jsonGenerator = factory.createGenerator(new File("java-jackson/src/main/resources/person.json"), JsonEncoding.UTF8);
    jsonGenerator.writeStartObject(); //开始写,也就是这个符号 {

    jsonGenerator.writeStringField("name", "YourBatman");
    jsonGenerator.writeNumberField("age", 18);

    // 写入Dog对象(枚举对象)
    jsonGenerator.writeFieldName("dog");
    jsonGenerator.writeStartObject();
    jsonGenerator.writeStringField("name", "旺财");
    jsonGenerator.writeStringField("color", "WHITE");
    jsonGenerator.writeEndObject();

    //写入一个数组格式
    jsonGenerator.writeFieldName("hobbies"); // "hobbies" :
    jsonGenerator.writeStartArray(); // [
    jsonGenerator.writeString("篮球"); // "篮球"
    jsonGenerator.writeString("football"); // "football"
    jsonGenerator.writeEndArray(); // ]

    jsonGenerator.writeEndObject(); //结束写,也就是这个符号 }

    // 关闭IO流
    jsonGenerator.close();
}

提示:源码地址请参见专栏里每篇文章的文首部分

执行此程序,最终会在resources目录下生成一个名为person.json的文件,内容如下(多次执行,效果是覆盖):

在这里插入图片描述
在这里插入图片描述

完完全全的手动档有木有,逐字逐句的都得我们手动“书写”,格式也都得手动来控制。所以说这么做的灵活度极大,效率极高,但相反的便是编码效率低、代码可读性差,易出错…

JsonParser 读
代码语言:javascript
复制
public static void main(String[] args) throws IOException {
    JsonFactory factory = new JsonFactory();

    // 此处InputStream来自于文件
    JsonParser jsonParser = factory.createParser(new File("java-jackson/src/main/resources/person.json"));

    // 只要还没到末尾,也就是}这个符号,就一直读取
    // {"name":"YourBatman","age":18,"dog":{"name":"旺财","color":"WHITE"},"hobbies":["篮球","football"]}
    JsonToken jsonToken = null; // token类型
    while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
        String fieldname = jsonParser.getCurrentName();
        if ("name".equals(fieldname)) {
            jsonToken = jsonParser.nextToken();
            System.out.println("==============token类型是:" + jsonToken);
            System.out.println(jsonParser.getText());
        } else if ("age".equals(fieldname)) {
            jsonToken = jsonParser.nextToken();
            System.out.println("==============token类型是:" + jsonToken);
            System.out.println(jsonParser.getIntValue());
        } else if ("dog".equals(fieldname)) {
            jsonToken = jsonParser.nextToken();
            System.out.println("==============token类型是:" + jsonToken);
            while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
                String dogFieldName = jsonParser.getCurrentName();
                if ("name".equals(dogFieldName)) {
                    jsonToken = jsonParser.nextToken();
                    System.out.println("======================token类型是:" + jsonToken);
                    System.out.println(jsonParser.getText());
                } else if ("color".equals(dogFieldName)) {
                    jsonToken = jsonParser.nextToken();
                    System.out.println("======================token类型是:" + jsonToken);
                    System.out.println(jsonParser.getText());
                }
            }
        } else if ("hobbies".equals(fieldname)) {
            jsonToken = jsonParser.nextToken();
            System.out.println("==============token类型是:" + jsonToken);
            while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
                System.out.println(jsonParser.getText());
            }
        }
    }

    // 关闭IO流
    jsonParser.close();
}

运行本程序,控制台里完美输出了各个属性的值:

代码语言:javascript
复制
==============token类型是:VALUE_STRING
YourBatman
==============token类型是:VALUE_NUMBER_INT
18
==============token类型是:START_OBJECT
======================token类型是:VALUE_STRING
旺财
======================token类型是:VALUE_STRING
WHITE
==============token类型是:START_ARRAY
篮球
football

核心API精讲

jackson-core内核模块里虽然有众多的类,但最为重要的只有如下3个:

  • JsonFactory:Jackson主要的工厂方法,用于配置和构建解析器(JsonParser)和生成器(JsonGenerator),这个工厂实例是线程安全的,所以可以重复使用
  • JsonGenerator:用来生成Json格式的内容的(序列化)
  • JsonParser:读取Json格式的内容(返序列化,必须是Json格式)

Demo案例中介绍了使用Streaming API完成最常规、最基本的读/写操作,下面针对于本案例涉及到的几个核心API进行分析和讲解

JsonFactory

Jackson的主要工厂类,用于 配置和构建 JsonGeneratorJsonParser,这个工厂实例是线程安全的,因此可以重复使用。该工厂最主要的方法截图展示如下:

创建JsonParser

在这里插入图片描述
在这里插入图片描述

创建JsonGenerator

在这里插入图片描述
在这里插入图片描述

关于本工厂的配置和构建,使用源代码 + 注释的方式展示如下:

代码语言:javascript
复制
public class JsonFactory ... {

	// 这三大Feature特征解析,会在专栏后面讲解特性配置章节中讲解
	protected final static int DEFAULT_FACTORY_FEATURE_FLAGS = JsonFactory.Feature.collectDefaults();
	protected final static int DEFAULT_PARSER_FEATURE_FLAGS = JsonParser.Feature.collectDefaults();
	protected final static int DEFAULT_GENERATOR_FEATURE_FLAGS = JsonGenerator.Feature.collectDefaults();

	...
	// 你可以对输入流、输出流进行干预
	// (这两个类均为抽象类,没有内置实现,若你有需要均需要你自己提供实现)
	protected InputDecorator _inputDecorator;
	protected OutputDecorator _outputDecorator;
	...

	// 构造器...
	// 99%情况下,使用空构造即可
	public JsonFactory() { this((ObjectCodec) null); }
	// ObjectCodec编码器并没有内置实现类。一般情况下我们不会自己去书写编码器
	// 提前透露只是:ObjectMapper便是一个(唯一一个)ObjectCodec实现类,它实现了自动绑定
	public JsonFactory(ObjectCodec oc) { ... }
	// 2.10版本新提供的基于Builder模式的构造方式,方便你进行配置
	public JsonFactory(JsonFactoryBuilder b) { ... }
	...

	// 2.10新增静态方法:builder模式的体现(JsonFactoryBuilder是2.10版本新增的类)
    public static TSFBuilder<?,?> builder() {
        return new JsonFactoryBuilder();
    }
    ...
    
    // 拷贝出一个新的实例
	public JsonFactory copy() { ... }
	
	// 默认输出的字段无序。
	// 若你是CSV或者Avro这种对字段顺序有要求的格式。请继承此类后然后把此属性改为true
    @Override
    public boolean requiresPropertyOrdering() { return false; }
    @Override
    public boolean canHandleBinaryNatively() { return false; }
    public boolean canUseCharArrays() { return true; }
    // 版本信息
    @Override
    public Version version() {
        return PackageVersion.VERSION;
    }

	... // Configuration, factory features/parser features/generator features
}

该工厂的API整体上非常简单,JsonGeneratorJsonParser并不能自己new实例,而只能是通过这个工厂去创建,因此建议掌握本工厂的相关配置方法。

SPI(ServiceLoader)方式创建实例

上面介绍,JsonFactory实例一般通过new构造函数的方式来创建一个工厂实例。作为如此优秀的Jackson库,自然考虑到了我们可能会有希望自己扩展JsonFactory的需求,因此它还提供了一种更具弹性的SPI方式来创建工厂实例:允许我们通过配置文件的形式来动态调整使用的具体工厂

说明:关于SPI是何意思,以及JDK提供的ServiceLoader机制到底怎么玩,建议还不清楚的同学可以先补补课(我的免费博文里也有详细讲述,出门左拐)

基于SPI方式,上面Demo中创建JsonFactory实例的方式可以改为:

代码语言:javascript
复制
public static void main(String[] args) throws IOException {
    // 1、直接new的方式
    // JsonFactory factory = new JsonFactory();
    // 2、更具弹性的SPI方式
    JsonFactory factory = null;
    ServiceLoader<JsonFactory> load = ServiceLoader.load(JsonFactory.class);
    Iterator<JsonFactory> it = load.iterator();
    if (it.hasNext()) { // 此处是if不是while,因为我只需要一个而已
        factory = it.next();
    }
    ...
}

这样能正常运行,是因为jackson-core这个包它内置了SPI的配置,如下截图:

在这里插入图片描述
在这里插入图片描述

使用自定义的JsonFactory

代码语言:javascript
复制
public class MyJsonFactory extends JsonFactory {
    public MyJsonFactory(){
        System.out.println("我是自定义的JsonFactory");
    }
}

写个ServiceLoader的配置文件:

在这里插入图片描述
在这里插入图片描述

运行如上程序,可以看见控制台上有输出:

代码语言:javascript
复制
我是自定义的JsonFactory
...

由此可见我自定义的MyJsonFactory最终被使用了,生效喽。 其实ServiceLoader.load()的时候把MyJsonFactory和系统内置的JsonFactory都加载到了,只是最终只实例化了我的,这是由加载配置文件的顺序决定的,而这种顺序往往是不可控的~

因此需要注意:ServiceLoader它不像SpringFactoriesLoader那样强大可以通过Order自己管理排序,so在实际使用中请务必做好相应的处理。

小建议:在实际代码书写中,若你想创建工厂实例,建议使用SPI方式,这样能让你的程序变得更富弹性

JsonToken

上面介绍了Streaming API中Token的含义,然而jackson-core里也提供了这样一个枚举类,枚举出了用于返回结果的一些token类型(也就是说当你遇上这些类型的token的时候,就可以去获取结果了)。

代码语言:javascript
复制
public enum JsonToken {

	START_OBJECT("{", JsonTokenId.ID_START_OBJECT),
	END_OBJECT("}", JsonTokenId.ID_END_OBJECT),
	START_ARRAY("[", JsonTokenId.ID_START_ARRAY),
	END_ARRAY("]", JsonTokenId.ID_END_ARRAY),
		
	...
	
	// 值的token类型
	// 嵌入式的Object,表示raw Object
	VALUE_EMBEDDED_OBJECT(null, JsonTokenId.ID_EMBEDDED_OBJECT),
	VALUE_STRING(null, JsonTokenId.ID_STRING),
	VALUE_NUMBER_INT(null, JsonTokenId.ID_NUMBER_INT),
	VALUE_NUMBER_FLOAT(null, JsonTokenId.ID_NUMBER_FLOAT),
	VALUE_TRUE("true", JsonTokenId.ID_TRUE),
	VALUE_FALSE("false", JsonTokenId.ID_FALSE),
	VALUE_NULL("null", JsonTokenId.ID_NULL);
}
JsonGenerator

它是用于writing JSON content的基类(抽象类),因为创建它的实例使用的是JsonFactory工厂,因此我们无需关心具体实现类,只需了解此基类的API即可。

代码语言:javascript
复制
public abstract class JsonGenerator... {

	// 用于漂亮格式的输出:便于人阅读。默认它是null:紧凑型输出
	protected PrettyPrinter _cfgPrettyPrinter;
	protected JsonGenerator() { }
    @Override
    public abstract Version version();
    public JsonGenerator setPrettyPrinter(PrettyPrinter pp) { ... }
    public abstract JsonGenerator useDefaultPrettyPrinter();

	... // Feature configuration特性相关配置,在专栏特性文章里会详细介绍

	// 获取当前值。它只用于高级数据绑定功能
	public Object getCurrentValue() { ... }
	public void setCurrentValue(Object v) { ... }
}

以上是此生成器的配置部分的源码说明,接下来便是它提供的写能力的核心API了(截图方式展示,更加的直观):

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这些便是它最为重要的基础性API,使用起来没有难度,Demo示例中有相关代码展示。

约定:为了不显得文章过于臃肿,出现本末倒置现象而重点不突出,本文包括后续文章像这种基础性API的使用就不会给出相关示例,有任何疑问的可以留言~

write方法说明

从API中可以看出,这种Low-Level的API是只能写基本类型的:如int、long、BigInteger…对于对象类型如Date、Person等,它都是不能直接写的。

虽然JsonGenerator有个writeObject(Object pojo)方法,但默认情况下仅是调用了toString()方法而已,意义并不大。若想要良好的格式输出,需要自定义ObjectCodec的实现~

所以说,针对于平时常说的Date/LocalDate类型是写为时间戳还是指定格式,这都是针对于高层API也就是ObjectMapper而言的,毕竟它自己就是个ObjectCodec嘛,所以实现了编码/解码过程。 而Streaming API只提供最为底层的、最为原子的方法,只有这样才能有最大的灵活性以及保证极致的性能

JsonParser

定义了一组API用于reading JSON content。它的实例也只能由工厂创建,所以也只需关心此基类的相关API即可:

代码语言:javascript
复制
public abstract class JsonParser ... {

	private final static int MIN_BYTE_I = (int) Byte.MIN_VALUE; //-2的7次方 = -128
	// 注意Byte.MAX_VALUE的最大值是127.而根据[JACKSON-804]规定把它扩大到了255(含)
	private final static int MAX_BYTE_I = (int) 255;
    private final static int MIN_SHORT_I = (int) Short.MIN_VALUE; // -2的15次方 = 32768
    private final static int MAX_SHORT_I = (int) Short.MAX_VALUE; // 2的15次方-1 = 32767

	// 内建支持的数字类型
    public enum NumberType {
        INT, LONG, BIG_INTEGER, FLOAT, DOUBLE, BIG_DECIMAL
    };

	...
	public Object getCurrentValue() { ... }
	public void setCurrentValue(Object v) { ... }
	@Override
    public abstract Version version();

	// 2.9提供了NIO的支持。默认是没有开启的(向下兼容)
	public boolean canParseAsync() { return false; }
    public NonBlockingInputFeeder getNonBlockingInputFeeder() {
        return null;
    }

	// 获取解析的当前上下文
	public abstract JsonStreamContext getParsingContext();
	// 关于token的操作(重要)
    public JsonToken currentToken() { return getCurrentToken(); }
    public int currentTokenId() { return getCurrentTokenId(); }
    public abstract JsonToken getCurrentToken();
    public abstract int getCurrentTokenId();
    public abstract boolean hasTokenId(int id);
    public abstract boolean hasToken(JsonToken t);
    public boolean isExpectedStartArrayToken() { return currentToken() == JsonToken.START_ARRAY; }
    public boolean isExpectedStartObjectToken() { return currentToken() == JsonToken.START_OBJECT; }
    public abstract void clearCurrentToken();
    public abstract JsonToken getLastClearedToken();
    public abstract String getCurrentName() throws IOException;
    public String currentName() throws IOException { return getCurrentName(); }

	// 输入的第一个字符的位置
	public abstract JsonLocation getTokenLocation();
	// 该方法返回最后处理的字符的位置(一般用于error的时候打印日志)
	public abstract JsonLocation getCurrentLocation();
	// 这是最主要的迭代方法。它将推进流来确定下一个令牌的类型(如果有的话),若没有下一个了就返回null
	public abstract JsonToken nextToken() throws IOException;
	// 迭代方法,获取下一个值类型
	public abstract JsonToken nextValue() throws IOException;
	// 简答的说就是调用nextToken,然后判断这个token是否是JsonToken.FIELD_NAME类型;
	// 并且getCurrentName()名称还和你指定的名称一样才会返回true
	public boolean nextFieldName(SerializableString str) throws IOException { ... }
	// 不解释
    public String nextFieldName() throws IOException {
        return (nextToken() == JsonToken.FIELD_NAME) ? getCurrentName() : null;
    }

	// nextToken + 获取值的 组合方法系列
    public String nextTextValue() throws IOException {
        return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
    }
    public int nextIntValue(int defaultValue) throws IOException {
        return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getIntValue() : defaultValue;
    }
   	... // 省略Long、Bool类型的组合方法

	// 该方法将跳过数组或的所有子标记当前指的对象
	public abstract JsonParser skipChildren() throws IOException;
	public void finishToken() throws IOException { ... }
    public boolean isNaN() throws IOException {
        return false;
    }
}

相较于JsonGenerator而言,它的API相对繁多。这是很容易理解的,毕竟反序列化一般都是比序列化麻烦很多的。 同样的,为了更直观的分类展示出核心API,下面还是以图示的方式列出:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这组数据读取API中,稍微不好理解的几个方法为:readValueAsXXX系列方法。为了扫清困惑,下面专门针对它们附加一个示例以辅助理解

readValueAs()系列方法Demo示例

该方法将JSON内容反序列化为非容器类型(但可以是数组类型),通常是一个bean,一个数组或包装器类型(如Boolean)。Note:该系列方法依赖于ObjectCodec对象编码/解码器

自定义实现一个ObjectCodec:

代码语言:javascript
复制
// 由于ObjectCodec需要实现的方法过多。本处只以一个实现为基准,各位举一反三即可
public class MyObjectCodec extends ObjectCodec {

    @Override
    public <T> T readValue(JsonParser jsonParser, Class<T> valueType) throws IOException {
        // {"name":"YourBatman","age":18,"dog":{"name":"旺财","color":"WHITE"},"hobbies":["篮球","football"]}
        // 使用JsonParser读取此json串,并且封装到valueType类型的JavaBean里
        // 重点说明:次数实例理应通过valueType来构造,且赋值方面大量用到反射。
        // 但本文仅想说明本质,因此不相关的步骤不在此处列出,各位知道便可
        Person person = new Person();

        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
            String fieldname = jsonParser.getCurrentName();
            if ("name".equals(fieldname)) {
                person.setName(jsonParser.nextTextValue());
            } else if ("age".equals(fieldname)) {
                person.setAge(jsonParser.nextIntValue(0));
            } else if ("dog".equals(fieldname)) {
                jsonParser.nextToken();
                // 构造一个dog实例(同样的,实际场景是利用反射构造的哦)
                Dog dog = new Dog();
                person.setDog(dog);

                while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
                    String dogFieldName = jsonParser.getCurrentName();
                    if ("name".equals(dogFieldName)) {
                        dog.setName(jsonParser.nextTextValue());
                    } else if ("color".equals(dogFieldName)) {
                        dog.setColor(Color.valueOf(jsonParser.nextTextValue()));
                    }
                }
            } else if ("hobbies".equals(fieldname)) {
                jsonParser.nextToken();

                List<String> hobbies = new ArrayList<>();
                person.setHobbies(hobbies);
                while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
                    // hobbies.add(jsonParser.nextTextValue()); // 请注意:此处不能用next哦~
                    hobbies.add(jsonParser.getText());
                }
            }
        }
        return (T) person;
    }

    @Override
    public Version version() {
        return PackageVersion.VERSION;
    }

    // ... 省略其它方法
}

测试用例如下,它最关键的一句代码为jsonParser.setCodec(new MyObjectCodec()),从而写的操作便委托给了对象编码/解码器去实现喽:

代码语言:javascript
复制
public static void main(String[] args) throws IOException {
    JsonFactory factory = new JsonFactory();

    // 此处InputStream来自于文件
    JsonParser jsonParser = factory.createParser(new File("java-jackson/src/main/resources/person.json"));
    jsonParser.setCodec(new MyObjectCodec()); // 若使用readValueAs系列方法,必须指定解码器

    Person person = jsonParser.readValueAs(Person.class);
    System.out.println(person);

    // 关闭IO流
    jsonParser.close();
}

运行程序,控制台完美输出:

代码语言:javascript
复制
Person(name=YourBatman, age=18, hobbies=[篮球, football], dog=Dog(name=旺财, color=WHITE))

注意:如果返回类型是List或者Map时,请不要使用readValueAs(Class<T> valueType)方法,原因是集合类型泛型类型会被擦除从而无法自省,推荐使用readValueAs(TypeReference<?> valueTypeRef)方法来处理

说明:专栏后面重点介绍的ObjectMapper,它就是ObjectCodec的(唯一)实现类,它的基础原理便来源于此

总结

本文介绍了jackson-core模块的流式API的使用,它作为JSON处理的基石,虽然极力不推荐直接使用,但这并不影响它的重要程度和地位。

对于流式API,虽然它在性能上有所特长,但是通过上面的Demo示例也可以知道:每一个 token都得自己增量处理(全手动档),换句话说,coder必须要非常小心地显示的处理每个token,这是很要命的,因为很可能会因为粗心导致丢掉/写错一些字符。而且这种方式书写的代码简洁性很差,可读性也不好,而且还得自己close流。因此,在不到需要考虑极致性能的时候,一定一定不要使用这种方式去操作JSON哦。

本文介绍它的目的并不是建议大家去使用,而是为了后面理解ObjectMapper夯实基础,毕竟做技术的要知其然且知其所以然了后才能坦然

关注A哥

Author

A哥(YourBatman)

个人站点

www.yourbatman.cn

E-mail

yourbatman@qq.com

微 信

fsx641385712

活跃平台

公众号

BAT的乌托邦(ID:BAT-utopia)

知识星球

BAT的乌托邦

每日文章推荐

每日文章推荐

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-12-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
    • 概念解释:流式、增量模式、JsonToken
      • Demo示例
        • 核心API精讲
        • 总结
        • 关注A哥
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档