효율적으로 입력 스트림에서 Android 읽기


152

내가 만들고있는 안드로이드 응용 프로그램을 위해 웹 사이트에 HTTP get 요청을하고 있습니다.

DefaultHttpClient를 사용하고 HttpGet을 사용하여 요청을 발행하고 있습니다. 엔티티 응답을 얻고이 페이지에서 html을 가져 오기위한 InputStream 객체를 얻습니다.

그런 다음 다음과 같이 회신을 순환합니다.

BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
String x = "";
x = r.readLine();
String total = "";

while(x!= null){
total += x;
x = r.readLine();
}

그러나 이것은 엄청나게 느립니다.

이것이 비효율적인가? 큰 웹 페이지 -www.cokezone.co.uk를 로드하지 않으므로 파일 크기가 크지 않습니다. 더 좋은 방법이 있습니까?

감사

앤디


실제로 행을 구문 분석하지 않으면 한 행씩 읽는 것이 의미가 없습니다. 고정 크기의 버퍼를 통해 char로 char을 읽는 것이 좋습니다
Mike76

답변:


355

코드의 문제는 많은 무거운 String객체를 만들고 내용을 복사하고 작업을 수행 한다는 것입니다. 대신, 각 추가에 StringBuilderString객체를 만들지 않고 char 배열을 복사 하지 않도록 사용해야 합니다. 귀하의 사례에 대한 구현은 다음과 같습니다.

BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder total = new StringBuilder();
for (String line; (line = r.readLine()) != null; ) {
    total.append(line).append('\n');
}

이제 total로 변환하지 않고 사용할 수 String있지만 결과를로 필요하면 String간단히 추가하십시오.

문자열 결과 = total.toString ();

나는 그것을 더 잘 설명하려고 노력할 것입니다 ...

  • a += b(나 a = a + b) 여기서 ab문자열, 복사의 내용입니다 모두 a b (당신은 또한 복사합니다 새로운 객체에 a포함, 축적을 String ), 당신은 각각의 반복에 그 사본을 다하고 있습니다.
  • a.append(b), where ais StringBuilder는에 b내용을 직접 추가 a하므로 반복 할 때마다 누적 된 문자열을 복사하지 않습니다.

23
보너스 포인트의 경우 StringBuilder가 가득 차면 재 할당을 피할 수있는 초기 용량을 제공하십시오. StringBuilder total = new StringBuilder(inputStream.available());
dokkaebi

10
이 줄 바꿈 문자를 잘라 내지 않습니까?
Nathan Schwermann

5
try / catch에서 while을 마무리하는 것을 잊지 마십시오. try {while ((line = r.readLine ())! = null) {total.append (line); }} catch (IOException e) {Log.i (tag, "inputStreamToString 함수에서 readline 문제"); }
botbot

4
@botbot : 예외를 무시하고 무시하는 것만으로는 예외를 무시하는 것보다 낫지 않습니다 ...
Matti Virkkunen

50
안드로이드에 내장 된 스트림-문자열 변환이 없다는 것은 놀라운 일입니다. 웹의 모든 코드 스 니펫과 행성의 앱을 다시 구현하면 readline루프가 어리 석습니다. 이 패턴은 70 년대에 완두콩으로 죽었을 것입니다.
Edward Brey

35

스트림을 문자열로 변환하기 위해 내장 메소드를 사용해 보셨습니까? Apache Commons 라이브러리 (org.apache.commons.io.IOUtils)의 일부입니다.

그런 다음 코드는 다음 한 줄입니다.

String total = IOUtils.toString(inputStream);

이에 대한 설명서는 http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29 에서 찾을 수 있습니다 .

Apache Commons IO 라이브러리는 다음 위치에서 다운로드 할 수 있습니다. http://commons.apache.org/io/download_io.cgi


응답이 늦었지만 Google 검색을 통해이 문제가 발생했습니다.
Makotosan

61
안드로이드 API는 IOUtils를 포함하지 않습니다
Charles Ma

2
그렇기 때문에 외부 라이브러리를 언급했습니다. 라이브러리를 Android 프로젝트에 추가했으며 스트림에서 쉽게 읽을 수있었습니다.
Makotosan

어디서 다운로드 할 수 있으며 어떻게 안드로이드 프로젝트로 가져 왔습니까?
사파리

3
다운로드해야한다면 "내장"이라고 부르지 않습니다. 그럼에도 불구하고, 나는 방금 그것을 다운로드하고 그것을 갈 것입니다.
B. Clay Shannon

15

구아바의 또 다른 가능성 :

의존: compile 'com.google.guava:guava:11.0.2'

import com.google.common.io.ByteStreams;
...

String total = new String(ByteStreams.toByteArray(inputStream ));

9

나는 이것이 충분히 효율적이라고 믿습니다 ... InputStream에서 String을 얻으려면 다음 메소드를 호출합니다.

