用 JSON 表现树的结构兼谈队列、堆栈的练习(二)

树的查找

查找,又叫作搜索 search。查找跟遍历的概念不同,遍历是全部的节点都要走一遍,而查找,找到目标节点就立刻返回,不会继续遍历了。当然,如果什么都没查找到,就是一次完整的遍历过程了。

查找的依据是什么?假设我们对 K/V 结构,也就是 Map,对其增加 id 字段,用来作为查找的依据,那么用户输入如果符合 id 就返回节点,表示查找成功。

单层的查找很好做,在递归的函数里 if(id = map.get("id") ) 判断一下就可以了。JSON 是多层的,怎么做多层的呢?

还是那个 json 例子,已经有 id 字段存在了。我们希望输入查找条件如 "product/new/yuecai" 返回 {id:yuecai, name : 粤菜} 这个节点。

调用方式如下

JsonStruTraveler t = new JsonStruTraveler();

assertEquals("关于我们", t.findByPath("about", list).get("name"));
assertEquals("企业文化", t.findByPath("about/cluture", list).get("name"));
assertEquals("粤菜", t.findByPath("product/new/yuecai", list).get("name"));

findByPath 返回是 map 类型,就是这个节点。如果你有了解过 JSONPath,其实是有点相似的。

由于水平有限,当前只能做的从根节点一步一步查找,非常简单的原理。不支持从非第一级节点开始的查找。

查找的原理

原理还是非常简单,依然是递归的调用。不过在输入条件的处理上,使用了一点技巧,就是队列的运用。

/**
 *
 * 根据路径查找节点
 * @param str
 *            Key 列表字符
 * @param list
 *            列表
 * @return Map
 */
public Map<String, Object> findByPath(String str, List<Map<String, Object>> list) {
	if (str.startsWith("/"))
		str = str.substring(1, str.length());
	if (str.endsWith("/"))
		str = str.substring(0, str.length() - 1);

	String[] arr = str.split("/");
	Queue<String> q = new LinkedList<>(Arrays.asList(arr));

	return findByPath(q, list);
}

/**
 * 真正的查找函数
 *
 * @param stack
 *            栈
 * @param list
 *            列表
 * @return Map
 */
@SuppressWarnings("unchecked")
private Map<String, Object> findByPath(Queue<String> queue, List<Map<String, Object>> list) {
	Map<String, Object> map = null;

	while (!queue.isEmpty()) {
		map = findMap(list, queue.poll());

		if (map != null) {
			if (queue.isEmpty()) {
				break;// found it!
			} else if (map.get(children) != null) {
				map = findByPath(queue, (List<Map<String, Object>>) map.get(children));
			} else {
				map = null;
			}
		}
	}

	return map;
}

完整代码在:http://git.oschina.net/sp42/ajaxjs/blob/master/ajaxjs-base/src/com/ajaxjs/util/collection/JsonStruTraveler.java

单测:http://git.oschina.net/sp42/ajaxjs/blob/master/ajaxjs-base/src/test/com/ajaxjs/util/collection/TestStruTravler.java

首先拆分输入的字符串,变为队列( Queue 窄化了 LinkedList)。当 findMap 函数找到有节点,此时已消耗了队列的一个元素(queue.poll())。如果队列已经空了,表示已经查找完毕,返回节点。否则再看看下级节点 children 是否有,这一步是递归的调用,私有函数 private Map<String, Object> findByPath 就是专门为递归用的函数。

如果不使用队列,也可以完成,但就比较累,要自己写多几行代码控制,就操作如下代码。

最开始的时候,我先想到的不是 队列,而是 栈。后来想想了,生成栈的过程比较繁琐,因为字符串 split 之后,是要反转数组才能变为栈的:

String[] arr = str.split("/");
Stack<String> stack = new Stack<>();

for (int i = arr.length - 1; i >= 0; i--)
	stack.push(arr[i]);

