Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ByteBuffer 介绍及 C++ 实现

ByteBuffer 介绍及 C++ 实现

作者头像
王强
发布于 2021-04-13 07:18:25
发布于 2021-04-13 07:18:25
1.7K00
代码可运行
举报
文章被收录于专栏:Python爬虫实战Python爬虫实战
运行总次数:0
代码可运行

ByteBuffer 介绍及 C++ 实现

之前的工作中遇到过需要打包数据然后通过 USB 发送的功能,当时写了一个简单的类用来存入各种类型的数据,然后将其 Buffer 内的数据发送,接收到数据后通过它的方法再取出各种类型的数据。后来接触到了 Java 的 ByteBuffer,发现两者功能大致相同。本文会用 C++ 实现 ByteBuffer 的大部分功能。

1. ByteBuffer 介绍

ByteBuffer类位于java.nio包下,它是一个字节缓存区,提供了一些 put 和 get 方法,可以方便的将一些数据放到缓存区或者从缓存区里读取某种类型的数据。ByteBuffer 的底层存储结构是数组,所有的操作都是基于该数组的操作。

以下内容结合 Java 版本 ByteBuffer 的原理以及 C++ 实现进行讲解。

2. ByteBuffer 的成员变量

2.1 几个位置变量

变量名称

含义

position

表示从写入或者读取的位置。

limit

处于写入状态时,limit 和 capacity 相等;处于读取状态时,表示数据索引的上限,也就是实际存放了多少数据。

mark

标记读取数据的起始位置,便于后续回退到该位置。

capacity

表示 ByteBuffer 的容量,也就是可以存放的最大字节数。

这四个变量之间的关系可以表示为:mark <= position <= limit <= capacity

数据的存入和读取只会影响 position,不会影响 limit。

在 C++ 实现中,设置如下成员变量:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int32_t  mark_;
uint32_t limit_;
uint32_t position_;
uint32_t capacity_;

提供如下三个方法分别获取 capacitypositionlimit

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
uint32_t capacity() const;
uint32_t position() const;
uint32_t limit() const;

提供如下两个方法可以重新设置 limitposition

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer& limit(uint32_t newLimit);
ByteBuffer& position(uint32_t newPosition);

2.2 缓存区

前面已经提到,ByteBuffer 提供一个缓存区来存储数据,在 C++ 实现中,使用一个 uint8_t 类型的数组进行数据的存储。在 ByteBuffer 类创建时申请空间,在 ByteBuffer 类销毁时释放空间。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
uint8_t*         p_buffer_;

2.3 ByteBuffer 名称

为了打印时的美观,为每一个 ByteBuffer 设置一个名称,该名称为 ByteBuffer 类的一个成员变量,在类创建时设置,默认为空:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
std::string      name_;

3. 创建 ByteBuffer

java.nio.Buffer 类是一个抽象类,不能被实例化。Buffer类的直接子类,如ByteBuffer等也是抽象类,所以也不能被实例化。但是 ByteBuffer 类提供了4个静态工厂方法来获得 ByteBuffer 的实例:

  • allocate(int capacity)
  • allocateDirect(int capacity)
  • wrap(byte[] array)
  • wrap(byte[] array, int offset, int length)

C++ 版本做了一下简化,提供两个构造方法进行创建。

3.1 创建指定大小的空的 ByteBuffer

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Default size of the buffer
#define DEFAULT_BUFFER_SIZE 2048

ByteBuffer(uint32_t capacity = DEFAULT_BUFFER_SIZE, const char* name = "") 
    : mark_(-1), 
    limit_(capacity), 
    position_(0),
    capacity_(capacity), 
    name_(name)
{
    p_buffer_ = NULL;
    p_buffer_ = (uint8_t*)calloc(capacity_, sizeof(uint8_t));
}

如果创建时未指定 capacity ,默认大小为 2048 字节。

