การอ่าน Android จากสตรีมอินพุตอย่างมีประสิทธิภาพ


152

ฉันกำลังสร้าง HTTP รับคำขอไปยังเว็บไซต์สำหรับแอปพลิเคชัน Android ที่ฉันกำลังทำ

ฉันใช้ DefaultHttpClient และใช้ HttpGet เพื่อออกคำขอ ฉันได้รับการตอบสนองเอนทิตีและจากนี้รับวัตถุ InputStream สำหรับรับ html ของหน้า

จากนั้นฉันจะวนรอบการตอบกลับโดยทำดังนี้:

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 ผ่านบัฟเฟอร์ขนาดคงที่: gist.github.com/fkirc/a231c817d582e114e791b77bb33e30e9
Mike76

คำตอบ:


355

ปัญหาในรหัสของคุณคือมันสร้างStringวัตถุจำนวนมากคัดลอกเนื้อหาและดำเนินการกับพวกเขา แต่คุณควรใช้StringBuilderเพื่อหลีกเลี่ยงการสร้างStringวัตถุใหม่ในแต่ละผนวกและเพื่อหลีกเลี่ยงการคัดลอกอาร์เรย์ถ่าน การดำเนินการสำหรับกรณีของคุณจะเป็นดังนี้:

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) ที่ไหนaและbเป็นสตริงคัดลอกเนื้อหาของทั้งสอง a และ bไปยังวัตถุใหม่ (โปรดทราบว่าคุณกำลังคัดลอกaซึ่งมีของสะสม String ) และคุณกำลังทำสำเนาเหล่านั้นในการทำซ้ำแต่ละครั้ง
  • a.append(b)ตรงไหนaa StringBuilder, ต่อท้ายbเนื้อหาโดยตรงaดังนั้นคุณไม่ต้องคัดลอกสตริงที่สะสมในแต่ละรอบซ้ำ

23
สำหรับคะแนนโบนัสให้ความสามารถเริ่มต้นเพื่อหลีกเลี่ยงการจัดสรรใหม่เมื่อ StringBuilder เติมเต็ม: StringBuilder total = new StringBuilder(inputStream.available());
dokkaebi

10
สิ่งนี้ไม่ตัดอักขระบรรทัดใหม่ออกหรือไม่
นาธานชวาร์มันน์

5
อย่าลืมที่จะห่อในขณะที่ลอง / จับเช่นนี้: ลอง {ในขณะที่ ((บรรทัด = r.readLine ())! = null) {total.append (บรรทัด); }} catch (IOException e) {Log.i (แท็ก "ปัญหาเกี่ยวกับ readline ในฟังก์ชั่น inputStreamToString"); }
botbot

4
@botbot: การบันทึกและเพิกเฉยข้อยกเว้นนั้นไม่ได้ดีไปกว่าการละเว้นข้อยกเว้น ...
Matti Virkkunen

50
มันวิเศษมากที่ Android ไม่มีการแปลงแบบสตรีมไปเป็นสตริงในตัว การมีโค้ดขนาดสั้นทุกอันบนเว็บและแอพบนโลกนี้จะนำ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
Android API ไม่รวม IOUtils
Charles Ma

2
ถูกต้องซึ่งเป็นเหตุผลที่ฉันพูดถึงห้องสมุดภายนอกที่มี ฉันเพิ่มห้องสมุดลงในโครงการ Android ของฉันและทำให้อ่านได้ง่ายจากสตรีม
Makotosan

ฉันจะดาวน์โหลดสิ่งนี้ได้อย่างไรและคุณนำเข้าสิ่งนั้นไปยังโครงการ Android ของคุณได้อย่างไร?
ซาฟารี

3
หากคุณต้องดาวน์โหลดฉันจะไม่เรียกมันว่า "built in"; อย่างไรก็ตามฉันเพิ่งดาวน์โหลดมันและจะปล่อยให้ไป
B. Clay Shannon

15

ความเป็นไปได้อีกครั้งกับ Guava:

พึ่งพา: compile 'com.google.guava:guava:11.0.2'

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

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

9

ฉันเชื่อว่ามันมีประสิทธิภาพเพียงพอ ... ในการรับ String จาก InputStream ฉันจะเรียกวิธีการต่อไปนี้:

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


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));
}

แก้ไข: จริง ๆ แล้วการเรียงลำดับนี้ครอบคลุมทั้งทั้งเหล็กและมอริซเพอร์รี่


ปัญหาคือ - ฉันไม่ทราบขนาดของสิ่งที่ฉันกำลังอ่านก่อนที่ฉันจะเริ่ม - ดังนั้นอาจต้องมีรูปแบบของอาร์เรย์ที่เพิ่มขึ้นเช่นกัน Inless คุณสามารถค้นหา InputStream หรือ URL ผ่าน http เพื่อค้นหาว่าการดึงข้อมูลสิ่งใหญ่เพียงใดคือการปรับขนาดของอาร์เรย์ไบต์ให้เหมาะสม ฉันต้องมีประสิทธิภาพเหมือนบนอุปกรณ์พกพาซึ่งเป็นปัญหาหลัก! อย่างไรก็ตามขอบคุณสำหรับความคิดนั้น - จะให้มันคืนนี้และแจ้งให้คุณทราบว่ามันจัดการในแง่ของประสิทธิภาพ!
RenegadeAndy