而后来想了下既然要反转数组,那不与直接使用与栈相反的 队列 好了。这里用栈或是队列是有差别的但差别不大,我们只是避免手写更多的行数来控制数组,对精简代码有好处。

扁平化 Map

JSON 树变成 MapList 之后,仍然是多层结构,如访问其中某个值(注意不是“节点”),必须逐级别访问。每级访问时都要判断对象是否为空,这就是要避免的 Java 空指针问题。例如 ((Map<String, Object>)list.get(0)).get("level"),比较繁琐。于是,我们希望可以写一个表达式就可以直接访问某个节点。上述如 product/new/yuecai 路径的方式是一种思路;这里再介绍一种不同的方式,它把 / 变成 .  如 product.new.yuecai,而且内部实现完全不同,速度会快很多。这就是扁平化 Map。

之所以强调扁平化 Map 中的 Map ,其意思是只强调处理 K/V 的 Map,输入类型要求是 Map,对于遇到 List 类型的 Value 不会去区分那是值还是子节点,一律当作下一级的子节点集合处理。这与“product/new/yuecai” 指定 children 项的方式来处理子集合稍有区别的,后者要求输入的是 List。而且前者通常是返回某个值,后者返回的是节点(Map),返回的内容其意义不一样。

首先定义内部接口 TravelMapList_Iterator,有三个方法:

/**
 * 遍历 MapList 的回调
 * @author Frank Cheung frank@ajaxjs.com
 */
public static interface TravelMapList_Iterator {
	/**
	 * 当得到了 Map 的 key 和 value 时调用
	 *
	 * @param key 键名称
	 * @param obj 键值
	 */
	public void handler(String key, Object obj);

	/**
	 * 当得到一个新的 key 时候
	 * @param key 键名称
	 */
	public void newKey(String key);

	/**
	 * 当退出一个当前 key 的时候
	 * @param key 键名称
	 */
	public void exitKey(String key);

}

接着定义递归函数遍历 MapList

/**
 * 遍历 MapList,允许 TravelMapList_Iterator 控制
 *
 * @param map
 *            输入 Map
 * @param iterator
 *            回调
 */
@SuppressWarnings("unchecked")
public static void travelMapList(Map<String, Object> map, TravelMapList_Iterator iterator) {
	for (String key : map.keySet()) {
		Object obj = map.get(key);

		if (iterator != null)
			iterator.handler(key, obj);

		if (obj != null) {
			if (obj instanceof Map) {
				if (iterator != null)
					iterator.newKey(key);

				travelMapList((Map<String, Object>) obj, iterator);

				if (iterator != null)
					iterator.exitKey(key);
			} else if (obj instanceof List) {
				List<Object> list = (List<Object>) obj;

				for (Object item : list) {
					if (item != null && item instanceof Map)
						travelMapList((Map<String, Object>) item, iterator);
				}
			}
		}
	}
}

TravelMapList_Iterator 到底有什么用?说到底是为了进栈和退栈用的。堆栈有后入先出的特性,在这里使用最适合不过了——用来记录所在层级。

/**
 * 扁平化多层 map 为单层
 * @param map
 * @return
 */
public static Map<String, Object> flatMap(Map<String, Object> map) {
	final Stack<String> stack = new Stack<>();
	final Map<String, Object> _map = new HashMap<>();

	travelMapList(map, new TravelMapList_Iterator() {
		@Override
		public void handler(String key, Object obj) {
			if (obj == null || obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
				StringBuilder sb = new StringBuilder();
				for (String s : stack) {
					sb.append(s + ".");
				}
				_map.put(sb + key, obj);
				//					System.out.println(sb + key + ":" + obj);
			}
		}

		@Override
		public void newKey(String key) {
			stack.add(key); // 进栈
		}

		@Override
		public void exitKey(String key) {
			stack.pop(); // 退栈
		}
	});

	return _map;
}

外界调用 API 其实就是这个 flatMap 方法。

