在GeoTools中的Shapefile属性表读取效率之Shp与Dbf对比

举报
夜郎king 发表于 2024/11/15 12:57:53 2024/11/15
【摘要】 本文将详细介绍使用Java语言开发,调用GeoTools程序分别读取SHP和DBF两种文件。然后根据不同的实验情况输出其内存占用和实现消耗对比。通过以上实验,能让您更加了解如何使用正确的方式去调用GeoTools程序,了解不同的属性表格的解析方式。

 目录


前言

一、POI测试数据简介

1、选用的POI数据

2、关于数据的属性数据 

二、属性数据读取的两种方式实现

1、基于DbaseFileReader的读取

2、基于SimpleFeatureSource的读取

三、实际运行对比

1、内存和CPU占用情况

2、运行耗时情况

四、总结



前言

        众所周知,在空间矢量数据Shapefile中,我们的属性表格数据是保存在Dbf文件当中的。因此,我们在读取Shapefile文件时,如果想读取属性数据。可以的选择至少有两个,第一个是直接读取dbf文件,第二个是读取shp文件。当然,两个的读取是有较大的区别的,主要的区别就在于,在我们的矢量数据中,空间字段Geometry是存放在shp文件中的,同时一个geometry字段对应一条属性记录。在一些场景下,比如我们需要在上传之前,解析Top N的属性表格数据来进行预览,就可以不需要读取Geometry信息。

        因此,针对这两种不同的读取方式。我们来做个对比实验,在读取同样大小和数据量的Shapefile文件时,分别带控制台输出和不带控制台输出两种方式,循环10次来调用同样的读取程序,来对比不同的读取模式在不同的输出模式下,其读取的速度和内存的消耗情况。为大家在实际进行项目开发时,根据不同的情况来选择适合的读取方式做一个参考。

        本文将详细介绍使用Java语言开发,调用GeoTools程序分别读取SHP和DBF两种文件,再对比不同的输出模式,比如一种需要向控制台输出信息,另外一种则直接读取就好。博客首先介绍读取的共同的POI数据的基本情况,包括数据的信息、属性表格的数据详情、总条数等,属性数据是整个对比测试实验的基础。然后根据不同的实验情况输出其内存占用和实现消耗对比。通过以上实验,能让您更加了解如何使用正确的方式去调用GeoTools程序,了解不同的属性表格的解析方式。如果您刚好对这方面有兴趣,不妨来这里看看。

一、POI测试数据简介

        为了测试两种不同的读取方式在不同的输出环境下的性能对比,我们首先准备一份基准数据。因此首先对基础数据的基本情况做个介绍,包括数据的字段信息、总数据量等等。

1、选用的POI数据

        为了让程序的读取占用一定的时间,因此我们需要准备稍微多一点的空间矢量数据。如果数据量太少,不同那种情况,其读取的效率都非常快,很快就读取完了。反之,如果太多,则会占用太多的时间和空间,因此我们采用推测的方法,即采用一定量的数据来预测大规模数据的读取性能。这里,我们选用某城市的餐饮POI数据,在QGIS中可以打开这些数据,如下所示:

2、关于数据的属性数据 

        在了解餐饮POI数据的基本情况之后,我们来看一下属性数据的基本情况。依然在QGIS中进行相应信息的查阅。打开数据的属性信息,先来看一些其参考坐标和总数据量的情况。

        可以看到,其空间参考是采用的EPSG:4490参考(即国家2000坐标系,这是目前比较常用的参考坐标系统)。同时,可以在这里看到要素的数目,即跟属性数据的总条数为:36006,差不多3.6余条。再来看一下它的属性字段,大致如下图所示:

        从上图可以看到,属性表格的字段有10个字段,不包括Geometry字段。由于我们只需要读取属性信息,因此暂时可以忽略Geometry信息,当然在空间信息中,Geometry比一般的属性信息更加重要。 在下面的文章中,主要就是对比读取餐饮POI数据中的3.6W条数据。

二、属性数据读取的两种方式实现

        在Geotools的官方文档中可以了解,想要读取Shapefile的属性数据,不仅可以通过读取Shp的方式,也可以读取DBF。其实,在读取Shp时,已经包含了DBF文件的读取,因为Shp中主要存储的是空间的Geometry信息,而属性表格数据全部都保存在DBF文件中。因此本文首先介绍如何使用Geotools来进行具体的读取。

1、基于DbaseFileReader的读取

        首先我们来介绍如何从dbf文件中直接读取属性信息。闲言少叙,这里直接给大家贴出展示代码。关键代码如下所示:

