Android 速度BufferedReader读取行和ArrayList的添加和获取
Android speed BufferedReader read line and ArrayList add and get
我创建了一个 Obj 文件 reader,它使用缓冲 reader 逐行读取,我必须检查每一行是否是一个顶点 我将它添加到顶点 ArrayList
如果它是一张脸,我创建一个 Face
对象,其中 get(i)
顶点 ArrayList
和另外 2 个列表 "normal and UVs" 然后添加到面列表中。
这是代码
public final void ctreateObject() {
float now = System.nanoTime();
BufferedReader bufferReader = new BufferedReader(inputStreamReader);
String line;
try {
while ((line = bufferReader.readLine()) != null) {
if (line.startsWith("f")) {
processFLine(line);
} else if (line.startsWith("vn")) {
processVNLine(line);
} else if (line.startsWith("vt")) {
processVTLine(line);
} else if (line.startsWith("v")) {
processVLine(line);
} else if (line.startsWith("usemtl")) {
mtlName = line.split("[ ]+", 2)[1];
} else if (line.startsWith("mtllib")) {
mtllib = line.split("[ ]+")[1];
} else if (line.startsWith("g") || line.startsWith("o")) {
if (faces.size() > 0) {
List<Face> theFaces = new ArrayList<Face>();
theFaces.addAll(faces);
Model model = new Model(id, theFaces, mtlName);
SharedData.models.add(model);
faces.clear();
}
}
Log.i("line", line);
ln++;
}
if (faces.size() > 0) {
List<Face> theFaces = new ArrayList<Face>();
theFaces.addAll(faces);
Model model = new Model(id, theFaces, mtlName);
SharedData.models.add(model);
faces.clear();
vertices.clear();
normals.clear();
uvs.clear();
}
inputStreamReader.close();
bufferReader.close();
} catch (IOException e) {
e.printStackTrace();
}
Log.i("Line", String.valueOf(ln));
Log.i("time", String.valueOf((System.nanoTime() - now) / 1000000000));
}
private void processVLine(String line) {
String[] tokens = line.split("[ ]+");
vertices.add(new float[] { Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3]) });
}
private void processVNLine(String line) {
String[] tokens = line.split("[ ]+");
normals.add(new float[] { Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3]) });
}
private void processVTLine(String line) {
String[] tokens = line.split("[ ]+");
uvs.add(new float[] { Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]) });
}
private void processFLine(String line) {
String[] tokens = line.split("[ ]+");
if (tokens.length == 4) {
makeFace3(tokens);
}
}
private void makeFace3(String[] tokens) {
if (tokens[1].matches("[0-9]+")) {// f: v
Face face = new Face(vertices.get(Integer.parseInt(tokens[1]) - 1), vertices.get(Integer.parseInt(tokens[2]) - 1), vertices.get(Integer.parseInt(tokens[1]) - 1));
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3]) - 1));
}
if (uvs.size() > 0) {
face.setAuv(uvs.get(Integer.parseInt(tokens[1]) - 1));
face.setBuv(uvs.get(Integer.parseInt(tokens[2]) - 1));
face.setCuv(uvs.get(Integer.parseInt(tokens[3]) - 1));
}
faces.add(face);
}
if (tokens[1].matches("[0-9]+/[0-9]+")) {
Face face = new Face(vertices.get(Integer.parseInt(tokens[1].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[2].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1].split("/")[0]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2].split("/")[0]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
}
if (uvs.size() > 0) {
face.setAuv(uvs.get(Integer.parseInt(tokens[1].split("/")[1]) - 1));
face.setBuv(uvs.get(Integer.parseInt(tokens[2].split("/")[1]) - 1));
face.setCuv(uvs.get(Integer.parseInt(tokens[3].split("/")[1]) - 1));
}
faces.add(face);
}
if (tokens[1].matches("[0-9]+//[0-9]+")) {// f: v//vn
Face face = new Face(vertices.get(Integer.parseInt(tokens[1].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[2].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
if (uvs.size() > 0) {
face.setAuv(uvs.get(Integer.parseInt(tokens[1].split("/")[0]) - 1));
face.setBuv(uvs.get(Integer.parseInt(tokens[2].split("/")[0]) - 1));
face.setCuv(uvs.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
}
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1].split("/")[2]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2].split("/")[2]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3].split("/")[2]) - 1));
}
faces.add(face);
}
if (tokens[1].matches("[0-9]+/[0-9]+/[0-9]+")) {
Face face = new Face(vertices.get(Integer.parseInt(tokens[1].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[2].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
if (uvs.size() > 0) {
face.setAuv(uvs.get(Integer.parseInt(tokens[1].split("/")[1]) - 1));
face.setBuv(uvs.get(Integer.parseInt(tokens[2].split("/")[1]) - 1));
face.setCuv(uvs.get(Integer.parseInt(tokens[3].split("/")[1]) - 1));
}
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1].split("/")[2]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2].split("/")[2]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3].split("/")[2]) - 1));
}
faces.add(face);
}
}
问题出在性能上,如果文件包含大约 120,000 行,这个过程需要大约 90 秒,这太长了,因为我想加载很多这样的模型,如果文件更复杂并且有 850,000 行,这个过程需要大约 280 秒不被接受,BufferReader
可以非常非常快地扫描行但是 ArrayList
处理导致缓慢,我测试了 LinkedList
但结果很糟糕“慢了 5 倍",那么我可以使用一种方法或其他解决方案来做到这一点吗?稍后我迭代面 ArrayList
以创建缓冲区并将其传递给 OpenGL
.
编辑
我对相同的 120,000 行文件使用了 Vector
结果是 109 秒(比 ArrayList
增加了 20 秒)
BufferedReader
和 ArrayList
不是问题。
您的性能问题基本上归结为两个问题:分配和解析。
解析 - 第一部分
String[] tokens = line.split("[ ]+");
您正在使用 正则表达式 标记字符串。这是将行拆分为标记的最慢方式。您可以通过自己遍历字符串并在进行时构建标记来加快速度。这就是 "low-hanging fruit",小的改变会给你带来大的加速。
if (tokens[1].matches("[0-9]+")) {// f: v
再次使用正则表达式。这会减慢您的速度。
解析 - 第二部分
为什么要重新解析已经解析过的内容?
Face face = new Face(vertices.get(Integer.parseInt(tokens[1]) - 1), vertices.get(Integer.parseInt(tokens[2]) - 1), vertices.get(Integer.parseInt(tokens[1]) - 1));
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3]) - 1));
}
您正在重复调用 Integer.parseInt(tokens[x]) - 1
。您应该将结果放入 int
并使用它。
分配
查看您调用 new
运算符的次数。每个对象分配都会在性能方面付出代价。查看循环中的每个 new
运算符并问:有什么方法可以一次完成所有这些操作吗?例如:每个顶点是一个float[3]
。与其一遍又一遍地调用 new float[3]
,不如从 numVertices
的试用值开始,然后只调用一次 new float[numVertices][3]
?这可能并非在所有情况下都可行,但您应该寻找机会减少 new
来电。
与此相关的是 ArrayList
的使用。使用ArrayList
来累加对象很简单,但是效率不是很高。请记住,每次数组达到最大值时,都必须分配一个新数组并将所有现有数据复制到其中。
该 OBJ 文件格式不是很友好,因为它会转储顶点、坐标、矢量等,而不会在使用前给您一个计数,这在这里会很有帮助。
我创建了一个 Obj 文件 reader,它使用缓冲 reader 逐行读取,我必须检查每一行是否是一个顶点 我将它添加到顶点 ArrayList
如果它是一张脸,我创建一个 Face
对象,其中 get(i)
顶点 ArrayList
和另外 2 个列表 "normal and UVs" 然后添加到面列表中。
这是代码
public final void ctreateObject() {
float now = System.nanoTime();
BufferedReader bufferReader = new BufferedReader(inputStreamReader);
String line;
try {
while ((line = bufferReader.readLine()) != null) {
if (line.startsWith("f")) {
processFLine(line);
} else if (line.startsWith("vn")) {
processVNLine(line);
} else if (line.startsWith("vt")) {
processVTLine(line);
} else if (line.startsWith("v")) {
processVLine(line);
} else if (line.startsWith("usemtl")) {
mtlName = line.split("[ ]+", 2)[1];
} else if (line.startsWith("mtllib")) {
mtllib = line.split("[ ]+")[1];
} else if (line.startsWith("g") || line.startsWith("o")) {
if (faces.size() > 0) {
List<Face> theFaces = new ArrayList<Face>();
theFaces.addAll(faces);
Model model = new Model(id, theFaces, mtlName);
SharedData.models.add(model);
faces.clear();
}
}
Log.i("line", line);
ln++;
}
if (faces.size() > 0) {
List<Face> theFaces = new ArrayList<Face>();
theFaces.addAll(faces);
Model model = new Model(id, theFaces, mtlName);
SharedData.models.add(model);
faces.clear();
vertices.clear();
normals.clear();
uvs.clear();
}
inputStreamReader.close();
bufferReader.close();
} catch (IOException e) {
e.printStackTrace();
}
Log.i("Line", String.valueOf(ln));
Log.i("time", String.valueOf((System.nanoTime() - now) / 1000000000));
}
private void processVLine(String line) {
String[] tokens = line.split("[ ]+");
vertices.add(new float[] { Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3]) });
}
private void processVNLine(String line) {
String[] tokens = line.split("[ ]+");
normals.add(new float[] { Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3]) });
}
private void processVTLine(String line) {
String[] tokens = line.split("[ ]+");
uvs.add(new float[] { Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]) });
}
private void processFLine(String line) {
String[] tokens = line.split("[ ]+");
if (tokens.length == 4) {
makeFace3(tokens);
}
}
private void makeFace3(String[] tokens) {
if (tokens[1].matches("[0-9]+")) {// f: v
Face face = new Face(vertices.get(Integer.parseInt(tokens[1]) - 1), vertices.get(Integer.parseInt(tokens[2]) - 1), vertices.get(Integer.parseInt(tokens[1]) - 1));
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3]) - 1));
}
if (uvs.size() > 0) {
face.setAuv(uvs.get(Integer.parseInt(tokens[1]) - 1));
face.setBuv(uvs.get(Integer.parseInt(tokens[2]) - 1));
face.setCuv(uvs.get(Integer.parseInt(tokens[3]) - 1));
}
faces.add(face);
}
if (tokens[1].matches("[0-9]+/[0-9]+")) {
Face face = new Face(vertices.get(Integer.parseInt(tokens[1].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[2].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1].split("/")[0]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2].split("/")[0]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
}
if (uvs.size() > 0) {
face.setAuv(uvs.get(Integer.parseInt(tokens[1].split("/")[1]) - 1));
face.setBuv(uvs.get(Integer.parseInt(tokens[2].split("/")[1]) - 1));
face.setCuv(uvs.get(Integer.parseInt(tokens[3].split("/")[1]) - 1));
}
faces.add(face);
}
if (tokens[1].matches("[0-9]+//[0-9]+")) {// f: v//vn
Face face = new Face(vertices.get(Integer.parseInt(tokens[1].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[2].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
if (uvs.size() > 0) {
face.setAuv(uvs.get(Integer.parseInt(tokens[1].split("/")[0]) - 1));
face.setBuv(uvs.get(Integer.parseInt(tokens[2].split("/")[0]) - 1));
face.setCuv(uvs.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
}
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1].split("/")[2]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2].split("/")[2]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3].split("/")[2]) - 1));
}
faces.add(face);
}
if (tokens[1].matches("[0-9]+/[0-9]+/[0-9]+")) {
Face face = new Face(vertices.get(Integer.parseInt(tokens[1].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[2].split("/")[0]) - 1), vertices.get(Integer.parseInt(tokens[3].split("/")[0]) - 1));
if (uvs.size() > 0) {
face.setAuv(uvs.get(Integer.parseInt(tokens[1].split("/")[1]) - 1));
face.setBuv(uvs.get(Integer.parseInt(tokens[2].split("/")[1]) - 1));
face.setCuv(uvs.get(Integer.parseInt(tokens[3].split("/")[1]) - 1));
}
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1].split("/")[2]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2].split("/")[2]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3].split("/")[2]) - 1));
}
faces.add(face);
}
}
问题出在性能上,如果文件包含大约 120,000 行,这个过程需要大约 90 秒,这太长了,因为我想加载很多这样的模型,如果文件更复杂并且有 850,000 行,这个过程需要大约 280 秒不被接受,BufferReader
可以非常非常快地扫描行但是 ArrayList
处理导致缓慢,我测试了 LinkedList
但结果很糟糕“慢了 5 倍",那么我可以使用一种方法或其他解决方案来做到这一点吗?稍后我迭代面 ArrayList
以创建缓冲区并将其传递给 OpenGL
.
编辑
我对相同的 120,000 行文件使用了 Vector
结果是 109 秒(比 ArrayList
增加了 20 秒)
BufferedReader
和 ArrayList
不是问题。
您的性能问题基本上归结为两个问题:分配和解析。
解析 - 第一部分
String[] tokens = line.split("[ ]+");
您正在使用 正则表达式 标记字符串。这是将行拆分为标记的最慢方式。您可以通过自己遍历字符串并在进行时构建标记来加快速度。这就是 "low-hanging fruit",小的改变会给你带来大的加速。
if (tokens[1].matches("[0-9]+")) {// f: v
再次使用正则表达式。这会减慢您的速度。
解析 - 第二部分
为什么要重新解析已经解析过的内容?
Face face = new Face(vertices.get(Integer.parseInt(tokens[1]) - 1), vertices.get(Integer.parseInt(tokens[2]) - 1), vertices.get(Integer.parseInt(tokens[1]) - 1));
if (normals.size() > 0) {
face.setAn(normals.get(Integer.parseInt(tokens[1]) - 1));
face.setBn(normals.get(Integer.parseInt(tokens[2]) - 1));
face.setCn(normals.get(Integer.parseInt(tokens[3]) - 1));
}
您正在重复调用 Integer.parseInt(tokens[x]) - 1
。您应该将结果放入 int
并使用它。
分配
查看您调用 new
运算符的次数。每个对象分配都会在性能方面付出代价。查看循环中的每个 new
运算符并问:有什么方法可以一次完成所有这些操作吗?例如:每个顶点是一个float[3]
。与其一遍又一遍地调用 new float[3]
,不如从 numVertices
的试用值开始,然后只调用一次 new float[numVertices][3]
?这可能并非在所有情况下都可行,但您应该寻找机会减少 new
来电。
与此相关的是 ArrayList
的使用。使用ArrayList
来累加对象很简单,但是效率不是很高。请记住,每次数组达到最大值时,都必须分配一个新数组并将所有现有数据复制到其中。
该 OBJ 文件格式不是很友好,因为它会转储顶点、坐标、矢量等,而不会在使用前给您一个计数,这在这里会很有帮助。