Какой самый быстрый способ чтения из System.in на Java?
Я читаю кучу целых чисел, разделенных пробелом или переводом строки из стандарта при использовании Scanner(System.in)
,
Есть ли более быстрый способ сделать это в Java?
5 ответов
Есть ли более быстрый способ сделать это в Java?
Да. Сканер довольно медленный (по крайней мере, по моему опыту).
Если вам не нужно проверять ввод, я предлагаю вам просто обернуть поток в BufferedInputStream и использовать что-то вроде String.split
/ Integer.parseInt
,
Небольшое сравнение:
Чтение 17 мегабайт (4233600 номеров) с использованием этого кода
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext())
sum += scanner.nextInt();
взял на моей машине 3,3 секунды. пока этот фрагмент
BufferedReader bi = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = bi.readLine()) != null)
for (String numStr: line.split("\\s"))
sum += Integer.parseInt(numStr);
заняло 0,7 секунды.
Путем путаницы кода дальше (перебирая line
с String.indexOf
/ String.substring
) Вы можете довольно легко уменьшить его до 0,1 секунды, но я думаю, что я ответил на ваш вопрос и не хочу превращать это в какой-нибудь код-гольф.
Я создал небольшой класс InputReader, который работает так же, как сканер Java, но превосходит его по скорости на многие величины, фактически он также превосходит BufferedReader. Вот гистограмма, которая показывает производительность класса InputReader, который я создал для чтения различных типов данных из стандартного ввода:
Вот два разных способа нахождения суммы всех чисел из System.in с помощью класса InputReader:
int sum = 0;
InputReader in = new InputReader(System.in);
// Approach #1
try {
// Read all strings and then parse them to integers (this is much slower than the next method).
String strNum = null;
while( (strNum = in.nextString()) != null )
sum += Integer.parseInt(strNum);
} catch (IOException e) { }
// Approach #2
try {
// Read all the integers in the stream and stop once an IOException is thrown
while( true ) sum += in.nextInt();
} catch (IOException e) { }
Если вы спросите с точки зрения конкурентного программирования, где, если представление недостаточно быстрое, это будет TLE.
Затем вы можете проверить следующий метод для извлечения String из System.in. Я взял у одного из лучших кодеров в Java(конкурентные сайты)
private String ns()
{
int b = skip();
StringBuilder sb = new StringBuilder();
while(!(isSpaceChar(b))){ // when nextLine, (isSpaceChar(b) && b != ' ')
sb.appendCodePoint(b);
b = readByte();
}
return sb.toString();
}`
StringTokenizer
гораздо более быстрый способ чтения ввода строк, разделенных токенами.
Проверьте ниже пример, чтобы прочитать строку целых чисел, разделенных пробелом и сохранить в arraylist,
String str = input.readLine(); //read string of integers using BufferedReader e.g. "1 2 3 4"
List<Integer> list = new ArrayList<>();
StringTokenizer st = new StringTokenizer(str, " ");
while (st.hasMoreTokens()) {
list.add(Integer.parseInt(st.nextToken()));
}
Вы можете прочитать из System.in
цифрой за цифрой. Посмотрите на этот ответ: /questions/11730599/chtenie-bolshogo-kolichestva-dannyih-iz-fajla-v-java/11730621#11730621.
Я копирую код здесь (едва модифицированный). По сути, он читает целые числа, разделенные всем, что не является цифрой. (Авторы оригинального автора.)
private static int readInt() throws IOException {
int ret = 0;
boolean dig = false;
for (int c = 0; (c = System.in.read()) != -1; ) {
if (c >= '0' && c <= '9') {
dig = true;
ret = ret * 10 + c - '0';
} else if (dig) break;
}
return ret;
}
В моей проблеме этот код был ок. В 2 раза быстрее чем при использовании StringTokenizer
, который был уже быстрее, чем String.split(" ")
, (Проблема заключалась в чтении 1 миллиона целых чисел до 1 миллиона каждый.)
Вы можете использовать BufferedReader для чтения данных
BufferedReader inp = new BufferedReader(new InputStreamReader(System.in));
int t = Integer.parseInt(inp.readLine());
while(t-->0){
int n = Integer.parseInt(inp.readLine());
int[] arr = new int[n];
String line = inp.readLine();
String[] str = line.trim().split("\\s+");
for(int i=0;i<n;i++){
arr[i] = Integer.parseInt(str[i]);
}
А для печати используйте StringBuffer
StringBuffer sb = new StringBuffer();
for(int i=0;i<n;i++){
sb.append(arr[i]+" ");
}
System.out.println(sb);
Чтение с диска снова и снова замедляет работу сканера. Мне нравится использовать комбинацию BufferedReader и Scanner, чтобы получить лучшее из обоих миров. т.е. скорость BufferredReader и богатый и легкий API сканера.
Scanner scanner = new Scanner(new BufferedReader(new InputStreamReader(System.in)));
С точки зрения программирования этот настраиваемый класс Scan and Print намного лучше, чем встроенные классы Java Scanner и BufferedReader.
import java.io.InputStream;
import java.util.InputMismatchException;
import java.io.IOException;
public class Scan
{
private byte[] buf = new byte[1024];
private int total;
private int index;
private InputStream in;
public Scan()
{
in = System.in;
}
public int scan() throws IOException
{
if(total < 0)
throw new InputMismatchException();
if(index >= total)
{
index = 0;
total = in.read(buf);
if(total <= 0)
return -1;
}
return buf[index++];
}
public int scanInt() throws IOException
{
int integer = 0;
int n = scan();
while(isWhiteSpace(n)) /* remove starting white spaces */
n = scan();
int neg = 1;
if(n == '-')
{
neg = -1;
n = scan();
}
while(!isWhiteSpace(n))
{
if(n >= '0' && n <= '9')
{
integer *= 10;
integer += n-'0';
n = scan();
}
else
throw new InputMismatchException();
}
return neg*integer;
}
public String scanString()throws IOException
{
StringBuilder sb = new StringBuilder();
int n = scan();
while(isWhiteSpace(n))
n = scan();
while(!isWhiteSpace(n))
{
sb.append((char)n);
n = scan();
}
return sb.toString();
}
public double scanDouble()throws IOException
{
double doub=0;
int n=scan();
while(isWhiteSpace(n))
n=scan();
int neg=1;
if(n=='-')
{
neg=-1;
n=scan();
}
while(!isWhiteSpace(n)&& n != '.')
{
if(n>='0'&&n<='9')
{
doub*=10;
doub+=n-'0';
n=scan();
}
else throw new InputMismatchException();
}
if(n=='.')
{
n=scan();
double temp=1;
while(!isWhiteSpace(n))
{
if(n>='0'&&n<='9')
{
temp/=10;
doub+=(n-'0')*temp;
n=scan();
}
else throw new InputMismatchException();
}
}
return doub*neg;
}
public boolean isWhiteSpace(int n)
{
if(n == ' ' || n == '\n' || n == '\r' || n == '\t' || n == -1)
return true;
return false;
}
public void close()throws IOException
{
in.close();
}
}
И настроенный класс печати может быть следующим
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class Print
{
private BufferedWriter bw;
public Print()
{
this.bw = new BufferedWriter(new OutputStreamWriter(System.out));
}
public void print(Object object)throws IOException
{
bw.append("" + object);
}
public void println(Object object)throws IOException
{
print(object);
bw.append("\n");
}
public void close()throws IOException
{
bw.close();
}
}
Вот полная версия быстрого чтения и писателя. Я также использовал буферизацию.
import java.io.*;
import java.util.*;
public class FastReader {
private static StringTokenizer st;
private static BufferedReader in;
private static PrintWriter pw;
public static void main(String[] args) throws IOException {
in = new BufferedReader(new InputStreamReader(System.in));
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
st = new StringTokenizer("");
pw.close();
}
private static int nextInt() throws IOException {
return Integer.parseInt(next());
}
private static long nextLong() throws IOException {
return Long.parseLong(next());
}
private static double nextDouble() throws IOException {
return Double.parseDouble(next());
}
private static String next() throws IOException {
while(!st.hasMoreElements() || st == null){
st = new StringTokenizer(in.readLine());
}
return st.nextToken();
}
}