오늘은 Exception과 LRU알고리즘에 대해 공부했다.
사실 이 개념에 대해 많이 공부하지 않았다. 오늘은 어제 공부했던 Map, ArrayList, Set에 대해 더 자세히 공부하고 프로그래밍 해보고 그랬는데, 여기다가 쓰지 않으려고한다. 오늘 배운 개념은 이것밖에 없어서 정리해보려고한다. 근데 오늘 딱 try 까지만 하고 다음에 또 할거다.
프로그래밍을 하다보면 다양한 종류의 오류를 만날 수 있다. 정리해보자.
- 컴파일 에러 (compile - time error) : 소스상의 문법 에러
- 런타임 에러 ( runtime error) : 입력 값이 틀렸거나, 배열의 인덱스범위를 벗어났거나,
계산식의 오류 등으로 발생하는 에러- 논리 에러 (logical error) : 문법상 문제가 없고, 런타임 에러도 발생하지 않지만,
개발자의 의도대로 작동하지 않는 경우- 시스템 에러 (system error) : 컴퓨터 오작동으로 인한 에러 -> 소스코드로 해결 불가능
이러한 상황들은 문제를 찾아내서 해결해주면 된다. 하지만 오늘 배울 개념은 Exception이다.
Exception
- 예외 라는 뜻을 가지고 있으며, 예외는 예기치 못한 상황을 말한다.
- 프로그래밍을 하다 보면 수많은 오류상황을 직면한다.
- 자바에서 예외란 프로그램을 만든 개발자가 예상한 정상적인 처리에서 벗아나는 경우에
이를 처리하기 위한 방법이다.- 예측 가능한 에러를 처리하는 것 이라고 볼 수 있다.
이렇게 예외처리를 하는 이유는 프로그램의 비정상적인 종료를 막고, 정상적인 실행상태를 유지하기 위함이다. 예외 상황이 발생된 경우 어떻게 처리할지에 대한 논리를 미리 구현해놓는 방식으로 이를 해결한다. 그때 이 try문이 나오는거다. 코드로 한번 보자.
public class ExceptionTest {
public void test1() {
Scanner sc = new Scanner(System.in);
while(true) {
System.out.print("정수를 입력하세요 : ");
int num=0;
try{
num = sc.nextInt();
}catch (Exception e) {
System.out.println("정수를입력하라구.");
sc.nextLine();
continue;//그래도 finally는 실행됨
}finally {
System.out.println("이건 무조건 실행!");
}
System.out.println("입력한 정수는 : "+num);
}
}
}
오늘은 이 코드 하나로 싹 정리할거다. 그만큼 내용이 없다.
위의 코드는 정수형 변수 num에 정수를 입력받도록 하고 그 값을 다시 출력해주는 코드이다.
그런데 사용자가 여기서 정수값을 입력하지 않으면 오류가 발생한다. 그때 이 try문을 사용한다.
문자열 abc를 입력하면 이 코드는 마지막 출력코드
System.out.println("입력한 정수는 : "+num);
으로 가지 않고,
catch (Exception e) {
System.out.println("정수를입력하라구.");
sc.nextLine();
continue;//그래도 finally는 실행됨
}
요 부분으로 간다. 이 부분은 코드가 오류나지않으면 그냥 실행되는 부분이다.
정상적인 값을 입력했을 때의 출력값과, 그렇지 않았을때의 출력값으로 정리해보자.
- 정상적인 값 입력
출력값 :
정수를 입력하세요 : 10
이건 무조건 실행!
입력한 정수는 : 10- 비정상적인 값 입력
출력값:
정수를 입력하세요 : affhs
정수를입력하라구.
이건 무조건 실행!
이렇게 되는거다. 여기서 왜 이건 무조건 실행! 이라는 부분은 계속 보이는가?
그건 finally문으로 작성되었기 때문이다. 이 부분은 에러가 나든 말든 무조건 실행된다.
Exception은 여기서 마치도록하자.
LRU 알고리즘이란, 쉽게 말해 배열의 저장공간이 다 차면, 가장 오래된 값을 버리고 새 값을 받아오는 알고리즘이다. 카카오 코딩테스트에 나왔던 문제를 보면서 구현해보자.
필자는 이 문제를 ArrayList와 HashMap 자료구조를 같이 사용하려고 뻘짓하다가 오랜시간 못풀었다. 근데 이 문제 난이도 하 다. 프로그래밍을 얼마 하지도 않았으면서 사치스럽게도 자괴감이든다. 안되는건 내가 못해서다. 더 공부해라 필자. 여튼 얼른 문제를 풀어보자.
필자는 ArrayList만 사용해서 풀 것이다. 그런데 사실 LinkedList를 사용하면 더 빠르고 쉽게 풀 수 있다고한다. 그건 다음에 해봐서 따로 올리겠다. 일단 필요한 자료들부터, Scanner와 ArrayList객체를 추가해주고 실행시간을 출력해줄 변수를 선언해준다.
public void main() {
ArrayList<String> lru = new ArrayList<String>(); //알고리즘을 구현할 자료구조 객체
ArrayList<String> cities = new ArrayList<String>(); // 사용자한테서 입력받은 값을 저장할 자료구조 객체
Scanner sc = new Scanner(System.in); // 그냥 스캐너
int totalTime = 0; //총 시간
왜 저렇게 했는지 주석에 써 놓았으니 궁금하신 분들은 읽어주시길 바란다. 근데 사실 내 글을 보러오시는 분들이 없다. 나도 안다. 그냥 나 공부한거 정리하려고 하는거다...
다음은 사용자한테서 캐시 사이즈와 도시의 배열을 직접 입력 받는다.
System.out.print("캐시의 크기를 입력하세요(0~30) : ");
int cacheSize = sc.nextInt();// 캐시의 크기
System.out.print("도시의 문자열을 입력하세요 : ");
sc.nextLine();//버퍼 비우기
String cityName = sc.nextLine();//도시들 이름 받기
위의 사진을 보면 "Jeju", "Pangyo", "Seoul", "NewYork", "LA", "Jeju", "Pangyo", "Seoul", "NewYork", "LA" 요런식으로 입력한다는 것을 알 수 있다.
입력받은 문자열을 StringTokenizer로 나눠주자.
StringTokenizer st = new StringTokenizer(cityName, ", ");// 입력받은 배열을 ","기준으로 나눔
while (st.hasMoreTokens()) {
String str1 = st.nextToken().toLowerCase();//모두 소문자로 변환해서 리턴
cities.add(str1);//ArrayList형 자료구조 cities에다 값을 입력
}
입력받은 값을 ", "기준으로 나눠주고, 그 값을 처음에 만들었던 cities에 넣어줬다.
시간을 계산하는 일만 남았다. 입력받은 캐시의 값이 0일때부터 만든다.
else {// if end // 캐시 사이즈가 0 이상인 경우
for (int i = 0; i < cities.size(); i++) {
String city = cities.get(i);
//현재 lru에 이번회차 도시이름이 존재하는지 확인 indexOf
//indexOf(매개변수) : 매개변수로 전달된 객체가 리스트의 몇번째에 존재하는지 리턴하는 메소드(존재하지않으면 -1 리턴)
int result = lru.indexOf(city);
indexOf를 사용해 다음에 받을 값을 result에 받아줬다. 이 변수로 lru의 값이 같은게 있는지 판단해준다. 그리고 상황마다 다르게 코드를 써준다.
if(result != -1) { //현재 캐시에 해당하는 도시명이 존재하는 경우
lru.add(0,lru.remove(result)); //원래 있던 값을 삭제하고 0번에 집어넣는 과정
totalTime += 1; //현재 값 +1
}
현재 같은 값이 lru에 들어있다면 원래 값이 들어있던 부분을 삭제하고, 키값이 0인 위치에 다시한번 값을 넣어줌으로써 lru.size를 늘리지 않늗다. 같은 값이 있을때는 걸린 시간을 +1해준다.
}else { //현재 캐시에 해당하는 도시명이 존재하지 않는 경우
if(lru.size() == 0) { // 리스트에 아무 값도 없을 때
lru.add(city);
이번엔 캐시에 입력받은 도시명이 존재하지 않을 경우이다. 이 경우 lru에 이미 값이 있을 경우와 없을 경우로 나눠서 코드를 짠다. 없을 경우에는 그냥 add해주면 된다.
}else {
if(lru.size()==cacheSize) { //리스트길이와 캐시 최대크기를 비교
lru.remove(lru.size()-1); //캐시가 꽉 차있으면 마지막데이터 삭제
}
lru.add(0,city); // 리스트에 값이 하나라도 있을 때
}
totalTime += 5; //현재 값 +5
이미 lru에 데이터가 있을 경우 입력받은 캐시사이즈와 현재 lru.size를 비교해서 만약 캐시사이즈와 lru.size가 같다면 lru.size가 캐시사이즈를 넘어가지 못하도록 가장 마지막 값을 삭제해준다. 왜 lru.size-1값을 삭제해주는지 알아보자.
lru.size-1 해주는 이유
캐시사이즈를 3이라고 가정해보자. 그리고 여태 lru에 넣어준 값이 3개라고 가정하면, lru는 ArrayList기 때문에 입력받은 값이 a,b,c라면
- lru(0,"c");
- lru(1,"b");
- lru(2,"a");
이렇게 될 것이다. 왜냐하면 우리가 넣어줄 때 가장 최근에 넣은 코드의 키값을 0으로 설정했기 때문이다. 그래서 가장 최근에 넣은 값이 키값 0 에 들어간다. 그러면 원래 존재하던 값들은 키값이 1씩 밀리면서 위치가 바뀐다.
가장 오래된 값이 가장 마지막에 위치하도록 만들었다는 것 이다.
lru알고리즘은 데이터의 크기가 꽉 찼을때, 가장 오래된 값을 삭제해주는 알고리즘이다. 그러므로 위와같은 상황에서는 lru(2,"a")값을 삭제해 주어야한다. 우리가 캐시 사이즈를 정해서 그 범위를 넘어가지 않게 해 놓았음으로 항상 가장 마지막 키값은 2일 것 이다. 그리고 2는 우리가 3으로 가정한 캐시사이즈-1의 값이다.
캐시 사이즈가 어떤 값이더라도 이 논리는 성립한다. 데이터가 꽉 찼을때, 가장 오래된 키값은 언제나 캐시 사이즈-1, 즉 lru.size-1의 값일 것 이라는거다.
위와 같은 논리가 성립하기 때문에 lru.size-1위치의 값을 삭제해주고 다시 0의 위치에 값을 넣어주는 것 이다. 그리고 위 코드와 값에 아무것도 들어있지 않았을 경우(윗윗 코드) 모두 총 시간을 5 더해준다. 마지막으로 총 시간이 몇인지 출력해주는 코드까지 작성하면 끝이다.
} // else end
System.out.println("총 시간 : "+totalTime);
끝이다! 고생했다!
원래 포스팅을 어제 올리려고 했는데, 새로나온 유희왕 마스터 듀얼 해보고싶어서 친구랑 추억새기면서 밤새 했다... 그래서 마저 작성하지 못하고 너무 늦게 자버렸다. 아침에 일어나서야 올린다...! 근데 유희왕 진짜 재밌다. 꿈에서도 썬더 드래곤 덱 굴리는 꿈을 꿨다.