Unity多语言本地化改进版

摘要:
在介绍之前,我使用了一个小工具来通过csv配置游戏多语言支持,但在使用过程中发现通过notepad++进行转码很不方便,而且在没有加密的情况下直接将配置的csv放在游戏中心并不太实用,因此这是一个新的解决方案。1.分析PC/MAC平台上的多语言配置,即在编辑器运行环境中分析csv或excel。2.在编辑器运行过程中生成多个语言对象,然后对文件进行序列化和加密。3.使用用户端(移动端)的资源
简介

之前捣鼓过一个通过csv配置游戏多语言支持的小工具,但是发现使用过程中,通过notepad++去进行转码很不方便,并且直接将配置的csv不加密的放在游戏中心里感觉不是很踏实
于是乎~~

新的方案

1.在PC/MAC平台上解析多语言配置,也就是editor运行环境中解析csv或者excel
2.通过在Editor运行过程中生成多个语言对象,然后序列化并加密存盘
3.在使用端(移动端)通过resources加载加密之后的文件
4.读取对应的语言序列化文件并实例化加载到游戏中进行使用

使用方法

由于不想让csv文件被打包入游戏工程中,所以选择与Assets文件夹并列的路径文件:
PS:当然路径可以自己指定
这里写图片描述
然后里面存放多语言csv
这里写图片描述
然后代码中是调用是这样的:
这里写图片描述

代码实现

序列化反序列化工具


using Newtonsoft.Json;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

public static class SaveHelper
{

    private const string M_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

    public static bool IsFileExist(string filePath)
    {
        return File.Exists(filePath);
    }

    public static bool IsDirectoryExists(string filePath)
    {
        return Directory.Exists(filePath);
    }

    public static void CreateFile(string fileName, string content)
    {
        StreamWriter streamWriter = File.CreateText(fileName);
        streamWriter.Write(content);
        streamWriter.Close();
    }

    public static void CreateDirectory(string filePath)
    {
        if (IsDirectoryExists(filePath))
        {
            return;
        }
        Directory.CreateDirectory(filePath);
    }

    private static string SerializeObject(object pObject)
    {
        string serializedString = string.Empty;
        serializedString = JsonConvert.SerializeObject(pObject);
        return serializedString;
    }

    private static object DeserializeObject(string pString, Type pType)
    {
        object deserializedObject = null;
        deserializedObject = JsonConvert.DeserializeObject(pString, pType);
        return deserializedObject;
    }

    private static string RijndaelEncrypt(string pString, string pKey)
    {
        byte[] keyArray = UTF8Encoding.UTF8.GetBytes(pKey);
        byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(pString);
        RijndaelManaged rDel = new RijndaelManaged();
        rDel.Key = keyArray;
        rDel.Mode = CipherMode.ECB;
        rDel.Padding = PaddingMode.PKCS7;
        ICryptoTransform cTransform = rDel.CreateEncryptor();
        byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
        return Convert.ToBase64String(resultArray, 0, resultArray.Length);
    }

    private static String RijndaelDecrypt(string pString, string pKey)
    {
        byte[] keyArray = UTF8Encoding.UTF8.GetBytes(pKey);
        byte[] toEncryptArray = Convert.FromBase64String(pString);
        RijndaelManaged rDel = new RijndaelManaged();
        rDel.Key = keyArray;
        rDel.Mode = CipherMode.ECB;
        rDel.Padding = PaddingMode.PKCS7;
        ICryptoTransform cTransform = rDel.CreateDecryptor();
        byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
        return UTF8Encoding.UTF8.GetString(resultArray);
    }

    public static void SaveData(string fileName, object pObject)
    {
        // 如果文件已存在,则删除
        if (File.Exists(fileName))
        {
            File.Delete(fileName);
        }
        string toSave = SerializeObject(pObject);
        toSave = RijndaelEncrypt(toSave, M_KEY);
        StreamWriter streamWriter = File.CreateText(fileName);
        streamWriter.Write(toSave);
        streamWriter.Close();
    }

    public static object ReadData(string str, Type pType, bool isFile = true)
    {
        string data;
        if (isFile)
        {
            // 如果文件不存在,则返回空
            if (!File.Exists(str))
            {
                return null;
            }
            StreamReader streamReader = File.OpenText(str);
            data = streamReader.ReadToEnd();
            streamReader.Close();
        }
        else
        {
            data = str;
        }

