2023-02-13
목터뷰 조사한 내용 정리 1일차...
1. (초급) 배열, 링크드리스트를 비교하여 설명해주실 수 있을까요?
Array(배열)
특정 크기만큼 연속된 메모리 공간에 데이터를 저장하는 자료구조
하지만 데이터를 빈번하게 데이터를 추가하거나 삭제할 때는 효율적이지 못하다.
만약 데이터를 중간에 추가하려고 하면 추가하려는 자리를 비우고 뒤에 있는 데이터를 한 칸씩 뒤로 밀어야 하기 때문이다.
배열의 주소는 배열에 저장되어 있는 첫 번째 원소의 주소와 같다.
배열에서 4번 데이터를 조회한다고 하면 3개의 데이터 크기만큼 넘어가면서 조회를 한다. 이를 통해 배열은 특정 데이터를 O(1) 로 조회할 수 있다.
LinkedList(연결 리스트)
이는 배열과 다르게 연속된 메모리 공간에 저장되어 있지 않다.
각각의 데이터가 메모리 공간 상에 고유한 노드로 존재하며, 이 노드는 자신의 앞에 있는 데이터와 뒤에 있는 데이터에 대한 주소를 기억하고 있다.
만약 연결 리스트에 저장되어 있는 특정 데이터를 조사하려 하면 처음부터 순차적으로 탐색해야 한다.
연결 리스트의 노드는 연속된 메모리 공간에 존재하지 않고 모두가 떨어져 있기 때문이다.
이는 각 노드가 기억하고 있는 앞의 데이터와 뒤의 데이터 주소에 의지할 수 밖에 없다. 따라서 연결 리스트는 특정 데이터를 O(N) 으로 조회한다.
즉, 연결 리스트는 A - B - C 와 같이 연결 리스트에 저장되어 있을 때, B 와 C 사이에 Z 를 추가하려고 한다.
그러면 B 에서 C 를 가리키는 주소를 Z 로 변경하고 C 에서 B 를 가리키는 주소를 Z 로 변경하면 된다. 이는 삭제 때에도 마찬가지로, 따라서 데이터 추가 및 삭제는 O(1) 로 가능하다.
배열 vs 연결 리스트
- 배열에 저장되어 있는 데이터를 조회할 때는 O(1) 로 가능하지만 연결리스트는 O(N)이 소요된다.
정리
둘 모두 데이터를 나열하는 것이고 사용 목적에 따라 잘 선택해서 사용해야 한다.
- 배열은 데이터의 입력이 순차적으로 이루어지며 물리적 주소 또한 순차적인 반면 LinkedList 는 논리적 순서로 이루어지며 물리적 주소는 순차적이지 않다.
- 배열은 인덱스가 있어 원하는 데이터에 한번에 접근이 가능하고 LinkedList 는 연결되어 있는 링크를 따라간다. 따라서 속도는 배열이 더 빠르다.
- 배열은 데이터 삽입, 삭제시 해당 위치의 다음 순서의 데이터의 위치 변경이 이루어 지지만 LinkedList 는 논리적 주소만 바꿔주면 되기 때문에 이 경우에는 LinkedList 가 더 유리하다.
- 배열은 크기가 처음부터 결정되어 추후 변경이 불가능 하지만 LinkedList 는 메모리 할당/해제를 해서 크기 변경이 불가능하다.
- 다만 배열이 꽉 차있을 경우 메모리를 재할당 하면 가능하나 최악의 경우엔 불가능하다.
- 데이터 삽입, 삭제를 좀 더 상세하게 풀이하면...
1. 삽입 시, 기존의 데이터들의 위치를 뒤로 이동 / 삭제 시, 기존의 데이터들의 위치를 앞으로 이동
2. 삽입 시, 리스트에 연결되어 있는 위치에 접근한 후 리스트 추가 / 삭제 시, 리스트에 연결되어 있는 위치에 접근하여 삭제 후 기존의 리스트들을 연결
1번 질문의 다른 사람들의 답변
배열은 입력된 데이터들이 메모리 공간에서 연속적으로 저장되어 있는 자료구조이며 메모리상에서 연속적으로 저장되어 있는 특징을 갖기 때문에 index를 통한 접근이 용이하다는 장점이 있으나 삽입/삭제가 오래 걸리고 배열 중간의 데이터가 삭제 되면 공간 낭비가 발생할 수 있는 단점이 존재합니다. 링크드리스트(연결리스트)는 여러 개의 노드들이 순차적으로 연결된 형태를 갖는 자료구조이며 각 노드는 데이터와 다음 노드를 가리키는 포인터로 이루어져 있는 트리(tree)구조의 근간이 되는 자료구조입니다. 배열과 달리 메모리를 연속적으로 사용하지 않아 삽입/삭제에 용이하다는 장점이 있습니다. 그러나 index로 임의 접근이 불가하며 처음부터 탐색을 해야하는 단점이 있습니다. 따라서 배열은 빠른 접근이 요구되고, 데이터의 삽입과 삭제가 적을 때 사용하고 링크드리스트는 삽입과 삭제 연산이 잦고, 검색 빈도가 적을 때 사용합니다.
배열은 정적인 자료구조로 연속된 메모리 주소 할당으로 인해 순차적인 인덱스 값으로 데이터 탐색에 용이하지만 크기가 고정적이라는 특징 때문에 재할당을 하지 않는 다면 빈 공간에 대한 메모리 낭비가 발생할 수 있습니다. 링크드리스트는 노드를 활용하기 때문에 크기가 가변적이고 데이터 추가,삭제에 용이하지만 메모리를 연속적으로 사용하지 않아 인덱스의 순서가 없어서 임의 접근이 불가능하고 순차적인 접근을 사용해야 합니다.
2. (초급) CORS란 무엇이고 어떻게 허용할 수 있나요?
교차 출처 리소스 공유 (Cross-Origin Resource Sharing, CORS) 는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처(프로토콜, 도메인, 포트번호) 의 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.
웹 애플리케이션은 리소스가 자신의 출처와 다를 때 교차 출처 HTTP 요청을 실행한다.
즉, 위에서 언급한 프로토콜, 도메인, 포트번호 가 일치하면 Same-Origin 이며, 하나라도 불일치시 Cross-Origin 이 된다.
그렇다면 CORS 가 필요한 이유는 뭘까?
웹에서 돌아가는 클라이언트 애플리케이션은 사용자 공격에 매우 취약하다.
개발자 도구만 열어도 DOM, JavaScript 코드 등 각종 통신 정보를 쉽게 열람이 가능하기 때문이다.
이를 통해서 사용자가 악의를 가지고 다른 사이트로 본 사이트의 모방이 가능한데 이렇게 되면 다른 출처의 애플리케이션이 통신하는 것에 제약이 없다면, 기존 사이트와 동일하게 동작하여 사용자의 정보가 탈취되기 쉬워진다.
다른 출처의 요청 정책으로는 Simple, preflight, Credntilaled Request 이렇게 세가지가 있다.
단순 요청의 경우 서버로 요청을 보내고 응답이 오면 브라우저가 요청한 Origin과 응답한 Access-Control-Request-Header의 값을 비교하여 유효한 요청의 경우에만 리소스를 응답하고 아닐시 막고 에러를 발생시킨다.
예비 요청은 헤더에 요청하는 HTTP method 와 요청시 사용할 헤더를 OPTIONS 메서드로 서버로 요청하는 것이 추가된다.
세가지 허용 법
프론트 서버에서 백엔드 서버로 요청을 보낼 때, 대상의 URL 을 변경 / 직접 헤더에 설정을 추가 / 스프링부트 에서는 설정 클래스를 만들고 WebMvcConfigurer 를 구현해서 CORS의 출처 및 설정 관리를 함으로 해결이 가능하다.
정리
브라우저에서 보안적인 이유로 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처(프로토콜, 도메인, 포트번호) 의 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.
간단히 말하면 서버측 응답에서 접근 권한을 주는 헤더를 추가해서 해결이 가능한데, 스프링 에서는 서버에서 WebMvcConfigurer 를 만들어서 CORS 의 출처 및 설정 관리를 해줌으로서 허용이 가능하다.
2번 질문의 다른 사람들의 답변
한 출처에 있는 자원에서 다른 출처에 있는 자원에 접근하도록 하는 것으로 교차되는 출처 자원들의 공유입니다. 다른 출처의 리소스를 가져오는 상황에서 SOP는 이 접근을 차단합니다. 하지만 CORS 설정을 통하여 Access-Control-Allpw-Origin을 서버의 응답헤더에 작성하게 되면 접근 권한을 얻을 수 있습니다.
Cross-Origin Resource Sharing(교차 출처 리소스 공유) 의 약자로 브라우저에서 실행 중인 스프립트에서 시작되는 cross-origin HTTP 요청을 제한하는 브라우저 보안 기능이다. 브라우저는 same-origin policy(동일 출처 정책)에 의해 cross-origin의 리소스를 요청을 차단한다. 제일 쉬운 해결방안은 서버 단에서 특정 origin 혹은 모든 origin 을 허용하도록 설정 하면 된다. 하지만 여기에서 끝나면 GET, POST 요청은 정상적으로 요청이 되지만 HEAD, PUT, DELETE HTTP method에 대해서는 CORS가 발생한다. SpringBoot 에서는 allowedOrigins 를 통해 CORS허용을 하면 기본적으로 GET, POST, HEAD 만 허용을 해준다. 따라서 allowedMethods를 이용해 허용할 HTTP Method를 추가해준다.
3. 시간복잡도와 공간복잡도가 무엇인지 설명해주실 수 있을까요?
알고리즘 성능을 평가하기 위해서 복잡도의 척도를 사용한다고 하는데,
시간 복잡도는 특정한 크기의 입력에 대하여 알고리즘의 수행 시간 분석
공간 복잡도는 특정한 크기의 입력에 대하여 알고리즘의 메모리 사용량 분석 을 의미한다.
정리
시간 복잡도 : 정확히 말하자면 특정 알고리즘이 어떤 문제를 해결하는데 걸리는 시간을 의미한다.
공간 복잡도 : 특정 알고리즘이 완료에 얼마나 많은 공간(메모리) 필요한지를 의미한다.
3번 질문의 다른 사람들의 답변
시간 복잡도란 특정 크기의 입력을 기준으로 할 때 필요한 연산의 횟수를 나타냅니다. 시간 복잡도에는 빅오 표기법이라는 복잡도를 나타내는 점근 표기법 중 가장 많이 사용되는 표기법을 사용합니다. 공간 복잡도는 프로그램 실행과 완료에 얼마나 많은 공간(메모리)가 필요한지를 나타냅니다. 알고리즘을 실행시키기 위해 필요한 공간, 고정 공간과 가변 공간이 있습니다.
시간복잡도(time complexity)란 알고리즘을 실행하여 종료할때까지 필요한 시간 입니다. 공간복잡도(space complexity)란 알고리즘을 실행하여 종료할때까지 필요한 기억창치의 크기 입니다. 시간복잡도는 알고리즘을 구성하는 명령어들이 몇 번이나 실행이 되는지를 센 결과에 각 명령어의 실행시간을 곱한 합계를 의미합니다. 그러나 각 명령어의 실행 시간은 특정 하드웨어 혹은 프로그래밍 언어에 따라서 그 값이 달라질 수 있기 때문에 알고리즘의 일반적인 시간 복잡도는 명령어의 실제 실행시간을 제외한 명령어의 실행 횟수만을 고려하게 됩니다. 공간 복잡도는 알고리즘과 무관한 부분과 밀접한 부분으로 분류 할 수 있는데, 일반적으로 공간 복잡도를 분석할때는 알고리즘과 밀접한 부분 , 알고리즘의 특성과 밀접한 관계가 있는 부분으로써 문제를 해결하기 위해 필요로 하는 공간, 즉, 변수를 저장하기 위한 공간이나 순환 프로그램일 경우 순환스택을 계산하여 공간 복잡도를 계산합니다.
4. 사용자 패스워드를 전송하고 보관하는 방법을 설명해주실 수 있을까요?
클라이언트가 요청 본문인 requestbody로 데이터를 전송하면 Java 에서는 해당 JSON 형식의 데이터를 받기 위해서 JSON 에서 Java Object 로 변환을 거친다.
스프링 MVC 에서 클라이언트에서 전송한 xml 데이터나 json 등 데이터를 컨트롤러에서 DOM 객체나 자바객체로 변환해서 송수신 할 수 있다.
클라이언트와 서버간 의 HTTP 통신에서 요청할 때 필요한 데이터를 body에 담아서 보낸다.
이 때, 요청하는 본문이 requestbody 이며, 이 과정에서 DispatcherServlet 에서 먼저 HttpRequest 의 미디어 타입을 확인하고, 타입에 맞는 MessageConverter 를 통해 요청 본문인 requestbody 를 통째로 변환해서 메서드로 전달해준다.
이후, 서버에서 비밀번호에 대한 정보만을 인코딩해서 비밀번호를 DB 의 유저정보에 등록한다.
정리
클라이언트가 서버에 requestbody 로 요청을 보내고 서버에서는 이를 @Requestbody 를 통해서 문자열을 가져와서 서버에서 지정한 타입으로 바꿔준다.
위 방식대로 유저가 입력한 패스워드를 받아와서 단방향으로 해싱해서 서버에 저장한다.
4번 질문의 다른 사람들의 답변
유저의 패스워드를 받은 클라이언트는 평문으로 서버로 전송합니다. 평문을 받은 서버는 패스워드를 단방향 해시 함수로 암호화하여 보관합니다. 단방향 해시함수는 수학적 연산에 의해 원본 데이터를 완전히 다른 암호화된 데이터(다이제스트)로 변환하는 것을 말합니다. 원본 데이터로는 다이제스트를 구할 수 있지만 다이제스트로는 원본데이터를 구할 수 없어야 합니다. 이것을 단방향이라 합니다. 단방향 해시함수는 브루트포스 공격으로 쉽게 당할 수 있기 때문에 이를 보완하기 위해 입력된 다이제스트를 N번 반복해서 생성하는 것인 key stretching과 원문 패스워드에 임의의 문자열을 추가하여 해싱하는 것인 salting을 이용해 보안의 강도를 높힐 수 있습니다.
사용자의 패스워드는 해시 함수를 사용하여 단방향의 암호화된 데이터로 저장한다. 이 때 레인보우테이블을 이용한 브루트포스 공격을 막기 위하여 SALT와 같은 기법이나, key stretching 통하여 분산도를 높여 예방할 수 있다.
5. 스택, 큐에 대해 설명해주실 수 있을까요?
STACK
Stack 은 "쌓다" 라는 의미로, 데이터를 차곡차곡 쌓아 올린 형태의 자료구조를 말한다.
이는 가장 마지막에 삽입된 자료가 가장 먼저 삭제되는 특이한 구조를 가지고 있다. 스택은 정해진 방향으로만 쌓을 수 있으며, top 으로 정한 곳을 통해서만 접근이 가능하다.
즉, 새로 삽입되는 자료도 top 이 가리키는 가장 맨 위에 쌓이고 삭제시에도 top 을 통해서만 삭제가 가능하다.
스택에서는 삽입 연산을 push, 삭제 연산을 pop 라고 한다. 이러한 구조를 "후입선출" 구조라고 하며 영어로 LIFO(Last In First Out) 이라고 부른다.
주 사용 사례로는 아래와 같다.
- 웹 브라우저의 방문기록(뒤로가기)
- 실행 취소(undo)
Queue
큐는 스택과 다르게 먼저 들어온 것이 먼저 나가는 "선입선출" 로, FIFO(First In First Out) 의 구조를 가지고 있다.
삭제 연산이 수행되는 곳을 프론트, 삽입 연산이 이루어지는 곳은 리어로, FIFO 구조를 위해서 스택과 다르게 큐의 한쪽 끝에는 삽입 작업이, 다른 한쪽 끝에서는 삭제 작업이 나뉘어서 이루어지고 있다.
큐는 리어(rear)에서 이루어지는 삽입 연산을 인큐(Enqueue) 라고 하며 프론트(front)에서 이루어지는 삭제 연산을 디큐(Dequque) 라고 한다.
주 사용 사례로는 아래와 같다.
- 은행 업무
- 서비스 센터의 대기시간
- 대기열 순서와 같은 우선순위의 작업 예약
정리
Stack 은 "쌓다" 라는 의미로, 데이터를 차곡차곡 쌓아 올린 형태의 자료구조를 말하며 이러한 구조를 "후입선출" 구조라고 하며 영어로 LIFO(Last In First Out) 이라고 부른다.
Queue 는 먼저 들어온 것이 먼저 나가는 "선입선출" 로, FIFO(First In First Out) 의 구조를 가지고 있는 자료구조를 말합니다.
+ 아래의 예시에서 말한 shift 가 무엇인지 찾아보니 JS 에서의 디큐를 의미한다고 한다.
5번 질문의 다른 사람들의 답변
스택이란 쌓다라는 의미로, 데이터를 차곡차곡 쌓아 올린 자료구조를 말합니다. 데이터가 순서대로 쌓이고 가장 마지막에 쌓인 자료가 가장 먼저 삭제되는 구조로 LIFO(Last In First Out)입니다. 스택에서 삽입 연산은 push(), 삭제 연산은 pop()입니다. 큐는 스택과 다르게 먼저 들어온 것이 먼저 나가는 선입선출 구조를 가지고 있습니다. 이를 FIFO(First In First Out)라고 합니다. 큐에서 삽입 연산은 push(), 삭제 연산은 shift()입니다.
스택은 LIFO로 후입선출방식입니다. 아래로부터 위로 데이터를 쌓아올리고 위의 데이터부터 꺼내쓰는 방식입니다. 예를 들어 가방에 물건을 넣고 다시 뺄때 나중에 넣은 것 부터 빼게 되는 방식으로 데이터가 출력됩니다. 큐는 FIFO 선입선출 방식으로 일방통행 도로를 예로 들 수 있습니다.
6. DI와 IoC에 대해 아는 만큼 설명해주실 수 있을까요?
DI(Dependency Injection)
스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로, 객체를 직접 생성하는게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다.
DI(의존성 주입) 을 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아진다.
예로 생성자 생성시 외부에서 생성 된 객체를 setter() 를 사용하는 방법으로 하면 이러한 것을 의존성 주입이 이라고 한다.
다만 DI 는 개발자가 직접 코딩을 하여 객체를 생성하는 것이 아니라, 컨테이너가 이를 생성시켜 주는 것이다.
이렇게 되면 코드에서 직접적인 연관 관계가 발생하지 않아 각 클래스들의 변경이 자유로워 진다. 이를 느슨한 결합 이라고한다.
IOC(Inversion of Control)
"제어의 역전" 이라는 의미로, 말 그대로 메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미한다. 간단히 말해 "제어의 흐름을 바꾼다" 라고 한다.
객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 해준다.
일반적인 프로그램의 흐름은 모든 오브젝트가 능동적으로 자신이 사용할 클래스를 생성하고, 사용하고, 폐기하는 과정을 담당하는 것이다. 한 마디로 자신이 사용할 오브젝트의 생명주기를 자기 자신이 담당하는 의미이다.
제어의 역전은 이런 관점이 뒤집어져서, 이제는 더 이상 자신이 사용할 오브젝트의 생명주기를 자기 자신이 담당하지 않는 것을 의미한다.
스프링에서는 스프링이 제어권을 가지고 생성 및 관계를 부여하는 오브젝트를 빈(Bean) 이라고 부르고, 빈들의 생성과 관계설정을 담당하는 IOC 오브젝트를 빈 팩토리 혹은 애플리케이션 컨텍스트라 부릅니다.
스프링이 모든 의존성 객체를 스프링이 실행될 때 다 만들어주고 필요한곳에 주입 시켜줌으로써 Bean들은 싱글톤 패턴의 특징을 가지며, 제어의 흐름을 사용자가 컨트롤 하는 것이 아니라 스프링에게 맡겨 작업을 처리하게 된다.
정리
스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로, 객체를 직접 생성하는게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다.
DI(의존성 주입) 을 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아진다.
"제어의 역전" 이라는 의미로, 말 그대로 메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미한다. 간단히 말해 "제어의 흐름을 바꾼다" 라고 한다.
객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 해준다.
6번 질문의 다른 사람들의 답변
DI란 하나의 객체에서 다른 객체가 필요할 때 객체를 생성하지 않고 이미 생성된 객체를 가져오는 것을 Dependency Injection 의존성 주입이라고 합니다. IoC은 Inversion of Control 제어의 역전이라고 합니다. 개발자 대신 스프링 컨테이너가 Bean을 관리해주는 행위를 말합니다. DI에서 주입되는 객체는 스프링 컨테이너가 관리하는 Bean입니다.
생성자 주입은 생성자의 호출 시점에 1회 호출 되는 것이 보장되기 때문에 주입받은 객체가 변하지 않거나, 반드시 객체의 주입이 필요한 경우 강제로 주입하기 위해 사용할 수 있다는 장점이 있습니다.
Spring Boot는 여러곳 에서 사용되는 특정 객체들(Repository, Controller등 Component에 해당)을 싱글톤(무조건 하나)만 만들어서 사용한다. 이렇게 싱글톤으로만 사용하는 이유는 Thread가 여러개 있을 경우 사용하는 객체들을 Thread갯수 만큼 늘려야 되고 이는 비효율을 초래한다. 그래서 싱글톤으로 단 하나의 객체만 생성하여 Thread 이를 사용하기 위한 방법에서 나온것이 Ioc, DI, Bean이다. IoC는 사용자가 만든 객체의 생성과 관리를 넘겨주는 주체이다. Bean은 IoC의 관리대상이 되는 객체이다. DI는 Bean을 사용하기 위해서 IoC가 객체를 넣어주는 것(주입)이다. DI는 3가지 방법이 있는데 (@Autowire, @RequirConstructor)을 사용하는 어노테이션의 2가지 방법, 생성자를 만들어서 주입해주는 방법이 있고 IoC는 선언된 변수의 자료형을 통해서 이를 구분한다. DI시 주의할점은 라이프 사이클인거 같음