سؤال

يمكنني استخدام ملفات ضخمة من البيانات, أحيانا أنا فقط بحاجة إلى معرفة عدد الخطوط في هذه الملفات عادة ما فتحها وقراءتها سطرا سطرا حتى تصل إلى نهاية الملف

أنا أتساءل عما إذا كان هناك طريقة ذكية للقيام بذلك

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

المحلول

وهذا هو أسرع نسخة ولقد وجدت حتى الآن، حوالي 6 مرات أسرع من readLines. في ملف سجل 150MB هذا يستغرق 0.35 ثانية، مقابل 2.40 ثواني عند استخدام readLines (). للمتعة فقط، ويأخذ لينكس "قيادة -l مرحاض 0.15 ثواني.

public static int countLinesOld(String filename) throws IOException {
    InputStream is = new BufferedInputStream(new FileInputStream(filename));
    try {
        byte[] c = new byte[1024];
        int count = 0;
        int readChars = 0;
        boolean empty = true;
        while ((readChars = is.read(c)) != -1) {
            empty = false;
            for (int i = 0; i < readChars; ++i) {
                if (c[i] == '\n') {
                    ++count;
                }
            }
        }
        return (count == 0 && !empty) ? 1 : count;
    } finally {
        is.close();
    }
}

وتحرير، وبعد 9 سنوات 1/2: لدي عمليا أي تجربة جافا، ولكن على أي حال لقد حاولت لقياس هذا الرمز ضد الحل LineNumberReader أدناه لأنه يزعجني أن لا أحد فعل ذلك. ويبدو أن خصوصا لملفات كبيرة بلدي الحل هو أسرع. على الرغم من أنه يبدو أن يستغرق بضعة أشواط حتى لا محسن على وظيفة لائقة. لقد لعبت قليلا مع الرمز، وأنتجت النسخة الجديدة التي هي دائما الأسرع:

public static int countLinesNew(String filename) throws IOException {
    InputStream is = new BufferedInputStream(new FileInputStream(filename));
    try {
        byte[] c = new byte[1024];

        int readChars = is.read(c);
        if (readChars == -1) {
            // bail out if nothing to read
            return 0;
        }

        // make it easy for the optimizer to tune this loop
        int count = 0;
        while (readChars == 1024) {
            for (int i=0; i<1024;) {
                if (c[i++] == '\n') {
                    ++count;
                }
            }
            readChars = is.read(c);
        }

        // count remaining characters
        while (readChars != -1) {
            System.out.println(readChars);
            for (int i=0; i<readChars; ++i) {
                if (c[i] == '\n') {
                    ++count;
                }
            }
            readChars = is.read(c);
        }

        return count == 0 ? 1 : count;
    } finally {
        is.close();
    }
}

وresuls المعيار لملف نصي 1.3GB، العمودي في ثوان. لقد أنجزت 100 أشواط مع نفس الملف، وقياس كل شوط مع System.nanoTime(). يمكنك أن ترى أن countLinesOld لديه بعض القيم المتطرفة، وcountLinesNew له لا شيء، وحين انها فقط أسرع قليلا، والفرق هو ذات دلالة إحصائية. LineNumberReader هو واضح أبطأ.

نصائح أخرى

ولقد نفذت حل آخر للمشكلة، وجدت أنه أكثر كفاءة في عد الصفوف:

try
(
   FileReader       input = new FileReader("input.txt");
   LineNumberReader count = new LineNumberReader(input);
)
{
   while (count.skip(Long.MAX_VALUE) > 0)
   {
      // Loop just in case the file is > Long.MAX_VALUE or skip() decides to not read the entire file
   }

   result = count.getLineNumber() + 1;                                    // +1 because line index starts at 0
}

والجواب المقبول لديها من قبل خطأ واحد لملفات خط متعددة التي لا تنتهي في السطر الجديد. ومن شأن ملف سطر واحد تنتهي بدون السطر الجديد عودة 1، لكن ملف اثنين من خط إنهاء بدون السطر الجديد عودة 1 أيضا. وإليك تنفيذ حل مقبول الذي يحدد ذلك. الشيكات endsWithoutNewLine هي الإسراف في كل شيء ولكن القراءة النهائية، ولكن ينبغي أن يكون الوقت تافهة الحكمة مقارنة الوظيفة العامة.