        data = RijndaelDecrypt(data, M_KEY);
        return DeserializeObject(data, pType);
    }
}

CSV解析器

using System.Collections.Generic;
using System.IO;
using System.Text;

// col是竖行,row是横排,防止我忘了
public class LTCSVLoader
{

    private TextReader inStream = null;

    private List<string> vContent;

    private List<List<string>> table;

    /// <summary>
    /// 只支持GBK2312的编码(WPS直接保存的编码支持,仅提供给Windows使用)
    /// </summary>
    /// <param name="fileName"></param>
    public void ReadFile(string fileName)
    {
        inStream = new StreamReader(fileName, Encoding.GetEncoding("GBK"));
        table = new List<List<string>>();
        List<string> temp = this.getLineContentVector();
        while (null != temp)
        {
            List<string> tempList = new List<string>();
            for (int i = 0; i < temp.Count; ++i)
            {
                tempList.Add(temp[i]);
            }
            table.Add(tempList);
            temp = this.getLineContentVector();
        }
    }

    /// <summary>
    /// 目前只支持UTF-8的编码(WPS直接保存的编码不支持)
    /// </summary>
    /// <param name="str"></param>
    public void ReadMultiLine(string str)
    {
        inStream = new StringReader(str);

        table = new List<List<string>>();
        List<string> temp = this.getLineContentVector();
        while (null != temp)
        {
            List<string> tempList = new List<string>();
            for (int i = 0; i < temp.Count; ++i)
            {
                tempList.Add(temp[i]);
            }
            table.Add(tempList);
            temp = this.getLineContentVector();
        }
    }

    private int containsNumber(string parentStr, string parameter)
    {
        int containNumber = 0;
        if (parentStr == null || parentStr.Equals(""))
        {
            return 0;
        }
        if (parameter == null || parameter.Equals(""))
        {
            return 0;
        }
        for (int i = 0; i < parentStr.Length; i++)
        {
            i = parentStr.IndexOf(parameter, i);
            if (i > -1)
            {
                i = i + parameter.Length;
                i--;
                containNumber = containNumber + 1;
            }
            else
            {
                break;
            }
        }
        return containNumber;
    }