private long readFromDBF(boolean consoleOut) throws IOException {
	Long startTime = System.currentTimeMillis();
	File dbfFile = new File(SHP_FILE);
	ShpFiles shpFile = new ShpFiles(dbfFile);
	System.out.println(Charset.defaultCharset().toString());
	DbaseFileReader dbfReader = new DbaseFileReader(shpFile, true, Charset.defaultCharset());
	// 读取 DBF 文件的头信息
	DbaseFileHeader header = dbfReader.getHeader();
	while (dbfReader.hasNext()) {
		Row row = dbfReader.readRow();
		for (int i = 0; i < header.getNumFields(); i++) {
			if(consoleOut) {
				System.out.print(row.read(i) + "\t");
			}else {
				row.read(i);
			}
		}
		if(consoleOut) {
			System.out.println("");
		}
	}
	System.out.println("属性字段数:" + header.getNumFields());
	System.out.println("数据记录数:" + header.getNumRecords());
	dbfReader.close();
	Long endTime = System.currentTimeMillis();
    Long time = endTime - startTime;
	System.out.println("程序运行耗时:"+ time + "毫秒");
	return time;
}

         通过代码可以看到,读取dbf的方式主要使用的类是:DbaseFileReader,这个类是专门是用来读取dbf文件的。而属性的字段信息,主要就是存放在DbaseFileHeader中,通过DbaseFileHeader就可获取字段,然后通过dbfReader.readRow();来获取数据,这样就可以循环header的表头来获取所有的数据。

编辑

        对dbf文件读取器的感兴趣的朋友,可以到源码中一探究竟。这里不进行深究。 介绍完直接读取dbf文件的形式后,我们来介绍一下读取shp的方式。

2、基于SimpleFeatureSource的读取

         除了直接使用DbaseFileReader的方式来读取属性数据,我们还可以基于SimpleFeatureSource来进行数据的读取。在前面系列文章中,曾经进行了比较详细的介绍,因此这里我们也是直接给出代码。关键代码如下所示:

private long readFromSHP(boolean consoleOut) throws Exception{
	Long startTime = System.currentTimeMillis();
	File file = new File(SHP_FILE);
	if (!file.exists()) {
		System.out.println("文件不存在");
		return 0L;
	}
	ShapefileDataStore store = new ShapefileDataStore(file.toURI().toURL());
	store.setCharset(Charset.forName("UTF-8"));// 设置中文字符编码
	store.getCharset();
	SimpleFeatureSource featureSource = store.getFeatureSource();
	// 执行查询
	SimpleFeatureCollection simpleFeatureCollection = featureSource.getFeatures();
	SimpleFeatureIterator itertor = simpleFeatureCollection.features();
	// 遍历featurecollection
	while (itertor.hasNext()) {
		SimpleFeature feature = itertor.next();
		Collection<Property> p = feature.getProperties();
		Iterator<Property> it = p.iterator();
		// 遍历feature的properties
		while (it.hasNext()) {
			Property pro = it.next();
			if (null != pro && null != pro.getValue()) {
				String field = pro.getName().toString();
				String value = pro.getValue().toString();
				if(consoleOut) {
					System.out.println(field + "===" + value);
				}
			}
		}
		if(consoleOut) {
			System.out.println("------------------------------------------------------");
		}
	}
	Long endTime = System.currentTimeMillis();
	Long time = endTime - startTime;
	System.out.println("程序运行耗时:"+ time + "毫秒");
	return time;
}

        与直接从DBF文件中直接读取类似,为了测试向控制台输出是否会影响效率和内存占用,因此我们在方法中增加日志输出的开关,通过开关来控制相应的输出。

        这里就给出了两种不同的实现方式的属性表格信息读取的关键代码。大家可以直接采用。前提是大家正确的配置了GeoTools的依赖。下面就进行实际例子的运行,同时综合对比指标来看一下实际的运行情况。

三、实际运行对比

        这里,我们将采用循环10次调用的方式,分别给出10次调用的耗时对比。同时使用Java VisualVM来进行运行内存的监控。通过对比实验来观察运行的一些资源消耗。实验分别从以下几个方面进行,对比不同运行模式的内存、CPU占比;对比不同运行模式的读取耗时。其测试代码如下所示:

        首先是是DBF读取模式的测试代码,如下所示:

@Test
public void readFromDbf() throws Exception {
	Long [] time1 = new Long[DEFAULT_SIZE];
	for(int i = 0;i < DEFAULT_SIZE;i++) {
		time1[i] = readFromDBF(true);
		Thread.sleep(5000);//线程休眠5秒钟
	}
	System.out.println("*******************************************");
	for (Long time : time1) {
		System.out.print(time+ "\t");
	}
	Long [] time2 = new Long[DEFAULT_SIZE];
	for(int i = 0;i < DEFAULT_SIZE;i++) {
		time2[i] = readFromDBF(false);
		Thread.sleep(5000);//线程休眠5秒钟
	}
	System.out.println("*******************************************");
	for (Long time : time2) {
		System.out.print(time+ "\t");
	}
}

        同样的,直接读取shp的方式测试代码如下:


@Test
public void readFromShp() throws Exception{
	Long [] time1 = new Long[DEFAULT_SIZE];
	for(int i = 0;i < DEFAULT_SIZE;i++) {
		time1[i] = readFromSHP(true);
		Thread.sleep(5000);//线程休眠5秒钟
	}
	System.out.println("*******************************************");
	System.out.println(time1);
	for (Long time : time1) {
		System.out.print(time+ "\t");
	}
	Long [] time2 = new Long[DEFAULT_SIZE];
	for(int i = 0;i < DEFAULT_SIZE;i++) {
		time2[i] = readFromSHP(false);
		Thread.sleep(5000);//线程休眠5秒钟
	}
	System.out.println("*******************************************");
	for (Long time : time2) {
		System.out.print(time+ "\t");
	}
}

1、内存和CPU占用情况

        首先来看下不同的运行模式的内存和CPU占用情况,这里采用的监控程序使用Java VisualVM(这是jdk自带的监控工具,可以同时监控CPU和内存)。

使用控制台输出的dbf读取资源占用情况图 

不使用控制台输出的dbf读取资源占用情况图  

使用控制台输出的shp读取资源占用情况图  

不使用控制台输出的shp读取资源占用情况图

        提供过以上的图表可以看到, 从CPU的占用来看,不带控制台输出的比带了控制台输出的的占用高。从内存的占用来看,最高占用基本两种方式都差不多。但是从稳定来看,带控制台输出的占比持续时间长一点。

2、运行耗时情况

        与CPU和内存情况相比,程序的运行耗时也是非常重要的一个指标。在之前的代码中,我们分别循环10次来进行程序的调用,然后取消耗的时间来做对比实验。

        采用dbf读取的方式的运行耗时如下:

开启输出 : 
3428, 2625, 2447, 2382, 2445, 2377, 2449, 2254, 2460, 2719

禁用输出 : 
1225, 254, 240, 238, 215, 248, 239, 215, 215, 213

        将上述数据做成echarts图表如下所示:

        通过图表的方式很明显的看出,禁用输出后,程序的执行时间有大幅的下降最快只要213毫秒就执行完成。 下面再来看一下shp的读取方式耗时情况。

        采用shp读取的运行耗时情况如下:

开启输出 : 
12131, 6508, 6104, 5849, 5442, 5702, 5250, 5569, 5211, 5030

禁用输出 : 
3766, 1082, 707, 839, 789, 736, 711, 720, 730, 685

        将上述数据做成echarts图表如下所示:

        使用shp的读取方式,同样是禁用了输出的耗时更短。而对比dbf和shp两种读取方式,开启输出和禁用输出的耗时几乎是10倍。而两种不同的读取方式,耗差别两到三倍。

四、总结

        以上就是本文的主要内容, 本文将详细介绍使用Java语言开发,调用GeoTools程序分别读取SHP和DBF两种文件,再对比不同的输出模式,比如一种需要向控制台输出信息,另外一种则直接读取就好。博客首先介绍读取的共同的POI数据的基本情况,包括数据的信息、属性表格的数据详情、总条数等,属性数据是整个对比测试实验的基础。然后根据不同的实验情况输出其内存占用和实现消耗对比。通过以上实验,能让您更加了解如何使用正确的方式去调用GeoTools程序,了解不同的属性表格的解析方式。如果您刚好对这方面有兴趣,不妨来这里看看。

        通过上面的对比实验,可以看到。在读取相同数量的数据和机器配置下。读取数据时不开启控制台输出,其性能更高,不仅耗时更短,同时CPU和内存的占比更低。如果不是必须在程序中要读取Geometry数据,建议使用dbf读取属性列表的信息方式,其效率更高,也许与geometry的联合读取有一定的关系。行文仓促,定有许多不足之处,如果不足,还请各位专家朋友在评论区留言批评指出,不慎荣幸。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。