ฉันใช้ไฟล์ข้อมูลขนาดใหญ่บางครั้งฉันจำเป็นต้องรู้จำนวนบรรทัดในไฟล์เหล่านี้เท่านั้นโดยปกติฉันจะเปิดอ่านแล้วอ่านทีละบรรทัดจนกว่าจะถึงจุดสิ้นสุดไฟล์
ฉันสงสัยว่ามีวิธีที่ชาญฉลาดกว่านี้หรือไม่
ฉันใช้ไฟล์ข้อมูลขนาดใหญ่บางครั้งฉันจำเป็นต้องรู้จำนวนบรรทัดในไฟล์เหล่านี้เท่านั้นโดยปกติฉันจะเปิดอ่านแล้วอ่านทีละบรรทัดจนกว่าจะถึงจุดสิ้นสุดไฟล์
ฉันสงสัยว่ามีวิธีที่ชาญฉลาดกว่านี้หรือไม่
คำตอบ:
นี่เป็นรุ่นที่เร็วที่สุดที่ฉันค้นพบจนถึงตอนนี้เร็วกว่า readLines ประมาณ 6 เท่า ในไฟล์บันทึก 150MB จะใช้เวลา 0.35 วินาทีกับ 2.40 วินาทีเมื่อใช้ readLines () เพื่อความสนุกสนานคำสั่ง wc -l ของ linux ใช้เวลา 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 แกน y ในไม่กี่วินาที ฉันได้ดำเนินการวิ่ง 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
}
LineNumberReader
's lineNumber
ฟิลด์เป็นจำนวนเต็ม ... จะไม่ได้เป็นเพียงแค่ห่อสำหรับไฟล์นานกว่า Integer.MAX_VALUE? ทำไมต้องข้ามไปที่นี่นาน?
wc -l
นับจำนวนตัวอักษรขึ้นบรรทัดใหม่ในไฟล์ ใช้งานได้เนื่องจากทุกบรรทัดถูกยกเลิกด้วยการขึ้นบรรทัดใหม่รวมถึงบรรทัดสุดท้ายในไฟล์ ทุกบรรทัดมีอักขระขึ้นบรรทัดใหม่รวมถึงบรรทัดว่างดังนั้นจำนวนตัวอักษรขึ้นบรรทัดใหม่ == จำนวนบรรทัดในไฟล์ ตอนนี้lineNumber
ตัวแปรในFileNumberReader
ยังแสดงถึงจำนวนตัวอักษรขึ้นบรรทัดใหม่ที่เห็น มันเริ่มต้นที่ศูนย์ก่อนที่จะพบบรรทัดใหม่และเพิ่มขึ้นเมื่อเห็นบรรทัดใหม่ทุกตัว ดังนั้นโปรดอย่าเพิ่มหนึ่งหมายเลขลงในหมายเลขบรรทัดโปรด
wc -l
รายงานไฟล์ประเภทนี้เช่นกัน โปรดดูstackoverflow.com/questions/729692/…
wc -l
จะคืน 1 ฉันสรุปว่าวิธีการทั้งหมดมีข้อบกพร่องและดำเนินการอย่างใดอย่างหนึ่งโดยขึ้นอยู่กับว่าฉันต้องการให้ประพฤติอย่างไร
คำตอบที่ยอมรับมีข้อผิดพลาดเดียวสำหรับไฟล์หลายบรรทัดที่ไม่ได้ขึ้นบรรทัดใหม่ ไฟล์หนึ่งบรรทัดที่ลงท้ายด้วยไม่มีบรรทัดใหม่จะส่งคืน 1 แต่ไฟล์สองบรรทัดที่ลงท้ายด้วยไม่มีบรรทัดใหม่จะส่งคืน 1 เช่นกัน นี่คือการใช้งานโซลูชันที่ยอมรับซึ่งแก้ไขปัญหานี้ ปลายไม่มีการตรวจสอบสายใหม่จะสิ้นเปลืองสำหรับทุกอย่างยกเว้นการอ่านครั้งสุดท้าย แต่ควรใช้เวลาเล็กน้อยเมื่อเทียบกับฟังก์ชั่นโดยรวม
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();
}
}
กับ Java-8คุณสามารถใช้สตรีม:
try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
long numOfLines = lines.count();
...
}
คำตอบที่มีการนับเมธอด () ด้านบนให้ฉันบรรทัด miscounts ถ้าไฟล์ไม่ได้ขึ้นบรรทัดใหม่ในตอนท้ายของไฟล์ - มันไม่สามารถนับบรรทัดสุดท้ายในไฟล์
วิธีนี้ใช้ได้ผลดีกว่าสำหรับฉัน:
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;
}
cnt
มีความยืดหยุ่นในการใช้งานประเภทข้อมูลนานสำหรับ
ฉันรู้ว่านี่เป็นคำถามเก่า แต่วิธีแก้ปัญหาที่ยอมรับไม่ตรงกับสิ่งที่ฉันต้องการให้ทำ ดังนั้นฉันจึงกลั่นตัวเพื่อยอมรับตัวเลือกบรรทัดต่าง ๆ (แทนที่จะเป็นเพียงแค่ตัวป้อนบรรทัด) และใช้การเข้ารหัสอักขระที่ระบุ (แทนที่จะเป็น ISO-8859- n ) ทั้งหมดในหนึ่งวิธี (refactor ตามความเหมาะสม):
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% (แม้ว่าการทดสอบเวลาใน Java จะไม่น่าเชื่อถือ)
ฉันทดสอบวิธีการด้านบนเพื่อนับจำนวนบรรทัดและนี่คือข้อสังเกตของฉันสำหรับวิธีการต่าง ๆ ที่ทดสอบในระบบของฉัน
ขนาดไฟล์: 1.6 Gb
ยิ่งไปกว่านั้นวิธีJava8ดูเหมือนว่ามีประโยชน์มาก:
Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]
/**
* 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;
}
}
ผ่านการทดสอบและเร็วมาก
Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1
และจำนวนบรรทัดก็ผิดด้วย
BufferedInputStream
เมื่อคุณจะอ่านลงในบัฟเฟอร์ของคุณเอง นอกจากนี้แม้ว่าวิธีการของคุณอาจมีข้อได้เปรียบด้านประสิทธิภาพเพียงเล็กน้อย แต่ก็มีความยืดหยุ่นเนื่องจากไม่รองรับตัววาง\r
สายแบบเดี่ยว(MacOS เก่า) อีกต่อไปและไม่รองรับการเข้ารหัสทุกตัว
วิธีการตรงไปข้างหน้าโดยใช้เครื่องสแกนเนอร์
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();
}
วิธีการเกี่ยวกับการใช้ระดับกระบวนการจากภายในรหัส Java? จากนั้นอ่านเอาต์พุตของคำสั่ง
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);
}
ต้องลองดู จะโพสต์ผลลัพธ์
หากคุณไม่มีโครงสร้างดัชนีคุณจะไม่สามารถอ่านไฟล์ทั้งหมดได้ แต่คุณสามารถปรับให้เหมาะสมได้โดยหลีกเลี่ยงการอ่านทีละบรรทัดและใช้ regex เพื่อจับคู่ตัวยุติบรรทัดทั้งหมด
ทางออกที่ตลกนี้ใช้ได้ดีจริง ๆ !
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;
}
}
บนระบบที่ใช้ Unix ให้ใช้wc
คำสั่งบนบรรทัดคำสั่ง
วิธีเดียวที่จะทราบจำนวนไฟล์ที่มีอยู่ในไฟล์คือการนับมัน แน่นอนคุณสามารถสร้างตัวชี้วัดจากข้อมูลของคุณโดยให้ความยาวเฉลี่ยหนึ่งบรรทัดจากนั้นรับขนาดไฟล์และหารด้วย avg ความยาว แต่นั่นจะไม่ถูกต้อง
รหัสเพิ่มประสิทธิภาพที่ดีที่สุดสำหรับไฟล์หลายบรรทัดที่ไม่มีอักขระบรรทัดใหม่ ('\ n') ที่ 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;
}
เครื่องสแกนด้วย regex:
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 นั้นเป็น int คุณต้องการข้อมูลชนิดยาวในการประมวลผลแถวสูงสุด ..
int
สามารถเก็บค่าขึ้นไปประมาณ 2 พันล้าน หากคุณกำลังโหลดไฟล์ที่มีมากกว่า 2 พันล้านบรรทัดคุณจะพบปัญหามากเกินไป ที่กล่าวว่าหากคุณกำลังโหลดไฟล์ข้อความที่ไม่มีดัชนีที่มีมากกว่าสองพันล้านบรรทัดคุณอาจมีปัญหาอื่น ๆ