public static String getStringFromInputStream(InputStream stream) throws IOException
{
    int n = 0;
    char[] buffer = new char[1024 * 4];
    InputStreamReader reader = new InputStreamReader(stream, "UTF8");
    StringWriter writer = new StringWriter();
    while (-1 != (n = reader.read(buffer))) writer.write(buffer, 0, n);
    return writer.toString();
}

나는 항상 UTF-8을 사용합니다. 물론 InputStream 외에도 charset을 인수로 설정할 수 있습니다.


6

이건 어때? 더 나은 성능을 제공하는 것 같습니다.

byte[] bytes = new byte[1000];

StringBuilder x = new StringBuilder();

int numRead = 0;
while ((numRead = is.read(bytes)) >= 0) {
    x.append(new String(bytes, 0, numRead));
}

편집 : 실제로이 종류는 스틸 바이트와 모리스 페리 모두를 포함합니다.


문제는-시작하기 전에 읽은 것의 크기를 모른다는 것입니다. 따라서 배열의 형태가 필요할 수도 있습니다. 필자는 http를 통해 InputStream 또는 URL을 쿼리하여 바이트 배열의 크기를 최적화하는 것이 얼마나 큰지 알아낼 수 있습니다. 나는 주요 문제인 모바일 장치에서 효율적이어야합니다! 그러나 그 아이디어에 감사드립니다-오늘 밤에 촬영하고 성능 향상 측면에서 어떻게 처리하는지 알려드립니다!
RenegadeAndy

들어오는 스트림의 크기가 그렇게 중요하지 않다고 생각합니다. 위의 코드는 한 번에 1000 바이트를 읽지 만 해당 크기를 늘리거나 줄일 수 있습니다. 내 테스트에서 날씨가 1000/10000 바이트를 사용하는 것과 큰 차이는 없었습니다. 그래도 간단한 Java 앱이었습니다. 모바일 장치에서 더 중요 할 수 있습니다.
Adrian

4
두 번의 후속 읽기로 잘린 유니 코드 엔터티가 생길 수 있습니다. BufferedReader가하는 것과 같은 \ n과 같은 일종의 경계 문자까지 읽을 때 더 좋습니다.
Jacob Nordfalk

4

Jaime Soriano의 답변보다 다소 빠르며 Adrian의 답변에 멀티 바이트 인코딩 문제가 없으면 다음과 같이 제안합니다.

File file = new File("/tmp/myfile");
try {
    FileInputStream stream = new FileInputStream(file);

    int count;
    byte[] buffer = new byte[1024];
    ByteArrayOutputStream byteStream =
        new ByteArrayOutputStream(stream.available());

    while (true) {
        count = stream.read(buffer);
        if (count <= 0)
            break;
        byteStream.write(buffer, 0, count);
    }

    String string = byteStream.toString();
    System.out.format("%d bytes: \"%s\"%n", string.length(), string);
} catch (IOException e) {
    e.printStackTrace();
}

왜 더 빠른지 설명 할 수 있습니까?
Akhil Dad

줄 바꿈 문자의 입력을 스캔하지 않지만 1024 바이트의 청크 만 읽습니다. 나는 이것이 실질적인 차이를 만들 것이라고 주장하지는 않는다.
heiner

@Ronald 답변에 대한 의견이 있으십니까? 그는 동일한 작업을 수행하지만 inputStream 크기와 동일한 큰 청크를 처리합니다. 또한 Nikola 응답으로 바이트 배열이 아닌 문자 배열을 스캔하면 어떻게 다른가요? 실제로 어떤 경우에 어떤 접근 방식이 가장 좋은지 알고 싶습니까? 또한 내의 readLine 제거합니다 \ n과 \ r은하지만 난 볼 그들이 작성한 Readline을 사용하는 IO 응용 프로그램 코드 구글
아킬 아빠

3

어쩌면 '한 번에 한 줄씩'을 읽고 문자열을 결합하고 줄 끝을 스캔하지 않도록하고 문자열 조인을 피하기 위해 '사용 가능한 모든 항목 읽기'를 시도하십시오.

즉, InputStream.available()InputStream.read(byte[] b), int offset, int length)


흠. 따라서 다음과 같습니다 : int offset = 5000; 바이트 [] bArr = 새로운 바이트 [100]; 바이트 [] 총계 = 바이트 [5000]; while (InputStream.available) {offset = InputStream.read (bArr, offset, 100); for (int i = 0; i <offset; i ++) {total [i] = bArr [i]; } bArr = 새로운 바이트 [100]; } 정말 효율적입니까? 아니면 내가 잘못 쓴 것입니까! 예를 들어주세요!
RenegadeAndy