调用例子:

Map<String, Object> f_map = JsonStruTraveler.flatMap(map);
assertEquals(7, f_map.get("data.jobCatalog_Id"));

输入 JSON

{
	"site" : {
		"titlePrefix" : "大华•川式料理",
		"keywords" : "大华•川式料理",
		"description" : "大华•川式料理饮食有限公司于2015年成立,本公司目标致力打造中国新派川菜系列。炜爵爷川菜料理系列的精髓在于清、鲜、醇、浓、香、烫、酥、嫩,擅用麻辣。在服务出品环节上,团队以ISO9000为蓝本建立标准化餐饮体系,务求以崭新的姿态面向社会各界人仕,提供更优质的服务以及出品。炜爵爷宗旨:麻辣鲜香椒,美味有诀窍,靓油用一次,精品煮御赐。 ",
		"footCopyright":"dsds"
	},
	"dfd":{
		"dfd":'fdsf',
		"id": 888,
		"dfdff":{
			"dd":'fd'
		}
	},
	"clientFullName":"大华•川式料理",
	"clientShortName":"大华",
	"isDebug": true,
	"data" : {
		"newsCatalog_Id" : 6,
		"jobCatalog_Id" :7
	}
}

最后得到 map 结构是这样子的,根节点的话则没有 . (点)。

由于是 map 查找,所以查找的速度会很快。

格式化 JSON

如果对栈的技术不甚明了,可以尝试通过格式化 JSON 这个例子去理解。尽管这例子没有使用的栈,但是有相仿的地方,都是异曲同工的,大家可以细细品味。

/**
 * 格式化 JSON,使其美观输出到控制或其他地方 请注意 对于json中原有\n \t 的情况未做过多考虑 得到格式化json数据 退格用\t
 * 换行用\r
 *
 * @param json
 *            原 JSON 字符串
 * @return 格式化后美观的 JSON
 */
public static String format(String json) {
	int level = 0;
	StringBuilder str = new StringBuilder();

	for (int i = 0; i < json.length(); i++) {
		char c = json.charAt(i);
		if (level > 0 && '\n' == str.charAt(str.length() - 1))
			str.append(StringUtil.repeatStr("\t", "", level));

		switch (c) {
			case '{':
			case '[':
				str.append(c + "\n");
				level++;
				break;
			case ',':
				if (json.charAt(i + 1) == '"')
					str.append(c + "\n"); // 后面必定是跟着 key 的双引号,但 其实 json 可以 key 不带双引号的
				break;
			case '}':
			case ']':
				str.append("\n");
				level--;
				str.append(StringUtil.repeatStr("\t", "", level));
				str.append(c);
				break;
			default:
				str.append(c);
				break;
		}
	}

	return str.toString();
}

其中,level 表示缩进数,level++ 可看着“进栈”的操作,反之,level-- 为“退栈”,这一退一进是不是与栈的 push/pop 很像?

StringUtil.repeatStr 是重复字符串多少次的函数,比较简单,这里就不贴出了。

时间: 2024-08-02 01:38:16

用 JSON 表现树的结构兼谈队列、堆栈的练习(二)的相关文章

用 JSON 表现树的结构兼谈队列、堆栈的练习(一)

