android底层库libutils之string8,string16研究

一、sharedBuffer&string如何牵手的      

在之前看android camera framework代码时,发现代码中大量使用了string8string16类。由于之前学习的是C#java等高级面向对象语言唯独没有认真研究过C++。所以对C++中的string比较陌生。虽然他们的用法都是一样的,但是这里还是想认真研究一下底层实现原理。

     string8和string16原理基本上是一样的,这里我们就分析常用的string8了,说到string8,我们就能想到和它有千丝万缕的sharedbuffer类。string8的内存就是通过sharebuffer类来管理的。每个string8对象对应一个sharebuffer的对象,sharedBuffer对象的下面就是我们的string8的内存空间。string8类中有一个mString的私有域就是指向sharedBuffer下面开始处的,如下图所示。



    为了说明string8对象就是sharedBuffer下面的内存空间,我们来看看shredBuffer对象的的的alloc方法。可以看到在申请了sizeof(SharedBuffer)+size大小的空间,其中size就是我们字符串大小。

SharedBuffer* SharedBuffer::alloc(size_t size)
{
    SharedBuffer* sb = static_cast<SharedBuffer *>(malloc(sizeof(SharedBuffer) + size));
    if (sb) {
        sb->mRefs = 1;
        sb->mSize = size;
    }
    return sb;
}

上面的sharedBuffer类我们只看到确实是多申请了Buffer,没看到是不是string8真的指向sharedBuffer对象下面的空间呢。我们来看看String8常用构造函数,下面的调用了allocFromUTF8方法。我们继续来到allocFromUTF8 慌然看到了sharedBuffer::alloc()方法,而且申请的大小是len+1,大家想想为啥还要+1.考虑一会...................

时间到了,大家在学习C语言的时候,都要在最后加一个'\n'换行符,也就是下面的str[len]=0;这点我思考了10分钟。哎。我们在看看这行代码char* str = (char*)buf->data();针对sharedBuffer的data方法,我们在下面会看到返回的是this+1.由于this指针的类型是sharedBuffer类型,所以+1就意味这加了sizeof(sharedBuffer)大小,加上这么大的大小就指向了紧挨着sharedBuffer的空间了,就是上图所示那样。不知道大家明白了没有。

String8::String8(const char* o)
    : mString(allocFromUTF8(o, strlen(o)))  //调用<span style="font-family:SimSun;">下面的函数</span>
{
    if (mString == NULL) {
        mString = getEmptyString();
    }
}

static char* allocFromUTF8(const char* in, size_t len)
{
    if (len > 0) {
        SharedBuffer* buf = SharedBuffer::alloc(len+1); //为什么<span style="font-family:SimSun;">多+1???</span>
        ALOG_ASSERT(buf, "Unable to allocate shared buffer");
        if (buf) {
            char* str = (char*)buf->data();  //笔锋转到下面的data方法
            memcpy(str, in, len);
            str[len] = 0;
            return str;
        }
        return NULL;
    }

    return getEmptyString();
}

//shardBuffer的data方法
const void* SharedBuffer::data() const {
    return this + 1;
}

二、sharedBuffer类介绍 

  下图是sharedBuffer类的全部属性和部分方法。从名字上来看,该类视乎和共享buffer有关系,其实仔细看看代码,它就只是对底层标准malloc等c库函数的再一次封装。我们就来给大家介绍几个我自认为重要的方法吧。




sharedBuffer::editResize()方法就是重新设置对象的大小。它的实现相对来说还是很简单的(其实就没难的地方,呵呵)。此方法在string8::lockBuffer()方法中有用到,在调用lockbuffer时,如果只有对象本身在用自己的buffer,那么就把自己的内存空间变小了(自我解脱了),如果还有别的引用对象在使用这片buffer的话,可以使用该引用对象调用lockbuffer(),那么它就会重新申请内存空间,并拷贝我们指定大小的空间,保证了源对象不会别破坏。好了,我们来说说editResize()函数吧,它的字面意思就是重新定义对象的大小了,具体它的注释,我就写在代码中了。如果想看代码,可以下载 附件
SharedBuffer* SharedBuffer::editResize(size_t newSize) const
{
    if (onlyOwner()) { //这里判断是不是只有owner在用这个sharedBuffer对象,具体代码请看附件吧
        SharedBuffer* buf = const_cast<SharedBuffer*>(this);//拿到当前对象的指针
        if (buf->mSize == newSize) return buf;
        buf = (SharedBuffer*)realloc(buf, sizeof(SharedBuffer) + newSize); //重新申请一个sharedBuffer对象,realloc会申请制定大小的内存并拷贝
        if (buf != NULL) {
            buf->mSize = newSize;
            return buf;  //返回这个新建的sharedBuffer对象,看来这只能在sharedBuffer类中使用。
        }
    }
    SharedBuffer* sb = alloc(newSize); //如果不只有一个拥有者,存在引用对象,那么就重新申请一个sharedBuffer对象,防止对源数据的影响。
    if (sb) {
        const size_t mySize = mSize;
        memcpy(sb->data(), data(), newSize < mySize ? newSize : mySize); //拷贝数据
        release(); //释放当前对象了。
    }
    return sb;//返回新创建的sharedBuffer对象
}
这个函数就是根据当前string对象,找到它的知己sharedBuffer对象,奇怪的地方就是代码中为什么-1,
SharedBuffer* SharedBuffer::bufferFromData(void* data) {
    return data ? static_cast<SharedBuffer *>(data)-1 : 0;  //为什么要-1,减1就指向了sharedBuffer对象地址了。
}
size_t SharedBuffer::sizeFromData(const void* data) {
    return data ? bufferFromData(data)->mSize : 0; //这是给sring类调用,用于返回string对象内存大小
}

