字符串優化加強版–StringPool

字符串優化加強版–StringPool

之前寫過一篇string字符串優化相關的文章,但是裏面是使用一個static靜態變量。先看下之前的代碼

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Lemon
{
    public class QString
    {
        private static StringBuilder CommonStringBuilder = new StringBuilder();
        private static StringBuilder InternalStringBuilder = new StringBuilder();

        public static StringBuilder GetStringBuilder()
        {
            CommonStringBuilder.Remove(0, CommonStringBuilder.Length);
            return CommonStringBuilder;
        }

        public static string Concat(string s1, string s2)
        {
            InternalStringBuilder.Remove(0, InternalStringBuilder.Length);
            InternalStringBuilder.Append(s1);
            InternalStringBuilder.Append(s2);
            return InternalStringBuilder.ToString();
        }

        public static string Concat(string s1, string s2, string s3)
        {
            InternalStringBuilder.Remove(0, InternalStringBuilder.Length);
            InternalStringBuilder.Append(s1);
            InternalStringBuilder.Append(s2);
            InternalStringBuilder.Append(s3);
            return InternalStringBuilder.ToString();
        }

        public static string Format(string src, params object[] args)
        {
            InternalStringBuilder.Remove(0, InternalStringBuilder.Length);
            InternalStringBuilder.AppendFormat(src, args);
            return InternalStringBuilder.ToString();
        }

    }
}

QString裏面的stringbuilder一共是兩個,一個是給外部用的是CommonStringBuilder,一個是內部使用的InternalStringBuilder。

private static StringBuilder CommonStringBuilder = new StringBuilder();
private static StringBuilder InternalStringBuilder = new StringBuilder();

之前一直是在Unity裏面主線程上面使用的,沒有用到多線程。昨天寫東西用到了多線程,因此這裏僅一個靜態的stringbuilder已經無法滿足需求了,因此StringPool產生了,底層實現其實是對stringbuilder使用了pool形式。

大家還記得之前的ObjectPool的文章麼,今天就使用ObjectPool來封裝一個StringBuilderPool。先貼上ObjectPool的代碼。

/**
*   Author:onelei
*   Copyright © 2019 - 2020 ONELEI. All Rights Reserved
*/
using System.Collections.Generic;
using UnityEngine.Events;

public class ObjectPool<T> where T : new()
{
    private readonly Stack<T> m_Stack = new Stack<T>();
    private readonly UnityAction<T> m_ActionOnGet;
    private readonly UnityAction<T> m_ActionOnRelease;

    public int countAll { get; private set; }
    public int countActive { get { return countAll - countInactive; } }
    public int countInactive { get { return m_Stack.Count; } }

    public ObjectPool(UnityAction<T> actionOnGet, UnityAction<T> actionOnRelease)
    {
        m_ActionOnGet = actionOnGet;
        m_ActionOnRelease = actionOnRelease;
    }

    public T Get()
    {
        T element;
        if (m_Stack.Count == 0)
        {
            element = new T();
            countAll++;
        }
        else
        {
            element = m_Stack.Pop();
        }
        if (m_ActionOnGet != null)
            m_ActionOnGet(element);
        return element;
    }

    public void Release(T element)
    {
        if (m_Stack.Count > 0 && ReferenceEquals(m_Stack.Peek(), element))
            QLog.LogError("Internal error. Trying to destroy object that is already released to pool.");
        if (m_ActionOnRelease != null)
            m_ActionOnRelease(element);
        m_Stack.Push(element);
    }
}

ObjectPool代碼來自UGUI源碼。

下面我們看看StringPool的代碼如何實現

/**
*   Author:onelei
*   Copyright © 2019 - 2020 ONELEI. All Rights Reserved
*/
using System.Text;

public static class StringPool
{
    private static int MaxCount = 100;