K/V 与 Array 接触 JSON 的人都知道,JSON 可通过 K/V(Key/Value) 结构很直观地表现一棵树,因为 V 可以"包含"另外一个 K/V 从而不断嵌套下去形成"树状"的结构.但 V 不一定必须为另外一个 K/V,而是可以为 Array 数组.数组中由可以"包含"更多的 K/V 或者又是数组类型--也是可以的.如此反复下去,可以形成层数很深的一棵树.例如 { aa : { cc :[ "dd", { e

基于AJAX的动态树型结构的设计与实现

ajax|动态|设计|树型结构 <B>摘 要</B>:简要介绍了一种通用的,动态树型结构的实现方案,该方案基于Asynchronous JavaScript and XML,结合Struts框架设计实现了结构清晰.扩展性良好的多层架构,数据存储于数据库,结合XML描述树的节点信息,使得任何按预定的XML文档描述的信息都可以通过动态树来展现.<br /><table border="0" cellspacing="0" cel

java json 数据转换-JAVA中如何将数据组装为json树状结构的数据

问题描述 JAVA中如何将数据组装为json树状结构的数据 我从数据库中查出的数据保存到一个集合List中,集合中是存的区域类Area.区域类的字段和数据库中结果的字段一样.图1中是我的数据库查询结果,想转行为json格式的树状结构.例如省-市-县这样的结构.就是图2的效果 图1: 图2: 弄了一天了还没出现,我太菜了.请大家帮帮忙 解决方案 要么你就自己纯拼字符串,要么就直接用fastjson这类json工具类直接转.只要类结构和json结构能对应,可以直接转就可以了. 解决方案二: 你定义一

winfrom 把数据库中的数据以树状结构返所有组信息,然后返回的树状结果以JSON格式构造

问题描述 新建Group表,表结构如下(组信息表)列名数据类型长度小数标识允许空默认说明GroupIDint40是/主键否组IDGroupNamevarchar500否ProjectIDint40是所属项目IDParentGroupIDint40是父级组别ID,若为空,则为根组publicstaticStringGetAllGroupTree()i.以树状结构返所有组信息ii.返回的树状结果以JSON格式构造宝宝新手,刚第一个问题写出来,可是第二个不知道怎么弄了,怎么把第二个的内容以Text控件

浅谈收录页面排名优先级与树状结构的关系

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 很多时候提及到了搜索引擎收录页面排名优先级的问题,除了说搜索引擎是以页面权重来进行决定页面排名优先级的;其实搜索引擎对网站收录页面排名优先级还与树状结构有一定关系,树状结构也是决定排名优先级的一个因素之一,那么我们通过下面举例来说明这个问题. 一.首先我们来看看以下这几个URL地址,考虑一下他们在搜索结果页面排名优先级顺序是怎么样的? 页面排

dtree和jquery构建树型结构

对于小型的树型应用来说,dtree是一个不错的选择. 先看一眼dtree给的例子 构造静态树 首先引入css文件和js文件 <link rel="StyleSheet" href="dtree.css" type="text/css" /> <script type="text/javascript" src="dtree.js"></script> 构造静态树其实很简单

C#使用Jquery zTree实现树状结构显示 异步数据加载_C#教程

C#使用Jquery zTree实现树状结构显示_异步数据加载 JQuery-Ztree下载地址:https://github.com/zTree/zTree_v3 JQuery-Ztree数结构演示页面:  http://www.treejs.cn/v3/demo.php 关于zTree的详细解释请看演示页面,还有zTree帮助Demo.  下面简要讲解下本人用到的其中一个实例(直接上关键代码了): 异步加载节点数据:  A-前台: <link href="zTree_v3-master

不用递归实现论坛树型结构的算法

递归|树型结构|算法 <jsp:useBean id="mybbs" scope="session" class="netzero.mydb" /> <%@ page contentType="text/html;charset=gb2312" %> <%@ page import="java.io.*" %> <%@ page import="java.

AJAX实现动态树型结构

ajax|动态|树型结构 树型结构是一类应用非常广泛的数据结构.人类社会中宗族的族谱和现代企业的组织形式都是树型结构.在计算机领域中,文件系统中文件的管理结构.存储器管理中的页表.数据库中的索引等也都是树型结构.随着Internet的飞速发展,树型结构在浏览器/服务器(Browser/Server,简称B/S)应用系统的应用也越来越广泛. 目前,在互联网上广泛存在.应用的树型结构一般分为两种:静态和动态结构.静态结构存在最多.实现简单,但是静态导致不能改变树的结构和内容,无法反映树的节点信息的变