目录
前言
一、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的联合读取有一定的关系。行文仓促,定有许多不足之处,如果不足,还请各位专家朋友在评论区留言批评指出,不慎荣幸。