2
아니요 아니요 아니요 아니요, 간단히 {byte total [] = new [instrm.available ()]; instrm.read (총, 0, 총 길이); } 그런 다음 문자열로 필요한 경우 {String asString = String (total, 0, total.length, "utf-8"); // utf8 :-)}
SteelBytes

2

한 번에 한 줄의 텍스트를 읽고 해당 줄을 개별적으로 문자열에 추가하면 각 줄을 추출하고 너무 많은 메소드 호출의 오버 헤드가 발생합니다.

스트림 데이터를 보유하기 위해 적절한 크기의 바이트 배열을 할당하고 필요할 때 더 큰 배열로 반복적으로 대체하고 배열이 보유 할 수있는 한 많이 읽으려고 노력함으로써 더 나은 성능을 얻을 수있었습니다.

어떤 이유로 든 코드에서 HTTPUrlConnection에 의해 반환 된 InputStream을 사용할 때 전체 파일을 반복적으로 다운로드하지 못했기 때문에 전체 파일을 가져 오거나 취소 할 수 있도록 BufferedReader와 수동 롤아웃 시간 초과 메커니즘을 모두 사용해야했습니다. 이동 수단.

private static  final   int         kBufferExpansionSize        = 32 * 1024;
private static  final   int         kBufferInitialSize          = kBufferExpansionSize;
private static  final   int         kMillisecondsFactor         = 1000;
private static  final   int         kNetworkActionPeriod        = 12 * kMillisecondsFactor;

private String loadContentsOfReader(Reader aReader)
{
    BufferedReader  br = null;
    char[]          array = new char[kBufferInitialSize];
    int             bytesRead;
    int             totalLength = 0;
    String          resourceContent = "";
    long            stopTime;
    long            nowTime;

    try
    {
        br = new BufferedReader(aReader);

        nowTime = System.nanoTime();
        stopTime = nowTime + ((long)kNetworkActionPeriod * kMillisecondsFactor * kMillisecondsFactor);
        while(((bytesRead = br.read(array, totalLength, array.length - totalLength)) != -1)
        && (nowTime < stopTime))
        {
            totalLength += bytesRead;
            if(totalLength == array.length)
                array = Arrays.copyOf(array, array.length + kBufferExpansionSize);
            nowTime = System.nanoTime();
        }

        if(bytesRead == -1)
            resourceContent = new String(array, 0, totalLength);
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }

    try
    {
        if(br != null)
            br.close();
    }
    catch(IOException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

편집 : 내용을 다시 인코딩 할 필요가 없다면 (즉, 내용을 그대로 원한다는 것이 밝혀졌습니다) ) Reader 하위 클래스를 사용하지 않아야합니다. 적절한 Stream 서브 클래스를 사용하십시오.

앞의 방법의 시작 부분을 다음의 해당 줄로 바꾸어 2 ~ 3 배 더 빠르게합니다 .

String  loadContentsFromStream(Stream aStream)
{
    BufferedInputStream br = null;
    byte[]              array;
    int                 bytesRead;
    int                 totalLength = 0;
    String              resourceContent;
    long                stopTime;
    long                nowTime;

    resourceContent = "";
    try
    {
        br = new BufferedInputStream(aStream);
        array = new byte[kBufferInitialSize];

이것은 위의 답변보다 훨씬 빠릅니다. 안드로이드에서 "리더"와 "스트림"을 어떻게 사용합니까?
SteveGSD

1

파일이 길면 각 줄에 문자열 연결을 사용하는 대신 StringBuilder에 추가하여 코드를 최적화 할 수 있습니다.


솔직히 말해서 웹 사이트 www.cokezone.co.uk의 페이지 소스는 그리 크지 않습니다. 확실히 100kb 미만.
RenegadeAndy

이것이 어떻게 더 효율적이 될 수 있는지 또는 심지어 비효율적인지에 대한 다른 아이디어가 있습니까? 후자가 사실이라면 왜 그렇게 오래 걸립니까? 나는 그 연결이 책임이라고 생각하지 않습니다.
RenegadeAndy

1
    byte[] buffer = new byte[1024];  // buffer store for the stream
    int bytes; // bytes returned from read()

    // Keep listening to the InputStream until an exception occurs
    while (true) {
        try {
            // Read from the InputStream
            bytes = mmInStream.read(buffer);

            String TOKEN_ = new String(buffer, "UTF-8");

            String xx = TOKEN_.substring(0, bytes);

1

InputStream을 String으로 변환하기 위해 BufferedReader.readLine () 메소드를 사용합니다 . BufferedReader가 null을 반환 할 때까지 반복 하여 읽을 데이터가 더 이상 없음을 의미합니다. 각 줄은 StringBuilder에 추가 되고 String으로 반환됩니다.

 public static String convertStreamToString(InputStream is) {

        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }
}`

마지막으로 변환하려는 클래스에서 함수 호출

String dataString = Utils.convertStreamToString(in);

완전한


-1

전체 데이터를 읽는 데 사용됩니다.

// inputStream is one instance InputStream
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
String dataString = new String(data);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.