【Azure 存储服务】使用 AppendBlobClient 对象实现对Blob进行追加内容操作
问题描述
在Azure Blob的官方示例中,都是对文件进行上传到Blob操作,没有实现对已创建的Blob进行追加的操作。如果想要实现对一个文件的多次追加操作,每一次写入的时候,只传入新的内容?
问题解答
Azure Storage Blob 有三种类型: Block Blob, Append Blob 和 Page Blob。其中,只有Append Blob类型支持追加(Append)操作。并且Blob类型在创建时就已经确定,无法后期修改。

在查看Java Storage SDK后,发现可以使用AppendBlobClient来实现。
/**
* Creates a new {@link AppendBlobClient} associated with this blob.
*
* @return A {@link AppendBlobClient} associated with this blob.
*/
public AppendBlobClient getAppendBlobClient() {
return new SpecializedBlobClientBuilder()
.blobClient(this)
.buildAppendBlobClient();
}
在 AppendBlobClient 类,有 appendBlock 和 appendBlockWithResponse 等多种方法来实现追加。方法定义源码如下:

/**
* Commits a new block of data to the end of the existing append blob.
* <p>
* Note that the data passed must be replayable if retries are enabled (the default). In other words, the
* {@code Flux} must produce the same data each time it is subscribed to.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.appendBlock#InputStream-long}
*
* @param data The data to write to the blob. The data must be markable. This is in order to support retries. If
* the data is not markable, consider using {@link #getBlobOutputStream()} and writing to the returned OutputStream.
* Alternatively, consider wrapping your data source in a {@link java.io.BufferedInputStream} to add mark support.
* @param length The exact length of the data. It is important that this value match precisely the length of the
* data emitted by the {@code Flux}.
* @return The information of the append blob operation.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public AppendBlobItem appendBlock(InputStream data, long length) {
return appendBlockWithResponse(data, length, null, null, null, Context.NONE).getValue();
} /**
* Commits a new block of data to the end of the existing append blob.
* <p>
* Note that the data passed must be replayable if retries are enabled (the default). In other words, the
* {@code Flux} must produce the same data each time it is subscribed to.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.appendBlockWithResponse#InputStream-long-byte-AppendBlobRequestConditions-Duration-Context}
*
* @param data The data to write to the blob. The data must be markable. This is in order to support retries. If
* the data is not markable, consider using {@link #getBlobOutputStream()} and writing to the returned OutputStream.
* Alternatively, consider wrapping your data source in a {@link java.io.BufferedInputStream} to add mark support.
* @param length The exact length of the data. It is important that this value match precisely the length of the
* data emitted by the {@code Flux}.
* @param contentMd5 An MD5 hash of the block content. This hash is used to verify the integrity of the block during
* transport. When this header is specified, the storage service compares the hash of the content that has arrived
* with this header value. Note that this MD5 hash is not stored with the blob. If the two hashes do not match, the
* operation will fail.
* @param appendBlobRequestConditions {@link AppendBlobRequestConditions}
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return A {@link Response} whose {@link Response#getValue() value} contains the append blob operation.
* @throws UnexpectedLengthException when the length of data does not match the input {@code length}.
* @throws NullPointerException if the input data is null.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Response<AppendBlobItem> appendBlockWithResponse(InputStream data, long length, byte[] contentMd5,
AppendBlobRequestConditions appendBlobRequestConditions, Duration timeout, Context context) {
Objects.requireNonNull(data, "'data' cannot be null.");
Flux<ByteBuffer> fbb = Utility.convertStreamToByteBuffer(data, length, MAX_APPEND_BLOCK_BYTES, true);
Mono<Response<AppendBlobItem>> response = appendBlobAsyncClient.appendBlockWithResponse(
fbb.subscribeOn(Schedulers.elastic()), length, contentMd5, appendBlobRequestConditions, context);
return StorageImplUtils.blockWithOptionalTimeout(response, timeout);
}
代码实现
第一步: 在Java项目 pom.xml 中引入Azure Storage Blob依赖
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
<version>12.13.0</version>
</dependency>
第二步: 引入必要的 Storage 类
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalTime;
import com.azure.core.http.rest.Response;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.AppendBlobItem;
import com.azure.storage.blob.models.AppendBlobRequestConditions;
import com.azure.storage.blob.specialized.AppendBlobClient;
第三步:创建 AppendBlobClient 对象,使用 BlobServiceClient 及连接字符串(Connection String)
String storageConnectionString = "DefaultEndpointsProtocol=https;AccountName=*****;AccountKey=*******;EndpointSuffix=core.chinacloudapi.cn";
String containerName = "appendblob";
String fileName = "test.txt";
// Create a BlobServiceClient object which will be used to create a container
System.out.println("\nCreate a BlobServiceClient Object to Connect Storage Account");
BlobServiceClient blobServiceClient = new BlobServiceClientBuilder()
.connectionString(storageConnectionString)
.buildClient();
BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(containerName);
if (!containerClient.exists())
containerClient.create();
// Get a reference to a blob
AppendBlobClient appendBlobClient = containerClient.getBlobClient(fileName).getAppendBlobClient();
第四步:调用 appendBlockWithResponse 方法追加内容,并根据返回状态码判断是否追加成功
boolean overwrite = true; // Default value
if (!appendBlobClient.exists())
System.out.printf("Created AppendBlob at %s%n",
appendBlobClient.create(overwrite).getLastModified());
String data = "Test to append new content into exists blob! by blogs lu bian liang zhan deng @"
+ LocalTime.now().toString() + "\n";
InputStream inputStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
byte[] md5 = MessageDigest.getInstance("MD5").digest(data.getBytes(StandardCharsets.UTF_8));
AppendBlobRequestConditions requestConditions = new AppendBlobRequestConditions(); // Context context = new Context("key", "value");
long length = data.getBytes().length; Response<AppendBlobItem> rsp = appendBlobClient.appendBlockWithResponse(inputStream, length, md5,
requestConditions, null, null); if (rsp.getStatusCode() == 201) {
System.out.println("append content successful........");
}
运行结果展示

但如果操作的Blob类型不是Append Blob,就会遇见错误 Status code 409 ---- The blob type is invalid for this operation 错误
Exception in thread "main" com.azure.storage.blob.models.BlobStorageException: Status code 409, "
<?xml version="1.0" encoding="utf-8"?><Error>><Code>InvalidBlobType</Code>
<Message>The blob type is invalid for this operation.
RequestId:501ee0b9-301e-0003-4f7b-829ca6000000
Time:2023-05-09T13:37:17.7509942Z</Message></Error>"
at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:484)
at com.azure.core.http.rest.RestProxy.instantiateUnexpectedException(RestProxy.java:343)
at com.azure.core.http.rest.RestProxy.lambda$ensureExpectedStatus$5(RestProxy.java:382)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815)
at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.signalCached(MonoCacheTime.java:337)
at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onNext(MonoCacheTime.java:354)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2397)
at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onSubscribe(MonoCacheTime.java:293)
at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:192)
at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:57)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at reactor.core.publisher.MonoCacheTime.subscribeOrReturn(MonoCacheTime.java:143)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:57)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:157)
at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:130)
at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:118)
at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:220)
at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:130)
at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:184)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815)
at reactor.core.publisher.MonoCollectList$MonoCollectListSubscriber.onComplete(MonoCollectList.java:128)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:259)
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:401)
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:416)
at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:470)
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:685)
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:1589)
Suppressed: java.lang.Exception: #block terminated with an error
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)
at reactor.core.publisher.Mono.block(Mono.java:1703)
at com.azure.storage.common.implementation.StorageImplUtils.blockWithOptionalTimeout(StorageImplUtils.java:128)
at com.azure.storage.blob.specialized.AppendBlobClient.appendBlockWithResponse(AppendBlobClient.java:259)
at test.App.AppendBlobContent(App.java:68)
at test.App.main(App.java:31)
参考资料
Blob(对象)存储简介 : https://docs.azure.cn/zh-cn/storage/blobs/storage-blobs-introduction
【Azure 存储服务】使用 AppendBlobClient 对象实现对Blob进行追加内容操作的更多相关文章
- 【Azure 存储服务】代码版 Azure Storage Blob 生成 SAS (Shared Access Signature: 共享访问签名)
问题描述 在使用Azure存储服务,为了有效的保护Storage的Access Keys.可以使用另一种授权方式访问资源(Shared Access Signature: 共享访问签名), 它的好处可 ...
- 解读 Windows Azure 存储服务的账单 – 带宽、事务数量,以及容量
经常有人询问我们,如何估算 Windows Azure 存储服务的成本,以便了解如何更好地构建一个经济有效的应用程序.本文我们将从带宽.事务数量,以及容量这三种存储成本的角度探讨这一问题. 在使用 W ...
- 玩转Windows Azure存储服务——网盘
存储服务是除了计算服务之外最重要的云服务之一.说到云存储,大家可以想到很多产品,例如:AWS S3,Google Drive,百度云盘...而在Windows Azure中,存储服务却是在默默无闻的工 ...
- 【Azure 存储服务】.NET7.0 示例代码之上传大文件到Azure Storage Blob
问题描述 在使用Azure的存储服务时候,如果上传的文件大于了100MB, 1GB的情况下,如何上传呢? 问题解答 使用Azure存储服务时,如果要上传文件到Azure Blob,有很多种工具可以实现 ...
- 【JAVA使用XPath、DOM4J解析XML文件,实现对XML文件的CRUD操作】
一.简介 1.使用XPath可以快速精确定位指定的节点,以实现对XML文件的CRUD操作. 2.去网上下载一个“XPath帮助文档”,以便于查看语法等详细信息,最好是那种有很多实例的那种. 3.学习X ...
- 玩转Windows Azure存储服务——高级存储
在上一篇我们把Windows Azure的存储服务用作网盘,本篇我们继续挖掘Windows Azure的存储服务——高级存储.高级存储自然要比普通存储高大上的,因为高级存储是SSD存储!其吞吐量和IO ...
- 【Azure 存储服务】Python模块(azure.cosmosdb.table)直接对表存储(Storage Account Table)做操作示例
什么是表存储 Azure 表存储是一项用于在云中存储结构化 NoSQL 数据的服务,通过无结构化的设计提供键/属性存储. 因为表存储无固定的数据结构要求,因此可以很容易地随着应用程序需求的发展使数据适 ...
- 【Azure 存储服务】Java Azure Storage SDK V12使用Endpoint连接Blob Service遇见 The Azure Storage endpoint url is malformed
问题描述 使用Azure Storage Account的共享访问签名(Share Access Signature) 生成的终结点,连接时遇见 The Azure Storage endpoint ...
- 【Azure 存储服务】如何把开启NFS 3.0协议的Azure Blob挂载在Linux VM中呢?(NFS: Network File System 网络文件系统)
问题描述 如何把开启NFS协议的Azure Blob挂载到Linux虚拟机中呢? [答案]:可以使用 NFS 3.0 协议从基于 Linux 的 Azure 虚拟机 (VM) 或在本地运行的 Linu ...
- 【Azure 存储服务】Hadoop集群中使用ADLS(Azure Data Lake Storage)过程中遇见执行PUT操作报错
问题描述 在Hadoop集中中,使用ADLS 作为数据源,在执行PUT操作(上传文件到ADLS中),遇见 400错误[put: Operation failed: "An HTTP head ...
随机推荐
- c#和JS数据加密(转)
前台提交按纽 后以赋值后台取值 Base64编解码 C# /* 编码规则 Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编码. 它将需要编码的数据拆分成字节数组. ...
- 在Windows上访问linux的共享文件夹
1. https://blog.csdn.net/weixin_44147924/article/details/123692155
- 观察APP运行日志
一.Android采用log工具打印日志,他将各类日志分为五个等级 1.log.e:表示错误信息,比如可能导致程序崩溃的异常 2.log.w:表示警告信息 3.log.i:表示一般信息 4.log.d ...
- python requests 上传文件_python3使用requests上传文件,content-type踩的坑
通常提交普通表单时,requests的post方法可以指定headers,所以我在使用requests模拟上传文件行为时,直接按照下面的方式写了: 然后服务器就报出了找不到分隔符Invalid mul ...
- 初识C 语言
程序语言 C语言是目前极为流行的一种计算机程序设计语言,它既具有高级语言的功能,又具有汇编语言的一些特性.支持ANSIC. C语言的特点:通用性及易写易读 是一种结构化程序设计语言 具有良好的可移 ...
- Gym - 101845E (图形转换思维)
题意:给你个边长为n(1 <= n <= 50)的下图这种三角形,图形所有点构成集合.找多少对a,b满足条件,条件为:ab两点之间还有其他点. 题解:刚开始以为直接找规律就行,wa了两次发 ...
- 设计一款可扩展和基于windows系统的一键处理表格小工具思路
原创总结/朱季谦 设计一款可扩展和基于windows系统的一键处理表格小工具思路 日常开发当中,业务人员经常会遇到一些重复性整理表格的事情,这时候,就可以通过一些方式进行自动化程序处理,提高工作(摸鱼 ...
- cost function 成本函数
cost function 成本函数 cost function-成本函数 1.目标 :实现和探索具有一个变量的线性回归的成本函数. import numpy as np %matplotlib wi ...
- 基于深度学习的车型识别系统(Python+清新界面+数据集)
摘要:基于深度学习的车型识别系统用于识别不同类型的车辆,应用YOLO V5算法根据不同尺寸大小区分和检测车辆,并统计各类型数量以辅助智能交通管理.本文详细介绍车型识别系统,在介绍算法原理的同时,给出P ...
- 天翼网关如何启用FTP服务器?
首先将U盘连接光猫(天翼网关),然后在浏览器或者FTP软件连接.FTP地址例如:ftp://192.168.1.1 用户名和密码一般为你的路由器管理页面也就是192.168.1.1页面的登录名和密码.