    // Object pool to avoid allocations.
    private static readonly ObjectPool<StringBuilder> Pool = new ObjectPool<StringBuilder>(Clear, null);
    static void Clear(StringBuilder s)
    {
        if (Pool.countAll >= MaxCount)
        {
            QLog.LogError("Pool count reach to MaxCount.");
        }
        s.Remove(0, s.Length);
    }

    public static StringBuilder GetStringBuilder()
    {
        StringBuilder stringBuilder = Pool.Get();
        return stringBuilder;
    }

    public static void Release(StringBuilder toRelease)
    {
        if (Pool.countAll >= MaxCount)
        {
            QLog.LogError("Pool count reach to MaxCount.");
        }

        Pool.Release(toRelease);
    }

    public static string Concat(string s1, string s2)
    {
        StringBuilder stringBuilder = Pool.Get();
        stringBuilder.Append(s1);
        stringBuilder.Append(s2);
        string result = stringBuilder.ToString();
        Release(stringBuilder);
        return result;
    }

    public static string Concat(string s1, string s2, string s3)
    {
        StringBuilder stringBuilder = Pool.Get();
        stringBuilder.Append(s1);
        stringBuilder.Append(s2);
        stringBuilder.Append(s3);
        string result = stringBuilder.ToString();
        Release(stringBuilder);
        return result;
    }

    public static string Format(string src, params object[] args)
    {
        StringBuilder stringBuilder = Pool.Get();
        stringBuilder.Remove(0, stringBuilder.Length);
        stringBuilder.AppendFormat(src, args);
        string result = stringBuilder.ToString();
        Release(stringBuilder);
        return result;
    }
}

我們在StringPool裏面使用ObjectPool保存了一個Pool對象池。

// Object pool to avoid allocations.
    private static readonly ObjectPool<StringBuilder> Pool = new ObjectPool<StringBuilder>(Clear, null);
    static void Clear(StringBuilder s) { s.Remove(0, s.Length); }

同時在Pool的Get函數裏面傳入Clear函數,在獲取到StringBuilder的時候執行Clear函數清除裏面的數據。

接下來我們來看如何使用,其實和之前區別並不大,函數接口都沒有改動,我們只需要改函數的內部實現即可,先看下Concat函數

    public static string Concat(string s1, string s2)
    {
        StringBuilder stringBuilder = Pool.Get();
        stringBuilder.Append(s1);
        stringBuilder.Append(s2);
        string result = stringBuilder.ToString();
        Release(stringBuilder);
        return result;
    }

首先Pool.Get()在緩存池裏面獲取一個StringBuilder變量,在操作好之後執行Release函數,將其放回池中。同理Format函數亦是如此。

我們注意到ObjectPool裏面有這三個變量

    public int countAll { get; private set; }
    public int countActive { get { return countAll - countInactive; } }
    public int countInactive { get { return m_Stack.Count; } }

countAll是在每次new一個StringBuilder的時候自動加一,也就是池中的對象總數。

countInactive是在每次Release的時候放回m_Stack裏面的,也就是被放回池中的,未使用對象。

剩下的countActive就是已經被利用的對象數量。

我們爲了防止池沒有被合理使用,我們可以設置一個池中的對象總數,作爲代碼防護,防止內存泄漏。大部分情況下是對象在用完之後,沒有被及時執行Release函數,導致池中不斷的new一個對象出來,因此我們可以在Get函數裏面做一個安全判斷,當超過池中最大容量的時候,拋出一個錯誤。

    static void Clear(StringBuilder s)
    {
        if (Pool.countAll >= MaxCount)
        {
            QLog.LogError("Pool count reach to MaxCount.");
        }
        s.Remove(0, s.Length);
    }

我們在Get的函數的回調函數Clear裏面添加以上判斷即可。

如果本文對你有所幫助,歡迎讚賞~~~

歡迎關注微信公衆號:Unity遊戲開發筆記
在這裏插入圖片描述
QQ羣:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章