    private bool isQuoteAdjacent(string p_String)
    {
        bool ret = false;
        string temp = p_String;
        temp = temp.Replace("""", "");
        if (temp.IndexOf(""") == -1)
        {
            ret = true;
        }
        return ret;
    }

    private bool isQuoteContained(string p_String)
    {
        bool ret = false;
        if (p_String == null || p_String.Equals(""))
        {
            return false;
        }
        if (p_String.IndexOf(""") > -1)
        {
            ret = true;
        }
        return ret;
    }

    private string[] readAtomString(string lineStr)
    {
        string atomString = "";// 要读取的原子字符串
        string orgString = "";// 保存第一次读取下一个逗号时的未经任何处理的字符串
        string[] ret = new string[2];// 要返回到外面的数组
        bool isAtom = false;// 是否是原子字符串的标志
        string[] commaStr = lineStr.Split(new char[] { ',' });
        while (!isAtom)
        {
            foreach (string str in commaStr)
            {
                if (!atomString.Equals(""))
                {
                    atomString = atomString + ",";
                }
                atomString = atomString + str;
                orgString = atomString;
                if (!isQuoteContained(atomString))
                {
                    // 如果字符串中不包含引号,则为正常,返回
                    isAtom = true;
                    break;
                }
                else
                {
                    if (!atomString.StartsWith("""))
                    {
                        // 如果字符串不是以引号开始,则表示不转义,返回
                        isAtom = true;
                        break;
                    }
                    else if (atomString.StartsWith("""))
                    {
                        // 如果字符串以引号开始,则表示转义
                        if (containsNumber(atomString, """) % 2 == 0)
                        {
                            // 如果含有偶数个引号
                            string temp = atomString;
                            if (temp.EndsWith("""))
                            {
                                temp = temp.Replace("""", "");
                                if (temp.Equals(""))
                                {
                                    // 如果temp为空
                                    atomString = "";
                                    isAtom = true;
                                    break;
                                }
                                else
                                {
                                    // 如果temp不为空,则去掉前后引号
                                    temp = temp.Substring(1, temp.LastIndexOf("""));
                                    if (temp.IndexOf(""") > -1)
                                    {
                                        // 去掉前后引号和相邻引号之后,若temp还包含有引号
                                        // 说明这些引号是单个单个出现的
                                        temp = atomString;
                                        temp = temp.Substring(1);
                                        temp = temp.Substring(0, temp.IndexOf("""))
                                                + temp.Substring(temp.IndexOf(""") + 1);
                                        atomString = temp;
                                        isAtom = true;
                                        break;
                                    }
                                    else
                                    {
                                        // 正常的csv文件
                                        temp = atomString;
                                        temp = temp.Substring(1, temp.LastIndexOf("""));
                                        temp = temp.Replace("""", """);
                                        atomString = temp;
                                        isAtom = true;
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                // 如果不是以引号结束,则去掉前两个引号
                                temp = temp.Substring(1, temp.IndexOf('"', 1))
                                        + temp.Substring(temp.IndexOf('"', 1) + 1);
                                atomString = temp;
                                isAtom = true;
                                break;
                            }
                        }
                        else
                        {
                            // 如果含有奇数个引号
                            if (!atomString.Equals("""))
                            {
                                string tempAtomStr = atomString.Substring(1);
                                if (!isQuoteAdjacent(tempAtomStr))
                                {
                                    // 这里做的原因是,如果判断前面的字符串不是原子字符串的时候就读取第一个取到的字符串
                                    // 后面取到的字符串不计入该原子字符串
                                    tempAtomStr = atomString.Substring(1);
                                    int tempQutoIndex = tempAtomStr.IndexOf(""");
                                    // 这里既然有奇数个quto,所以第二个quto肯定不是最后一个
                                    tempAtomStr = tempAtomStr.Substring(0, tempQutoIndex)
                                            + tempAtomStr.Substring(tempQutoIndex + 1);
                                    atomString = tempAtomStr;
                                    isAtom = true;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
        // 先去掉之前读取的原字符串的母字符串
        if (lineStr.Length > orgString.Length)
        {
            lineStr = lineStr.Substring(orgString.Length);
        }
        else
        {
            lineStr = "";
        }
        // 去掉之后,判断是否以逗号开始,如果以逗号开始则去掉逗号
        if (lineStr.StartsWith(","))
        {
            if (lineStr.Length > 1)
            {
                lineStr = lineStr.Substring(1);
            }
            else
            {
                lineStr = "";
            }
        }
        ret[0] = atomString;
        ret[1] = lineStr;
        return ret;
    }

    private bool readCSVNextRecord()
    {
        // 如果流未被初始化则返回false
        if (inStream == null)
        {
            return false;
        }
        // 如果结果向量未被初始化,则初始化
        if (vContent == null)
        {
            vContent = new List<string>();
        }
        // 移除向量中以前的元素
        vContent.Clear();
        // 声明逻辑行
        string logicLineStr = "";
        // 用于存放读到的行
        StringBuilder strb = new StringBuilder();
        // 声明是否为逻辑行的标志,初始化为false
        bool isLogicLine = false;
        while (!isLogicLine)
        {
            string newLineStr = inStream.ReadLine();
            if (newLineStr == null)
            {
                strb = null;
                vContent = null;
                isLogicLine = true;
                break;
            }
            if (newLineStr.StartsWith("#"))
            {
                // 去掉注释
                continue;
            }
            if (!strb.ToString().Equals(""))
            {
                strb.Append("
");
            }
            strb.Append(newLineStr);
            string oldLineStr = strb.ToString();
            if (oldLineStr.IndexOf(",") == -1)
            {
                // 如果该行未包含逗号
                if (containsNumber(oldLineStr, """) % 2 == 0)
                {
                    // 如果包含偶数个引号
                    isLogicLine = true;
                    break;
                }
                else
                {
                    if (oldLineStr.StartsWith("""))
                    {
                        if (oldLineStr.Equals("""))
                        {
                            continue;
                        }
                        else
                        {
                            string tempOldStr = oldLineStr.Substring(1);
                            if (isQuoteAdjacent(tempOldStr))
                            {
                                // 如果剩下的引号两两相邻,则不是一行
                                continue;
                            }
                            else
                            {
                                // 否则就是一行
                                isLogicLine = true;
                                break;
                            }
                        }
                    }
                }
            }
            else
            {
                // quotes表示复数的quote
                string tempOldLineStr = oldLineStr.Replace("""", "");
                int lastQuoteIndex = tempOldLineStr.LastIndexOf(""");
                if (lastQuoteIndex == 0)
                {
                    continue;
                }
                else if (lastQuoteIndex == -1)
                {
                    isLogicLine = true;
                    break;
                }
                else
                {
                    tempOldLineStr = tempOldLineStr.Replace("","", "");
                    lastQuoteIndex = tempOldLineStr.LastIndexOf(""");
                    if (lastQuoteIndex == 0)
                    {
                        continue;
                    }
                    if (tempOldLineStr[lastQuoteIndex - 1] == ',')
                    {
                        continue;
                    }
                    else
                    {
                        isLogicLine = true;
                        break;
                    }
                }
            }
        }
        if (strb == null)
        {
            // 读到行尾时为返回
            return false;
        }
        // 提取逻辑行
        logicLineStr = strb.ToString();
        if (logicLineStr != null)
        {
            // 拆分逻辑行,把分离出来的原子字符串放入向量中
            while (!logicLineStr.Equals(""))
            {
                string[] ret = readAtomString(logicLineStr);
                string atomString = ret[0];
                logicLineStr = ret[1];
                vContent.Add(atomString);
            }
        }
        return true;
    }

    private List<string> getLineContentVector()
    {
        if (this.readCSVNextRecord())
        {
            return this.vContent;
        }
        return null;
    }

    private List<string> getVContent()
    {
        return this.vContent;
    }

    public int GetRow()
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,请检查是否成功读取");
        }
        return table.Count;
    }

    public int GetCol()
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,请检查是否成功读取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table内容为空");
        }
        return table[0].Count;
    }

    public int GetFirstIndexAtCol(string str, int col)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,请检查是否成功读取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table内容为空");
        }
        if (col >= table[0].Count)
        {
            throw new System.Exception("参数错误:col大于最大行");
        }
        for (int i = 0; i < table.Count; ++i)
        {
            if (table[i][col].Equals(str))
            {
                return i;
            }
        }
        return -1;
    }

    public int GetFirstIndexAtRow(string str, int row)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,请检查是否成功读取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table内容为空");
        }
        if (row >= table.Count)
        {
            throw new System.Exception("参数错误:cow大于最大列");
        }
        int tempCount = table[0].Count;
        for (int i = 0; i < tempCount; ++i)
        {
            if (table[row][i].Equals(str))
            {
                return i;
            }
        }
        return -1;
    }

    public int[] GetIndexsAtCol(string str, int col)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,请检查是否成功读取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table内容为空");
        }
        if (col >= table[0].Count)
        {
            throw new System.Exception("参数错误:col大于最大行");
        }
        List<int> tempList = new List<int>();
        for (int i = 0; i < table.Count; ++i)
        {
            if (table[i][col].Equals(str))
            {
                // 增加
                tempList.Add(i);
            }
        }
        return tempList.ToArray();
    }

    public int[] GetIndexsAtRow(string str, int row)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,请检查是否成功读取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table内容为空");
        }
        if (row >= table.Count)
        {
            throw new System.Exception("参数错误:cow大于最大列");
        }
        int tempCount = table[0].Count;
        List<int> tempList = new List<int>();
        for (int i = 0; i < tempCount; ++i)
        {
            if (table[row][i].Equals(str))
            {
                tempList.Add(i);
            }
        }
        return tempList.ToArray();
    }

    public string GetValueAt(int col, int row)
    {
        if (null == table)
        {
            throw new System.Exception("table尚未初始化,请检查是否成功读取");
        }
        if (table.Count == 0)
        {
            throw new System.Exception("table内容为空");
        }
        if (row >= table.Count)
        {
            throw new System.Exception("参数错误:row大于最大列");
        }
        if (col >= table[0].Count)
        {
            throw new System.Exception("参数错误:col大于最大行");
        }
        return table[row][col];
    }

}