ฉันไม่คิดว่าขนาดของสตรีมขาเข้านั้นสำคัญขนาดนั้น โค้ดด้านบนอ่านได้ครั้งละ 1,000 ไบต์ แต่คุณสามารถเพิ่ม / ลดขนาดนั้นได้ ด้วยการทดสอบของฉันมันไม่ได้ทำให้อากาศแตกต่างกันมากฉันใช้ 1,000/10000 ไบต์ นั่นเป็นเพียงแอป Java ธรรมดา ๆ อาจมีความสำคัญมากกว่าบนอุปกรณ์พกพา
Adrian

4
คุณสามารถจบลงด้วยเอนทิตี Unicode ที่ถูกตัดเป็นสองครั้งต่อมาอ่าน ดีกว่าที่จะอ่านจนกว่าอักขระขอบเขตบางอย่างเช่น \ n ซึ่งเป็นสิ่งที่ BufferedReader ทำ
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 คำตอบ? เขากำลังทำแบบเดียวกัน แต่สำหรับก้อนขนาดใหญ่เท่ากับขนาดอินพุตสตรีม มันแตกต่างกันมากแค่ไหนถ้าฉันสแกน char char แทนที่จะเป็นอาร์เรย์ byte ตามที่ Nikola ตอบ? จริงๆแล้วฉันแค่อยากรู้ว่าวิธีไหนดีที่สุดในกรณีนี้? นอกจากนี้ readLine จะลบ \ n และ \ r แต่ฉันเห็นแม้กระทั่งรหัสแอป Google io ที่พวกเขาใช้ readline
Akhil Dad

3

อาจจะค่อนข้างอ่าน 'ทีละบรรทัด' และเข้าร่วมสตริงลอง 'อ่านทั้งหมดที่มีอยู่' เพื่อหลีกเลี่ยงการสแกนจุดสิ้นสุดของบรรทัดและเพื่อหลีกเลี่ยงการรวมสตริง

เช่นInputStream.available()และInputStream.read(byte[] b), int offset, int length)


อืมมม ดังนั้นมันจะเป็นเช่นนี้: int offset = 5000; ไบต์ [] bArr = ใหม่ไบต์ [100]; ไบต์ [] ทั้งหมด = ไบต์ [5000]; ในขณะที่ (InputStream.available) {offset = InputStream.read (bArr, offset, 100); สำหรับ (int i = 0; i <offset; i ++) {total [i] = bArr [i]; } bArr = ไบต์ใหม่ [100]; } นั่นมีประสิทธิภาพมากกว่าจริงหรือ - ฉันเขียนมันแย่มาก! กรุณายกตัวอย่าง!
RenegadeAndy

2
ไม่ไม่ไม่ไม่ฉันหมายถึงเพียงแค่ {byte total [] = new [instrm.available ()]; instrm.read (รวม 0, total.length); } และถ้าคุณต้องการใช้เป็นสตริงให้ใช้ {String asString = String (total, 0, total.length, "utf-8"); // สมมติ utf8 :-)}
SteelBytes

2

การอ่านข้อความทีละบรรทัดและการต่อท้ายบรรทัดที่กล่าวถึงสตริงนั้นเป็นการใช้เวลานานทั้งในการแยกแต่ละบรรทัดและโอเวอร์เฮดของการเรียกใช้เมธอดจำนวนมาก

ฉันสามารถรับประสิทธิภาพที่ดีขึ้นโดยการจัดสรรอาร์เรย์ขนาดที่เหมาะสมเพื่อเก็บข้อมูลสตรีมและแทนที่ด้วยอาร์เรย์ขนาดใหญ่กว่าเมื่อจำเป็นและพยายามอ่านให้มากที่สุดเท่าที่อาร์เรย์สามารถเก็บได้

ด้วยเหตุผลบางอย่าง Android ล้มเหลวในการดาวน์โหลดไฟล์ทั้งหมดซ้ำ ๆ เมื่อรหัสที่ใช้ InputStream ที่ส่งคืนโดย HTTPUrlConnection ดังนั้นฉันจึงต้องใช้ทั้ง 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];

นี่คือเร็วกว่าข้างต้นและคำตอบที่ยอมรับ คุณใช้ "Reader" และ "Stream" บน Android ได้อย่างไร?
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คืนค่าว่างซึ่งหมายความว่าไม่มีข้อมูลให้อ่านอีกต่อไป แต่ละบรรทัดจะผนวกเข้ากับStringBuilderและส่งกลับเป็นสตริง

 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.