public int count(String filename) throws IOException {
    InputStream is = new BufferedInputStream(new FileInputStream(filename));
    try {
        byte[] c = new byte[1024];
        int count = 0;
        int readChars = 0;
        boolean endsWithoutNewLine = false;
        while ((readChars = is.read(c)) != -1) {
            for (int i = 0; i < readChars; ++i) {
                if (c[i] == '\n')
                    ++count;
            }
            endsWithoutNewLine = (c[readChars - 1] != '\n');
        }
        if(endsWithoutNewLine) {
            ++count;
        } 
        return count;
    } finally {
        is.close();
    }
}

يمكنك استخدام تيارات:

try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
  long numOfLines = lines.count();
  ...
}

والجواب مع العد طريقة () أعلاه أعطاني أخطاء العد خط إذا لم يكن لديك ملف سطر جديد في نهاية الملف - أنه فشل في الاعتماد على السطر الأخير في ملف

وهذا الأسلوب يعمل بشكل أفضل بالنسبة لي:

public int countLines(String filename) throws IOException {
    LineNumberReader reader  = new LineNumberReader(new FileReader(filename));
int cnt = 0;
String lineRead = "";
while ((lineRead = reader.readLine()) != null) {}

cnt = reader.getLineNumber(); 
reader.close();
return cnt;
}

وأعرف أن هذا هو السؤال القديم، إلا أن حل مقبول لا تتطابق تماما ما أنا في حاجة إلى القيام به. لذا، أنا تكريره لقبول الإنهاء خط مختلف (بدلا من سطر تغذية فقط) واستخدام الترميز الحرف المحدد (بدلا من ISO-8859- <ط> ن ). كل ذلك في أسلوب واحد (ريفاكتور حسب الاقتضاء):

public static long getLinesCount(String fileName, String encodingName) throws IOException {
    long linesCount = 0;
    File file = new File(fileName);
    FileInputStream fileIn = new FileInputStream(file);
    try {
        Charset encoding = Charset.forName(encodingName);
        Reader fileReader = new InputStreamReader(fileIn, encoding);
        int bufferSize = 4096;
        Reader reader = new BufferedReader(fileReader, bufferSize);
        char[] buffer = new char[bufferSize];
        int prevChar = -1;
        int readCount = reader.read(buffer);
        while (readCount != -1) {
            for (int i = 0; i < readCount; i++) {
                int nextChar = buffer[i];
                switch (nextChar) {
                    case '\r': {
                        // The current line is terminated by a carriage return or by a carriage return immediately followed by a line feed.
                        linesCount++;
                        break;
                    }
                    case '\n': {
                        if (prevChar == '\r') {
                            // The current line is terminated by a carriage return immediately followed by a line feed.
                            // The line has already been counted.
                        } else {
                            // The current line is terminated by a line feed.
                            linesCount++;
                        }
                        break;
                    }
                }
                prevChar = nextChar;
            }
            readCount = reader.read(buffer);
        }
        if (prevCh != -1) {
            switch (prevCh) {
                case '\r':
                case '\n': {
                    // The last line is terminated by a line terminator.
                    // The last line has already been counted.
                    break;
                }
                default: {
                    // The last line is terminated by end-of-file.
                    linesCount++;
                }
            }
        }
    } finally {
        fileIn.close();
    }
    return linesCount;
}

وهذا الحل هو مماثل في سرعة إلى حل مقبول، حوالي 4٪ أبطأ في بلدي التجارب (على الرغم من توقيت الاختبارات في جاوة لا يمكن الاعتماد عليها بشكل ملاحظ).

أنا اختبرت الطرق المذكورة أعلاه لحساب خطوط هنا هي ملاحظاتي على أساليب مختلفة كما تم اختبارها على نظام بلدي

حجم الملف :1.6 Gb الأساليب:

  1. باستخدام الماسح الضوئي :35s تقريبا
  2. باستخدام BufferedReader :5s تقريبا
  3. باستخدام جافا 8 :5s تقريبا
  4. باستخدام LineNumberReader :5s تقريبا