bool SharedBuffer::onlyOwner() const {
    return (mRefs == 1); //引用计数器为1时,只有创建者自己在使用此对象。
}

三、string8类介绍

下图是string8全部的属性和部分方法,同样这里我也只贴出部分我自认为重要的方法,来分析一下,详细伙计们还是看源码吧。



下面这个方法一看大家都明白,就是为当前string对象设置数据了
status_t String8::setTo(const char* other)
{
    const char *newString = allocFromUTF8(other, strlen(other)); //申请新的内存空间,其实生成了新的sharedBuffer对象
    SharedBuffer::bufferFromData(mString)->release(); //释放旧的内存空间
    mString = newString;
    if (mString) return NO_ERROR;

    mString = getEmptyString();
    return NO_MEMORY;
}

void String8::setTo(const String8& other)
{
    SharedBuffer::bufferFromData(other.mString)->acquire(); //被引用的string8对象,引用计数+1.
    SharedBuffer::bufferFromData(mString)->release();
    mString = other.mString;
}
status_t String8::append(const char* other) //在原有数据基础上,添加对应的数据,还是看源码吧^_^
{
    return append(other, strlen(other));
}
此函数有点不太好理解,我在本地调试时,试了很多情况,结果也没变化。后来发现原来是引用计数+1,-1原子操作时存在问题。当一个对象存在多个引用对象时,调用lockBuffer()方法,会返回一个新的string对象,以保护源数据。
char* String8::lockBuffer(size_t size)
{
    SharedBuffer* buf = SharedBuffer::bufferFromData(mString) //找到当前string对象的好基友sharedBuffer对象
        ->editResize(size+1);
    if (buf) {
        char* str = (char*)buf->data();
        mString = str;
        return str;
    }
    return NULL;
}

四、测试代码

下面只列出了main函数的实现内容,完整的代码大家可以下载 附件,这里就是演示一下常用的函数。
int main(int argc, char **argv)
{
	android::initialize_string8();
	android::initialize_string16();
// start of your code

	String8 str1("armwind");
	String8 str2(" is a good man! ^_^");
	String8 add8 = str1 + str2; //这个在类中有重载运算符的实现
	cout<<"name:"<<add8.string()<<endl;
	add8.toUpper();
	cout<<"toUpper:"<<add8.string()<<endl;
        String8 &str4 =add8; //下面3行都是在后来添加的
        str4.setTo(str3); //add8的引用计数会+1
        str4.setTo(str3);
        cout<<"lockBuffer:"<<add8.lockBuffer(8)<<endl;//这里调用lockBuffer,会返回一个字符串,显示的只是我们想要访问的8字节。
	String8 str3 = add8;
	cout<<"lockBuffer_test:"<<str3.string()<<endl;

	add8.setTo("hello");//重新设置成某个字符串
	cout<<"name:"<<add8.string()<<endl;

	add8.append(" is a good boy!"); //扩展
	cout<<"name:"<<add8.string()<<endl;

	add8.clear();
	cout<<"clear:"<<add8.string()<<endl;
	cout<<"format:"<<String8::format("hello %c","world").string()<<endl;

//end of your code
	android::terminate_string16();
	android::terminate_string8();

    return kPassed;
}

打印结果:
name:armwind is a good man! ^_^
toUpper:ARMWIND IS A GOOD MAN! ^_^
lockBuffer:ARMWIND IS A GOOD MAN! ^_^
lockBuffer_test:ARMWIND IS A GOOD MAN! ^_^
name:hello
name:hello is a good boy!
clear:
format:hello p

分析:上面的代码只是部分main函数,完整的代码大家可以下载 附件,除了initialize_string()和terminate_string()函数,其它的函数都是在探究一下string8的特性。目前在调用format函数时,输出的结果并不是我们意向中的“hello world”,这里可能在移植到x86下,一些数据类型不匹配导致的吧。具体原因我就不找了,如果大家找到原因了,可以贴到评论上^_^,此外示例源码中android_atomic_add()函数也存在问题,即在执行+1,-1操作时,没有将修改后的值赋给引用计数器(大家下载下来自己动手改改吧,我就不上传了)。这样就会导致后面lockbuffer()时探索遇到了点问题。

开始和结尾的init代码(如下所示),本来是在static.cpp代码中调用的,这里由于不想在加一个源文件,就直接在这里调用了。
    android::initialize_string8();
    android::initialize_string16();

    android::terminate_string16();
    android::terminate_string8();
下面可以看到,在类的构造函数和析构函数中,调用了相应的初始化函数和终结函数。而在类声明的下方就定义了LibUtilsFirstStatics 对象 gFirstStatics 该对象其它文件中也没调用,也许就是为了调用init函数和终结函数吧。
class LibUtilsFirstStatics
{
public:
    LibUtilsFirstStatics()
    {
        initialize_string8();
        initialize_string16();
    }
    
    ~LibUtilsFirstStatics()
    {
        terminate_string16();
        terminate_string8();
    }
};

static LibUtilsFirstStatics gFirstStatics;

五、调试过程中遇到的问题

1) 在调试过程,由于命名空间的使用问题,导致一些方法找不到,编译不过。解决办法就是添加对应的命名空间

2) 假如在类A中使用了类B,在没有包含类B时,编译时提示找不到类B相应的方法。解决办法就是包含对应的类,如直接包含class String8; 结构体也可以这样包含。

3) main函数不能包含在命名空间中,要不然编译会出问题的。



  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值