我的应用程序期望json请求包含仅包含整数和可能为null值的(可能的多维)未排序数组。像[6, 2, [4, 3],[[[5], nil], 1]]
由于我无法解析无效的json,所以我不得不求助于使用正则表达式来完成肮脏的工作,而且它非常慢。
例如,上面的测试用例大约需要1.xx seconds
才能完成,而包含10000
个元素的平面数组要少于1 second
当前,我以字符串形式获取请求正文,然后应用正则表达式。
static ArrayList<Integer> getIntegers(String requestData) {
// Apply a regex to the request body
final String regularExpression = "([^\\d])+";
// to get all the nested arrays
Pattern pattern = Pattern.compile(regularExpression);
String[] results = pattern.split(requestData);
ArrayList<Integer> numbers = new ArrayList<>();
// loop over the results and add to numbers array
for (String result : results) {
try {
numbers.add(Integer.valueOf(result));
} catch (NumberFormatException e) {
// Catch and skip any non integers
}
}
return numbers;
}
}
无论如何,我是否可以加快速度?或者是否有另一种性能更好的替代方法?如果我需要处理包含20000个元素的多维数组,那就太慢了。
答案 0 :(得分:3)
我做了一些修改,并创建了以下课程:
class JsonNumberParser {
private final String json;
private final int length;
private final List<Integer> result;
private final char[] buffer = new char[64];
private int bufferIndex = 0;
public JsonNumberParser(String json) {
this.json = json;
length = json.length();
result = new ArrayList<>(length);
}
public List<Integer> parse() {
char c;
for (int i = 0; i < length; i++) {
c = json.charAt(i);
// if we encounter a comma and the buffer contains data
if (c == ',' && bufferIndex > 0) {
// then we add the new number
addBuffer();
// and reset the buffer
while (bufferIndex > 0) {
buffer[--bufferIndex] = '\0';
}
} else if (c == '-' || (c >= '0' && c <= '9')) {
buffer[bufferIndex++] = c;
}
}
// add the last possible number, if there was any
if (bufferIndex > 0) {
addBuffer();
}
// return the result
return result;
}
private void addBuffer() {
result.add(Integer.valueOf(new String(buffer, 0, bufferIndex)));
}
}
当然,您可以将所有这些放到一个方法中,但是随后您会得到一些有关添加Integers
的代码的重复。
此解析器的工作方式是,它使用缓冲区来缓冲数字,直到遇到逗号为止。这样,我们可以在json中包含大数字(在此实现中,最大为64位)。
您可以像以下示例中所示使用它:
List<Integer> integers = new JsonNumberParser(jsonRequest).parse();
关于性能,我希望它比使用Regex
快得多。但是可惜我手头没有基准测试设置
请记住,这不是验证程序,因此json字符串:
[[,,,]}]
只会产生一个空的List
(也许)改进:我已经考虑并搜索了更多内容。以下是一些可以改善性能的改进:
1。。只需为buffer
分配一个new int[64]
即可重置extension MyHelpTableViewCell {
override class var nib: UINib {
return UINib(nibName: "MyHelpTableViewCell", bundle: nil)
}
,这会产生更多的垃圾,但最终可能会更快。
2。。通过使用建议的答案here,可以改善数字的解析。它仅使用简单的旧数学,而没有创建字符串和解析整数。
答案 1 :(得分:2)
This answer已经指向正确的方向。重要的第一步是将昂贵的Pattern.compile
操作移出该方法,因为Pattern
实例可以重复使用。
此外,遍历数字匹配项可以使您免于创建split
的数组。现在,您也可以跳过子String
的创建:
static final Pattern NUMBER = Pattern.compile("\\d+");
static ArrayList<Integer> getIntegers(String requestData) {
ArrayList<Integer> numbers = new ArrayList<>();
Matcher m = NUMBER.matcher(requestData);
while(m.find()) numbers.add(Integer.parseInt(requestData, m.start(), m.end(), 10));
return numbers;
}
Integer.parseInt(CharSequence s, int beginIndex, int endIndex, int radix)
已在Java 9中添加。如果您使用的是旧版本,则可以创建自己的变体。为了简化,现在仅支持10的基数:
static final Pattern NUMBER = Pattern.compile("-?\\d+");
static ArrayList<Integer> getIntegers(String requestData) {
ArrayList<Integer> numbers = new ArrayList<>();
Matcher m = NUMBER.matcher(requestData);
while(m.find()) numbers.add(parseInt(requestData, m.start(), m.end()));
return numbers;
}
static int parseInt(CharSequence cs, int start, int end) {
int pos = start;
if(pos >= end) throw format(cs, start, end);
boolean negative = cs.charAt(pos) == '-';
if((negative || cs.charAt(pos) == '+') && ++pos==end)
throw format(cs, start, end);
int value = 0;
for(; pos < end; pos++) {
int next = cs.charAt(pos) - '0';
if(next < 0 || next > 9) throw format(cs, start, end);
if(value < Integer.MIN_VALUE/10) throw size(cs, start, pos, end);
value = value * 10 - next;
}
if(value > 0 || !negative && value == Integer.MIN_VALUE)
throw size(cs, start, pos, end);
return negative? value: -value;
}
private static RuntimeException format(CharSequence cs, int start, int end) {
return start > end? new IndexOutOfBoundsException(end+" < "+start):
new NumberFormatException(start == end?
"empty string": cs.subSequence(start, end).toString());
}
private static RuntimeException size(CharSequence cs, int start, int pos, int end) {
for(; pos < end; pos++)
if(cs.charAt(pos) < '0' || cs.charAt(pos) > '9') return format(cs, start, end);
return new NumberFormatException(cs.subSequence(start, end)+" outside the int range");
}
答案 2 :(得分:0)
如果您遇到的问题是性能,那么我认为流API不是一个好的解决方案。
static ArrayList<Integer> getIntegers(String requestData) {
char[] charArray = requestData.toCharArray();
ArrayList<Integer> numbers = new ArrayList<>();
for(char c : charArray) {
if(Character.isDigit(c)) {
numbers.add(Integer.valueOf(c) - 48);
}
}
return numbers;
}
答案 3 :(得分:0)
如何使用堆栈?
我们可以升级balanced braces问题。
在迭代字符串时,如果字符为notBracket()
,则它应该是一个数字。不用说,您将忽略所有逗号。同时,它还将验证数组结构。
这具有O(n)
的摊销复杂度。
答案 4 :(得分:0)
通过解析正向模式(例如\d+
)而不是负向模式([^\d]+
),可以获得更好的性能。
private static final Pattern NUMBER = Pattern.compile("\\d+");
List<Integer> extractNumbersRegex(String str) throws IOException {
Matcher m = NUMBER.matcher(str);
ArrayList<Integer> numbers = new ArrayList<>();
while (m.find()) {
numbers.add(Integer.parseInt(m.group()));
}
return numbers;
}
这对于从字符串中提取是可以的,但是对于大数据,可能会切换到更高效的方式,而这种方式不依赖于正则表达式而是直接匹配字符:
List<Integer> extractNumbersHandcoded(String str) throws IOException {
ArrayList<Integer> numbers = new ArrayList<>();
int start = 0;
while (start < str.length()) {
if (Character.isDigit(str.charAt(start))) {
break;
}
start++;
}
int bufferedInt = 0;
for (int i = start; i < str.length(); i++) {
char c = str.charAt(i);
if (Character.isDigit(c)) {
bufferedInt = bufferedInt * 10 + (c - '0');
} else {
numbers.add(bufferedInt);
bufferedInt = 0;
}
}
return numbers;
}
如果您的数据与流一样大,则可以考虑使用Streamtokenizer
的解决方案:
List<Integer> extractNumbersStreamTokenizer(String str) throws IOException {
StreamTokenizer s = new StreamTokenizer(new StringReader(str));
ArrayList<Integer> numbers = new ArrayList<>();
int token;
while ((token = s.nextToken()) != StreamTokenizer.TT_EOF) {
if (token == StreamTokenizer.TT_NUMBER) {
numbers.add((int) s.nval);
}
}
return numbers;
}
所有解决方案均假定数据仅包含整数文字(不包含浮点文字)。