3.2 从一个数组创建指定大小的 ByteBuffer

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer(uint8_t* arr, uint32_t length, const char* name = "")
    : mark_(-1), 
    limit_(length), 
    position_(0),
    capacity_(length), 
    name_(name)
{
    p_buffer_ = NULL;
    p_buffer_ = (uint8_t*)calloc(capacity_, sizeof(uint8_t));

    putBytes(arr, capacity_);
    clear();
}

putBytes() 方法负责将一个现有数组的指定长度存到 ByteBuffer 中,后面会对该方法做介绍。

3.3 析构方法

析构方法主要作用是释放已经申请的内存:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
~ByteBuffer()
{
    if (p_buffer_)
    {
        free(p_buffer_);
        p_buffer_ = NULL;
    }
}

4. 状态相关

申请一个容量为 10 的 ByteBuffer bf,以下演示都基于 bf

4.1 初始状态

image-20210323235205921

初始状态下,四个变量的值分别为:

  • mark:-1
  • position:0
  • limit:10
  • capacity:10

将 ByteBuffer 置为初始状态的方法:

  • ByteBuffer 创建之后调用其它方法之前就是初始状态
  • 调用 clear() 方法可以重置到初始状态。

clear() 方法的 C++ 实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer& clear() 
{
    position_ = 0;
    mark_     = -1;
    limit_    = capacity_;
    return *this;
}

4.2 写入状态

假设向 bf 写入 hello 几个字符,此时四个变量的状态如图所示:

image-20210323235228538

position 会随着数据的写入而后移。

将 ByteBuffer 置为写入状态的方法:

  • ByteBuffer 创建之后就是写入状态,可以调用一系列 put 方法写入数据;

4.3 读取状态

bf 进入读取状态时四个变量的状态如图所示:

image-20210324000012817

调用一系列 get 方法从 bf 中读取数据,position 随着数据的读取会向后移动,但不会超过 limit

bf 从写入状态进入读取状态需要调用 flip() 方法,调用 flip() 方法后,limit 被设置为原 position 的值,表示已经存储数据的位置;position 被置为 0。

flip() 方法的 C++ 实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer& flip() 
{
    limit_    = position_;
    position_ = 0;
    mark_     = -1;
    return *this;
}

4.4 mark() && discardMark()

这两个方法比较简单,mark() 方法将 mark 值设置为当前的 positiondiscardMark() 方法将 mark 重置为 -1。调用 mark()discardMark() 方法后 mark 位置的变化如图所示:

这两个方法的 C++ 代码实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer& mark()
{
    mark_ = position_;
    return *this;
}

ByteBuffer& discardMark() 
{
    mark_ = -1;
    return *this;
}

4.5 reset()

reset() 方法将 position 恢复到 mark 的位置。调用 reset() 方法后的 position 变化如图所示:

image-20210324140402132

reset() 方法的 C++ 实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer& reset()
{
    if (mark_ >= 0)
        position_ = mark_;

    return *this;
}

4.6 rewind()

rewind() 方法负责将 position 置为 0,将 mark 置为 -1,数据的内容不会改变,一般在把数据重写入Buffer前调用。调用 rewind() 方法后 markposition 的变化如图所示:

image-20210324140832110

rewind() 方法的 C++ 实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer& rewind()
{
    mark_ = -1;
    position_ = 0;

    return *this;
}

4.7 compact()

压缩缓存区。把缓存区当前 positionlimit 之间的数据移动到缓存区的开头。也就是说,将索引 p=position() 处的字节复制到索引 0 处,将索引 p+1 处的字节复制到索引 1 处。以此类推,直到 limit - 1 处的字节复制到索引 n=limit-1-p 处。然后将缓存区的 position 设置为 n+1(也就是不能再读取数据了,但是可以写入数据),并将 limit 的值设置为 capacity

调用 compact() 方法后,几个变量的位置以及数据的变化如图所示:

image-20210324192748457

compact() 方法的 C++ 实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer& compact()
{
    do 
    {
        if (position_ >= limit_)
        {
            position_ = 0;
            break;
        }

        for (uint32_t i = 0; i < limit_ - position_; i++)
        {
            p_buffer_[i] = p_buffer_[position_ + i];
        }
        position_ = limit_ - position_;
    } while (0);        

    limit_ = capacity_;
    return *this;
}

