코딩 테스트를 풀다보면 시간 초과가 나는 경우가 많습니다.
그 이유는 다양하겠지만, 시간을 줄이는 첫번째 단계는 입력과 출력 시간을 줄여주는 것이라 생각합니다.
저는 자바로 코딩 테스트 할 때, 단순한 입출력이 아닌 버퍼를 사용해 입력과 출력을 받아줬습니다.
C#을 공부하면서 C#에서 빠른 입출력을 원할 때 어떤 방법을 써야하는지 공부한 내용을 정리하겠습니다.
백준 2741번으로 테스트를 해봤습니다.
https://www.acmicpc.net/problem/2741
문제는 간단히 숫자 N을 입력받으면, 1부터 N까지 출력하는 문제입니다.
이 문제에서 N의 범위가 최대 100,000이고, 시간 제한은 1초입니다.
먼저 가장 기본적으로 입출력을 해보겠습니다.
가장 기본적인 입출력 방식으로 문제를 풀어봤습니다.
시간 초과가 났습니다.
사실 굳이 백준에서 검사하지 않고,
그냥 실행해서 100000을 입력해보면 딱 봐도 1초가 넘는 것을 볼 수 있습니다.
다음 방법은 StringBuilder를 사용하는 방식입니다.
값을 StringBuilder에 저장한 후, 한번에 StringBuilder를 출력하는 방식입니다.
1초 안에 성공적으로 실행하였습니다.
하나씩 출력하지 않고 StringBuilder로 한번에 출력하는 것이 더 빠른 것을 확인할 수 있습니다.
문자열 양이 많아질수록 String보다 StringBuilder가 성능이 좋습니다.
물론, 각각 장단점이 있기 때문에 적절한 상황에 사용하는 것이 좋습니다.
입력도 빠르게 받을 수 있으면 실행 시간이 더 단축되지 않을까요?
여기서 Stream을 사용해서 입출력을 더 빠르게 받을 수 있습니다.
StreamReader와 StreaemWriter를 사용해서 입출력을 해봤습니다.
두 클래스를 사용하기 위해선 using System.IO;를 해줘야 합니다.
StringBuilder보다 더 빠른 결과를 보입니다.
이 문제는 입력을 한번 밖에 받지 않기 때문에 큰 차이는 없지만,
입력을 여러번 받는다면 이 차이는 더 커질 것 입니다.
그럼 여기서 Stream은 무엇이고, 그 안에 사용된 다른 코드들은 무엇인지 알아보겠습니다.
Stream
스트림을 간단히 말하면,
데이터를 읽고 쓸 때, 데이터가 이동하는 경로입니다.
바이트 단위로 읽고 씁니다.
실생활로 예를 들어보면,
수도꼭지에 고무관을 끼워봅니다.
그러면 물은 수도꼭지에서 시작돼서 고무관으로 흐른 후, 밖으로 나옵니다.
여기서 물이 데이터, 고무관이 스트림으로 생각하면 될 것 같습니다.
Stream 클래스는 모든 스트림의 추상 기본 클래스입니다.
Stream 클래스는 FileStream, NetworkStream, MemoryStream, BufferedStream이 있습니다.
저희는 BufferedStream에 대해 알아보겠습니다.
BufferedStream
BufferedStream은 자바의 BufferedReader/BufferedWriter와 비슷합니다.
버퍼 크기만큼 데이터를 저장했다가 한번에 읽고 씁니다.
기본 버퍼 사이즈는 (1024 * 4 = 4096)바이트입니다.
실생활로 예를 들면,
물건을 하나씩 옮기는 것보다,
수레에 담을 수 있는 양만큼 실은 후 한번에 옮기는 것이 더 빠릅니다.
물건이 데이터, 수레가 버퍼라 생각하면 됩니다.
그래서 버퍼링 되지 않은 작업보다 버퍼링 된 작업이 더 빠릅니다.
OpenStandardInput()은 표준 입력 스트림을 가져오고,
OpenStandardOutput()은 표준 출력 스트림을 가져오는 메소드입니다.
StreamReader/StreamWriter는 데이터를 스트림에 읽고 쓰는데 사용됩니다.
그래서,
StreamWriter sw = new StreamWriter(new BufferedStream(Console.OpenStandardOutput()));
StreamReader sr = new StreamReader(new BufferedStream(Console.OpenStandardInput()));
를 해석하면,
"콘솔의 표준 입출력을 버퍼를 이용해 스트림에 데이터를 읽거나 쓴다."가 저의 해석입니다.
Stream을 사용할 때 주의할 점이 있습니다.
Stream 사용이 모두 끝났으면 Close()로 메모리를 반환해야 합니다.
Close()는 메모리를 반환하기 전에 자동으로 Flush()를 호출합니다.
Close()를 호출해주지 않으면 원하는 출력이 되지 않을 수 있습니다.
여기서 Flush()는 데이터를 모두 내보내는 메소드입니다.
사용방법 입니다.
sr.Read(); //한 문자씩 읽기
sr.ReadLine(); //한 줄씩 읽기
sr.ReadToEnd(); //전체를 한 번에 읽기
sw.Write(str); //str을 스트림에 쓴다.
sw.WriteLine(str); //str을 스트림에 쓴다. 뒤에 개행 문자가 추가된다. 매개변수가 없으면 줄바꿈만 수행한다.
제가 공부한 부분을 정리한 내용이기 때문에 틀린 부분 있을 수 있습니다!!
혹시 틀린 부분이 있으면 알려주세요!!
'C#' 카테고리의 다른 글
[C#] 다형성, virtual과 override (0) | 2022.09.06 |
---|---|
[C#] 상속을 해보자 (0) | 2022.08.27 |
[C#] 암묵적 구현과 명시적 구현 (0) | 2022.08.23 |
[C#] 추상 클래스와 인터페이스 (0) | 2022.08.19 |
[C#] 객체지향 언어에 대하여(OOP) (0) | 2022.08.10 |