كيفية ذاكرة التخزين المؤقت inputstream للاستخدام المتعدد

StackOverflow https://stackoverflow.com/questions/924990

سؤال

لدي Inputstream من ملف واستخدم مكونات Apache Poi للقراءة منه مثل هذا:

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);

المشكلة هي أنني بحاجة إلى استخدام نفس الدفق عدة مرات ويشل poifsfilesystems الدفق بعد الاستخدام.

ما هي أفضل طريقة لتخزين البيانات من دفق الإدخال ثم تخدم المزيد من تدفقات الإدخال إلى نظام poifsfilesysty مختلفة؟

تحرير 1:

بواسطة ذاكرة التخزين المؤقت، قلت تخزينها للاستخدام لاحقا، وليس كوسيلة لتسريع التطبيق. هل من الأفضل أيضا قراءة دفق الإدخال في صفيف أو سلسلة ثم قم بإنشاء تدفقات الإدخال لكل استخدام؟

تحرير 2:

آسف لإعادة فتح السؤال، ولكن الظروف مختلفة إلى حد ما عند العمل داخل تطبيق سطح المكتب وتطبيق الويب. بادئ ذي بدء، فإن Inputstream أحصل عليها من Org.apache.commons.fileupload.fileitem في تطبيق Tomcat Web الخاص بي لا يدعم العلامات وبالتالي لا يمكن إعادة تعيين.

ثانيا، أود أن أكون قادرا على الاحتفاظ بالملف في الذاكرة للحصول على أسرع وقوع مشكلات أقل من IO عند التعامل مع الملفات.

هل كانت مفيدة؟

المحلول

يمكنك تزيين المياه التي يتم تمريرها إلى Poifsfilesystem. مع إصدار عند إغلاق () يسمى أنه يستجيب مع إعادة تعيين ():

class ResetOnCloseInputStream extends InputStream {

    private final InputStream decorated;

    public ResetOnCloseInputStream(InputStream anInputStream) {
        if (!anInputStream.markSupported()) {
            throw new IllegalArgumentException("marking not supported");
        }

        anInputStream.mark( 1 << 24); // magic constant: BEWARE
        decorated = anInputStream;
    }

    @Override
    public void close() throws IOException {
        decorated.reset();
    }

    @Override
    public int read() throws IOException {
        return decorated.read();
    }
}

حالة اختبار

static void closeAfterInputStreamIsConsumed(InputStream is)
        throws IOException {
    int r;

    while ((r = is.read()) != -1) {
        System.out.println(r);
    }

    is.close();
    System.out.println("=========");

}

public static void main(String[] args) throws IOException {
    InputStream is = new ByteArrayInputStream("sample".getBytes());
    ResetOnCloseInputStream decoratedIs = new ResetOnCloseInputStream(is);
    closeAfterInputStreamIsConsumed(decoratedIs);
    closeAfterInputStreamIsConsumed(decoratedIs);
    closeAfterInputStreamIsConsumed(is);
}

تحرير 2.

يمكنك قراءة الملف بأكمله في بايت [] (وضع Slurp) ثم تمريره إلى BytearrayInputstream

نصائح أخرى

جرب BufferedInputStream، مما يضيف وظائف العلامة وإعادة تعيين وظيفة إدخال إدخال آخر، وتجاوز طريقة إغلاقها فقط:

public class UnclosableBufferedInputStream extends BufferedInputStream {

    public UnclosableBufferedInputStream(InputStream in) {
        super(in);
        super.mark(Integer.MAX_VALUE);
    }

    @Override
    public void close() throws IOException {
        super.reset();
    }
}

وبالتالي:

UnclosableBufferedInputStream  bis = new UnclosableBufferedInputStream (inputStream);

واستخدام bis أينما استخدمت inputstream من قبل.

هذا يعمل بشكل صحيح:

byte[] bytes = getBytes(inputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(new ByteArrayInputStream(bytes));

حيث getbytes مثل هذا:

private static byte[] getBytes(InputStream is) throws IOException {
    byte[] buffer = new byte[8192];
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
int n;
baos.reset();

while ((n = is.read(buffer, 0, buffer.length)) != -1) {
      baos.write(buffer, 0, n);
    }

   return baos.toByteArray();
 }

استخدم أدناه التنفيذ لمزيد من الاستخدام المخصص -

public class ReusableBufferedInputStream extends BufferedInputStream
{

    private int totalUse;
    private int used;

    public ReusableBufferedInputStream(InputStream in, Integer totalUse)
    {
        super(in);
        if (totalUse > 1)
        {
            super.mark(Integer.MAX_VALUE);
            this.totalUse = totalUse;
            this.used = 1;
        }
        else
        {
            this.totalUse = 1;
            this.used = 1;
        }
    }

    @Override
    public void close() throws IOException
    {
        if (used < totalUse)
        {
            super.reset();
            ++used;
        }
        else
        {
            super.close();
        }
    }
}

ماذا تعني بالضبط مع "ذاكرة التخزين المؤقت"؟ هل تريد أن يبدأ النظام الآخر Poifsfilesyysty في بداية الدفق؟ إذا كان الأمر كذلك، فليس هناك أي نقطة تخزلق بأي شيء في رمز Java الخاص بك؛ سيتم القيام به من قبل نظام التشغيل، فقط افتح دفق جديد.

أو هل ترغب في مواصلة القراءة عند النقطة التي توقف فيها أول poifsfilesyystem؟ هذا ليس التخزين المؤقت، ومن الصعب جدا القيام به. الطريقة الوحيدة التي يمكنني التفكير فيها إذا لم تتمكن من تجنب إغلاق الدفق هو كتابة مجمع رقيق يحسب عدد البايتات التي تم قراءتها ثم فتح دفق جديد وتخطي العديد من البايتات. ولكن هذا قد يفشل عندما يستخدم poifsfilesystem داخليا شيئا مثل bufferedinputstream.

إذا كان الملف ليس كبيرا، اقرأه في byte[] صفيف وإعطاء بوي أ ByteArrayInputStream تم إنشاؤها من تلك الصفيف.

إذا كان الملف كبيرا، فلا ينبغي أن تهتم، لأن نظام التشغيل سيفعل التخزين المؤقت لك بأفضل ما يمكن.

تحرير] استخدام Apache Commons-io لقراءة الملف في صفيف بايت بطريقة فعالة. لا تستخدم int read() لأنه يقرأ الملف بايت بواسطة بايت وهو جدا بطيء!

إذا كنت ترغب في القيام بذلك بنفسك، استخدم File الاعتراض للحصول على طول، وإنشاء الصفيف والحلقة التي تقرأ البايتات من الملف. يجب أن الحلقة منذ read(byte[], int offset, int len) يمكن أن تقرأ أقل من len بايت (وعادة ما تفعل).

هذه هي الطريقة التي سأقوم بها، لاستخدامها بأمان مع أي inputstream:

  • اكتب مجمع InputStream الخاص بك حيث يمكنك إنشاء ملف مؤقت لتعكس محتوى الدفق الأصلي
  • تفريغ كل شيء يقرأ من دفق الإدخال الأصلي في هذا الملف المؤقت
  • عند قراءة الدفق بالكامل، سيكون لديك جميع البيانات معكوسة في الملف المؤقت
  • استخدم InputStream.Reset لتبديل (تهيئة) الدفق الداخلي إلى ملف fileinputstream (mirrored_content_file)
  • من الآن فصاعدا سوف تفقد مرجع الدفق الأصلي (يمكن جمعها)
  • أضف إصدار طريقة جديدة () مما سيقوم بإزالة الملف المؤقت وإصدار أي دفق مفتوح.
  • يمكنك حتى استدعاء الإصدار () من وضع اللمسات الأخيرة على للتأكد من أن الملف المؤقت هو إصدار في حال نسيت الاتصال بالإصدار () (معظم الوقت يجب عليك تجنبه وضع اللمسات الأخيرة على, ، دائما استدعاء طريقة لإطلاق موارد كائن). يرى لماذا تنفذ أي وقت مضى الانتهاء ()؟