4.8 状态相关方法总结

函数名

描述

flip()

把 limit 设置为当前 position,把 position 置为 0

clear()

重置ByteBuffer的 position = 0; limit = capacity; mark = -1,数据内容无变化

reset()

将position恢复到mark处的位置

rewind()

执行后position = 0, mark = -1,数据内容不变

mark()

将mark值设置为当前的position

discardMark()

将mark的位置重置为-1

compact()

删除已读过的数据,将position到limit之间的数据移动到0和limit-position处,并将mark重置为-1,position放到数据结尾,总结一下,就是可以继续写数据了,但是不能读数据

5. put 数据

ByteBuffer 提供一系列的 put 方法将各种类型的数据放到 buffer 中,具体的类型有 char、short、int、long、float、double、char 数组以及 Bytebuffer。

5.1 扩容机制

Java 的 ByteBuffer 在创建时容量就固定了,如果存放的数据超出容量,会抛出异常。C++ 版本的 ByteBuffer 增加了扩容机制。理论上,每次向 buffer 中写入数据都要检查空间是否足够,如果空间不够,则扩大容量。

ByteBuffer 定义成员变量 BUFFER_SIZE_INCREASE 表示扩容的步长,即每次扩大的容量都为 BUFFER_SIZE_INCREASE 的整数倍,其值为 2048

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const uint32_t BUFFER_SIZE_INCREASE = 2048;

定义 checkSize() 方法检查容量是否足够,如果足够,不做处理;如果不够,则计算需要多少容量并扩容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void ByteBuffer::checkSize(uint32_t index, uint32_t increase)
{
    if (index + increase <= capacity_)
            return;

    uint32_t newSize = capacity_ + (increase + BUFFER_SIZE_INCREASE - 1) /
        BUFFER_SIZE_INCREASE * BUFFER_SIZE_INCREASE;
    uint8_t* pBuf = (uint8_t*)realloc(p_buffer_, newSize);
    if (!pBuf)
    {
        std::cout << "relloc failed!" << std::endl;
        exit(1);
    }

    p_buffer_ = pBuf;
    capacity_ = newSize;
}

void ByteBuffer::checkSize(uint32_t increase)
{
    checkSize(position_, increase);
}

在每个 put() 方法里首先调用 checkSize() 检查容量,然后再放入数据。

5.2 模板方法

为了简化存放数据的过程,用一个模板方法去适配各种类型:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<typename T>
void append(T data)
{
    if (!p_buffer_)
        return;

    uint32_t s = sizeof(data);
    checkSize(s);

    memcpy(&p_buffer_[position_], (uint8_t*)&data, s);
    position_ += s;
}

template<typename T>
void insert(T data, uint32_t index)
{
    uint32_t s = sizeof(data);
    checkSize(index, s);

    position_ = index;
    append<T>(data);
}

append() 方法将数据写入到当前的 position_ 处,并相应增加 position_

insert() 方法将数据写入到指定的位置,首先要将 position_ 设置为 index 然后调用 append() 方法写入数据。

5.3 put 方法

ByteBuffer 提供的所有 put 方法返回值类型都为 ByteBuffer& 便于链式操作,比如 bf.put(1).put("hello", 5).put(3.1415926),所有方法如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer& put(ByteBuffer* bb);
ByteBuffer& put(uint8_t value);
ByteBuffer& put(uint8_t value, uint32_t index);
ByteBuffer& putBytes(const uint8_t* buf, uint32_t len);
ByteBuffer& putBytes(const uint8_t* buf, uint32_t len, uint32_t index);
ByteBuffer& putChar(char value);
ByteBuffer& putChar(char value, uint32_t index);
ByteBuffer& putShort(uint16_t value);
ByteBuffer& putShort(uint16_t value, uint32_t index);
ByteBuffer& putInt(uint32_t value);
ByteBuffer& putInt(uint32_t value, uint32_t index);
ByteBuffer& putLong(uint64_t value);
ByteBuffer& putLong(uint64_t value, uint32_t index);
ByteBuffer& putFloat(float value);
ByteBuffer& putFloat(float value, uint32_t index);
ByteBuffer& putDouble(double value);
ByteBuffer& putDouble(double value, uint32_t index);

