Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge: Performance improvements: DiskLruCache, HttpResponseCache. #26

Merged
merged 1 commit into from
Sep 19, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions src/main/java/libcore/io/DiskLruCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package libcore.io;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.EOFException;
Expand Down Expand Up @@ -228,13 +227,14 @@ public static DiskLruCache open(File directory, int appVersion, int valueCount,
}

private void readJournal() throws IOException {
InputStream in = new BufferedInputStream(new FileInputStream(journalFile));
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile),
Charsets.US_ASCII);
try {
String magic = Streams.readAsciiLine(in);
String version = Streams.readAsciiLine(in);
String appVersionString = Streams.readAsciiLine(in);
String valueCountString = Streams.readAsciiLine(in);
String blank = Streams.readAsciiLine(in);
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
Expand All @@ -246,13 +246,13 @@ private void readJournal() throws IOException {

while (true) {
try {
readJournalLine(Streams.readAsciiLine(in));
readJournalLine(reader.readLine());
} catch (EOFException endOfJournal) {
break;
}
}
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(reader);
}
}

Expand Down
241 changes: 241 additions & 0 deletions src/main/java/libcore/io/StrictLineReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package libcore.io;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import libcore.util.Charsets;

/**
* Buffers input from an {@link InputStream} for reading lines.
*
* This class is used for buffered reading of lines. For purposes of this class, a line ends with
* "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated line at
* end of input is invalid and will be ignored, the caller may use {@code hasUnterminatedLine()}
* to detect it after catching the {@code EOFException}.
*
* This class is intended for reading input that strictly consists of lines, such as line-based
* cache entries or cache journal. Unlike the {@link BufferedReader} which in conjunction with
* {@link InputStreamReader} provides similar functionality, this class uses different
* end-of-input reporting and a more restrictive definition of a line.
*
* This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
* and 10, respectively, and the representation of no other character contains these values.
* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
* The default charset is US_ASCII.
*/
public class StrictLineReader implements Closeable {
private static final byte CR = (byte)'\r';
private static final byte LF = (byte)'\n';

private final InputStream in;
private final Charset charset;

/*
* Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
* and the data in the range [pos, end) is buffered for reading. At end of input, if there is
* an unterminated line, we set end == -1, otherwise end == pos. If the underlying
* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
*/
private byte[] buf;
private int pos;
private int end;

/**
* Constructs a new {@code StrictLineReader} with the default capacity and charset.
*
* @param in the {@code InputStream} to read data from.
* @throws NullPointerException if {@code in} is null.
*/
public StrictLineReader(InputStream in) {
this(in, 8192);
}

/**
* Constructs a new {@code LineReader} with the specified capacity and the default charset.
*
* @param in the {@code InputStream} to read data from.
* @param capacity the capacity of the buffer.
* @throws NullPointerException if {@code in} is null.
* @throws IllegalArgumentException for negative or zero {@code capacity}.
*/
public StrictLineReader(InputStream in, int capacity) {
this(in, capacity, Charsets.US_ASCII);
}

/**
* Constructs a new {@code LineReader} with the specified charset and the default capacity.
*
* @param in the {@code InputStream} to read data from.
* @param charset the charset used to decode data.
* Only US-ASCII, UTF-8 and ISO-8859-1 is supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if the specified charset is not supported.
*/
public StrictLineReader(InputStream in, Charset charset) {
this(in, 8192, charset);
}

/**
* Constructs a new {@code LineReader} with the specified capacity and charset.
*
* @param in the {@code InputStream} to read data from.
* @param capacity the capacity of the buffer.
* @param charset the charset used to decode data.
* Only US-ASCII, UTF-8 and ISO-8859-1 is supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if {@code capacity} is negative or zero
* or the specified charset is not supported.
*/
public StrictLineReader(InputStream in, int capacity, Charset charset) {
if (in == null || charset == null) {
throw new NullPointerException();
}
if (capacity < 0) {
throw new IllegalArgumentException("capacity <= 0");
}
if (!(charset.equals(Charsets.US_ASCII) || charset.equals(Charsets.UTF_8) ||
charset.equals(Charsets.ISO_8859_1))) {
throw new IllegalArgumentException("Unsupported encoding");
}

this.in = in;
this.charset = charset;
buf = new byte[capacity];
}

/**
* Closes the reader by closing the underlying {@code InputStream} and
* marking this reader as closed.
*
* @throws IOException for errors when closing the underlying {@code InputStream}.
*/
@Override
public void close() throws IOException {
synchronized (in) {
if (buf != null) {
buf = null;
in.close();
}
}
}

/**
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
* this end of line marker is not included in the result.
*
* @return the next line from the input.
* @throws IOException for underlying {@code InputStream} errors.
* @throws EOFException for the end of source stream.
*/
public String readLine() throws IOException {
synchronized (in) {
if (buf == null) {
throw new IOException("LineReader is closed");
}

// Read more data if we are at the end of the buffered data.
// Though it's an error to read after an exception, we will let {@code fillBuf()}
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
if (pos >= end) {
fillBuf();
}
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
String res = new String(buf, pos, lineEnd - pos, charset);
pos = i + 1;
return res;
}
}

// Let's anticipate up to 80 characters on top of those already read.
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
@Override
public String toString() {
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
return new String(buf, 0, length, charset);
}
};