public static void main(String[] args) throws IOException {
    BufferedInputStream inputStream = new BufferedInputStream(IOUtils.toInputStream("Foobar"));
    inputStream.mark(Integer.MAX_VALUE);
    System.out.println(IOUtils.toString(inputStream));
    inputStream.reset();
    System.out.println(IOUtils.toString(inputStream));
}

هذا يعمل. ioutils هو جزء من المشاع IO.

هذه الإجابة تكرار في تلك السابقة 1|2 على أساس BufferInputStream. وبعد التغييرات الرئيسية هي أنها تسمح بإعادة الاستخدام اللانهائي. ويعتني بإغلاق مجرى الإدخال المصدر الأصلي لموارد النظام. يحدد نظام التشغيل الخاص بك حد على هؤلاء وأنت لا تريد أن ينفد البرنامج من مقابض الملفات (وهذا أيضا لماذا يجب عليك دائما استجابات "تستهلك" على سبيل المثال مع Apache EntityUtils.consumeQuietly()). تعديل تحديث التعليمات البرمجية للتعامل مع المستهلكين Gready التي تستخدم read(buffer, offset, length), ، في هذه الحالة قد يحدث ذلك BufferedInputStream يحاول بجد للنظر في المصدر، يحمي هذا الرمز ضد هذا الاستخدام.

public class CachingInputStream extends BufferedInputStream {    
    public CachingInputStream(InputStream source) {
        super(new PostCloseProtection(source));
        super.mark(Integer.MAX_VALUE);
    }

    @Override
    public synchronized void close() throws IOException {
        if (!((PostCloseProtection) in).decoratedClosed) {
            in.close();
        }
        super.reset();
    }

    private static class PostCloseProtection extends InputStream {
        private volatile boolean decoratedClosed = false;
        private final InputStream source;

        public PostCloseProtection(InputStream source) {
            this.source = source;
        }

        @Override
        public int read() throws IOException {
            return decoratedClosed ? -1 : source.read();
        }

        @Override
        public int read(byte[] b) throws IOException {
            return decoratedClosed ? -1 : source.read(b);
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            return decoratedClosed ? -1 : source.read(b, off, len);
        }

        @Override
        public long skip(long n) throws IOException {
            return decoratedClosed ? 0 : source.skip(n);
        }

        @Override
        public int available() throws IOException {
            return source.available();
        }

        @Override
        public void close() throws IOException {
            decoratedClosed = true;
            source.close();
        }

        @Override
        public void mark(int readLimit) {
            source.mark(readLimit);
        }

        @Override
        public void reset() throws IOException {
            source.reset();
        }

        @Override
        public boolean markSupported() {
            return source.markSupported();
        }
    }
}

لإعادة استخدامها فقط أغلقها أولا إذا لم تكن كذلك.

قيود واحدة على الرغم من أنه إذا تم إغلاق الدفق قبل قراءة المحتوى بأكمله من الدفق الأصلي، فسيتم قراءة هذا المزخرف بيانات غير مكتملة، لذلك تأكد من قراءة الدفق بالكامل قبل الإغلاق.

أنا فقط أضيف حلاي هنا، لأن هذا يعمل بالنسبة لي. انها أساسا هو مزيج من أفضل اثنين من الإجابات :)

    private String convertStreamToString(InputStream is) {
    Writer w = new StringWriter();
    char[] buf = new char[1024];
    Reader r;
    is.mark(1 << 24);
    try {
        r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        int n;
        while ((n=r.read(buf)) != -1) {
            w.write(buf, 0, n);
        }
        is.reset();
    } catch(UnsupportedEncodingException e) {
        Logger.debug(this.getClass(), "Cannot convert stream to string.", e);
    } catch(IOException e) {
        Logger.debug(this.getClass(), "Cannot convert stream to string.", e);
    }
    return w.toString();
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top