注意:由于 Java 采用 Unicode 编码,一个 Char 类型占两个字节,但是在 C++ 中 char 类型占一个字节,所以两个版本的 putChar() 方法有些差异。

着重讲解一下 ByteBuffer& put(ByteBuffer* bb) 方法,该方法将另一个 ByteBuffer 的内容(从 0limit() 之间的数据)拷贝到当前 ByteBuffer,其实现为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer& put(ByteBuffer* bb)
{
    for (uint32_t i = 0; i < bb->limit(); i++)
        append<uint8_t>(bb->get(i));

    return *this;
}

6. get 数据

ByteBuffer 提供一系列的 get 方法将各种类型的数据放到 buffer 中。

6.1 模板方法

为了简化数据的获取,实现模板方法获取各种类型的数据。注意:带有 index 参数的 read() 方法不会改变 position 的值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template <typename T>
T read(uint32_t index) const
{
    if (!p_buffer_ || index + sizeof(T) > limit_)
        return 0;

    return *((T*)&p_buffer_[index]);
}

template <typename T>
T read()
{
    T data = read<T>(position_);
    position_ += sizeof(T);
    return data;
}

6.2 get 方法

所有 get() 方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
uint8_t  get();
uint8_t  get(uint32_t index) const;
void     getBytes(uint8_t* buf, uint32_t len);
void     getBytes(uint32_t index, uint8_t* buf, uint32_t len) const;
char     getChar();
char     getChar(uint32_t index) const;
uint16_t getShort();
uint16_t getShort(uint32_t index) const;
uint32_t getInt();
uint32_t getInt(uint32_t index) const;
uint64_t getLong();
uint64_t getLong(uint32_t index) const;
float    getFloat();
float    getFloat(uint32_t index) const;
double   getDouble();
double   getDouble(uint32_t index) const;

注意:带有 index 参数的方法不会改变 position_ 值。

看一下 void getBytes(uint32_t index, uint8_t* buf, uint32_t len) const 方法的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void ByteBuffer::getBytes(uint32_t index, uint8_t* buf, uint32_t len) const
{
    // 合法性检测
    if (!p_buffer_ || index + len > limit_)
        return;

    uint32_t pos = index;
    for (uint32_t i = 0; i < len; i++)
    {
        buf[i] = p_buffer_[pos++];
    }
}

为了实现只做一次合法性检测,并没有调用 read() 模板方法。

7. 其他方法

7.1 equals

函数原型:bool equals(ByteBuffer* other);

描述:比较两个 ByteBuffer 是否相等;

实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool equals(ByteBuffer* other)
{
    uint32_t len = limit();
    if (len != other->limit())
        return false;

    for (uint32_t i = 0; i < len; i++)
    {
        if (get(i) != other->get(i))
            return false;
    }
    return true;
}

7.2 duplicate

函数原型:ByteBuffer* duplicate();

描述:复制一个 ByteBuffer;

实现:新的 ByteBuffer 的 mark-1,不一样和原 ByteBuffer 相同。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer* ByteBuffer::duplicate()
{
    ByteBuffer* newBuffer = new ByteBuffer(capacity_);

    // copy data
    newBuffer->put(this);

    newBuffer->limit(limit_);
    newBuffer->position(position_);

    return newBuffer;
}

7.3 hasRemaining

函数原型:bool hasRemaining()

描述:表示 positionlimit 之间是否还有数据;

实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool hasRemaining()
{
    return limit_ > position_;
}

7.4 remaining

函数原型:uint32_t remaining() const

描述:返回 positionlimit 之间字节数;

