웹 서버를 공부해가며 짧막한 테스트 코드를 작성했을 때 웹 브라우저에 원하는 결과가 나오지 않고 글씨가 깨져 보이는 현상은 누구라도 한 번은 겪어봤을 것이다. 이런 현상은 대부분 이클립스 인코딩 방식이 UTF-8이 아니기 때문에 발생하는 것이다. 서론에서 짧게 인코딩이란 무엇인지, UTF-8은 또 무엇인지 알아보고 설정하는 과정을 보도록 하자. 설정하는 방법만 보고자 한다면 바로 스크롤을 내리자.

 

 Encoding

 데이터를 전송할 때 암호화하는 것을 인코딩이라 한다. 반대로 데이터를 수신한 측에서 암호화 된 데이터를 부호화하는 것을 디코딩이라 한다. 웹 환경에서 송수신의 주체는 웹 브라우저와 웹 서버가 된다.

 

 MS949, EUC-KR, UTF-8

 이클립스에서 웹 개발을 할 때 사용되는 html, jsp등의 텍스트 파일은 기본적으로 "MS949"이라는 문자셋으로 인코딩되되도록 되어 있는데 여기서 MS949는 EUC-KR의 확장된 문자셋이다. EUC-KR은 UTF-8과 더불어 대표적인 한글 문자셋으로 EUC-KR은 문자당 2byte로 한글, 한국에서 통용되는 일부 한자, 영문을 표현할 수 있고, UTF-8은 1~4byte의 가변 길이를 가지며 유니코드를 표현할 수 있다. 유니코드는 전세계 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이다.

 

 UTF-8을 사용하는 이유

 UTF-8은 유니코드를 사용하기 때문에 전 세계 모든 언어와 호환된다는 장점이 있다. 때문에 전 세계에서 UTF-8 방식이 가장 많이 사용되고 있고 유니코드를 지원하는 프로그램의 경우 대부분 UTF-8을 사용한다.

 

 


 

 

 이클립스 UTF-8 환경으로 설정하기

 이후로 진행하는 모든 환경 설정은 [Window - Prefeerences]에서 진행한다.

 

 

 Workspace 인코딩 설정

 좌측 최상단에서 "workspace"를 입력하면 메뉴 중에 문자열이 일치하는 메뉴가 나타나는데 여기서 [General → Workspace]를 선택한다. 그리고 하단의 Text file encoding에서 Other를 선택하고 목록에서 "UTF-8"을 선택한다.

 

 

 Java, JSP 인코딩 설정

 검색창에 "content types"를 검색한다. 목록에서 [General → Content Types]를 클릭한 후 중앙의 박스에서 Java Class File을 클릭한다. 아래의 Default encoding에 "UTF-8"을 입력한 후 Update를 눌러준다.

 

 

 위 화면에서 그대로 진행한다. 중앙 박스에서 Text 카테고리를 펼쳐서 JSP를 찾아서 선택한다. Default Encoding이 "ISO-8859-1"로 되어 있는데 UTF-8로 수정한 뒤 Update를 눌러준다.

 

 

 "HTML"을 검색한 후 [Web → HTML Files]를 클릭한 후 중앙에 보이는 Encoding을 "UTF-8"로 설정한다.

 

 

 위 화면의 검색결과에서 [Web → JSP Files]를 선택한 후 Encoding을 "UTF-8"로 변경한다.

 웹 개발을 시작하려면 웹 어플리케이션 서버(WAS; Web Application Server)가 있어야 하는데 대표적으로 아파치에서 제공하는 Tomcat과 마이크로 소프트에서 제공하는 IIS가 있다. 여기서는 이클립스에서 가장 일반적으로 사용되는 Tomcat을 설치하는 방법에 대해 다룬다. 

 

 

 

 1. 아파치 톰캣 다운받기

https://tomcat.apache.org/download-90.cgi

 

Apache Tomcat® - Apache Tomcat 9 Software Downloads

Welcome to the Apache Tomcat® 9.x software download page. This page provides download links for obtaining the latest version of Tomcat 9.0.x software, as well as links to the archives of older releases. Unsure which version you need? Specification version

tomcat.apache.org

 

 톰캣을 다운받기 위해서 아파치 공식 홈페이지에 접속한다. 

 

 

 위 링크는 9.0으로 연결되는데 9.0 외에 다른 버전을 받아야 한다면 좌측 Download 카테고리에서 원하는 버전을 선택한다.

 

 

 위 링크로 들어오면 다운로드할 수 있는 목록을 볼 수 있는데 현재 컴퓨터 환경에 맞는 것을 선택한다. 필자는 64bit 아키텍쳐이기 때문에 "64-bit Windows zip"를 선택했다.

 

 

 다운받은 파일의 압축을 푼다. 다운로드 폴더나 바탕화면 같이 아무 곳에나 저장하기 보다는 C드라이브에 전용 폴더를 만들고 모아 놓는 것이 관리하기 좋다. 필자는 C 드라이브에  devTools 폴더를 생성해 압축을 풀었다.

 

 

 이클립스에서 톰캣에 대한 설정을 하는데 기존 콘솔 창이 있던 하단을 보면 Servers 탭이 있다. 여기서 "No servers are..."을 클릭한다.

 

 

 만약 servers 탭이 없다면 [Windows → Show View → Servers]를 눌러 Servers 탭을 활성화시킬 수 있다.

 

 

 서버를 생성하려면 서버 타입을 선택해야 하는데 우리는 위에서 Tomcat 9를 다운받았기 때문에 Apache 폴더를 펼쳐 "Tomcat v9.0 Server"를 선택한다. 서버 타입을 선택하면 Next가 활성화 되니 눌러주자.

 

 

 Tomcat installation directory에 아까 알집을 풀어준 경로를 ctrl+c, v 해준다. "Browse.."를 눌러 찾아도 상관없다. 마지막으로 finish를 눌러 서버 생성을 마친다.

 

 

 서버 생성을 마치면 하단 Servers 탭에 서버가 추가된 것을 확인할 수 있고 프로젝트 목록에 Servers가 추가된 것을 확인할 수 있다.

[OS : Windows 10]

[Java : 11]

[Eclipse version : 2021-12(4.22.0)] 

 

목차(바로가기)

1. Java 설치

2. Eclipse 설치