وعلاوة على ذلك Java8 النهج يبدو مفيد جدا :ملفات.خطوط(مسارات.الحصول على(أسم دليل), محارف.defaultCharset()).عدد() [نوع الإرجاع :طويل]

/**
 * Count file rows.
 *
 * @param file file
 * @return file row count
 * @throws IOException
 */
public static long getLineCount(File file) throws IOException {

    try (Stream<String> lines = Files.lines(file.toPath())) {
        return lines.count();
    }
}

واختبارها على JDK8_u31. ولكن في الواقع أداء بطيء مقارنة هذه الطريقة:

/**
 * Count file rows.
 *
 * @param file file
 * @return file row count
 * @throws IOException
 */
public static long getLineCount(File file) throws IOException {

    try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(file), 1024)) {

        byte[] c = new byte[1024];
        boolean empty = true,
                lastEmpty = false;
        long count = 0;
        int read;
        while ((read = is.read(c)) != -1) {
            for (int i = 0; i < read; i++) {
                if (c[i] == '\n') {
                    count++;
                    lastEmpty = true;
                } else if (lastEmpty) {
                    lastEmpty = false;
                }
            }
            empty = false;
        }

        if (!empty) {
            if (count == 0) {
                count = 1;
            } else if (!lastEmpty) {
                count++;
            }
        }

        return count;
    }
}

واختبارها وسريع جدا.

وهناك طريقة مستقيمة إلى الأمام باستخدام الماسح الضوئي

static void lineCounter (String path) throws IOException {

        int lineCount = 0, commentsCount = 0;

        Scanner input = new Scanner(new File(path));
        while (input.hasNextLine()) {
            String data = input.nextLine();

            if (data.startsWith("//")) commentsCount++;

            lineCount++;
        }

        System.out.println("Line Count: " + lineCount + "\t Comments Count: " + commentsCount);
    }

وخلصت إلى أن wc -l: طريقة ق عد أسطر جديدة على ما يرام ولكن بإرجاع نتائج غير بديهية على الملفات التي لا تنتهي السطر الأخير مع سطر جديد.

و @حل er.vikas على أساس LineNumberReader لكن بإضافة واحد إلى خط العد عاد النتائج غير بديهية على ملفات أين ينتهي السطر الأخير مع السطر الجديد.

وأنا بالتالي إجراء البرودة؛ الصقيع الذي يعالج كما يلي:

@Test
public void empty() throws IOException {
    assertEquals(0, count(""));
}

@Test
public void singleNewline() throws IOException {
    assertEquals(1, count("\n"));
}

@Test
public void dataWithoutNewline() throws IOException {
    assertEquals(1, count("one"));
}

@Test
public void oneCompleteLine() throws IOException {
    assertEquals(1, count("one\n"));
}

@Test
public void twoCompleteLines() throws IOException {
    assertEquals(2, count("one\ntwo\n"));
}

@Test
public void twoLinesWithoutNewlineAtEnd() throws IOException {
    assertEquals(2, count("one\ntwo"));
}

@Test
public void aFewLines() throws IOException {
    assertEquals(5, count("one\ntwo\nthree\nfour\nfive\n"));
}

ويبدو مثل هذا:

static long countLines(InputStream is) throws IOException {
    try(LineNumberReader lnr = new LineNumberReader(new InputStreamReader(is))) {
        char[] buf = new char[8192];
        int n, previousN = -1;
        //Read will return at least one byte, no need to buffer more
        while((n = lnr.read(buf)) != -1) {
            previousN = n;
        }
        int ln = lnr.getLineNumber();
        if (previousN == -1) {
            //No data read at all, i.e file was empty
            return 0;
        } else {
            char lastChar = buf[previousN - 1];
            if (lastChar == '\n' || lastChar == '\r') {
                //Ending with newline, deduct one
                return ln;
            }
        }
        //normal case, return line number + 1
        return ln + 1;
    }
}