多语言实现类

using UnityEngine;
using System.Collections.Generic;

public class LTLocalization
{

    public const string LANGUAGE_ENGLISH = "EN";
    public const string LANGUAGE_CHINESE = "CN";
    public const string LANGUAGE_JAPANESE = "JP";
    public const string LANGUAGE_FRENCH = "FR";
    public const string LANGUAGE_GERMAN = "GE";
    public const string LANGUAGE_ITALY = "IT";
    public const string LANGUAGE_KOREA = "KR";
    public const string LANGUAGE_RUSSIA = "RU";
    public const string LANGUAGE_SPANISH = "SP";

    private const string KEY_CODE = "KEY";
    private const string FILE_PATH = "LTLocalization/localization";

    private SystemLanguage language = SystemLanguage.Chinese;
    private Dictionary<string, string> textData = new Dictionary<string, string>();

    private static LTLocalization mInstance;

    private LTLocalization()
    {
    }

    private static string GetLanguageAB(SystemLanguage language)
    {
        switch (language)
        {
            case SystemLanguage.Afrikaans:
            case SystemLanguage.Arabic:
            case SystemLanguage.Basque:
            case SystemLanguage.Belarusian:
            case SystemLanguage.Bulgarian:
            case SystemLanguage.Catalan:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.Chinese:
            case SystemLanguage.ChineseTraditional:
            case SystemLanguage.ChineseSimplified:
                return LANGUAGE_CHINESE;
            case SystemLanguage.Czech:
            case SystemLanguage.Danish:
            case SystemLanguage.Dutch:
            case SystemLanguage.English:
            case SystemLanguage.Estonian:
            case SystemLanguage.Faroese:
            case SystemLanguage.Finnish:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.French:
                return LANGUAGE_FRENCH;
            case SystemLanguage.German:
                return LANGUAGE_GERMAN;
            case SystemLanguage.Greek:
            case SystemLanguage.Hebrew:
            case SystemLanguage.Icelandic:
            case SystemLanguage.Indonesian:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.Italian:
                return LANGUAGE_ITALY;
            case SystemLanguage.Japanese:
                return LANGUAGE_JAPANESE;
            case SystemLanguage.Korean:
                return LANGUAGE_KOREA;
            case SystemLanguage.Latvian:
            case SystemLanguage.Lithuanian:
            case SystemLanguage.Norwegian:
            case SystemLanguage.Polish:
            case SystemLanguage.Portuguese:
            case SystemLanguage.Romanian:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.Russian:
                return LANGUAGE_RUSSIA;
            case SystemLanguage.SerboCroatian:
            case SystemLanguage.Slovak:
            case SystemLanguage.Slovenian:
                return LANGUAGE_ENGLISH;
            case SystemLanguage.Spanish:
                return LANGUAGE_SPANISH;
            case SystemLanguage.Swedish:
            case SystemLanguage.Thai:
            case SystemLanguage.Turkish:
            case SystemLanguage.Ukrainian:
            case SystemLanguage.Vietnamese:
            case SystemLanguage.Unknown:
                return LANGUAGE_ENGLISH;
        }
        return LANGUAGE_CHINESE;
    }

