我有一个大的JSON文件(2.5MB),包含大约80000行。
它看起来像这样:
{
"a": 123,
"b": 0.26,
"c": [HUGE irrelevant object],
"d": 32
}
我只希望存储键a
,b
和d
的整数值,而忽略其余JSON(即忽略c
值中的所有值) )。
我无法修改原始的JSON,因为它是由第三方服务创建的,该服务是从其服务器下载的。
如何在不将整个文件加载到内存的情况下执行此操作?
我尝试使用gson库并创建了这样的bean:
public class MyJsonBean {
@SerializedName("a")
@Expose
public Integer a;
@SerializedName("b")
@Expose
public Double b;
@SerializedName("d")
@Expose
public Integer d;
}
但是即使如此,为了使用Gson反序列化它,我需要先下载+读取内存中的整个文件,然后将其作为字符串传递给Gson?
File myFile = new File(<FILENAME>);
myFile.createNewFile();
URL url = new URL(<URL>);
OutputStream out = new BufferedOutputStream(new FileOutputStream(myFile));
URLConnection conn = url.openConnection();
HttpURLConnection httpConn = (HttpURLConnection) conn;
InputStream in = conn.getInputStream();
byte[] buffer = new byte[1024];
int numRead;
while ((numRead = in.read(buffer)) != -1) {
out.write(buffer, 0, numRead);
}
FileInputStream fis = new FileInputStream(myFile);
byte[] data = new byte[(int) myFile.length()];
fis.read(data);
String str = new String(data, "UTF-8");
Gson gson = new Gson();
MyJsonBean response = gson.fromJson(str, MyJsonBean.class);
System.out.println("a: " + response.a + "" + response.b + "" + response.d);
有什么方法可以避免加载整个文件,而只是获取我需要的相关值?
答案 0 :(得分:3)
您绝对应该检查其他方法和库。如果您真的很在意性能检查,请执行以下操作:Gson
,Jackson
和JsonPath
库,并选择最快的一个。绝对,您必须将整个JSON
文件加载到本地磁盘(可能是TMP
文件夹)中,然后进行解析。
简单的JsonPath
解决方案如下所示:
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import java.io.File;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
DocumentContext documentContext = JsonPath.parse(jsonFile);
System.out.println("" + documentContext.read("$.a"));
System.out.println("" + documentContext.read("$.b"));
System.out.println("" + documentContext.read("$.d"));
}
}
请注意,我没有创建任何POJO
,只是使用JSONPath
功能类似于XPath
读取给定值。您可以使用Jackson
做同样的事情:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonFile);
System.out.println(root.get("a"));
System.out.println(root.get("b"));
System.out.println(root.get("d"));
}
}
我们不需要JSONPath
,因为我们需要的值直接在root
节点中。如您所见,API
看起来几乎一样。我们还可以创建POJO
结构:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.math.BigDecimal;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
Pojo pojo = mapper.readValue(jsonFile, Pojo.class);
System.out.println(pojo);
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
class Pojo {
private Integer a;
private BigDecimal b;
private Integer d;
// getters, setters
}
即使如此,两个库都允许直接从JSON
读取URL
有效负载,我建议使用您能找到的最佳方法在另一步骤中下载它。有关更多信息,请阅读本文:Download a File From an URL in Java。
答案 1 :(得分:1)
有一些优秀的库,可以用最少的资源来解析大型JSON文件。一种是流行的GSON library。它具有与解析流和对象一样的解析文件的效果。它会处理每条通过的记录,然后丢弃该流,从而保持较低的内存使用率。
如果您对使用GSON方法感兴趣,这里有一个很好的教程。 Detailed Tutorial
答案 2 :(得分:1)
我只希望存储键a,b和d的整数值,并忽略其余JSON(即,忽略c值中的任何值)。 ...如何在不将整个文件加载到内存的情况下执行此操作?
一种方法是使用{-{3}}的所谓的流解析器,该解析器由--stream选项调用。这正是您想要的,但是在时间和空间之间需要权衡,通常使用流解析器会更加困难。
例如,在当前情况下,使用非流式(即默认)解析器,可以简单地编写:
jq '.a, .b, .d' big.json
使用流解析器,您将必须编写如下内容:
jq --stream 'select(length==2 and .[0][-1] == ("a","b","c"))[1]' big.json
或者,如果您愿意:
jq -c --stream '["a","b","d"] as $keys | select(length==2 and (.[0][-1] | IN($keys[])))[1]' big.json
尽管jq有Java绑定(请参见jq中的“?:哪些语言绑定可用于Java?”),但我不知道任何与--stream选项一起使用的东西。
但是,由于jq的2.5MB很小,因此您可以使用其中一个可用的Java-jq绑定而不必担心流解析器。