إذا كنت تريد نتائج بديهية، يمكنك استخدام هذا. إذا كنت ترغب فقط التوافق wc -l، بسيط حل er.vikas استخدام @، ولكن لا تقم بإضافة واحد إلى نتيجة ومحاولة تخطي:

try(LineNumberReader lnr = new LineNumberReader(new FileReader(new File("File1")))) {
    while(lnr.skip(Long.MAX_VALUE) > 0){};
    return lnr.getLineNumber();
}

وماذا عن استخدام الفئة عملية من داخل كود جافا؟ ومن ثم قراءة إخراج الأمر.

Process p = Runtime.getRuntime().exec("wc -l " + yourfilename);
p.waitFor();

BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
int lineCount = 0;
while ((line = b.readLine()) != null) {
    System.out.println(line);
    lineCount = Integer.parseInt(line);
}

وتحتاج أن تحاول ذلك على الرغم من. سيتم نشر النتائج.

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

وهذا الحل مضحك يعمل جيدا حقا في الواقع!

public static int countLines(File input) throws IOException {
    try (InputStream is = new FileInputStream(input)) {
        int count = 1;
        for (int aChar = 0; aChar != -1;aChar = is.read())
            count += aChar == '\n' ? 1 : 0;
        return count;
    }
}

في النظم القائمة على يونكس، استخدم الأمر wc على سطر الأوامر.

الطريقة الوحيدة لمعرفة كم من الخطوط في الملف إلى الاعتماد عليها.بالطبع يمكنك إنشاء متري من البيانات الخاصة بك مما يتيح لك بمتوسط طول خط واحد ومن ثم الحصول على حجم الملف وتقسيم هذا مع avg.طول لكن ذلك لن تكون دقيقة.

وكود أفضل الأمثل لملفات خط متعددة عدم وجود السطر ( '\ ن') حرف في EOF.

/**
 * 
 * @param filename
 * @return
 * @throws IOException
 */
public static int countLines(String filename) throws IOException {
    int count = 0;
    boolean empty = true;
    FileInputStream fis = null;
    InputStream is = null;
    try {
        fis = new FileInputStream(filename);
        is = new BufferedInputStream(fis);
        byte[] c = new byte[1024];
        int readChars = 0;
        boolean isLine = false;
        while ((readChars = is.read(c)) != -1) {
            empty = false;
            for (int i = 0; i < readChars; ++i) {
                if ( c[i] == '\n' ) {
                    isLine = false;
                    ++count;
                }else if(!isLine && c[i] != '\n' && c[i] != '\r'){   //Case to handle line count where no New Line character present at EOF
                    isLine = true;
                }
            }
        }
        if(isLine){
            ++count;
        }
    }catch(IOException e){
        e.printStackTrace();
    }finally {
        if(is != null){
            is.close();    
        }
        if(fis != null){
            fis.close();    
        }
    }
    LOG.info("count: "+count);
    return (count == 0 && !empty) ? 1 : count;
}

والماسح الضوئي مع التعابير المنطقية:

public int getLineCount() {
    Scanner fileScanner = null;
    int lineCount = 0;
    Pattern lineEndPattern = Pattern.compile("(?m)$");  
    try {
        fileScanner = new Scanner(new File(filename)).useDelimiter(lineEndPattern);
        while (fileScanner.hasNext()) {
            fileScanner.next();
            ++lineCount;
        }   
    }catch(FileNotFoundException e) {
        e.printStackTrace();
        return lineCount;
    }
    fileScanner.close();
    return lineCount;
}

وألم سجلت عليه.

وإذا كنت تستخدم هذا

public int countLines(String filename) throws IOException {
    LineNumberReader reader  = new LineNumberReader(new FileReader(filename));
    int cnt = 0;
    String lineRead = "";
    while ((lineRead = reader.readLine()) != null) {}

    cnt = reader.getLineNumber(); 
    reader.close();
    return cnt;
}

وكنت غير قادر على تشغيل لصفوف الأسطوانات الكبيرة، يحب 100K الصفوف، لأن العودة من reader.getLineNumber هي كثافة العمليات. كنت بحاجة إلى نوع طويلة من البيانات لمعالجة القصوى الصفوف ..

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top