3. Spring Framework 설치

 

 윈도우를 기준으로 스프링을 시작하기 위해 Java, Eclipse, Spring을 설치하는 과정입니다.

 

 

 자바, 이클립스 버전에 주의!

 여기서 굳이 자바와 이클립스 버전을 설명하는 이유는 버전이 잘 맞물리지 않으면 스프링 프로젝트를 생성할 때 "An error has occurred. See error log for more details. java.lang.ExceptionInInitializerError"라는 에러를 보게 되기 때문이다. 이 에러를 해결하기 위해 구글링을 하면 ini 파일을 수정한다던가 이클립스 경로에서 clean을 한다던가하는 해결책이 존재하는데 별 짓을 다해도 해결이 쉽지 않다. 이 에러가 일어나는 이유는 이클립스와 자바 버전이 맞물리지 않기 때문이다.

 

 이클립스는 3개월 주기(3, 6, 9, 12)로 새로운 패키지가 제공되는데, 2020-09 버전부터는 자바 8이 아닌 자바 11을 사용해야 스프링을 사용할 수 있다. 자바 8을 사용해야만 하는 환경이라면 2020-06버전을 다운받아야 스프링 프로젝트를 생성할 수 있다.

 

 

1. Java 설치

 자바를 다운받기 위해 오라클 사이트에 접속한다.

 (바로가기 : https://www.oracle.com/java/technologies/)

 

 해당 링크로 접속하면 바로 최신 버전의 자바를 버전별로 보여준다. 필자는 이클립스 최신버전을 설치할 예정이기 때문에 Java 11 버전인 위에서 2번째 "Java SE 11.0.14(LTS)"을 선택했다.

 

 

 다음 페이지에서 Windows 탭을 선택한 후 첫 번째 인스톨러를 다운받는다. 다운로드를 위해서는 로그인이 필요한데 자바를 비롯해 오라클에서 제공하는 프로그램은 다운받을 때마다 로그인이 필요하니 가입해두자.

 

 (설치는 별거 없기 때문에 생략)

 

 


 

 

2. Eclipse 설치

 이클립스를 다운받기 위해 사이트로 이동한다. 이 글을 보는 시점에서 가장 최신의 Eclipse를 다운받고자 한다면 홈페이지를 경유해 우측 상단의 Download를 선택하면 된다.(첫 번째 링크 선택) 개발 환경에 의해 버전에 제약이 있어 이전 버전으로 다운로드 하고자 할 경우 두 번째 링크로 이동해서 원하는 버전을 찾아서 설치하도록 하자. 이 글은 전자를 기준으로 한다.

 

 (홈페이지 바로가기 : https://www.eclipse.org/)

 (다운로드 페이지 바로가기 : https://www.eclipse.org/downloads/packages/)

 

 

 먼저 홈페이지로 이동한 후 우측 상단의 Download를 클릭한다.

 

 

 이동한 페이지에서 좌측 하단에 있는 Get Eclipse IDE 2021-12의 Download x86_64를 클릭한다.

 

 

 Download를 선택해 설치 툴을 다운받는다.

 

 

 다운받은 툴을 실행시키면 위와 같은 화면이 나온다. 목록에서 위에서 2번째 "Eclipse IDE for Enterprise java..."를 선택한 후 원하는 경로를 잡고 INSTALL을 눌러 설치한다.

 

 


 

 

3. Spring Framework 설치

 이클립스를 실행한 후 상단 리본 메뉴에서 [Help → Eclipse Marketplace..] 선택

 

 

 검색창에 "spring"을 검색하고 "Spring Tools 4.."를 install한 뒤 다음 화면에서 "install More"를 클릭해 메인 화면으로 다시 돌아온다. 메인에서 "Spring Tools 3 Add On For..."를 install하고 하단의 "Install Now"를 선택한다. 

 

 

 위와 같은 화면이 나왔다면 모두 체크된 상태인지 확인하고 "confirm"을 클릭한다.

 

 

 라이센스에 동의하고 finish를 클릭해 설치를 진행한다. 이클립스 하단의 상태표시줄 우측을 보면 다운로드 진행 현황을 볼 수 있다. 다운로드가 끝나면 이클립스를 재시작하라는 메시지 창이 나오니 재시작을 수행해준다.

 

 

 스프링 프로젝트를 생성하기 위해 자바 프로젝트를 생성할 때처럼 [File - New]로 진입해도 Spring이 보이지 않는다. 현재 이클립스 레이아웃이 EE에 맞춰져 있기 때문이다. "Other"를 선택해 Spring을 직접 찾아도 되지만 Spring 프로젝트를 진행하기에 최적화 된 레이아웃을 따로 선택하자.

 

 

 상단 리본 메뉴에서 우측 끝으로 가면 위와 같이 창문모양 아이콘을 볼 수 있다. 이 아이콘을 클릭하면 이클립스의 레이웃을 원하는 개발환경에 맞춰 변경할 수 있다. Spring을 개발할 것이기 때문에 Spring을 선택해준다.

 

 

 스프링을 선택하면 좌측 메뉴에 Spring 프로젝트를 생성하는 메뉴가 생긴 것과 하단에 Servers 탭이 추가된 것을 볼 수 있다. [File → New]로 생성가능한 프로젝트 목록에도 스프링이 추가된 것을 볼 수 있다. 한 번 이상 선택된 Perspectives는 단축키 Ctrl + F8을 통해서 빠르게 전환할 수 있다.

 

 Spring 설치는 끝났다. 이제 Spring 프로젝트를 생성할 수 있지만 서버로서 역할을 하려면 Apache Tomcat이 있어야 한다. 톰캣 설치가 필요하다면 다음 포스팅을 참조하도록 하자.

 

 

[Server] 아파치 톰캣(Apache Tomcat) 9 설치하기

 웹 개발을 시작하려면 웹 어플리케이션 서버(WAS; Web Application Server)가 있어야 하는데 대표적으로 아파치에서 제공하는 Tomcat과 마이크로 소프트에서 제공하는 IIS가 있다. 여기서는 이클립스에

ku-develog.tistory.com

 

 인터넷에서 "맨 아래로"나 "맨 위로"같은 태그를 눌러 페이지 내의 스크롤을 이동하는 방법이다.

 

 텍스트를 하이퍼링크로 만들기 위해 <a href> 태그를 사용하고 해당 텍스트가 클릭되면 특정 텍스트로 이동되게 한다. 대상이 되는 텍스트는 <a> 태그를 사용하여 name 혹은 id 값을 부여하는데 이 속성이 a href가 가리키는 식별자가 된다. 이동 대상은 굳이 텍스트가 아니어도 되고 페이지 최상단이나 하단에 단독으로 삽입하고 식별자를 부여하는 것도 가능하다.

 

 

 1. 글 작성/수정 페이지에서 HTML로 모드 바꾸기

 

 

 2. 이동 대상이 되는 텍스트에 <a> 태그 삽입하기

이동 타겟이 될 텍스트 탐색
a 태그를 삽입

 id 혹은 name 값을 부여할 수 있는데 여기서는 name으로 부여한다. 필자는 "link_search"라는 name을 부여하였는데 이는 페이지 내에서 스크롤을 이동할 때 유일한 식별자가 되어야 하기 때문에 태그 중에 name 속성이 겹치는 일이 없어야 한다.

 

 

 3. 이벤트를 발생시킬 텍스트 탐색

 ctrl+f 눌러서 이벤트를 적용시킬 텍스트 탐색한다.

 

 

 4. <a href> 태그 삽입하여 하이퍼 링크 적용

 

 여기서 a href 의 값은 위에서 적용한 대상 텍스트의 name 속성이다. 앞에 붙는 #은 이 값이 name 속성이라는 것을 나타낸다.

 

 이제 "데이터 검색"이라는 텍스트는 하이퍼링크가 되었다. 클릭할 경우 a href가 가리키고 있는 name 속성의 태그를 찾아 스크를을 이동시키는 것을 볼 수 있다.

VO(Value Object)

  • 객체가 가지는 값들에 대해 오직 읽기만 가능해야 하는 특성을 가지는 클래스다.
  • 모든 멤버 변수에 대한 getter 메서드가 반드시 존재해야 하며 값을 변경할 수 있는 setter 메서드는 생성하면 안된다.
  • 생성자 중 모든 멤버 변수를 파라미터로 가지는 생성자는 반드시 존재해야 한다. 객체를 생성하고 나면 데이터를 초기화할 수 없기 때문이다. 
  • 멤버 변수에 final 키워드를 사용해 변경할 수 없도록 한다.
  • 타 데이터 클래스와 구분하기 위하여 패키지로 분류하거나 접미사로 "*VO.java"를 붙여 구분한다.

 

 

DTO(Data Transfer Object)

  • MVC와 같은 계층 구조에서 계층간 데이터를 주고 받을 때 꼭 필요한 데이터만을 가지는 클래스다.
  • 네트워크를 통해 클라이언트(혹은 서버)의 요청에 따라 DB로부터 읽어온 객체에 불필요한 데이터가 있을 경우 꼭 필요한 데이터만으로 구성된 DTO 클래스로 재구성하여 전송한다.
  • 타 데이터 클래스와 구분하기 위하여 패키지로 분류하거나 접미사로 "*DTO.java"를 붙여 구분한다.

 

 

Entity

  • 데이터베이스에서 데이터를 읽어올 때 테이블의 구조(속성들)와 일치하는 구조를 갖는 클래스.
  • 데이터베이스에서 레코드 단위로 값을 읽어올 때 레코드 1개가 하나의 데이터 클래스로 대응될 수 있어야 한다.
  • 타 데이터 클래스와 구분하기 위하여 패키지로 분류하거나 접미사로 "*Entity.java"를 붙여 구분한다.

 

 

 

 데이터 클래스들은 "*.data.entity"와 같이 전용 패키지를 생성하여 관리하는 것이 좋다. 프로젝트의 규모가 커지면 커질수록 늘어나는 데이터 클래스들의 관리가 어려워진다.

 

 세 클래스는 데이터를 저장하기 위해 설계된 클래스라는 공통점이 있지만 목적에 따라 다른 특성을 가져야 하기 때문에 같은 대상이라도 여러 개의 데이터 클래스를 생성할 수 있다. 예를 들어 사용자가 내가 서비스 중인 어플에서 개인 정보를 열람하려 할 때 스마트폰을 이용 중인 사람이 본인이 아닐 수 있기에 최소한으로만 보여줄 필요가 있다. 다른 사람들에게 보여지는 닉네임, 이름, 아이디 등은 공개할 수 있지만 비밀번호는 노출되어서는 안되고 인증 수단(이메일, 휴대폰 번호) 역시 쉽게 노출되선 안되므로 인증을 거친 뒤 보여주는 것이 일반적이다. 하지만 일반적으로 이러한 데이터들은 모두 하나의 테이블에서 관리되므로 디비에서 데이터를 읽어 오면 모든 데이터가 들어 있다. 이러한 경우 먼저 특정 레코드를 Entity 클래스로 받은 뒤 사용자에게 보낼 데이터는 DTO 클래스로 재구성하여 보내는 것이다.

 들어가며

 사실 단일 연결 리스트는 운좋게 적합한 상황이 나와주지 않는 이상 사용될 일이 잘 없고 데이터 순회가 양방향으로 가능한 이중 연결 리스트가 더 많이 사용된다. 그럼에도 불구하고 단일 연결 리스트는 한 번 쯤 구현해보는 것이 좋다고 생각한다. 자료구조에서 ArrayList, Stack과 같은 배열 기반의 자료구조들을 제외하면 대부분의 자료구조가 노드와 같이 데이터와 참조를 가진 형태로 구현되는데 그러한 자료구조 중 단일 연결 리스트의 구현 난이도가 가장 낮다고 생각하기 때문이다.  

 

 

 연결 리스트(Linked List)

 https://ku-develog.tistory.com/25

 

 

 

 단일 연결 리스트(Singly Linked List)

Singly Linked List

 단일 연결 리스트를 그림으로 표현하면 위와 같다. 가장 기본적이고 심플한 연결 리스트 구조로 head에서 시작해서 노드의 참조가 null일때까지 순방향으로 조회한다.

 

 

 

연결 리스트 구현하기

LinkedList 클래스

public class LinkedList<E> {
	private Node<E> head;
	private int size;
	..
}

 

 연결 리스트의 메인이 되는 클래스다. 여기서 연결 리스트의 동작(Method)을 작성한 뒤 메인 메서드에서 객체화하여 사용할 것이다. 간단하게 멤버 변수를 보면 앞서 설명했던 첫 노드의 참조를 가지는 head가 존재하는 것을 볼 수 있다.

 

 두 번째로 size는 이름과 자료형에서 추측할 수 있듯 리스트에 추가된 요소들의 개수를 의미한다. 연결 리스트의 특성상 데이터의 총 개수를 확인하려면 모든 데이터를 순회하며 카운팅해야 하는 번거로움이 있기 때문에 이렇게 변수를 두고 사용하는 것이 일반적이다. 데이터가 추가/삭제될 때마다 값을 증감시켜서 리스트의 총 개수 정보를 유지할 것이다. 

 

 

Node 클래스

class Node<E> {
	private E element;
	private Node<E> next;
	
	public Node(E element) {
		this.element = element;
	}
	
	public Node(Node<E> next, E element) {
		this.next = next;
		this.element = element;
	}
}

 노드 클래스는 저장될 데이터의 타입을 아직 모르기 때문에 제네릭 타입으로 지정하였다. 간단하게 멤버 변수를 살펴보면 다음 노드에 대한 참조를 저장하는 next 변수, 실질적인 데이터가 저장될 element 변수를 가지고 있다. 예제인 만큼 코드 길이를 줄이기 위해서 이 클래스는 LinkedList의 내부 클래스로 사용한다.

 

 

범위 확인 메서드

private void rangeCheck(int index) {
	if(index >= size || index < 0)
		throw new IndexOutOfBoundsException(
				"Index " + index + " out of bounds for length " + size);
}

 특정 위치의 노드 삭제, 탐색 시에 사용되는 범위 체크 메서드다. 예를 들어 특정 위치의 노드를 삭제하려고 했으나 범위에서 벗어난 값인 경우 예외를 발생시킨다.

 

 이렇게 여러 메서드에서 공통으로 사용되는 코드는 하나의 메서드로 만들어서 공통으로 사용하면 전체 코드의 길이를 줄일 수 있고 코드 수정이 발생해도 이 코드만 수정하면 되는 장점이 있다.

 

 

 

삽입, 삭제, 탐색

삽입

 노드를 삽입은 다음과 같이 여러 가지 방법이 존재하는데 여기서는 간단하게 처음과 끝에 삽입하는 것을 구현해 볼 것이다.

  • 첫 번째에 노드 삽입
  • 특정 위치에 노드를 삽입
  • 마지막에 삽입
  • 리스트를 처음부터 순회하여 특정 데이터를 찾아 삭제

 

 

첫 번째에 노드 삽입

public void addFirst(E e) {
	Node<E> newNode = new Node<>(e);
	newNode.next = head;
	head = newNode;
	size++;
}

 리스트의 처음 위치 즉, head의 앞에 노드를 추가하는 메서드다. 이 작업은 연결 리스트 기초 이론과 객체 참조에 대한 개념만 제대로 잡혀 있다면 굉장히 쉽다. 새로운 Node 객체를 생성하면서 파라미터로 받은 데이터를 초기화한다. 그리고 다음 새로운 노드의 참조를 head 노드로 지정한 뒤 연결 리스트가 가진 head를 새로운 노드로 교체해주면 된다.

 

 

마지막에 삽입

public void addLast(E e) {
	if(size == 0) {
		addFirst(e);
		return;
	}
	
	Node<E> newNode = new Node<>(e);
	Node<E> preNode = head;
	
	while(preNode.next != null)
		preNode = preNode.next;
	
	preNode.next = newNode;
	size++;
}

 리스트의 마지막 위치에 노드를 추가하는 메서드다. 연결 리스트 객체는 첫 번째 노드의 위치만 알고 있기 때문에 마지막 노드에 도달하기 위해서는 모든 노드를 순회해야만 한다.

 

 새롭게 추가될 노드 객체를 초기화하고 순회를 하는데 사용할 참조 변수 preNode에 head의 위치를 초기화한다. preNode는 반복문을 통해 다음 참조(next)가 null인 노드, 즉 마지막 노드를 찾을 때 까지 순회한다. 마지막 노드에 도달했을 때 반복문을 종료하고 마지막 노드의 다음 참조에 새로운 노드를 초기화해주는 것으로 데이터를 연결해줄 수 있다.

 

 

삭제

 노드를 삭제하는 방법은 다음과 같이 여러 가지 방법이 존재하는데 삽입과는 반대로 특정 위치에 노드를 삭제하는 것을 구현해본다.

  • 첫 번째 노드 삭제
  • 특정 위치의 노드를 삭제
  • 마지막 노드 삭제
  • 리스트를 처음부터 순회하며 특정 데이터를 찾아 삭제

 

 

특정 위치의 노드를 삭제

public E remove(int index) {
	rangeCheck(index);
	
	if(index == 0) { // 삭제 대상이 첫 번째 노드일 경우
		Node<E> removeNode = head;
		head = head.next;
		
		E removeElement = removeNode.element;
		removeNode.element = null;
		removeNode.next = null;
			
		if(--size == 0)
			head = null;
		
		return removeElement;
	}
   
	// 삭제 대상이 첫 번째 노드가 아닐 경우
	Node<E> preNode = head;
	
	for(int i = 1; i < index -1; i++)
		preNode = preNode.next;
		
	Node<E> removeNode = preNode.next;
	E removeElement = removeNode.element;
	preNode.next = removeNode.next;
	removeNode.element = null;
	removeNode.next = null;
	
	size--;
	
	return removeElement;
}

 코드를 보면 먼저 범위 체크를 통해 삭제하려는 노드의 위치가 범위를 벗어났는지 체크한다. 범위에 문제가 없을 경우 삭제 위치가 head 즉, 0인지 확인한다. 삭제 위치가 첫 번째 노드라면 작업이 매우 간단해지는데, head의 참조를 다음 노드로 변경시켜주기만 하면 된다. 이 때 삭제하는 노드의 값들을 null로 초기화해주면서 GC에 의해 수거될 수 있도록 한다.

 

 삭제 대상이 head가 아닐 경우 head 노드를 시작으로 삭제 노드 이전까지 순회한다. 그리고 다음 노드(삭제 대상)의 next값을 가져와 현재 노드의 next로 초기화하는 것으로 삭제 노드 앞과 뒤의 노드가 연결하면 특정 위치에 있는 노드를 삭제할 수 있다.

 

 특정 위치의 데이터를 삭제하는 방식의 핵심은 삭제 노드 이전까지만 리스트를 순회를 하는 것이다. 이렇게 하는 이유는 각 노드가 다음 노드에 대한 참조만 알고 있고 이전 노드의 존재는 모르고 있기 때문에 특정 노드를 삭제하고 앞뒤의 노드를 연결해주는 작업을 하기 위해서는 삭제 바로 앞의 노드가 필요한 것이다.

 

 마무리 작업으로 size 변수를 감소시켜 리스트의 총 요소 수를 유지해준다. 또한 삭제 대상이 된 노드 객체가 GC에 수거될 수 있도록 참조를 없애준다.

 

 실제로 노드 삭제를 구현할 땐 좀 더 세세하게 메서드를 나누지만 여러 메서드를 나누어서 구현한 걸 올리려다 보니 글이 너무 길어지고 설명할 것이 많아져 하나로 통합했다.

 

 

탐색

public E get(int index) {
	rangeCheck(index);
	Node<E> node = head;
	for(int i = 0; i < index; i++)
		node = node.next;
	return node.element;
}

 삭제 파트에서 노드 순회를 구현해봤으니 탐색은 간단하게 구현할 수 있을 것이다. 특정 위치의 노드까지 순회하고 노드가 가진 데이터를 반환한다.

 

 


 

 

이중 연결 리스트보다 단일 연결 리스트가 더 나은 경우

 포스팅을 하면서 단일 연결 리스트가 나은 경우가 과연 얼마나 있을까하는 생각이 들었다. 이중 연결 리스트의 구현 난이도는 단일 연결 리스트보다 상대적으로  복잡할 뿐 사실 둘 다 굉장히 쉬운 편에 속한다. 구현 난이도는 비슷하지만 장단점을 생각해봤을 때 이중 연결리스트가 훨씬 나은데, 과연 단일 연결 리스트를 사용할 상황이 있을까?

 

 개인적으로 생각해봐도 구현 난이도가 상대적으로 낮아진다는 것 외에는 딱히 떠오르는 것이 없어서 Stackflow를 뒤져봤다.

  • 데이터 탐색이 반드시 순방향으로만 이루어질 경우
  • 사용할 수 있는 메모리의 양이 극한으로 제한적인 경우
  • 기초에 충실한 기본적인 스택을 구현할 경우

연결 리스트(Linked List)

 연결 리스트는 노드(Node)라는 단위로 데이터를 저장하는 자료구조다. 배열처럼 일정한 크기의 메모리 공간을 할당받아 데이터를 저장하는 것이 아닌 노드(데이터)가 삽입될 때마다 메모리를 할당받고 마지막 노드가 새로운 노드에 대한 참조를 저장해 나가는 방식으로 리스트를 구성한다.

 

 

 노드(Node)

 연결 리스트는 데이터를 저장할 때 노드(Node) 단위로 데이터를 저장하는데 노드는 데이터와 또 다른 노드가 있는 곳을 가리키는 참조 변수로 구성된다. 여기서 참조 변수는 리스트의 구현 방식에 따라 1개가 될 수도 있고 2개 이상이 될 수도 있다. 

 

Node Class of Singly Linked List

 Node를 클래스로 표현하면 위와 같다. 이 노드는 단일 연결 리스트의 노드 클래스로 연결 리스트 중에서는 가장 최소화된 구조라고 할 수 있다.(element가 데이터, next가 link) 노드 클래스는 연결 리스트의 종류에 따라 링크의 개수, 변수 이름(역할에 따른 직관성)정도가 달라질 뿐 같은 형태를 취하고 있다.

 

 

 

배열과 연결 리스트

배열, 연결 리스트가 할당된 메모리 상태

 

 Array

 배열은 초기화할 때 할당받은 크기로 메모리 길이가 고정되며, 할당받은 공간만큼의 메모리만 사용할 수 있다는 특징이 있다. 이러한 특징은 저장할 데이터의 개수가 정해져 있을 때에는 메모리 공간 낭비가 없어 효율적이지만 저장할 데이터의 개수를 모르는 상황에서는 다르다. 배열의 특성상 메모리 공간을 할당받으려면 크기가 명확하게 정해져야 하기 때문에 저장될 데이터 수를 정확히 모르더라도 예상해서 공간을 할당받을 수밖에 없다. 이 과정에서 너무 많은 공간을 할당받는 것은 메모리 누수를 초래할 수 있고 반대로 너무 적은 공간을 할당받으면 오버 플로우를 초래하게 된다.

 

 

 Array, LinkedList

 반면 연결 리스트는 배열에 비해 공간 확장의 제한을 받지 않는다. 왜냐하면 배열처럼 메모리 공간을 미리 할당받지 않고 데이터가 삽입될 때마다 메모리를 할당받은 뒤 노드끼리는 참조로 연결되기 때문이다. 이러한 구조는 고정적인 크기의 배열과 달리 메모리 공간이 허용하는 한 계속해서 노드를 늘려갈 수 있다.

 

 

 

연결 리스트 노드 삽입

 연결 리스트를 조작하는 방법 중 삽입에 대해서만 알아보자. 

단일 연결 리스트 데이터 삽입 과정

 연결 리스트 객체는 Node 타입의 head라는 멤버 변수를 갖는다. head는 null로 초기화되어 있다가 처음으로 노드가 추가될 때 비로소 초기화되며 이 때 link는 null 값을 가진다. link는 다른 노드의 참조값이나 null 중 하나가 초기화되는데 참조값을 가진 경우 연결된 노드가 있다고 보고 null일 경우 해당 노드가 마지막이라고 본다. 따라서 첫 노드가 head에 초기화되었고 link에 null을 초기화하고 있다는 것은 이 노드가 처음이자 마지막이라는 뜻이 된다.

 

 두 번째 노드부터는 연결 리스트의 끝, 즉 마지막 노드의 뒤에 노드를 추가한다. 마지막 노드를 찾기 위해선 연결 리스트의 head 노드가 가진 링크를 시작으로 노드를 계속해서 참조해나가나다 link가 null인 노드를 찾으면 순회를 멈추고 다시 노드를 연결한다.

 

 

 

연결 리스트 종류

  연결 리스트는 크게 3가지 종류가 있다. 

  • 단일 연결 리스트(Singly Linked List)
  • 이중 연결 리스트(Doubly Linked List)
  • 원형 연결 리스트(Circular Linked List)

 이 외에 청크 연결 리스트(Chunked Linked List), 다중 연결 리스트(Multi Linked List) 등이 있다.

 

 

단일 연결 리스트(Singly Linked List)

Singly Linked List

 단순 연결 리스트라고도 하며 연결 리스트들 중에서 가장 기본이 되는 연결 리스트로, 각 노드는 다음 노드에 대한 정보만을 가지고 노드끼리는 서로 인식하지 못하는 기본적인 구조를 띄고 있다. 구현 난이도는 ArrayList보다 약간 어려운 정도다. 

 

 

 이중 연결 리스트(Doubly Linked List)

Doubly Linked List

 양방향 연결 리스트라고도 하며 단일 연결 리스트와 다르게 이전 노드의 링크를 저장하고 있다는 것이 특징이다. 노드는 자신의 앞뒤 노드를 인식하고 있기 때문에 노드를 순회하다가 앞으로 돌아갈 일이 생기면 링크를 따라서 이전 노드로 돌아갈 수 있다. 노드를 연결/삭제할 때마다 이전 노드까지 신경 써줘야 하지만 단일 연결 리스트를 구현할 수 있다면 이 또한 어렵지 않게 구현할 수 있다.

 

 

 원형 연결 리스트(Circular Linked List)

Circular Linked List

 환형 연결 리스트라고도 하며 특징은 그럼처럼 리스트의 마지막 노드가 null이 아닌 첫 번째 노드를 가리키고 있다는 점이다. 앞서 나온 두 리스트와는 달리 노드의 끝에 도달해도 끝나지 않고 head로 돌아가는 것이 특징이다.

 

 

 

연결 리스트의 head, tail

 연결 리스트의 끝을 가리키는 tail

 연결 리스트의 처음 위치에 데이터를 삽입한다고 생각해보자. 첫 번째 노드 즉, head의 앞에 삽입하면 되는 매우 간단한 작업이다. 새로운 노드의 다음 링크를 head로 지정한 뒤 연결 리스트가 가리키는 head의 위치를 새로운 노드로 변경해 주면 된다.

 

 반대로 연결 리스트의 끝에 노드를 삽입한다고 생각해보자. 데이터를 삽입하려면 마지막 노드를 알아야만 한다. 새로 생성한 노드를 마지막 노드로 만들기 위해서는 현재 마지막 노드의 link를 수정해야 하기 때문이다. 마지막 노드를 찾는 것은 매우 간단하다. 그냥 head부터 연결된 모든 노드를 거치면 된다. 구현도, 이론도 매우 간단하지만 데이터가 많을 수록 거쳐야 하는 노드가 늘어나는 문제가 있다. 

 

 여기서 tail이 등장한다. 말 그대로 꼬리, 연결 리스트가 헤드와 더불어 마지막 노드의 참조를 가지는 것이다. 첫 번째 노드(head)와 더불어 마지막 노드(tail)에 대한 참조를 가지고 있으면 리스트 양 끝에 한 번에 접근이 가능하다.

 

 tail을 무조건 구현해야 하는 것은 아니다. 상황에 따라 head만 필요한 경우도 있기 때문에 상황에 맞게 구현하면 된다.

 ArrayList는 배열을 사용하는 스택, 큐와 더불어 가장 구현하기 쉬운 자료구조가 아닐까 생각헌다. 배열과 메모리에 대한 이해가 어느정도 되어 있다면 그리 어렵지 않을 것이라 생각하고, 추가적으로 인터페이스와 제네릭, 오버로딩에 대한 지식이 필요하다.

 

 iterator는 구현하지 않았기 때문에 컬렉션에서 제공하는 ArrayList처럼 for each로 순회하거나 iterator()를 호출할 수 없다. 반복자까지 구현하면 포스팅이 너무 커져서 생략했다.

 

 구현하면서 멤버 변수, 메서드의 이름, 파라미터 이름 등은 ArrayList API Documentation를 보고 작성했다. 또한 배열 인덱스 범위를 확인하는데 있어 ArrayList 클래스에서 사용하는 방법이 너무 마음에 들어 나도 적용해보았다. 내가 작성한 코드는 add, remove 등 배열에 접근해야 하는 메서드를 작성할 때 메서드 초기에 계속 범위 체크를 해주고 있었다. 그런데 ArrayList 클래스는 "rangeCheck.." 같은 범위 체크 메서드를 만들어놓고 단 한 줄로 호출하여 검사하니 코드가 눈에 띄게 줄어들었고 중복이 상당히 줄어들어 있었다.

 

 ArrayList를 다루는 메서드들(add, remove, etc)에 대한 구현은 그다지 어렵지 않았다. 하지만 구현하고 난 뒤 ArrayList에서 범위 체크 메서드를 따라하니 거의 같은 코드가 되어 버린 것 같다.

 

 목차 겸 바로가기

 메서드 요약

 멤버 변수

 생성자

 데이터 삽입

 데이터 삭제

 데이터 검색

 데이터 수정

 배열(메모리) 확장 및 축소

 

 

 

 

 아래 "전체 코드 보기"를 누르면 전체 코드를 볼 수 있다.

 

더보기
public class ArrayList<E> {
	private int size;
	private final int DEFAULT_CAPACITY = 10;
	
	private final Object[] EMPTY_ELEMENT_DATA = {};
	private Object[] elementData;
	
	public ArrayList() {
		elementData = EMPTY_ELEMENT_DATA;
	}
	
	public ArrayList(int initialCapacity) {
		if(initialCapacity > 0)
			elementData = new Object[initialCapacity];
		else if(initialCapacity == 0)
			elementData = EMPTY_ELEMENT_DATA;
		else if (initialCapacity < 0)
			throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
	}
	
	private void rangeCheckForAdd(int index) {
		if(index > size || index < 0)
			throw new IndexOutOfBoundsException(
					"Index " + index + " out of bounds for length " + size);
	}
	
	private void rangeCheck(int index) {
		if(index >= size || index < 0)
			throw new IndexOutOfBoundsException(
					"Index " + index + " out of bounds for length " + size);
	}
	
	private void reallocate() {
		if(elementData == EMPTY_ELEMENT_DATA) {
			elementData = new Object[DEFAULT_CAPACITY];
			return;
		}
		
		int capacity = elementData.length + (elementData.length >> 1);
		reallocate(capacity);
	}
	
	private void reallocate(int capacity) {
		Object[] newArray = new Object[capacity];
		
		for(int i = 0; i < size; i++) {
			newArray[i] = elementData[i];
			elementData[i] = null;
		}
		
		elementData = newArray;
	}
	
	public void ensureCapacity(int capacity) {
		if(capacity > elementData.length
				&& capacity >= DEFAULT_CAPACITY)
			reallocate(capacity);
	}
	
	public void trimToSize() {
		if(size < elementData.length) {
			if(size == 0)
				elementData = EMPTY_ELEMENT_DATA;
			else
				reallocate(size);
		}
	}
	
	public boolean add(E value) {
		add(size, value);
		return true;
	}

	public void add(int index, E e) {
		rangeCheckForAdd(index);
		if(elementData.length == size)
			reallocate();
		
		if(index == size) { 
			elementData[size] = e;
		} else {
			for(int i = index; i < size; i++)
				elementData[i+1] = elementData[i];
			elementData[index] = e;
		}

		size++;
	}

	@SuppressWarnings("unchecked")
	public E remove(int index) {
		rangeCheck(index);
		
		E removeElement = (E)elementData[index];
		
		if(index != size-1)
			for(int i = index; i < size-1; i++)
				elementData[i] = elementData[i+1];
		
		elementData[--size] = null;
		return removeElement;
	}

	public boolean remove(Object o) {
		int idx = indexOf(o);
		
		if(idx != -1) {
			for(int k = idx; k < size-1; k++)
				elementData[k] = elementData[k+1];
			elementData[--size] = null;
			return true;
		}
		
		return false;
	}
	
	@SuppressWarnings("unchecked")
	public E get(int index) {
		rangeCheck(index);
		return (E)elementData[index];
	}

	@SuppressWarnings("unchecked")
	public E set(int index, E e) {
		rangeCheck(index);
		
		Object oldElement = elementData[index];
		elementData[index] = e;
		return (E)oldElement;
	}

	public boolean contains(Object o) {
		return indexOf(o) == -1 ? true : false;
	}

	public int indexOf(Object o) {
		for(int i = 0; i < size; i++)
			if(elementData[i].equals(o))
				return i;
		
		return -1;
	}
	
	public int size() {
		return size;
	}

	public boolean isEmpty() {
		return size == 0;
	}

	public void clear() {
		for(int i = 0; i < size; i++)
			elementData[i] = null;
		size = 0;
	}
}

 

 


 

Method Summary (public)

Method Description
boolean add(E)  리스트 끝에 데이터 삽입
void add(int, E)  리스트 index 위치에 전달받은 요소 추가
E remove(int)  리스트 index 위치에 존재하는 요소 삭제하고 삭제된 요소 반환
boolean remove(Object)  리스트에서 특정 요소를 0부터 탐색하여 처음 발견하는 요소를 삭제
E get(int)  리스트 index 위치에 있는 요소를 반환
E set(int, E)  리스트 index 위치에 있는 요소를 수정하고 수정하기 전의 값을 반환
void ensureCapacity(int)
 배열의 크기를 capacity로 축소 혹은 확장
void trimToSize()  배열의 크기를 현재 리스트가 가진 요소의 수에 맞게 축소
boolean contains(Object)  리스트에서 특정 요소에 대한 존재여부를 반환
int indexOf(Object);  리스트에서 특정 요소가 존재하는 index를 반환
int size();  리스트가 가진 요소 수를 반환
boolean isEmpty()  리스트가 비었는지에 대한 여부를 반환
void clear()  리스트를 초기화

 

Method Summary (private)

Method Description
void rangeCheckForAdd(int) 데이터 삽입에 있어서의 index에 대한 유효 범위 확인
void rangeCheck(int) 데이터 삽입을 제외하고 index를 사용하는 상황에서의 유효 범위 확인
reallocate() 배열의 크기를 일정한 배율(1.5)로 확장
reallocate(int) 배열의 크기를 capacity에 따라 축소 혹은 확장

 

 


 

 

Variable

private int size;
private final int DEFAULT_CAPACITY = 10;
	
private final Object[] EMPTY_ELEMENT_DATA = {};
private Object[] elementData;
  • size : 현재 배열에 담긴 요소의 개수를 카운팅하는 변수.
  • DEFAULT_CAPACITY : 사용자가 배열 크기를 지정해주지 않았을 경우의 배열 크기 기본 값.
  • EMPTY_ELEMENT_DATA : 사용자가 배열 크기를 할당하지 않았을 경우 elementData가 가리키게 되는 빈 배열로, length가 0인 것과는 달리 할당되지 않은 배열이라는 의미.
  • elementData : 실제 데이터가 저장될 Object 타입 배열.(모든 클래스는 Object 클래스를 상속받기 때문에 타입이 Object인 배열은 어떠한 데이터도 담을 수 있음을 의미한다.)

 

 

 생성자

public ArrayList() {
	elementData = EMPTY_ELEMENT_DATA;
}
	
public ArrayList(int initialCapacity) {
	if(initialCapacity > 0)
		elementData = new Object[initialCapacity];
	else if(initialCapacity == 0)
		elementData = EMPTY_ELEMENT_DATA;
	else if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}

 

 파라미터가 없는 생성자와 정수형 변수 1개를 파라미터로 받는 생성자가 있다. 파라미터가 없는 생성자를 호출할 경우 elementData는 크기가 0인 EMPTY_ELEMENT_DATA를 가리키게 되는데, 이는 아직 메모리가 할당된 적이 없다는 것을 나타낸다. 

 

 파라미터가 1개인 생성자는 파라미터로 배열의 크기를 정할 수 있다. 받는 값이 정수이기 때문에 음수, 0, 정수 등이 입력될 수 있는데 배열을 할당받을 때 음수를 입력할 경우 예외를 발생시키고 0을 입력할 경우 의미없는 코드가 된다. 때문에 0을 입력할 경우 파라미터가 없는 생성자를 호출하였을 때와 같은 동작을 하게 하고 음수를 입력할 경우 인위적으로 예외를 발생시킨다. 파라미터로 1 이상의 값이 들어올 경우 비로서 정상적으로 배열을 할당한다.

 

 

 데이터 삽입

public boolean add(E value) {
	add(size, value);
	return true;
}

public void add(int index, E e) {
	rangeCheckForAdd(index);
	if(elementData.length == size)
		reallocate();
	
	if(index == size) { 
		elementData[size] = e;
	} else {
		for(int i = index; i < size; i++)
			elementData[i+1] = elementData[i];
		elementData[index] = e;
	}
	size++;
}

 

 데이터를 삽입할 때 호출되는 메서드들이다. 데이터만 삽입하는 메서드 add(E)와 특정 위치를 지정하여 데이터를 삽입하는 메서드 add(int, E)가 있다. 데이터만 삽입하는 경우 elementData의 마지막 위치인 index에 삽입되고, 인덱스를 지정하여 삽입하는 경우 해당 위치에 있는 데이터부터 마지막에 있는 데이터까지 모든 데이터를 한칸 씩 뒤로 미루고 해당 위치에 삽입한다.

 

 단 데이터를 삽입하기 전에 index가 정상 범주에 속하는지 rangeCheckForAdd 메서드를 통해 체크한다. 문제가 없다면 배열의 현재 크기와 데이터 수를 비교하여 데이터가 다 찼을 경우 배열의 크기를 재할당한다. 

 

 

 데이터 삭제

@SuppressWarnings("unchecked")
public E remove(int index) {
	rangeCheck(index);
	
	E removeElement = (E)elementData[index];
	
	if(index != size-1)
		for(int i = index; i < size-1; i++)
			elementData[i] = elementData[i+1];
	
	elementData[--size] = null;
	return removeElement;
}

public boolean remove(Object o) {
	int idx = indexOf(o);
	
	if(idx != -1) {
		for(int k = idx; k < size-1; k++)
			elementData[k] = elementData[k+1];
		elementData[--size] = null;
		return true;
	}
	
	return false;
}

 

 데이터를 삭제할 때 호출되는 메서드들이다. 특정 위치에 있는 데이터를 삭제하는 메서드 remove(int)와 특정 데이터를 삭제하는 메서드 remove(Object)가 있다. remove(int)는 먼저 인덱스가 정삼 범주 내인지 확인한 후 정상 범주라면 해당 위치에 있는 데이터를 반환하기 위해 미리 저장해둔다. 삭제 위치가 마지막이 아닐 경우 반복문을 구성하여 삭제할 위치의 데이터를 기준으로 뒤의 데이터를 하나씩 앞으로 당기는 작업을 수행한다. 마지막으로 배열의 마지막 요소에 null을 저장하고 이전에 저장해둔 값을 리턴한다.

 

 remove(Object)는 내부의 indexOf(Object) 메서드를 호출하여 파라미터로 들어온 값이 elementData에 있는지 체크한다. 존재할 경우 0이상의 값을, 존재하지 않을 경우 -1을 리턴한다. -1이 아닐 경우 루프를 구성하여 삭제할 위치의 데이터를 기준으로 뒤의 데이터를 하나씩 앞으로 당기는 작업을 수행하고 elementData의 마지막에 null을 저장한 후 true를 반환한다. 반대로 -1일 경우 false를 반환한다.

 

 

 데이터 검색

@SuppressWarnings("unchecked")
public E get(int index) {
	rangeCheck(index);
	return (E)elementData[index];
}
    
public boolean contains(Object o) {
	return indexOf(o) == -1 ? true : false;
}

public int indexOf(Object o) {
	for(int i = 0; i < size; i++)
		if(elementData[i].equals(o))
			return i;
	
	return -1;
}

 

 주로 특정 데이터를 찾는데 쓰이는 메서드들이다. get(int)는 파라미터로 받은 index로 elementData를 참조하여 데이터를 반환한다.

 

 contains(Object)는 파라미터로 받은 값이 elementData에 존재하는지 탐색하여 존재하는지 존재하지 않는지에 대한 판단만 하여 boolean 값으로 반환한다.

 

  indexOf(Object)는 파라미터로 받은 값이 elementData에 존재하는지 탐색하여 그 위치를 정수형으로 반환한다. 존재한다면 0이상의 값을, 존재하지 않을 경우 -1을 반환한다.

 

 

 데이터 수정

@SuppressWarnings("unchecked")
public E set(int index, E e) {
	rangeCheck(index);
	
	Object oldElement = elementData[index];
	elementData[index] = e;
	return (E)oldElement;
}

 

 데이터를 수정하는 메서드로 특정 인덱스를 지정하여 해당 위치에 있는 값을 수정할 수 있다. 먼저 수정될 위치에 있는 값을 복사한 후 elementData에 있는 값을 수정한다. 마지막으로 처음에 복사해둔 값을 반환한다.

 

 

 내부에서의 배열 관리

private void reallocate() {
	if(elementData == EMPTY_ELEMENT_DATA) {
		elementData = new Object[DEFAULT_CAPACITY];
		return;
	}
	
	int capacity = elementData.length + (elementData.length >> 1);
	reallocate(capacity);
}

private void reallocate(int capacity) {
	Object[] newArray = new Object[capacity];
	
	for(int i = 0; i < size; i++) {
		newArray[i] = elementData[i];
		elementData[i] = null;
	}
	
	elementData = newArray;
}

 

 ArrayList의 핵심은 유동적인 배열의 크기라고 할 수 있다. 파라미터가 없는 메서드가 호출되면 elementData가 할당되어있는 상태인지, 아닌지를 판단한 후 할당되어 있지 않다면 기본값인 10으로 배열을 할당받는다. 이에 해당하지 않는 경우는 현재 배열이 "element.length == size"인 경우 뿐이다. 따라서 배열의 크기를 증가시켜줘야 하는데 이때 새 배열의 크기를 현재 배열 크기에서 1.5배 곱한 크기로 결정하였다. 이 메서드에서는 오로지 생성할 배열의 크기만 결정하고 그 크기를 아래 메서드로 넘겨 실제로 배열 크기를 증설한다.

 

 파라미터가 있는 메서드의 경우 파라미터에 들어온 값으로 배열 크기를 증설한다. 파라미터로 들어온 값으로 배열을 생성하고 기존 배열에 있는 값을 복사한다. 마지막으로 elementData가 가리키는 주소를 새로 만든 배열의 주소로 변경해준다.

 

 

 외부에서의 배열 관리

public void ensureCapacity(int capacity) {
	if(capacity > elementData.length
			&& capacity >= DEFAULT_CAPACITY)
		reallocate(capacity);
}

public void trimToSize() {
	if(size < elementData.length) {
		if(size == 0)
			elementData = EMPTY_ELEMENT_DATA;
		else
			reallocate(size);
	}
}

 

 ensureCapacity 메서드는 파라미터로 들어온 값이 elementData의 크기보다 크면서 DEFAULT_CAPACITY보다 클 경우 파라미터 크기로 배열을 재할당한다. 해당 메서드는 접근제한자가 private인 reallocate와는 달리 public이다. 즉 외부에서 원하는 타이밍에 호출할 수 있는 배열 재할당 메서드라는 뜻이다.

 

 데이터가 계속 삽입되면 증설이 필요하지만 반대로 데이터를 계속 삭제하면 남는 공간이 점점 늘어나게 되는데 이는 메모리 공간 낭비로 이어지게 된다. reallocate가 내부에서 증설을 담당했다면 trimToSize 메서드는 elementData의 크기를 현재 데이터 개수인 size로 알맞게 재할당하는 메서드라고 볼 수 있다. 이 메서드 또한 접근 제한자가 public인 만큼 외부에서 호출가능하다.

+ Recent posts