实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
uint32_t remaining() const
{
    return position_ < limit_ ? limit_ - position_ : 0;
}

7.5 printInfo

函数原型:void printInfo() const

描述:打印 markpositionlimitcapacity 的值

实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void printInfo() const
{
    std::cout << "ByteBuffer " << name_ << ":\n"
        << "\tmark(" << mark_ << "), "
        << "position(" << position_ << "), "
        << "limit(" << limit_ << "), "
        << "capacity(" << capacity_ << ")." << std::endl;
}

8. ByteBuffer 的缺点

ByteBuffer 缺点如下:

  • ByteBuffer 并不是线程安全的,如果想要在并发情况下使用,需要自己为缓存区做同步控制;
  • ByteBuffer 长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当需要编码的对象大于 ByteBuffer 的容量时,会发生索引越界异常;
  • ByteBuffer 只有一个标识位的指针 position,读写的时候需要手工调用 flip()rewind()等,使用者必须小心谨慎地处理这些 API,否则很容易导致程序处理失败;
  • ByteBuffer 的 API 功能有限,一些高级和实用的特性它不支持,需要使用者自己编程实现。

本文的 C++ 实现只对第二点做了调整,支持了主动扩容;同样存在其它几个缺点。

ByteBuf 是 Netty 里的封装的数据缓存区,区别于 ByteBuffer 里需要 positionlimitcapacity 等属性来操作 ByteBuffer 数据读写,而 ByteBuf 是通过两个指针协助完成缓存区的读写操作,后续可能实现 C++ 版本的 ByteBuf 或者对当前 C++ ByteBuffer 进行修改。

参考链接

[1]

Class ByteBuffer: https://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html

[2]

java.nio.Buffer 中的 flip()方法: https://blog.csdn.net/hbtj_1216/article/details/53129588

[3]

ByteBuffer常用方法详解: https://blog.csdn.net/moakun/article/details/80630477

[4]

ByteBuffer详解: http://bcoder.com/java/explaination-of-bytebuffer

[5]

图解ByteBuffer和ByteBuf: https://www.gameboys.cn/article/193

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 C与Python实战 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
手机照片删除了怎么恢复?教你恢复照片的方法
手机照片删除了怎么恢复?前段时间在外面旅游拍摄了很多照片留在手机里面,为了把一些拍的不好的照片删除却不小心删除了之前的重要照片,想要把之前的照片恢复回来不知道该怎么办,手机里面的照片删除了如何找回?手机照片删除了怎么恢复?
科技第六人
2019/08/28
2.2K0
手机照片删除了怎么恢复?教你恢复照片的方法
手机相册里的照片误删怎么恢复?教你最全方法
  手机相册里的照片误删怎么恢复?很多人的手机中都会有太多的照片在手机里面,除了在外面拍摄的照片或者自拍,还有很多人喜欢从网上保存一些网图到手机里面,有时候因为手机卡顿,不得不去清理手机里面的照片,但是一时手误将其他照片也删除了怎么办?手机相册里的照片误删怎么恢复?
科技第六人
2019/10/25
1.7K0
手机相册里的照片误删怎么恢复?教你最全方法
手机照片删除了怎么恢复?这些方法你要会
        手机照片删除了怎么恢复?现在使用手机拍照的人越来越多了,几乎每个使用到手机的人手机相册中都会有很多的照片,无论是一些自拍照还是什么照片都会存放在手机里面。
科技第六人
2019/08/29
6570
手机照片删除了怎么恢复?这些方法你要会
手机误删照片怎么恢复?最好的方法教你恢复
  手机误删照片怎么恢复?我们手机上都会有很多的照片在手机里面,不过有时候会觉得里面的照片太多就想清理掉一些用不到的照片,不过少部分照片会掺杂在重要的照片中,但是删除可能会出现一些手残的情况,有时候会手误将重要的照片删除了该怎么办?手机误删照片怎么恢复?