    private static string GetWinReadPath(string fileName)
    {
        return Application.dataPath + "/../" + fileName + ".csv";
    }

    private static string GetWinSavePath(string fileName)
    {
        return Application.dataPath + "/Resources/LTLocalization/" + fileName + ".txt";
    }

    private void ReadData()
    {
#if UNITY_EDITOR
        // 在Windows平台下读取语言配置文件
        string CSVFilePath = GetWinReadPath(FILE_PATH);
        LTCSVLoader loader = new LTCSVLoader();
        loader.ReadFile(CSVFilePath);
        // 将配置文件序列化为多个语言类
        int csvRow = loader.GetRow();
        int csvCol = loader.GetCol();
        Debug.Log("row:" + csvRow + "col:" + csvCol);
        for (int tempCol = 1; tempCol < csvCol; ++tempCol)
        {
            LTLocalizationData languageData = new LTLocalizationData();
            // 获取第一行数据(语言类型)
            languageData.LanguageType = loader.GetValueAt(tempCol, 0);
            // 遍历生成变量
            languageData.LanguageData = new Dictionary<string, string>();
            for (int tempRow = 1; tempRow < csvRow; ++tempRow)
            {
                languageData.LanguageData.Add(loader.GetValueAt(0, tempRow), loader.GetValueAt(tempCol, tempRow));
            }
            // 将语言对象序列化存档
            SaveHelper.SaveData(GetWinSavePath(languageData.LanguageType), languageData);

            if (GetLanguageAB(language).Equals(languageData.LanguageType))
            {
                textData = languageData.LanguageData;
            }
        }
#else
        // 读取对应的语言对象
        TextAsset tempAsset = (TextAsset)Resources.Load("LTLocalization/" + GetLanguageAB(language), typeof(TextAsset));
        if (null == tempAsset)
        {
            tempAsset = (TextAsset)Resources.Load("LTLocalization/" + "EN", typeof(TextAsset));
        }
        if (null == tempAsset)
        {
            Debug.LogError("未检测到语言配置文件");
        }
        else
        {
            string saveData = tempAsset.text;
            LTLocalizationData currentLanguageData = (LTLocalizationData)SaveHelper.ReadData(saveData, typeof(LTLocalizationData), false);
            textData = currentLanguageData.LanguageData;
        }
#endif
    }