while (true) {
out.write(buf, pos, end - pos);
// Mark unterminated line in case fillBuf throws EOFException or IOException.
end = -1;
fillBuf();
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
if (i != pos) {
out.write(buf, pos, i - pos);
}
pos = i + 1;
return out.toString();
}
}
}
}
}

/**
* Read an {@code int} from a line containing its decimal representation.
*
* @return the value of the {@code int} from the next line.
* @throws IOException for underlying {@code InputStream} errors or conversion error.
* @throws EOFException for the end of source stream.
*/
public int readInt() throws IOException {
String intString = readLine();
try {
return Integer.parseInt(intString);
} catch (NumberFormatException e) {
throw new IOException("expected an int but was \"" + intString + "\"");
}
}

/**
* Check whether there was an unterminated line at end of input after the line reader reported
* end-of-input with EOFException. The value is meaningless in any other situation.
*
* @return true if there was an unterminated line at end of input.
*/
public boolean hasUnterminatedLine() {
return end == -1;
}

/**
* Reads new input data into the buffer. Call only with pos == end or end == -1,
* depending on the desired outcome if the function throws.
*
* @throws IOException for underlying {@code InputStream} errors.
* @throws EOFException for the end of source stream.
*/
private void fillBuf() throws IOException {
int result = in.read(buf, 0, buf.length);
if (result == -1) {
throw new EOFException();
}
pos = 0;
end = result;
}
}

43 changes: 18 additions & 25 deletions src/main/java/libcore/net/http/HttpResponseCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import libcore.io.DiskLruCache;
import libcore.io.IoUtils;
import libcore.io.Streams;
import libcore.io.StrictLineReader;
import libcore.util.Charsets;
import libcore.util.ExtendedResponseCache;
import libcore.util.IntegralToString;
Expand Down Expand Up @@ -104,7 +105,7 @@ private String uriToKey(URI uri) {
if (snapshot == null) {
return null;
}
entry = new Entry(new BufferedInputStream(snapshot.getInputStream(ENTRY_METADATA)));
entry = new Entry(snapshot.getInputStream(ENTRY_METADATA));
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
Expand Down Expand Up @@ -374,29 +375,30 @@ private static final class Entry {
*/
public Entry(InputStream in) throws IOException {
try {
uri = Streams.readAsciiLine(in);
requestMethod = Streams.readAsciiLine(in);
StrictLineReader reader = new StrictLineReader(in, Charsets.US_ASCII);
uri = reader.readLine();
requestMethod = reader.readLine();
varyHeaders = new RawHeaders();
int varyRequestHeaderLineCount = readInt(in);
int varyRequestHeaderLineCount = reader.readInt();
for (int i = 0; i < varyRequestHeaderLineCount; i++) {
varyHeaders.addLine(Streams.readAsciiLine(in));
varyHeaders.addLine(reader.readLine());
}

responseHeaders = new RawHeaders();
responseHeaders.setStatusLine(Streams.readAsciiLine(in));
int responseHeaderLineCount = readInt(in);
responseHeaders.setStatusLine(reader.readLine());
int responseHeaderLineCount = reader.readInt();
for (int i = 0; i < responseHeaderLineCount; i++) {
responseHeaders.addLine(Streams.readAsciiLine(in));
responseHeaders.addLine(reader.readLine());
}

if (isHttps()) {
String blank = Streams.readAsciiLine(in);
if (blank.length() != 0) {
String blank = reader.readLine();
if (!blank.isEmpty()) {
throw new IOException("expected \"\" but was \"" + blank + "\"");
}
cipherSuite = Streams.readAsciiLine(in);
peerCertificates = readCertArray(in);
localCertificates = readCertArray(in);
cipherSuite = reader.readLine();
peerCertificates = readCertArray(reader);
localCertificates = readCertArray(reader);
} else {
cipherSuite = null;
peerCertificates = null;
Expand Down Expand Up @@ -463,25 +465,16 @@ private boolean isHttps() {
return uri.startsWith("https://");
}

private int readInt(InputStream in) throws IOException {
String intString = Streams.readAsciiLine(in);
try {
return Integer.parseInt(intString);
} catch (NumberFormatException e) {
throw new IOException("expected an int but was \"" + intString + "\"");
}
}

private Certificate[] readCertArray(InputStream in) throws IOException {
int length = readInt(in);
private Certificate[] readCertArray(StrictLineReader reader) throws IOException {
int length = reader.readInt();
if (length == -1) {
return null;
}
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Certificate[] result = new Certificate[length];
for (int i = 0; i < result.length; i++) {
String line = Streams.readAsciiLine(in);
String line = reader.readLine();
byte[] bytes = Base64.decode(line.getBytes("US-ASCII"));
result[i] = certificateFactory.generateCertificate(
new ByteArrayInputStream(bytes));
Expand Down
Loading