科技第六人
2019/10/29
1.8K0
手机误删照片怎么恢复?最好的方法教你恢复
手机删除的照片如何恢复?超简单方法教你
  手机删除的照片如何恢复?现在来说很多人都比较喜欢使用手机拍照,如今的手机相机像素越来越好,喜欢拍照的小伙伴也很多,这样在手机中就会存入大量的照片在手机中,有时候还会去查看照片,当我们发现手机中有些照片被删除了怎么办?手机删除的照片如何恢复?
科技第六人
2019/09/26
6110
手机删除的照片如何恢复?超简单方法教你
手机相册里的照片误删怎么恢复?简单学习方法
  手机相册里的照片误删怎么恢复?在我们的相册中都会有很多的照片,尽管不喜欢拍照的小伙伴手机中也会有照片,既然存入在手机相册中那么肯定是比较重要或者有意义的,因为垃圾照片或者没用的照片我们都会去删除掉。但将手机中重要的照片删除了那么该怎么办?手机相册里的照片误删怎么恢复?
科技第六人
2019/11/13
1.7K0
手机相册里的照片误删怎么恢复?简单学习方法
手机照片误删怎么恢复?简单教你两招
  手机照片误删怎么恢复?随着手机上的功能越来越强大,如今手机相机是手机中比较重要的功能之一了,对于喜欢拍照的小伙伴来讲像素的要求都会非常高,不仅如此在手机中也是有很多的照片在手机中,不过有时候会清理掉手机中不需要的照片,若将重要的照片不小心删除了怎么办?手机照片误删怎么恢复?
科技第六人
2019/11/05
8600
手机照片误删怎么恢复?简单教你两招
手机照片删除了怎么恢复?比较实用的教程
  手机照片删除了怎么恢复?很多人对手机的像素都有很高的要求,喜欢拍照的人谁不需要像素高的手机呢?自然这些手机中就会存在很多的照片在手机中,不管是自己拍摄的照片还是从其他地方保存下来的照片都会在手机里,但是如果不小心删除了一些照片该怎么办呢?手机照片删除了怎么恢复?
科技第六人
2019/10/17
6430
苹果手机删除的照片如何恢复?最好的方法恢复
  苹果手机删除的照片如何恢复?在我们的手机中会存有大量的照片在里面,很多在外面拍摄的照片都存入到手机中,有时候我们会将一些不重要的照片删除,不仅可以释放手机空间还可以快速找到需要的照片,若不小心清除了重要的照片有什么方法可以恢复呢?苹果手机删除的照片如何恢复?
科技第六人
2019/10/31
1.7K0
苹果手机删除的照片如何恢复?最好的方法恢复
手机照片误删怎么恢复?轻松几分钟就能恢复
  手机照片误删怎么恢复?很多人的手机上都会存在很多照片在里面,不管是什么照片都会有一些的,不过有很多比较重要的照片在手机里面,而且有时候会去整理一些不需要的照片进行删除,当在整理照片的时候不小心删除一些照片怎么办?手机照片误删怎么恢复?
科技第六人
2019/08/21
8580
手机照片误删怎么恢复?轻松几分钟就能恢复
如何恢复手机删除的照片?最简单不过的方法
  如何恢复手机删除的照片?很多喜欢旅游的小伙伴手机里面肯定会有很多旅游照片,在手机上都会存很多的照片在手机里,有时候还会去看看里面的一些美好的回忆,不过有时候看到不用的照片就会删除掉。当我们发现一些照片被不小心删除了怎么办?如何恢复手机删除的照片?
科技第六人
2019/10/21
6990
如何恢复手机删除的照片?最简单不过的方法
如何恢复手机删除的短信?这样恢复才简单
  如何恢复手机删除的短信?现在经常会收到很多的短信,其实并不是什么重要的人发送的短信,而是一些垃圾消息,每天都能接收到很多类似的短信,时间久了这样的短信就开始堆积起来,想要找之前重要的短信都难就想删除掉。将一些重要的短信删除了怎么办?如何恢复手机删除的短信?