    private void SetLanguage(SystemLanguage language)
    {
        this.language = language;
    }

    public static void Init()
    {
        mInstance = new LTLocalization();
        mInstance.SetLanguage(Application.systemLanguage);
        mInstance.ReadData();
    }

    public static void ManualSetLanguage(SystemLanguage setLanguage)
    {
        if (null == mInstance)
        {
            mInstance = new LTLocalization();
        }
        mInstance.SetLanguage(setLanguage);
        mInstance.ReadData();
    }

    public static string GetText(string key)
    {
        if (null == mInstance)
        {
            Init();
        }
        if (mInstance.textData.ContainsKey(key))
        {
            return mInstance.textData[key];
        }
        return "[NoDefine]" + key;
    }

}

多语言数据对象

using UnityEngine;
using System.Collections.Generic;

public class LTLocalizationData
{

    public string LanguageType;

    public Dictionary<string, string> LanguageData;

    public override string ToString()
    {
        string result = "LanguageType:" + LanguageType;
        List<string> tempKeys = new List<string>(LanguageData.Keys);
        for (int i = 0; i < tempKeys.Count; ++i)
        {
            result += "
Key:[" + tempKeys[i] + "]|Value:[" + LanguageData[tempKeys[i]] + "]";
        }
        return result;
    }

}

总结

感觉比上一个版本好了很多
1.不用考虑csv编码的问题了
2.反序列化速度比读取csv更快
3.加了密更可靠
4.不同语言分开读取,占用内存更小
缺点嘛,暂时觉得还不错~~继续先用着,有问题再改

免责声明:文章转载自《Unity多语言本地化改进版》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇C#线程篇---Task(任务)和线程池不得不说的秘密(5)在OpenWrt中安装Wiwiz实现portal认证下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

【学习】026 Zookeeper

什么Zookeeper Zookeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance)等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKe...

25 Zabbix系统数据表结构介绍

点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 25 Zabbix系统数据表结构介绍自学Zabbix之路15.1 Zabbix数据库表结构简单解析-Hosts表、Hosts_groups表、Interface表自学Zabbix之路15.2 Zabbix数据库表结构简单解析-Items表自学Zabbix之...

MySQL之对数据库库表的字符集的更改

数据字符集修改步骤: 对于已有的数据库想修改字符集不能直接通过 "alter database character set *"或 "alter table tablename character set *",这两个命令都没有更新已有记录的字符集,而只是对新创建的表或者记录生效。 已经有记录的字符集的调整,必须先将数据导出,经过修改字符集后重新导入后才可...

[转]C# 将类的内容写成JSON格式的字符串

将类的内容写入到JSON格式的字符串中 本例中建立了Person类,赋值后将类中内容写入到字符串中 运行本代码需要添加引用动态库Newtonsoft.Json 程序代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; using S...

PHP执行系统命令的有几个常用的函数

PHP执行系统命令的有几个常用的函数,如有:system函数、exec函数、popen函数,passthru,shell_exec函数他们都可以执行系统命令,不过前提时必须系统给了权限了哦。 system函数 说明:执行外部程序并显示输出资料。 语法:string system(string command, int [return_var]); 返回值:...

(转)Asp.Net(C#) XML+Xslt转Excel的解决方案

1. 新建一个Excel文档,并填写表头与两行左右的内容,然后另存为XML表格 格式 并修改成Xslt模板;2. 将要导入的数据生成XML格式文档;3. 通过Xslt模板将数据生成,并设定Response.ContentType = "application/vnd.ms-excel"; 4. 刷新输出页保存文件即为Excel格式的文档 ExportCar...