Respuestas:
Esta es la versión más rápida que he encontrado hasta ahora, aproximadamente 6 veces más rápido que readLines. En un archivo de registro de 150 MB, esto lleva 0,35 segundos, frente a 2,40 segundos cuando se usa readLines (). Solo por diversión, el comando wc -l de linux tarda 0,15 segundos.
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();
}
}
EDITAR, 9 años y medio después: prácticamente no tengo experiencia en Java, pero de todos modos he tratado de comparar este código con la LineNumberReader
solución a continuación, ya que me molestó que nadie lo hiciera. Parece que, especialmente para archivos grandes, mi solución es más rápida. Aunque parece tomar algunas carreras hasta que el optimizador hace un trabajo decente. He jugado un poco con el código y he producido una nueva versión que es consistentemente más rápida:
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();
}
}
Resultados de referencia para un archivo de texto de 1.3GB, eje y en segundos. He realizado 100 ejecuciones con el mismo archivo, y he medido cada ejecución con System.nanoTime()
. Puede ver que countLinesOld
tiene algunos valores atípicos y countLinesNew
ninguno, y aunque es solo un poco más rápido, la diferencia es estadísticamente significativa. LineNumberReader
Es claramente más lento.
He implementado otra solución al problema, lo encontré más eficiente al contar filas:
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
El lineNumber
campo de 'es un número entero ... ¿No se ajustará solo a los archivos más largos que Integer.MAX_VALUE? ¿Por qué molestarse en pasar tanto tiempo aquí?
wc -l
cuenta el número de caracteres de nueva línea en el archivo. Esto funciona ya que cada línea se termina con una nueva línea, incluida la línea final en un archivo. Cada línea tiene un carácter de nueva línea, incluidas las líneas vacías, de ahí que el número de caracteres de nueva línea == número de líneas en un archivo. Ahora, la lineNumber
variable en FileNumberReader
también representa el número de caracteres de nueva línea vistos. Comienza en cero, antes de que se encuentre una nueva línea, y aumenta con cada carácter de nueva línea visto. Así que no agregue uno al número de línea por favor.
wc -l
también se informa este tipo de archivo. Ver también stackoverflow.com/questions/729692/…
wc -l
devolvería 1. Llegué a la conclusión de que todos los métodos tienen fallas, e implementé uno basado en cómo me gustaría que se comportara, vea mi otra respuesta aquí.
La respuesta aceptada tiene un error de uno por uno para los archivos de varias líneas que no terminan en nueva línea. Un archivo de una línea que termina sin una nueva línea devolvería 1, pero un archivo de dos líneas que termina sin una nueva línea también devolvería 1. Aquí hay una implementación de la solución aceptada que soluciona esto. Las comprobaciones finalesWithoutNewLine son un desperdicio para todo menos la lectura final, pero deben ser triviales en cuanto al tiempo en comparación con la función general.
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();
}
}
Con java-8, puedes usar transmisiones:
try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
long numOfLines = lines.count();
...
}
La respuesta con el método count () anterior me dio un recuento incorrecto de líneas si un archivo no tenía una nueva línea al final del archivo; no se pudo contar la última línea del archivo.
Este método funciona mejor para mí:
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
.
Sé que esta es una vieja pregunta, pero la solución aceptada no coincidía con lo que necesitaba hacer. Entonces, lo refiné para aceptar varios terminadores de línea (en lugar de solo un avance de línea) y para usar una codificación de caracteres específica (en lugar de ISO-8859- n ). Método todo en uno (refactorizar según corresponda):
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;
}
Esta solución es comparable en velocidad a la solución aceptada, aproximadamente un 4% más lenta en mis pruebas (aunque las pruebas de temporización en Java son notoriamente poco confiables).
Probé los métodos anteriores para contar líneas y aquí están mis observaciones para diferentes métodos según lo probado en mi sistema
Tamaño de archivo: 1.6 Gb Métodos:
Además, el enfoque Java8 parece bastante útil:
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();
}
}
Probado en JDK8_u31. Pero, de hecho, el rendimiento es lento en comparación con este método:
/**
* 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;
}
}
Probado y muy rápido.
Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1
Y el número de líneas es aún demasiado mal
BufferedInputStream
todos modos no debe usar a cuando vaya a leer en su propio búfer. Además, incluso si su método puede tener una ligera ventaja de rendimiento, pierde flexibilidad, ya que ya no admite \r
terminadores de línea única (MacOS antiguo) y no admite todas las codificaciones.
Una forma sencilla de usar el escáner
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);
}
Llegué a la conclusión de que wc -l
: el método de contar nuevas líneas está bien, pero devuelve resultados no intuitivos en archivos donde la última línea no termina con una nueva línea.
Y la solución @ er.vikas basada en LineNumberReader pero agregando uno al recuento de líneas devolvió resultados no intuitivos en archivos donde la última línea termina con nueva línea.
Por lo tanto, hice un algo que se maneja de la siguiente manera:
@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"));
}
Y se ve así:
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;
}
}
Si desea resultados intuitivos, puede usar esto. Si solo desea wc -l
compatibilidad, simplemente use la solución @ er.vikas, pero no agregue una al resultado y vuelva a intentar omitirla:
try(LineNumberReader lnr = new LineNumberReader(new FileReader(new File("File1")))) {
while(lnr.skip(Long.MAX_VALUE) > 0){};
return lnr.getLineNumber();
}
¿Qué tal usar la clase Process desde el código Java? Y luego leyendo la salida del comando.
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);
}
Aunque necesito probarlo. Publicará los resultados.
Si no tiene ninguna estructura de índice, no obtendrá la lectura del archivo completo. Pero puede optimizarlo evitando leerlo línea por línea y usar una expresión regular para que coincida con todos los terminadores de línea.
¡Esta divertida solución funciona realmente bien!
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;
}
}
En sistemas basados en Unix, use el wc
comando en la línea de comandos.
La única forma de saber cuántas líneas hay en el archivo es contarlas. Por supuesto, puede crear una métrica a partir de sus datos para obtener una longitud promedio de una línea y luego obtener el tamaño del archivo y dividirlo con prom. longitud pero eso no será exacto.
Mejor código optimizado para archivos de varias líneas que no tienen carácter de nueva línea ('\ n') en 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;
}
Escáner con expresiones regulares:
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;
}
No lo he marcado.
si usas esto
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;
}
no puede correr a grandes filas numéricas, le gustan las filas de 100K, porque el retorno de reader.getLineNumber es int. necesita un tipo de datos largo para procesar filas máximas.
int
puede contener valores de hasta, aproximadamente, 2 mil millones. Si está cargando un archivo con más de 2 mil millones de líneas, tiene un problema de desbordamiento. Dicho esto, si está cargando un archivo de texto no indexado con más de dos mil millones de líneas, probablemente tenga otros problemas.