科技第六人
2019/09/04
2.5K0
如何恢复手机删除的短信?这样恢复才简单
手机照片误删怎么恢复?简单方法即可恢复
  手机照片误删怎么恢复?在我们的手机上都会有着很多的照片,不管是自己的照片还是他人的照片都会留存在手机里,有时候去查看的时候会发现有些照片不在手机中,貌似被删除了,可是不知道该怎么办?在我们手机中的照片删除了怎么办?手机照片误删怎么恢复?
科技第六人
2019/09/17
1.4K0
手机照片误删怎么恢复?简单方法即可恢复
手机删除的短信怎么恢复?简单几分钟教会你
  手机删除的短信怎么恢复?现在的社交软件逐渐开始变多,很久已经没有接触短信这样的功能了,现在随时随刻都能接收到各种各样的短信,所以我们也并没有在意很多,有些时候收到了重要的短信也在里面被一起清除了想要恢复该怎么办?手机删除的短信怎么恢复?
科技第六人
2019/10/15
2K0
手机相册里的照片误删怎么恢复?简单的两招
  手机相册里的照片误删怎么恢复?在我们手机中有很多比较重要的数据,尤其是照片是这些数据中量最多的,而且每张照片都是一些美好的回忆,不过有时候在查看手机照片是会不小心将照片删除,如果出现手机照片删除的情况该怎么办呢?手机相册里的照片误删怎么恢复?
科技第六人
2019/11/08
1.6K0
手机相册里的照片误删怎么恢复?简单的两招
手机删除的照片如何恢复?看完之后恍然大悟
  手机删除的照片如何恢复?在如今的颜值时代相机就起着很重要的作用了,在我们手机里面都会存在很多比较好看的照片,自己的自拍照当然也是少不了的,更何况想在的相机美颜技术实在太强,不过我们通常会删除手机中不好看的照片,那么将很多重要照片删除了怎么办?手机删除的照片如何恢复?
科技第六人
2019/09/10
6690
手机删除的照片如何恢复?看完之后恍然大悟
手机删除的照片如何恢复?分享两个小妙招
  手机删除的照片如何恢复?我们在生活中都会用到手机相机这一个手机功能,现在手机像素特别好,很多人都喜欢用手机来拍照,自然手机中也会有很多比较美好的照片,在手机中这些照片存在手机相册中而且还会去查看,如果手机中的重要照片被删除了怎么办?手机删除的照片如何恢复?
科技第六人
2019/08/13
1.2K0
手机删除的照片如何恢复?分享两个小妙招
手机删除的照片如何恢复?简单方法汇总
  手机删除的照片如何恢复?现在很多手机上都会存有大量的照片,一般存入的照片都是比较有纪念意义或者有价值的,但是也包括很多没有清理的垃圾照片,不过在清理过程中很容易出现照片误删的情况,如果遇到这种情况该怎么办呢?手机删除的照片如何恢复?
科技第六人
2019/10/10
9440
手机删除的照片如何恢复?简单方法汇总
苹果手机误删照片怎么恢复?教你有用的方法
  苹果手机误删照片怎么恢复?在很多小伙伴的手机中都会有很多照片在里面,很多照片可以说是比较重要的,但是不重要的照片也有挺多的,不过有时候会去清理掉手机中不需要的照片,如果在清理过程中将重要照片误删了怎么办?苹果手机误删照片怎么恢复呢?
科技第六人
2019/11/04
8830
苹果手机误删照片怎么恢复?教你有用的方法
微信删除的聊天记录怎么恢复?常用方法推荐
  微信删除的聊天记录怎么恢复?作为最主流的社交工具之一的微信相信很多人已经用过很久了,从而在我们微信里面就会有很多的聊天记录存在,重要的和不重要的聊天记录都会有,有时候会去清理一些文件时容易将聊天记录删除,那么怎么才可以恢复聊天记录?微信删除的聊天记录怎么恢复?
科技第六人
2019/10/18
2K0
微信删除的聊天记录怎么恢复?常用方法推荐
推荐阅读
相关推荐
手机照片删除了怎么恢复?教你恢复照片的方法
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验