520程序员的浪漫,给CSDN近两万的粉丝比心心(python爬虫 | Unity循环复用列表 | 头像加载与缓存)
2021-05-19 22:35
1566 查看
文章目录
一、前言
点关注不迷路,持续输出
Unity干货文章。
嗨,大家好,我是新发。
今天是
2021年5月19日,明天就是
5.20了(可能粉丝们看到这篇文章时已经
5.20了),该表示表示了。我的粉丝数量马上突破两万了,我决定用
Unity做一个
Demo,给我这近两万粉丝比心。那么,开始吧~
二、最终效果
最终
Unity运行效果如下:
点击比心,访问对应的博客主页:
本文
Demo工程已上传到
CodeChina,感兴趣的同学可自行下载学习。
地址:https://codechina.csdn.net/linxinfa/UnityCSDNFansList
注:我使用的
Unity版本:
Unity 2020.1.14f1c1 (64-bit)。
三、读取CSDN粉丝列表数据
1、分析粉丝列表页面结构
首先,分析一下
CSDN粉丝页面的页面结构。在浏览器中按
F12调试,可以看到粉丝名字的节点
class是
sub-people-username。
进一步跟进到节点中,还可以看到粉丝博客
url和头像的
url。
部分粉丝写了简介,也一起读取下来吧。
2、爬数据
开始爬数据…
注:爬虫我就不教大家啦,怕教坏小朋友。之前看新闻看到有程序员因为弄爬虫被抓的。
最后生成一个
json文件,如下:
四、Unity制作
1、文件读取
把上面生成的
json文件放到
Unity工程的
Resources目录中。
这样,我们就可以直接通过
Resources.Load来读取文件。
var fansJsonStr = Resources.Load<TextAsset>("fans_list").text;
注:关于
Unity文件目录结构以及Resources.Load读取文件的教程,可以参见我之前写的这篇文章:《学Unity的猫》——第五章:规范Unity的工程目录结构,
以及这篇文章:《Unity游戏开发——新发教你做游戏(三):3种资源加载方式》
2、c#解析json
读取了
json文件内容后,我们需要对数据进行解析。
Unity中大家常用的
json解析库是
LitJson,可以从
GitHub找到源码。
GitHub地址:https://github.com/LitJSON/litjson
不过我这边弄了一个迷你版的
jsoin解析库,可以参见我之前写的这篇文章:《用C#实现一个迷你json库,无需引入dll(可直接放到Unity中使用)》
解析
json的逻辑封装在
JSONConvert类中,源码参见文章末尾。
这样,我们就可以解析
json数据了。
var fansJsonStr = Resources.Load<TextAsset>("fans_list").text;// 解析json数据 var fansJsonArray = JSONConvert.DeserializeArray(fansJsonStr); foreach (JSONObject dataItem in fansJsonArray) { var fansName = (string)dataItem["name"]; // 昵称 var fansIntro = (string)dataItem["intro"]; // 简介 var fansBlogUrl = (string)dataItem["blog_url"]; // 博客地址 var fansImageUrl = (string)dataItem["img_url"]; // 头像地址 // ... }
3、UGUI循环复用列表
由于粉丝数据达到近两万条,我们要在一个列表中显示这么多数据,如果创建近两万个
ui item的话性能肯定是很差的,所以必须循环复用
ui item。
循环复用列表的原理其实就是,列表向上滑动时,当
item超过显示区域的上边界时,把
item移动到列表底部,重复使用
item并更新
item的
ui显示,向下滑动同理,把超过显示区域底部的item复用到顶部。
为了方便大家理解,我画成图,如下:
注:循环列表的具体实现,我之前写过一篇教程,可以参见我之前写的这篇文章:《Unity UGUI实现循环复用列表,显示巨量列表信息,含Demo工程源码》
循环列表的逻辑封装在
RecyclingListView和
RecyclingListViewItem类中,源码参见文章末尾。
4、头像的加载
我们拿到的头像数据是一个
https链接,我们可以通过
UnityWebRequest来请求下载头像。
例:
string url = "https://profile.csdnimg.cn/6/6/4/3_m0_57622304"; var request = new UnityWebRequest(url); downloadHandlerTexture = new DownloadHandlerTexture(true); request.downloadHandler = downloadHandlerTexture; request.SendWebRequest(); while(!request.isDone) { // 等待 // 这里的while等待逻辑,可以改成协程yield return request.SendWebRequest(); } if (string.IsNullOrEmpty(request.error)) { Texture2D tex2D = downloadHandlerTexture.texture; // TODO }
注:关于
UnityWebRequest的详细使用教程,可以参见我之前写的这篇文章:《长江后浪推前浪,UnityWebRequest替代WWW》
5、gif头像问题
运行过程中,我发现有不少头像加载出来是一个问号。
我手动下载对应头像,发现它们其实是
gif格式,比如这个:
虽然它看起来不会动,但它实际上是
gif格式的,我们可以使用二进制查看器查看它的头部,可以看到是
47 49 46 38 39 61,即
GIF 89a格式。
如果是
JPG格式,则头两个字节是
FF D8
更多的二进制文件头如下:
文件格式 | 头部字节 | 尾部字节 |
---|---|---|
JPG | FF D8 | FF D9 |
PNG | 89 50 4E 47 0D 0A 1A 0A | |
GIF 89a | 47 49 46 38 39 61 | |
GIF 87a | 47 49 46 37 39 61 | |
TGA未压缩 | 00 00 02 00 00 | |
TGA压缩 | 00 00 10 00 00 | |
BMP | 42 4D | |
PCX | 0A | |
TIFF | 4D 4D 或 49 49 | |
ICO | 00 00 01 00 01 00 20 20 | |
CUR | 00 00 02 00 01 00 20 20 | |
IFF | 46 4F 52 4D | |
ANI | 52 49 4646 |
注:二进制查看器,推荐大家一个工具:
Hex Editor,非常的轻巧,而且用它可以打开大型的文本文件。
HexEditor下载地址:https://hexeditor.en.softonic.com/
关于HexEditor可以参见我之前写的这篇文章:《超大文本文件怎么打开(使用Hex Editor)》
我们在工程中放一张默认的
JPG头像:
我们下载头像时,对头部二进制进行检测,判断是否是
GIF,如果是
GIF,则使用过默认头像显示。
var bytes = downloadHandlerTexture.data; if(0x47 == bytes[0] && 0x49 == bytes[1] && 0x46 == bytes[2]) { // 是gif,显示默认头像 }
注,
Unity中如果想要播放GIF也是可以的,感兴趣的同学可以参见我之前写的这篇文章:《Unity解析和显示/播放GIF图片,支持http url,支持本地file://,支持暂停、继续播放》
6、头像缓存
头像是动态加载的,加载过的头像,我们可以缓存起来,下次再显示时,可以不请求
https了,直接从内存中读取。
/// <summary> /// 头像缓存 /// </summary> public class HeadCache { static Dictionary<string, Texture2D> cache = new Dictionary<string, Texture2D>(); public static void CacheTexture(string url, Texture2D tex) { if (!cache.ContainsKey(url)) cache
养成习惯,做好的界面保存成预设:
,[url=https://blog.csdn.net/linxinfa/article/details/82756625]《Unity2018.3.0.b1 版本的预设新工作流方式的使用体验》// using using System.IO; // Texture2D tex; string savePath = Application.persistentDataPath + "/head_icons"; var bytes = tex.EncodeToJPG(); using (FileStream fs = new FileStream(savePath, FileMode.Create)) { using (BinaryWriter bw = new BinaryWriter(fs)) { bw.Write(bytes); } }7、界面制作
界面使用
UGUI来制作。
养成习惯,做好的界面保存成预设:
注:关于预设的相关教程,可以参见我之前写的这两篇文章:[url=https://blog.csdn.net/linxinfa/article/details/110091098]《学Unity的猫——第八章:Unity预设文件,无限纸团喷射机》
8、比心,拉起浏览器访问博客
点击比心按钮,拉起浏览器访问博客,用的是
Application.OpenURL接口。
例:var blog_url = "https://blog.csdn.net/linxinfa"; UnityEngine.Application.OpenURL(blog_url);点击比心按钮,效果如下:
可以看到,拉起的是系统默认的浏览器,事实上,我们也可以做内置浏览器。注:关于
Unity的浏览器插件,可以参见我之前写的这些文章:
《Unity内嵌浏览器插件(Android、iOS、Windows)》
《新发的无聊小发明——PC端自制迷你浏览器给Unity调用(Windows窗体应用/WebBrowser/EXE)》
《unity内置浏览器插件UniWebView的使用(支持Android,ios,Mac)》
《新发的日常小实验——使用c# winfrom窗体应用制作浏览器,实现c#与html js交互》五、结束语
感谢所有粉丝的支持。
末了,喜欢Unity的同学,不要忘记点击关注,如果有什么Unity相关的技术难题,也欢迎留言或私信~六、附录:工程源码
工程总共6个脚本。
1、CSDNFansPanel.cs
using System.Collections.Generic; using UnityEngine; using Random = UnityEngine.Random; using UnityEngine.UI; public class CSDNFansPanel : MonoBehaviour { public RecyclingListView scrollList; /// <summary> /// 列表数据 /// </summary> private List<FansData> list = new List<FansData>(); private void Start() { ReadJson(); // 列表item更新回调 scrollList.ItemCallback = PopulateItem; // 设置数据,此时列表会执行更新 scrollList.RowCount = list.Count; } private void ReadJson() { var fansList = Resources.Load<TextAsset>("fans_list").text; var jsonArray = JSONConvert.DeserializeArray(fansList); int index = jsonArray.Count; foreach (JSONObject dataItem in jsonArray) { FansData fans = new FansData(); fans.Name = (string)dataItem["name"]; fans.Intro = (string)dataItem["intro"]; fans.BlogUrl = (string)dataItem["blog_url"]; fans.ImageUrl = (string)dataItem["img_url"]; fans.Row = index; list.Add(fans); --index; } } /// <summary> /// item更新回调 /// </summary> /// <param name="item">复用的item对象</param> /// <param name="rowIndex">行号</param> private void PopulateItem(RecyclingListViewItem item, int rowIndex) { var child = item as FansItem; child.Data = list[rowIndex]; } }2、FansItem.cs
using UnityEngine.Networking; using UnityEngine.UI; public class FansItem : RecyclingListViewItem { // 昵称 public Text nameText; // 简介 public Text introText; // 比心按钮 public Button btn; public HeadIconLoader iconLoader; public Text rowText; private FansData data; public FansData Data { get { return data; } set { data = value; nameText.text = data.Name; rowText.text = $"第{data.Row}位粉丝"; introText.text = data.Intro; iconLoader.LoadIcon(data.ImageUrl); } } private void Start() { // 比心按钮 btn.onClick.AddListener(() => { UnityEngine.Application.OpenURL(data.BlogUrl); }); } } public struct FansData { public string Name; public string Intro; public string BlogUrl; public string ImageUrl; public int Row; }3、HeadIconLoader.cs
using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI; using System.Collections.Generic; public class HeadIconLoader : MonoBehaviour { public RawImage headIcon; public Texture2D defaultTexture; private UnityWebRequest request; private DownloadHandlerTexture downloadHandlerTexture; private bool loading = false; private string headUrl; public void LoadIcon(string url) { headUrl = url; var tex = HeadCache.GetFromCache(url); if(null != tex) { headIcon.texture = tex; loading = false; return; } if (null != request) { request.Dispose(); } headIcon.texture = null; request = new UnityWebRequest(url); downloadHandlerTexture = new DownloadHandlerTexture(true); request.downloadHandler = downloadHandlerTexture; loading = true; request.SendWebRequest(); } private void Update() { if (loading && request.isDone) { loading = false; if (string.IsNullOrEmpty(request.error)) { var bytes = downloadHandlerTexture.data; if(0x47 == bytes[0] && 0x49 == bytes[1] && 0x46 == bytes[2]) { // 是gif,显示默认头像 headIcon.texture = defaultTexture; HeadCache.CacheTexture(headUrl, defaultTexture); } else { headIcon.texture = downloadHandlerTexture.texture; HeadCache.CacheTexture(headUrl, downloadHandlerTexture.texture); } } } } } /// <summary> /// 头像缓存 /// </summary> public class HeadCache { static Dictionary<string, Texture2D> cache = new Dictionary<string, Texture2D>(); public static void CacheTexture(string url, Texture2D tex) { if (!cache.ContainsKey(url)) cache[url] = tex; } public static Texture2D GetFromCache(string url) { if (cache.ContainsKey(url)) return cache[url]; return null; } }4、RecyclingListView.cs
using System; using UnityEngine; using UnityEngine.UI; /// <summary> /// 循环复用列表 /// </summary> [RequireComponent(typeof(ScrollRect))] public class RecyclingListView : MonoBehaviour { [Tooltip("子节点物体")] public RecyclingListViewItem ChildObj; [Tooltip("行间隔")] public float RowPadding = 15f; [Tooltip("事先预留的最小列表高度")] public float PreAllocHeight = 0; public enum ScrollPosType { Top, Center, Bottom, } public float VerticalNormalizedPosition { get => scrollRect.verticalNormalizedPosition; set => scrollRect.verticalNormalizedPosition = value; } /// <summary> /// 列表行数 /// </summary> protected int rowCount; /// <summary> /// 列表行数,赋值时,会执行列表重新计算 /// </summary> public int RowCount { get => rowCount; set { if (rowCount != value) { rowCount = value; // 先禁用滚动变化 ignoreScrollChange = true; // 更新高度 UpdateContentHeight(); // 重新启用滚动变化 ignoreScrollChange = false; // 重新计算item ReorganiseContent(true); } } } /// <summary> /// item更新回调函数委托 /// </summary> /// <param name="item">子节点对象</param> /// <param name="rowIndex">行数</param> public delegate void ItemDelegate(RecyclingListViewItem item, int rowIndex); /// <summary> /// item更新回调函数委托 /// </summary> public ItemDelegate ItemCallback; protected ScrollRect scrollRect; /// <summary> /// 复用的item数组 /// </summary> protected RecyclingListViewItem[] childItems; /// <summary> /// 循环列表中,第一个item的索引,最开始每个item都有一个原始索引,最顶部的item的原始索引就是childBufferStart /// 由于列表是循环复用的,所以往下滑动时,childBufferStart会从0开始到n,然后又从0开始,以此往复 /// 如果是往上滑动,则是从0到-n,再从0开始,以此往复 /// </summary> protected int childBufferStart = 0; /// <summary> /// 列表中最顶部的item的真实数据索引,比如有一百条数据,复用10个item,当前最顶部是第60条数据,那么sourceDataRowStart就是59(注意索引从0开始) /// </summary> protected int sourceDataRowStart; protected bool ignoreScrollChange = false; protected float previousBuildHeight = 0; protected const int rowsAboveBelow = 1; protected virtual void Awake() { scrollRect = GetComponent<ScrollRect>(); ChildObj.gameObject.SetActive(false); } protected virtual void OnEnable() { scrollRect.onValueChanged.AddListener(OnScrollChanged); ignoreScrollChange = false; } protected virtual void OnDisable() { scrollRect.onValueChanged.RemoveListener(OnScrollChanged); } /// <summary> /// 供外部调用,强制刷新整个列表,比如数据变化了,刷新一下列表 /// </summary> public virtual void Refresh() { ReorganiseContent(true); } /// <summary> /// 供外部调用,强制刷新整个列表的局部item /// </summary> /// <param name="rowStart">开始行</param> /// <param name="count">数量</param> public virtual void Refresh(int rowStart, int count) { // only refresh the overlap int sourceDataLimit = sourceDataRowStart + childItems.Length; for (int i = 0; i < count; ++i) { int row = rowStart + i; if (row < sourceDataRowStart || row >= sourceDataLimit) continue; int bufIdx = WrapChildIndex(childBufferStart + row - sourceDataRowStart); if (childItems[bufIdx] != null) { UpdateChild(childItems[bufIdx], row); } } } /// <summary> /// 供外部调用,强制刷新整个列表的某一个item /// </summary> public virtual void Refresh(RecyclingListViewItem item) { for (int i = 0; i < childItems.Length; ++i) { int idx = WrapChildIndex(childBufferStart + i); if (childItems[idx] != null && childItems[idx] == item) { UpdateChild(childItems[i], sourceDataRowStart + i); break; } } } /// <summary> /// 清空列表 /// </summary> public virtual void Clear() { RowCount = 0; } /// <summary> /// 供外部调用,强制滚动列表,使某一行显示在列表中 /// </summary> /// <param name="row">行号</param> /// <param name="posType">目标行显示在列表的位置:顶部,中心,底部</param> public virtual void ScrollToRow(int row, ScrollPosType posType) { scrollRect.verticalNormalizedPosition = GetRowScrollPosition(row, posType); } /// <summary> /// 获得归一化的滚动位置,该位置将给定的行在视图中居中 /// </summary> /// <param name="row">行号</param> /// <returns></returns> public float GetRowScrollPosition(int row, ScrollPosType posType) { // 视图高 float vpHeight = ViewportHeight(); float rowHeight = RowHeight(); // 将目标行滚动到列表目标位置时,列表顶部的位置 float vpTop = 0; switch (posType) { case ScrollPosType.Top: { vpTop = row * rowHeight; } break; case ScrollPosType.Center: { // 目标行的中心位置与列表顶部的距离 float rowCentre = (row + 0.5f) * rowHeight; // 视口中心位置 float halfVpHeight = vpHeight * 0.5f; vpTop = Mathf.Max(0, rowCentre - halfVpHeight); } break; case ScrollPosType.Bottom: { vpTop = (row+1) * rowHeight - vpHeight; } break; } // 滚动后,列表底部的位置 float vpBottom = vpTop + vpHeight; // 列表内容总高度 float contentHeight = scrollRect.content.sizeDelta.y; // 如果滚动后,列表底部的位置已经超过了列表总高度,则调整列表顶部的位置 if (vpBottom > contentHeight) vpTop = Mathf.Max(0, vpTop - (vpBottom - contentHeight)); // 反插值,计算两个值之间的Lerp参数。也就是value在from和to之间的比例值 return Mathf.InverseLerp(contentHeight - vpHeight, 0, vpTop); } /// <summary> /// 根据行号获取复用的item对象 /// </summary> /// <param name="row">行号</param> protected RecyclingListViewItem GetRowItem(int row) { if (childItems != null && row >= sourceDataRowStart && row < sourceDataRowStart + childItems.Length && row < rowCount) { // 注意这里要根据行号计算复用的item原始索引 return childItems[WrapChildIndex(childBufferStart + row - sourceDataRowStart)]; } return null; } protected virtual bool CheckChildItems() { // 列表视口高度 float vpHeight = ViewportHeight(); float buildHeight = Mathf.Max(vpHeight, PreAllocHeight); bool rebuild = childItems == null || buildHeight > previousBuildHeight; if (rebuild) { int childCount = Mathf.RoundToInt(0.5f + buildHeight / RowHeight()); childCount += rowsAboveBelow * 2; if (childItems == null) childItems = new RecyclingListViewItem[childCount]; else if (childCount > childItems.Length) Array.Resize(ref childItems, childCount); // 创建item for (int i = 0; i < childItems.Length; ++i) { if (childItems[i] == null) { var item = Instantiate(ChildObj); childItems[i] = item; } childItems[i].RectTransform.SetParent(scrollRect.content, false); childItems[i].gameObject.SetActive(false); } previousBuildHeight = buildHeight; } return rebuild; } /// <summary> /// 列表滚动时,会回调此函数 /// </summary> /// <param name="normalisedPos">归一化的位置</param> protected virtual void OnScrollChanged(Vector2 normalisedPos) { if (!ignoreScrollChange) { ReorganiseContent(false); } } /// <summary> /// 重新计算列表内容 /// </summary> /// <param name="clearContents">是否要清空列表重新计算</param> protected virtual void ReorganiseContent(bool clearContents) { if (clearContents) { scrollRect.StopMovement(); scrollRect.verticalNormalizedPosition = 1; } bool childrenChanged = CheckChildItems(); // 是否要更新整个列表 bool populateAll = childrenChanged || clearContents; float ymin = scrollRect.content.localPosition.y; // 第一个可见item的索引 int firstVisibleIndex = (int)(ymin / RowHeight()); int newRowStart = firstVisibleIndex - rowsAboveBelow; // 滚动变化量 int diff = newRowStart - sourceDataRowStart; if (populateAll || Mathf.Abs(diff) >= childItems.Length) { sourceDataRowStart = newRowStart; childBufferStart = 0; int rowIdx = newRowStart; foreach (var item in childItems) { UpdateChild(item, rowIdx++); } } else if (diff != 0) { int newBufferStart = (childBufferStart + diff) % childItems.Length; if (diff < 0) { // 向前滑动 for (int i = 1; i <= -diff; ++i) { // 得到复用item的索引 int wrapIndex = WrapChildIndex(childBufferStart - i); int rowIdx = sourceDataRowStart - i; UpdateChild(childItems[wrapIndex], rowIdx); } } else { // 向后滑动 int prevLastBufIdx = childBufferStart + childItems.Length - 1; int prevLastRowIdx = sourceDataRowStart + childItems.Length - 1; for (int i = 1; i <= diff; ++i) { int wrapIndex = WrapChildIndex(prevLastBufIdx + i); int rowIdx = prevLastRowIdx + i; UpdateChild(childItems[wrapIndex], rowIdx); } } sourceDataRowStart = newRowStart; childBufferStart = newBufferStart; } } private int WrapChildIndex(int idx) { while (idx < 0) idx += childItems.Length; return idx % childItems.Length; } /// <summary> /// 获取一行的高度,注意要加上RowPadding /// </summary> private float RowHeight() { return RowPadding + ChildObj.RectTransform.rect.height; } /// <summary> /// 获取列表视口的高度 /// </summary> private float ViewportHeight() { return scrollRect.viewport.rect.height; } protected virtual void UpdateChild(RecyclingListViewItem child, int rowIdx) { if (rowIdx < 0 || rowIdx >= rowCount) { child.gameObject.SetActive(false); } else { if (ItemCallback == null) { Debug.Log("RecyclingListView is missing an ItemCallback, cannot function", this); return; } // 移动到正确的位置 var childRect = ChildObj.RectTransform.rect; Vector2 pivot = ChildObj.RectTransform.pivot; float ytoppos = RowHeight() * rowIdx; float ypos = ytoppos + (1f - pivot.y) * childRect.height; float xpos = 0 + pivot.x * childRect.width; child.RectTransform.anchoredPosition = new Vector2(xpos, -ypos); child.NotifyCurrentAssignment(this, rowIdx); // 更新数据 ItemCallback(child, rowIdx); child.gameObject.SetActive(true); } } /// <summary> /// 更新content的高度 /// </summary> protected virtual void UpdateContentHeight() { // 列表高度 float height = ChildObj.RectTransform.rect.height * rowCount + (rowCount - 1) * RowPadding; // 更新content的高度 var sz = scrollRect.content.sizeDelta; scrollRect.content.sizeDelta = new Vector2(sz.x, height); } protected virtual void DisableAllChildren() { if (childItems != null) { for (int i = 0; i < childItems.Length; ++i) { childItems[i].gameObject.SetActive(false); } } } }5、RecyclingListViewItem.cs
using UnityEngine; /// <summary> /// 列表item,你自己写的列表item需要继承该类 /// </summary> [RequireComponent(typeof(RectTransform))] public class RecyclingListViewItem : MonoBehaviour { private RecyclingListView parentList; /// <summary> /// 循环列表 /// </summary> public RecyclingListView ParentList { get => parentList; } private int currentRow; /// <summary> /// 行号 /// </summary> public int CurrentRow { get => currentRow; } private RectTransform rectTransform; public RectTransform RectTransform { get { if (rectTransform == null) rectTransform = GetComponent<RectTransform>(); return rectTransform; } } private void Awake() { rectTransform = GetComponent<RectTransform>(); } /// <summary> /// item更新事件响应函数 /// </summary> public virtual void NotifyCurrentAssignment(RecyclingListView v, int row) { parentList = v; currentRow = row; } }6、JSONConvert.cs
using System.Collections.Generic; using System.Text; public static class JSONConvert { #region Global Variables private static char[] _charary; private static int _aryend; #endregion #region JSON Deserialization /// <summary> /// Convert string to JSONObject /// </summary> /// <param name="text"></param> /// <returns></returns> private static JSONObject DeserializeSingletonObject(ref int left) { JSONObject localjson = new JSONObject(); while (left <= _aryend) { char c = _charary[left]; if (c == ' ' || c == '\r' || c == '\n' || c == '\t') //skip empty char { left++; continue; } if (c == ',') { left++; continue; } char r = '\0'; if (c == '\"' || c == '\'') //beginning of key { left++; r = c; } else if (c == '}') //end of JSONObject { left++; break; } int column = left; if (r == '\0') { while (_charary[column] != ':') column++; } else { while (!(_charary[column] == r && _charary[column - 1] != '\\' && _charary[column + 1] == ':')) column++; } string key = new string(_charary, left, column - left); //get the key if (r == '\0') left = column + 1; else left = column + 2; c = _charary[left]; while (c == ' ' || c == '\r' || c == '\n' || c == '\t') //skip empty char { left++; c = _charary[left]; } if (c == '\"' || c == '\'') //if value is string { left++; int strend = left; while (_charary[strend] != c || _charary[strend - 1] == '\\') strend++; localjson[key] = new string(_charary, left, strend - left); left = strend + 1; } else if (c == '{') // JSONObject { left++; localjson[key] = DeserializeSingletonObject(ref left); } else if (c == '[') //JSONArray { left++; localjson[key] = DeserializeSingletonArray(ref left); } else { //other class, such as boolean, int //all are converted to string, it can be enriched if in need int comma = left; char co = _charary[comma]; while (co != ',' && co != '}') { comma++; co = _charary[comma]; } int em = comma - 1; co = _charary[em]; while (co == ' ' || co == '\r' || co == '\n' || co == '\t') { em--; co = _charary[em]; } localjson[key] = new string(_charary, left, em - left + 1); left = comma; } } return localjson; } /// <summary> /// Convert string to JSONArray /// </summary> /// <param name="text"></param> /// <returns></returns> private static JSONArray DeserializeSingletonArray(ref int left) { JSONArray jsary = new JSONArray(); while (left <= _aryend) { char c = _charary[left]; if (c == ' ' || c == '\r' || c == '\n' || c == '\t') //skip empty char { left++; continue; } if (c == ',') { left++; continue; } if (c == ']') { left++; break; } if (c == '{') //JSONObject { left++; jsary.Add(DeserializeSingletonObject(ref left)); } else if (c == '[') //JSONArray { left++; jsary.Add(DeserializeSingletonArray(ref left)); } else if (c == '\"' || c == '\'') //string { left++; int strend = left; while (_charary[strend] != c || _charary[strend - 1] == '\\') strend++; jsary.Add(new string(_charary, left, strend - left)); left = strend + 1; } else { //other class, such as boolean, int //all are converted to string, it can be enriched if in need int comma = left; char co = _charary[comma]; while (co != ',' && co != ']') { comma++; co = _charary[comma]; } int em = comma - 1; co = _charary[em]; while (co == ' ' || co == '\r' || co == '\n' || co == '\t') { em--; co = _charary[em]; } jsary.Add(new string(_charary, left, em - left + 1)); left = comma; } } return jsary; } #endregion #region Public Interface /// <summary> /// Get a JSONObject instance from char[] /// </summary> /// <param name="text"></param> /// <returns></returns> public static JSONObject DeserializeCharToObject(char[] input) { _charary = input; _aryend = _charary.Length - 1; while (_aryend > 0) if (_charary[_aryend] != '}') _aryend--; else break; int start = 0; while (start < _aryend) if (_charary[start] != '{') start++; else break; start++; if (_aryend < start + 1) return null; return DeserializeSingletonObject(ref start); } /// <summary> /// Get a JSONObject instance from string /// </summary> /// <param name="text"></param> /// <returns></returns> public static JSONObject DeserializeObject(string input) { return DeserializeCharToObject(input.ToCharArray()); //The first char must be '{' } /// <summary> /// Get a JSONArray instance from char[] /// </summary> /// <param name="text"></param> /// <returns></returns> public static JSONArray DeserializeCharsToArray(char[] input) { _charary = input; _aryend = _charary.Length - 1; while (_aryend > 0) if (_charary[_aryend] != ']') _aryend--; else break; int start = 0; while (start < _aryend) if (_charary[start] != '[') start++; else break; start++; if (_aryend < start + 1) return null; return DeserializeSingletonArray(ref start); } /// <summary> /// Get a JSONArray instance from string /// </summary> /// <param name="text"></param> /// <returns></returns> public static JSONArray DeserializeArray(string input) { return DeserializeCharsToArray(input.ToCharArray()); } /// <summary> /// Serialize a JSONObject instance /// </summary> /// <param name="jsonObject"></param> /// <returns></returns> public static string SerializeObject(JSONObject jsonObject) { StringBuilder sb = new StringBuilder(); sb.Append("{"); foreach (KeyValuePair<string, object> kvp in jsonObject) { if (kvp.Value is JSONObject) { sb.Append(string.Format("\"{0}\":{1},", kvp.Key, SerializeObject((JSONObject)kvp.Value))); } else if (kvp.Value is JSONArray) { sb.Append(string.Format("\"{0}\":{1},", kvp.Key, SerializeArray((JSONArray)kvp.Value))); } else if (kvp.Value is string) { sb.Append(string.Format("\"{0}\":\"{1}\",", kvp.Key, kvp.Value)); } else if (kvp.Value is int || kvp.Value is long) { sb.Append(string.Format("\"{0}\":{1},", kvp.Key, kvp.Value)); } else { sb.Append(string.Format("\"{0}\":\"{1}\",", kvp.Key, "")); } } if (sb.Length > 1) sb.Remove(sb.Length - 1, 1); sb.Append("}"); return sb.ToString(); } /// <summary> /// Serialize a JSONArray instance /// </summary> /// <param name="jsonArray"></param> /// <returns></returns> public static string SerializeArray(JSONArray jsonArray) { StringBuilder sb = new StringBuilder(); sb.Append("["); for (int i = 0; i < jsonArray.Count; i++) { if (jsonArray[i] is JSONObject) { sb.Append(string.Format("{0},", SerializeObject((JSONObject)jsonArray[i]))); } else if (jsonArray[i] is JSONArray) { sb.Append(string.Format("{0},", SerializeArray((JSONArray)jsonArray[i]))); } else if (jsonArray[i] is string) { sb.Append(string.Format("\"{0}\",", jsonArray[i])); } else { sb.Append(string.Format("\"{0}\",", "")); } } if (sb.Length > 1) sb.Remove(sb.Length - 1, 1); sb.Append("]"); return sb.ToString(); } #endregion } public class JSONObject : Dictionary<string, object> { public void put(string key, string value) { this[key] = value; } public void put(string key, int value) { this[key] = value.ToString(); } } public class JSONArray : List<object> { }
相关文章推荐
- 实现app上对csdn的文章列表上拉刷新下拉加载以及加入缓存文章列表的功能 (制作csdn app 四)
- 实现app上对csdn的文章列表上拉刷新下拉加载以及加入缓存文章列表的功能 (制作csdn app 四)
- 爬虫"用python抓取某音乐网站,然后将歌曲名、歌手、链接存入excel表格中" 列表、循环、函数都有用,有些可能需要先‘pip install’下载模块)
- 实现app上对csdn的文章列表上拉刷新下拉加载以及加入缓存文章列表的功能 (制作csdn app 四)
- 实现app上对csdn的文章列表上拉刷新下拉加载以及加入缓存文章列表的功能 (制作csdn app 四)
- 实现app上对csdn的文章列表上拉刷新下拉加载以及加入缓存文章列表的功能 (制作csdn app 四)
- 实现app上对csdn的文章列表上拉刷新下拉加载以及加入缓存文章列表的功能 (制作csdn app 四)
- 实现app上对csdn的文章列表上拉刷新下拉加载以及加入缓存文章列表的功能 (制作csdn app 四)...
- 实现app上对csdn的文章列表上拉刷新下拉加载以及加入缓存文章列表的功能 (制作csdn app 四)
- Python实现抓取CSDN热门文章列表
- 【Python爬虫3】在下载的本地缓存做爬虫
- Python 实用爬虫-04-使用 BeautifulSoup 去水印下载 CSDN 博客图片
- python中用for循环遍历的过程中删除列表中元素的注意点
- [Python模块学习]使用linecache模块加载和缓存文件内容
- Python:数据类型list中 利用循环的基本操作--补充 (1)列表的遍历
- leetcode-1:python基础,循环、条件、列表、字典的应用,enumerate、range函数以及2.0和3.0print的区别
- python循环和列表介绍 01/15
- Python爬虫Csdn系列III
- Webmagic爬虫--②爬自己的CSDN博客列表
- Python 爬虫简单实战之CSDN