https://www.inflearn.com/course/아파치-카프카-입문/dashboard


🪼 카프카란 무엇인가

카프카 이전

  • 데이터를 전송하는 소스 애플리케이션과 데이터를 받는 타겟 애플리케이션
  • 처음에는 단방향 통신
  • 시간이 지나면서 소스 애플리케이션과 타겟 애플리케이션이 많아지면서 데이터를 조성하는 라인이 매우 복잡해짐
  • 소스 애플리케이션과 타겟 애플리케이션이 많아질수록 데이터 라인도 많아짐 -> 배포와 장애에 대응 어려움
  • 데이터를 전송할 때 프로토콜 포맷의 파편화 심해짐 -> 유지보수 매우 어려워짐

kafka 탄생

  • kafka: 이러한 복잡함 해결하기 위한 오픈소스 프로그램
  • 소스 애플리케이션과 타겟 애플리케이션의 커플링을 약하게 하기 위해 개발됨
  • 소스 애플리케이션 -> kafka -> 타겟 애플리케이션
스크린샷 2023-04-14 오전 7 31 40
  • 소스 애플리케이션에서 보낼 수 있는 데이터 포맷은 거의 제한이 없음
  • 카프카의 Topic
    • 각종 데이터를 담음
    • Queue
    • 큐에 데이터를 넣는 Producer
    • 큐에서 데이터를 가져가는 Consumer
  • Producer와 Consumer는 라이브러리로 되어 있어 애플리케이션에서 구현 가능

  • 고가용성 -> 데이터 손실 없이 복구 가능
  • 낮은 지연과 높은 처리량을 통해 효과적으로 빅데이터 처리 가능

🪼 카프카 토픽

  • 토픽: 데이터가 들어가는 공간

  • 여러개 생성 가능

  • DB의 테이블이나 파일시스템의 폴더와 유사한 성질


    스크린샷 2023-04-14 오전 8 14 53
  • 토픽에 프로듀서가 데이터를 넣고, 컨슈머가 데이터 가져감

  • 목적에 따라 이름을 가질 수 있음

    • 클릭로그, send sms, location log 등과 같이 무슨 데이터를 담는지 명확하게 명시

카프카 토픽 내부

  • 하나의 토픽은 여러 개의 파티션으로 구성
  • 파티션 번호는 0번부터 시작
  • 하나의 파티션은 Queue와 같이 데이터가 파티션 끝부터 쌓임

파티션 1개

스크린샷 2023-04-14 오전 8 17 44
  • 컨슈머는 0번째부터 데이터 가져감
  • 컨슈머가 데이터를 가져가더라도 데이터는 삭제되지 않음
  • 파티션에 남은 데이터는 새로운 컨슈머가 붙었을 때 0번부터 또 가져갈 수 있음
    • 컨슈머 그룹이 달라야 함
    • auto.offset.reset = earlieat로 설정
    • 동일 데이터를 두 번 처리할 수 있음

파티션 2개

스크린샷 2023-04-14 오전 8 21 26
  • 데이터가 들어갈 경우 들어갈 파티션 key를 지정할 수 있음
    • key가 null인 경우 라운드 로빈으로 할당
    • key가 있고, 기본 파티셔너를 사용할 경우 키의 해시값을 구하고, 특정 파티션에 할당

  • 파티션을 늘리는건 가능하지만, 다시 줄일 수는 없기 때문에 주의해서 늘려야 함!
  • 늘리는 이유: 파티션을 늘리면 컨슈머의 개수를 늘려서 데이터 처리를 분산시킬 수 있음

  • 데이터 삭제되는 시간은 옵션에 따라 다름
  • 레코드가 저장되는 최대 시간과 크기 지정 가능
    • log.retention.ms: 최대 레코드 보존 시간
    • log.retention.byte: 최대 레코드 보존 크기(byte)

https://www.acmicpc.net/problem/1085

 

1085번: 직사각형에서 탈출

한수는 지금 (x, y)에 있다. 직사각형은 각 변이 좌표축에 평행하고, 왼쪽 아래 꼭짓점은 (0, 0), 오른쪽 위 꼭짓점은 (w, h)에 있다. 직사각형의 경계선까지 가는 거리의 최솟값을 구하는 프로그램

www.acmicpc.net

import java.util.Scanner;

public class J1085 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int x = sc.nextInt();
        int y = sc.nextInt();
        int w = sc.nextInt();
        int h = sc.nextInt();

        int left = x;
        int right = w-x;
        int up = h-y;
        int down = y;

        int[] arr = {left, right, up, down};
        int min = arr[0];
        for(int num : arr) {
            if(num < min) {
                min = num;
            }
        }
        System.out.print(min);
    }
}

 

- Scanner 객체로 현재 위치(x, y)와 오른쪽 끝 점(w, h)을 입력받음

- 직사각형의 테두리까지의 거리를 각각 left, right, up, down으로 저장

- 각 거리를 배열에 넣고, 반복문을 통해 최소값을 min으로 저장 후 출력

'Software > JAVA' 카테고리의 다른 글

[JAVA] Day14. 멀티 스레딩  (2) 2023.01.18
[JAVA] Day13. 파일 입출력  (0) 2023.01.18
[JAVA] Day12. 제네릭과 컬렉션  (0) 2023.01.11
[JAVA] Day11. 자바 그래픽  (5) 2023.01.10
[JAVA] Day10. 스윙 컴포넌트  (0) 2023.01.09

1. 회귀 분석

- 독립변수 x에 대응하는 종속변수 y와 가장 유사한 값을 갖는 함수 f(x)를 찾는 과정

→ f(x)를 통해 미래 사건 예측

    ^y = f(x) ≈ y

 

- 회귀 분석을 통해 구한 함수 f(x)가 선형 함수일 때 f(x) = 회귀 직선

- 선형 회귀 분석

  - 특성과 타겟 사이의 관계를 잘 나타내는 선형 회귀 모형을 찾고, 이들의 상관관계는 가중치/계수(m), 편향(b)에 저장됨

  => ^y = w * x + b

 

2. 비용 함수 = 손실 함수

- 선형 모델의 예측과 훈련 데이터 사이의 거리를 재는 함수

- 비용 함수의 결과값이 작을수록 선형 모델의 예측이 정확함

 

- 선형 회귀는 선형 모델이라는 가설을 세우는 방식이므로, 실제 데이터(훈련 데이터)와 선형 모델의 예측 사이에 차이 존재

- 실제 데이터와 선형 모델의 예측 사이의 차이를 평가하는 함수 → 비용 함수를 사용하여 정확도 계산

- MSE 가장 많이 사용 [실제값과 예측값의 차이인 오차들의 제곱의 평균]

 

3. 선형 회귀 구현

1) 선형 회귀 모델 구현

# 훈련 세트, 테스트 세트 생성
from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = train_test_split(perch_length, perch_weight, random_stae = 42)
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)

# 50cm 농어 평균 무게 예측
from sklearn.linear_model import LinearRegression

lr = LinearRegression()
lr.fit(train_input, train_target)
print(lr.predict([50]))

# 1241.83860323

 

2) 회귀 확인

# 회귀 직선 ^y = W * x + b 구하기
print(lr.coef_, lr.intercept_)

# [39.01714496], -709.0186449535477
# => y = lr.coef_ * x + lr.intercept_

plt.plot([15, 50], [15*lr.coef_+lr.intercept_, 50*lr.coef_+lr.intercept_])

 

3) 훈련 세트 산점도 그리기

plt.plot([15, 50], [15*lr.coef_ + lr.intercept_, 50*lr.coef_ + lr.intercept_])

plt.scatter(train_input, train_target)

plt.scatter(50, 1241, 8, marker = '^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

- 회귀 직선이 어느 정도는 데이터를 잘 나타내고 있음

- 그러나, 훈련 세트와 테스트 세트 모두 결정 계수가 너무 낮음 → 과소 적합

 + 산점도에서 매우 작은 길이를 가진 농어의 경우 회귀 직선이 데이터를 설명하지 못함

 

4. 선형 회귀 모델의 최적화 → 경사 하강법

- 머신러닝, 딥러닝 알고리즘을 학습시킬 때 사용하는 방법

- 비용 함수의 기울기를 계속 낮은 쪽으로 이동시켜 극값(최적값)에 이를 때까지 반복하는 것

 

- 경사 하강법을 이용하여 비용 함수에서 기울기가 '0'일 때의 비용(오차)값을 구할 수 있음

- 비용 함수의 최소값을 구하면 이때 회귀 함수를 최적화 할 수 있게 됨 

 

- 비용 함수에서 기울기가 '0'일 때 (비용 함수가 최솟값일 때) 모델의 기울기와 y절편을 구하여 회귀 함수 최적화

 

5. 경사 하강법 - learning rate(학습률)

- 선형 회귀에서 가중치(w)와 편향(b)을 경사 하강법에서 반복 학습시킬 때, 한 번 반복 학습시킬 때마다 포인트를 얼만큼씩 이동시킬 것인지 정하는 상수

- 학습률이 너무 작은 경우: local minimum에 빠질 수 있음

- 학습률이 너무 큰 경우: 수렴이 일어나지 않음

 

=> 적당한 learning rate를 찾는 것이 중요!

- 시작을 0.01로 시작해서 overshooting이 일어나면 값을 줄이고, 학습 속도가 매우 느리다면 값을 올리는 방향으로 진행

 

import numpy as np
import matplotlib.pyplot as plt

X = np.random.rand(100)
Y = 0.2 * X + 0.5       # 실제값 함수 가정

plt.figure()
plt.scatter(X, Y)
plt.show()

 

# 실제값, 예측값 산점도 그리는 함수
def plot_prediction(pred, y):
    plt.figure()
    plt.scatter(X, Y)
    plt.scatter(X, pred)
    plt.show()

# 경사 하강법 구현
W = np.random.uniform(-1, 1)
b = np.random.uniform(-1, 1)

learning_rate = 0.7    # 임의

for epoch in range(100):
    Y_pred = W * X + b    # 예측값
    
    error = np.abs(Y_pred - Y).mean()
    if error < 0.001:
        break
    
    # gradient descent 계산 (반복할 때마다 변경되는 W, b값)
    w_grad = learning_rate * ((Y_pred-Y) * X).mean()
    b_grad = learning_rate * ((Y_pred-Y)).mean()
    
    # W, b 값 갱신
    W = W - w_grad
    b = b - b_grad
    
    # 실제값과 예측값이 얼마나 근사해지는지 epoch % 5 ==0 될 때마다 그래프 그림
    if epoch % 5 == 0:
        Y_pred = W * X + b
        plot_prediction(Y_pred, Y)

[파랑: 실제값, 주황: 예측값]

- 반복문이 실행되면서 오차가 점차 작아짐을 알 수 있음

- 최종적으로 오차가 줄어들며 실제값을 정확히 추정할 수 있음!

 

6. 다항 회귀

- 다항식을 사용한 선형 회귀

- y = a * x² + b * x + c 에서 x²을 z로 치환하면 y = a * z + b * x + c라는 선형식으로 쓸 수 있음

 

=> 다항식을 이용해서도 선형 회귀를 할 수 있음 → 최적의 곡선 찾기

- 비선형성을 띄는 데이터도 선형 모델을 활용하여 학습시킬 수 있다는 것

 

- 다항 회귀 기법: log, exp, 제곱 등을 적용해 선형식으로 변형한 뒤 학습시키는 것

위의 선형회귀 모델의 문제점 해결

# 50cm 농어 평균 무게 예측

# 훈련 세트, 테스트 세트의 길이를 제곱한 값의 열 추가 - 새로운 훈련, 테스트 세트 생성
train_poly = np.column_stack((train_input**2, train_input))
test_poly = np.column_statck((test_input**2, test_input))

# 새로운 훈련 세트, 테스트 세트로 선형 회귀 모델 훈련
lr = LinearRegression()
lr.fit(train_poly, train_target)

# 무게 예측
print(lr.predict([50**2, 50]))

# [1573.98423528]

 

7. 다항 회귀 구현

1) 회귀 다항식 y = a * x² + b * x + c 구하기

print(lr.coef_, lr.intercept_)

# [1.01433211 -21.55792498] 116.05021078278259

=> 회귀 다항식) 무게 = 1.01 * 길이² - 21.6 * 길이 + 116.05

 

2) 훈련 세트의 산점도, 회귀 다항식 그리기

# 훈련 세트 산점도
plt.scatter(train_input, train_target)

point = np.arange(15, 50)

# 15~49까지 회귀 다항식 그래프 그리기
plt.plot(point, 1.01*point**2 -21.6*point + 116.05)

# 농어 데이터 표시하기
plt.scatter(50, 1574, marker = '^')

plt.xlabel('length')
plt.ylabel('weight')
plt.show()

# 결정 계수
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))

# 0.9706807451768623
# 0.9775935108325121

- 결정 계수 점수가 1에 근접하므로, 해당 모델은 위의 선형 모델보다 정확성이 높음

'ML & DL' 카테고리의 다른 글

[BITAmin] K-최근접 이웃 알고리즘  (2) 2023.01.23
[BITAmin] 데이터 전처리  (0) 2023.01.23

1. K-NN

- 주변의 가장 가까운 K개의 데이터를 보고 새로운 데이터를 판단하는 알고리즘

- N개의 특성을 가진 데이터는 n차원의 공간에 점으로 표현됨

- 유사한 특성의 데이터는 거리가 가깝고, 다양한 거리 함수를 통해 데이터 간 거리를 구할 수 있음

 

- KNN 분류

  - 종속변수 Y : 범주형 데이터 -> 어떤 범주에 속하는지, K개 중 과반수 의결에 의해 분류

 

- KNN 회귀

  - 종속변수 Y : 연속형 데이터 -> K개의 최근접 이웃이 가진 평균

 

2. 최적의 K 값 찾는 방법

- 최적의 K값은 데이터에 의존적이며, 현실적으로 만족할만한 수준의 값을 찾음

  - Trial & Error → Python 반복문 사용

- 매우 작은 K는 overfitting 초래: 데이터의 지역적 특성을 지나치게 반영함

- 매우 큰 K는 underfitting 초래: 모델이 과하게 정규화됨

 

1) 방법

- K 후보군 결정 : 1부터 학습데이터 개수 -1까지, 데이터 개수가 많다면 1부터 √n까지

- Train & Test 도는 Validation 데이터에 대해 knn 에러율 구함

- 에러율이 가장 낮은 K 선택

 

2) 종속변수가 범주형일 경우

- Tie 문제를 막기 위해 K는 홀수 권장

- 짝수일 경우 2:2, 3:3 등으로 동일하게 나뉠 수 있기 때문 

 

3) 종속변수가 연속형일 경우

- Inverse distance weighted average 고려 가능

- 거리가 가까운 관측치들에게 가중치를 부과해 가중평균으로 구하는 것

 

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt

cancer = load_breast_cancer()
x_train, x_test, y_train, y_test = train_test_split(cancer.data, cancer.target, 
                                   test_size = 0.3, stratify = cancer.target, random_state = 100)
                                   
training_accuracy = []
test_accuracy = []
neighbors_settings = range(1, 101)

for n_neighbors in neighbors_settings:
    clf = KNeighborsClassifier(n_neighbors = n_neighbors)
    clf.fit(x_train, y_train)
    training_accuracy.append(clf.score(x_train, y_train))
    test_accuracy.append(clf.score(x_test, y_test))
    
plt.plot(neighbors_settings, training_accuracy, label = 'train_accuracy')
plt.plot(neighbors_settings, test_accuracy, label = 'test_accuracy')
plt.ylabel('accuracy')
plt.xlabel('n_neighbors')
plt.legend()
plt.show()

 

3. K-NN 회귀

- K개의 최근접 다변량 이웃 샘플로부터 연속 변수를 예측하기 위해 사용

- 이웃 샘플의 타겟값을 평균하여 예측하고자 하는 데이터의 예측값으로 사용

- 다양한 회귀 모델의 평가지표를 통해 모델 성능 측정

  - 결정계수(R²), MSE, RMSE, MAE, MAPE, MPE

- 훈련 세트, 테스트 세트에 대한 평가 점수를 통해 과대/과소 적합 / 적정 판단

 

1) 장점

- 학습 데이터의 노이즈에 크게 영향 받지 않음

- 학습 데이터의 수가 충분하다면 상당히 좋은 성능을 보임

- 훈련 단계가 빠름

- 데이터의 분산까지 고려하면 상당히 robust 해짐

 

2) 단점

- 모델을 따로 생성하지 않아 특징과 클래스 간의 관계를 이해하는 부분에 제약이 있음

- 최적 이웃 수와 사용할 거리 척도를 데이터 각각의 특성에 맞게 연구자가 임의로 설정해야 함

- 새 관측치와 각 학습 데이터 간의 거리를 전부 측정해야하므로 '분류, 예측' 단계가 느림

 

3) 사용시 유의점

- combining rule : KNN은 주변 이웃의 분포에 따라 예측 결과가 달라짐

  - 다수결

  - 가중합 : 거리(유사도)가 가까울수록 더 큰 가중치 부여

 

- 정규화

  - 데이터 간 분포가 크게 다를 경우 각 변수의 차이를 해석하기 어려움

  - 알고리즘의 적절한 적용을 위해 정규화 과정 필요

 

4. 거리 함수

1) 유클리드 거리(Euclidean Distance) = L2 Distance

- 연속 변수에서 가장 일반적으로 사용하는 거리 척도

- 관측치 사이의 최단거리

 

- 유클리드 함수를 채택할 경우 K를 정하기 위해 반드시 '정규화'가 선행되어야 함

 

2) 맨해튼 거리(Manhattan Distance) = L1 Distance

- 각 좌표축 방향으로만 이동할 경우에 계산되는 거리

 

3) 해밍 거리(Hamming Distance)

- 두 개의 이진 데이터 문자열을 비교하기 위한 지표

- 두 문자열의 각 위치의 문자 비교 → 동일하지 않은 문자 수 = '해밍 거리'

- 길이가 같은 두 개의 이진 문자열 비교 시 해밍 거리는 두 비트가 다른 비트 위치의 개수

- 해밍 거리 d가 d >= 2a + 1이면 a개의 오류를 정정할 수 있음

 

- Feature를 벡터화할 때

- Clustering에서 독립변수가 범주형일 경우

- 네트워크를 통해 전송될 때 오류 감지 / 수정에 사용

- 자연어 처리에서 데이터 간 형태적 유사성(단어 간 거리)을 계산해 유사도 평가할 때

 

5. 회귀 모델 평가 지표

1) 결정계수 (R² Score)

- 모델이 데이터를 얼마나 잘 예측하는지에 대한 지표

- 실제 값의 분산 대비 예측 값의 분산 비율

- 평균으로 예측했을 때의 오차(총오차)보다 모델을 사용했을 대 얼마나 더 좋은 성능을 내는지 비율로 나타낸 값

- R : 표본 상관계수

- SST : 총 제곱합          SST = Σ(yᵢ - y̅)²

- SSE : 설명된 제곱합    SSE = Σ(^yᵢ - y̅)² 

- SSR : 잔차 제곱합       SSR = Σ(^uᵢ)²              # ^uᵢ 잔차 = 표본집단 회귀식에서 예측된 값 - 실제 관측값

 

-  = 0 : x, y는 선형 관계 없음

-  = 1 : x, y는 완벽한 선형 관계

- R²가 1에 가까울수록 회귀 모형이 적합함

# sklearn
r2_score(y_true, y_pred, sample_weight = None, multioutput = 'uniform_average')

'''
y_true : 올바른 목표값
y_pred : 예상 목표값
sample_weight : 가중치
'uniform_average' : 모든 출력의 오차를 평균 무게로 평균화
'''

 

2) 오차 제곱 평균 MSE (Mean Squared Error)

- 예측 값과 실제 값의 차이의 제곱에 대해 평균을 낸 값

- 이상치에 민감

- MSE 값이 작을수록 좋지만, 과대적합이 될 수 있음

- MSE의 범위는 0~무한대

- MSE가 100일 때 이 모형이 좋은지 기준이 없어 판단이 어려움 → MAPE의 퍼센트 값을 이용해 성능 평가하기도 함

from sklearn.metrics import mean_squared_error

mse = mean_squared_error(y_true, y_pred)
print("mse = {:.3f}".format(mse_))

# mse = 11.264

 

3) 평균 제곱근 오차 RMSE (Root Mean Squared Error)

- MSE에 루트를 씌운 값

- MSE의 장단점을 거의 그대로 따라감

- 제곱 오차에 대한 왜곡 줄여줌 + robust함에서 강점을 보임

- 오류 지표를 루트를 통해 실제값과 유사한 단위로 변환하기 때문에 해석이 쉬움

- 실생활에서 쓰는 계산법을 벗어났기 때문에 'RMSE로 구한 에러값만큼 모델이 틀리다'고 말할 수는 없음

- 그럼에도 큰 오류값 차이에 대해 큰 패널티를 주는 이점

rmse = np.sqrt(mse)

 

4) 평균 절대 오차 MAE (Mean Absolute Error)

- 절대값을 취하기 때문에 가장 직관적으로 알 수 있는 지표

- MSE보다 특이치에 robust (제곱하지 않아 이상치에 덜 민감)

- 절대값을 취하기 때문에 모델이 underperformance인지 overperformance인지 알 수 없음

from sklearn.metrics import mean_absolute_error

mean_absolute_error(y_true, y_pred)

 

5) 평균 절대비 오차 MSPE (Mean Absolute Percentage Error)

- MAE와 마찬가지로 MSE보다 이상치에 robust

- MAPE는 퍼센트값을 가지며 0에 가까울수록 회귀 모형의 성능이 좋다고 볼 수 있음

- 0~100% 의 값을 가지므로 성능 비교 해석 가능

- 추가적으로 모델에 대한 편향 존재 → MPE를 통해 대응 & 0 근처의 값에 대해서는 사용하기 어려움

mean_absolute_percentage_error(y_true, y_pred)

 

6) MPE (Mean Percentage Error)

- MAPE에서 절대값을 제외한 지표

- 과대평가인지 과소평가인지 알 수 있어 유용함

- MAE, MSE는 절대 오차 측정 / MAPE, MPE는 상대 오차 측정

- sklearn에서 구현된 것이 보이지 않음

def MPE(y_true, y_pred):
    return np.mean((y_true - y_pred) / y_true) * 100

MPE(y_true, y_pred)

 

6. KNN 회귀 구현

class sklearn.neighbors.KNeighborsRegressor(n_neighbhors=5, *, weights='uniform', algorithm='auto', 
                                            leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None):
                                            
'''
n_neighbors: 이웃의 수 K (defualt = 5)
weights: 예측에 사용되는 가중 방법 결정 (default = 'uniform') or callable
  "uniform" : 각각의 이웃이 모두 동일한 가중치
  "distance" : 거리가 가까울수록 높은 가중치
  callable : 사용자가 직접 정의한 함수 사용
algorithm('auto', 'ball_tree', 'kd_tree', 'brute') : 가장 가까운 이웃을 계산할 때 사용할 알고리즘
  "auto" : 입력된 훈련 데이터에 기반해 가장 적절한 알고리즘 사용
  "ball_tree" : Ball-Tree 구조
  "kd_tree" : KD-Tree 구조
  "brute" : Brute-Force 탐색 사용
leaf_size : Ball-Tree나 KD-Tree의 leaf size 결정 (default = 30)
  - 트리를 저장하기 위한 메모리, 트리의 구성과 쿼리 처리 속도에 영향
p : 민코프스키 미터법의 차수 결정 (1이면 맨해튼 거리, 2이면 유클리드 거리)
'''

 

from sklearn.neighbors import KNeighborRegressor

knr = KNeighborsRegressor()

knr.fit(train_input, train_target)
knr.score(test_input, test_target)          # 테스트 모델에 대한 평가
# 0.9928094061
test_prediction = knr.predict(test_input)   # 테스트 세트에 대한 예측
knr.score(train_input, train_target)        # 훈련 모델에 대한 평가
# 0.9698823289                                이 경우는 과소적합 (훈련 < 테스트 점수)

knr.n_neighbors = 3                         # 모델을 훈련세트에 잘 맞게 하기 위해 k 줄임 (5→3)
knr.fit(train_input, train_target)          # 재훈련
knr.score(train_input, train_target)
# 0.9804899950
knr.score(test_input, test_target)
# 0.9746459963                              # 훈련 > 테스트 점수이고, 차이가 크지 않으므로 적합!

knr.KNeighborRegressor()
x = np.arange(5, 45).reshape(-1, 1)
knr.n_neighbors = 3

knr = KNeighborsRegressor()
x =np.arange(5, 45).reshape(-1, 1)           # 5에서 45까지 x 좌표 생성 
for n in [1, 5, 10]:
    knr.n_neighbors = n
    knr.fit(train_input, train_target)       # 모델 훈련
    prediction = knr.predict(x)              # 지정한 범위 x에 대한 예측
    plt.scatter(train_input, train_target)
    plt.plot(x, prediction)
    plt.title('n_neighbors = {}'.format(n))
    plt.xlabel('length')
    plt.ylabel('weight')
    plt.show()

 

7. 과대 적합 / 과소 적합

1) 과대 적합 overfitting

- 모델이 훈련 세트에는 좋은 성능을 내지만, 테스트 세트에서는 낮은 성능을 내는 경우

- 훈련 세트와 테스트 세트에서 측정한 성능 간격이 큼 = 분산이 큼

- 주요 원인: 훈련 세트에 충분히 다양한 샘플이 포함되지 않음

- 해결 방법

  - 훈련 세트에 충분히 다양한 샘플 포함시키기

  - 모델이 훈련 세트에 집착하지 않도록 가중치 제한하기 (모델의 복잡도 낮춤)

  - 훈련 데이터의 잡음 줄이기 (outlier, error 제거)

 

2) 과소 적합 underfitting

- 훈련 세트와 테스트 세트의 성능에는 차이가 크지 않지만 모두 낮은 성능을 내는 경우

- 훈련 세트와 테스트 세트의 성능이 서로 가까워지면 성능 자체가 낮음

- 편향이 큼

- 해결 방법

  - 복잡도가 더 높은 모델 사용

  - 가중치 규제 완화

 

'ML & DL' 카테고리의 다른 글

[BITAmin] 선형 회귀  (0) 2023.01.23
[BITAmin] 데이터 전처리  (0) 2023.01.23

1. 데이터 전처리

- 특정 분석에 적합하게 데이터를 가공하는 작업

 

- 완결성: 필수로 기입되어 있어야 하는 데이터는 모두 입력되어야 한다.

- 유일성: 동일한 데이터가 불필요하게 중복되어 있으면 안된다.

- 통일성: 데이터가 모두 동일한 형식으로 입력되어야 한다.

 

2. 주의해야 하는 점

- 잡음 Noise: 측정 과정에서 무작위로 발생하여 측정값의 에러를 발생시키는 것. 이상치와 달리 예측하기 어려움

- 아티펙트 Digital Artifact: 어떤 기술적인 요인으로 인해 반복적으로 발생하는 왜곡이나 에러

- 정밀도 Precision: 동일한 대상을 반복적으로 측정했을 때, 각 결과의 친밀성을 나타내는 것. 측정 결과의 표준편차로 표현 가능

- 편향 bias: 측정 장비에 포함된 시스템적인 변동 (ex. 영점 조절이 되지 않은 체중계)

- 정확도 Accuracy: 평향에 영향 받음. 유효 숫자의 사용에 있어서 중요함

- 이상치 Outlier: 대부분의 데이터와 다른 특성을 보이너가, 특정 속성의 값이 다른 개체들과 달리 유별난 값을 가지는 데이터. 잡음과 다름

- 결측치 Missing Value: 값이 표기되지 않은 값

- 모순, 불일치 Inconsistent Value: 동일한 개체에 대한 측정 데이터가 다르게 나타나는 경우

- 중복 Duplicated data: 중복된 데이터 사이에 속성의 차이나, 값의 불일치가 발생하면 문제가 됨

 

3. 전처리 순서

1) 데이터 수집: 분석이나 학습에 필요한 데이터를 수집하는 작업

- 데이터 분석 및 모델 생성의 첫 과정

- 목적에 맞는 정보 수집을 위해 문제 정의 필요

 

2) 데이터 정제: 비어있는 데이터나 잡음, 모순된 데이터를 정합성이 맞도록 교정하는 작업

- 데이터를 활용할 수 있도록 만드는 과정

- 컴퓨터가 읽을 수 없는 요소의 제거 및 숫자나 날짜 등의 형식에 대해 일관성 유지

- 누락값, 불일치 값, 오류 값 수정

 

3) 데이터 통합: 여러 개의 데이터 베이스, 데이터 집합 또는 파일을 통합하는 작업

- 서로 다른 데이터 세트가 호환이 가능하도록 통합

- 같은 객체, 같은 단위나 좌표로 통합

 

4) 데이터 축소: 샘플링, 차원 축소, 특징 선택 및 추출을 통해 데이터 크기를 줄이는 작업

- 대용량 데이터에 대한 복잡한 데이터 분석은 실행하기 어렵거나 불가능한 경우가 많음

 

5) 데이터 변환: 데이터를 정규화, 이산화 또는 집계를 통해 변환하는 작업

 

4. 데이터 전처리 기법

- 집계 Aggregation

- 샘플링 Sampling

- 차원 축소 Dimensionality Reduction

- 특징 선택 Feature Subset Selection

- 특징 생성 Feature Creation

- 이산화와 이진화 Discretization and Binarization

- 속성 변화 Attribute Transformation

 

5. 전처리 전 데이터 확인

- shape: 데이터 크기 출력

- head(): 데이터 상위 5개 행 출력

- tail(): 데이터 하위 5개 행 출력

- info(): 데이터 전반적인 정보 제공 (행/열 크기, 컬럼명, 컬럼을 구성하는 값의 자료형 등)

- describe(): 데이터 컬럼별 요약 통계량 제공

 

6. 결측치 처리

- NA : 값이 표기되지 않은 값. 결측치

- 제거, 대치, 예측모델로 처리

 

1) 전체 결측치 확인

df.isnull()
pd.isnull(df)

# 결측치일 때 true 반환
# isnull -> notnull : 결측치일 때 false 반환

 

2) 인덱싱 후 결측치 개수 확인하기

df['col'].isnull()

 

3) 결측치 만들기

df.ix[[row1, row2], ['col']] = None

 

4) 전체 결측치 개수 확인

df.isnull().sum()
df.isnull().value_counts()
df.isnull().sum(1)

 

5-1) 결측치 제거

- dropna() : 판다스에서 누락 데이터를 제거하는 함수

- 목록삭제 : 결측치가 있는 행/열은 전부 삭제

df = df.dropna()           # default, 행 제거
df = df.dropna(axis = 1)   # 열 제거

 

- 단일값 삭제 : 행/열 자체가 결측치일 때, 혹은 조건부 삭제

df = df.dropna(how = 'all')   
df = df.dropna(thresh = 1)     

df = df.dropna(subset=['col1', 'col2', 'col3'], how = 'all')  # 모두 결측치일 때 해당 행 삭제
df = df.dropna(subset=['col1', 'col2', 'col3'], thresh = 2)   # 특정 열에 2개 초과의 결측치가 있을 때 해당 행 삭제

 

5-2) 대치

- 단순 대치: 중앙값, 최빈값, 0, 분위수, 주변값, 예측값 등으로 결측치 대치

- 다중 대치: 단순 대치법을 여러번! (대치 - 분석 - 결합)

 

- 판다스에서 결측치 대치하는 함수들

fillna()

# 전체 결측치를 특정 단일값으로 대치
df.fillna(0)  

# 특정 열에 결측치가 있을 경우 다른 값으로 대치
df['col'] = df['col'].fillna(0)
df['col'] = df['col'].fillna(df['col'].mean())

# 결측치 바로 이후 행 값으로 채우기
df.fillna(method='bfill')

# 결측치 바로 이전 행 값으로 채우기
df.fillna(method='pad')
replace()

# 결측치 값 0으로 채우기
df.replace(to_replace = np.nan, value = 0)
interpolate()

# 인덱스를 무시하고, 값을 선형적으로 같은 간격으로 처리
df.interpolate(method = 'linear', limit_direction = 'forward')

 

5-3) 예측 모델

- 결측값을 제외한 데이터로부터 모델을 훈련하고 추정값을 계산하고 결측치 대체

- K-NN, 가중 K-NN, 로지스틱 회귀, SVM, 랜덤 포레스트 방식 등

 

7. 중복 데이터 처리

- 중복은 언제든지 발생할 수 있지만, 중복 데이터 사이에 속성의 차이나 값의 불일치가 발생한 경우, 처리해야 함

- 두 개체를 합치거나 응용에 적합한 속성을 가진 데이터를 선택하는 등

# 중복 데이터 확인
df.duplicated(['col'])

# 중복 데이터 삭제
drop_duplicates()

# 해당 열의 첫 행을 기준으로 중복 여부 판단 후, 중복되는 나머지 행 삭제
drop_duplicated(['col'])

df.drop_duplicates(keep = )
    subset = None           # default, 특정 열 지정 X, 모든 열에 대해 작업 수행
    keep = 'first'          # 가장 처음에 나온 데이터만 남김
    keep = 'last'           # 가장 마지막에 나온 데이터만 남김
    keep = False            # 중복된 어떤 데이터도 남기지 않음

 

8. 불균형 데이터 처리

- 분류를 목적으로 하는 데이터 셋에 클래스 라벨의 비율이 불균형한 경우

- 각 클래스에 속한 데이터 개수 차이가 큰 데이터

- 정상 범주의 관측치 수와 이상 범주의 관측치 수가 현저히 차이나는 데이터

- 이상 데이터를 정확히 찾아내지 못할 수 있음

 

8-1) Under Sampling

- 다수 범주의 데이터를 소수 범주의 데이터 수에 맞게 줄이는 샘플링 방식

- Random Undersampling, Tomek's Link, CNN

 

8-2) Over Sampling

- 소수 범주의 데이터를 다수 범주의 데이터 수에 맞게 늘리는 샘플링 방식

- Random Oversampling

- ADASYN, SMOTE

 

9. 이상치 탐지 기법

1) z-score

- z = (x - μ) / σ

- 변수가 정규분포 따른다고 가정, 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지 나타냄

- z-score가 임계치보다 크거나 작은 관측치를 이상치라고 규정

- 임계치 조정함으로써 기준 조정

def z_score_outlier(df, col, thres = 3):
    z_score = (df[col] - df[col].mean()) / df[col].std()
    return df[(z_score > thres) | (z_score < -thres)]

 

2) IQR

- IQR = Q3 - Q1

- Q3 + 1.5*IQR 이상이거나, Q1 - 1.5*IQR 이하인 경우 이상치라고 규정

- 1.5 대신 다른 값 이용해 기준 조정 가능

def IQR_outlier(df, col, scale = 1.5):
    Q1 = df[col].quantile(0.25)
    Q3 = df[col].quantile(0.75)
    IQR = Q3 - Q1
    lower_limit = Q1 - scale * IQR
    upper_limit = Q3 + scale * IQR
    
    return df[(df[col] > upper_limit) | df[col] < lower_limit)]

 

3) DBSCAN

- 밀도 기반 군집화 알고리즘

- 하이퍼 파라미터: eps(반경, default=0.5), min_samples(core point가 되기 위한 최소한의 이웃 수, default=5)

# 이상치 탐지
from sklearn.cluster import DBSCAN

model = DBSCAN(eps = .3, min_samples = 10)
pred = model.fit_predict(abalone)

# 이상치 개수
(pred == -1).sum()

 

10. 레이블 인코딩

- 문자열 카테고리 피처를 코드형 숫자 값으로 변환하는 것

# pandas
df.factorize()

# scikit-learn
LabelEncoder()
encoder.fit_transform() # 학습, 변환 한번에

 

11. 원핫 인코딩

- 피처값의 유형에 따라 새로운 피처를 생성해 고유값에 해당하는 컬럼에만 1 표시, 나머지 컬럼에는 0을 표시하는 방식

- 숫자의 크기 차이를 만드는 레이블 인코딩의 단점 보완

 

12. Feature Scaling

- 서로 다른 변수 값의 범위를 일정한 수준으로 맞추는 작업

- 변수 값의 범위 또는 단위가 달라서 발생 가능한 문제 예방

- 머신러닝 모델이 특정 데이터의 bias 갖는 것 방지

 

1) 표준화 Standardization

- 서로 다른 범위의 변수들을 평균이 0이고 분산이 1인 가우시안 정규분포를 가진 값으로 변환

- ScandardScaler()

 

2) 정규화 Normalization

- 변수값들을 모두 0과 1 사이의 값으로 변환하는 방식

- MinMaxScaler()

'ML & DL' 카테고리의 다른 글

[BITAmin] 선형 회귀  (0) 2023.01.23
[BITAmin] K-최근접 이웃 알고리즘  (2) 2023.01.23

[출처: 실무로 배우는 빅데이터 기술, 김강원 저]

 

1. 플럼 Flume

1) 플럼

- 빅데이터를 수집할 때 다양한 수집 요구사항들을 해결하기 위한 기능으로 구성된 소프트웨어

- 통신 프로토콜, 메시지 포맷, 발생 주기, 데이터 크기 등 데이터를 수집할 때 고려해야 할 것들을 쉽게 해결할 수 있는 기능과 아키텍처 제공

 

2) 주요 구성요소

- Source : 다양한 원천 시스템의 데이터를 수집하기 위해 Avro, Thrift, JMS, Spool Dir, Kafka 등 컴포넌트 제공 / 수집한 데이터 Channel로 전달

- Channel : Source와 Sink 연결 / 데이터를 버퍼링하는 컴포넌트로 메모리, 파일, 데이터베이스를 채널의 저장소로 활용

- Sink : 수집한 데이터를 Channel로부터 전달받아 최종 목적지에 저장하기 위한 기능 / HDFS, Hive, Logger, Avro, ElasticSearch, Thrift 등 제공

- Interceptor : Source와 Channel 사이에서 데이터 필터링 및 가공하는 컴포넌트 / Timestamp, Host, Regex Filtering 등 기본 제공 + 필요 시 사용자 정의 Interceptor 추가

- Agent : Source → (Interceptor) → Channel → Sink 컴포넌트 순으로 구성된 작업 단위 / 독립된 인스턴스로 생성

 

3) 플럼 아키텍처

- 플럼 메커니즘 : Source, Channel, Sink 만을 활용하는 매우 단순하고 직관적인 구조

- Source에서 데이터 로드, Channel에서 데이터 임시 저장, Sink를 통해 목적지에 최종 적재

 

3-1) 유형 1

- 가장 단순한 플럼 에이전트 구성

- 원천 데이터를 특별한 처리 없이 단순 수집/적재

 

3-2) 유형 2

- Interceptor를 추가해 데이터 가공

- 데이터의 특성에 따라 Channel에서 다수의 Sink 컴포넌트로 라우팅이 필요할 때 구성

* 데이터 통신에서의 라우팅: 네트워크상에서 주소를 이용하여 목적지까지 메시지를 전달하는 방법을 체계적으로 결정하는 경로선택 과정

- 한 개의 플럼 에이전트 안에서 두 개 이상의 S-C-S 컴포넌트 구성 및 관리도 가능

 

3-3) 유형 3

- 플럼 에이전트에서 수집한 데이터를 에이전트 2, 3에 전송할 때 로드밸런싱, 복제, 페일오버 등의 기능을 선택적으로 수행 가능

- 수집해야 할 원천 시스템은 한 곳이지만, 높은 성능과 안정성이 필요할 때 주로 사용

* 로드밸런싱(부하분산)서버가 처리해야 할 업무 혹은 요청을 여러 대의 서버로 나누어 처리하는 것. 한 대의 서버로 부하가 집중되지 않도록 트래픽을 관리해 각각의 서버가 최적의 퍼포먼스를 보일 수 있도록 하는 것이 목적

* 페일오버(장애 극복 기능): 컴퓨터 서버, 시스템, 네트워크 등에서 이상이 생겼을 때 예비 시스템으로 자동전환되는 기능. 시스템 설계에서 높은 가용성과 신뢰성이 요구되는 경우 페일오버 기능을 탑재하는 것이 일반적 

 

3-4) 유형 4

 

- 수집해야 할 원천 시스템이 다양하고, 대규모의 데이터가 유입될 때

- 에이전트 1, 2, 3, 4에서 수집한 데이터를 에이전트 5에서 집계하고, 이때 에이전트 6으로 이중화해서 성능과 안정성 보장

 

4) 활용 방안

- 스마트카에서 발생하는 로그 직접 수집하는 역할. 로그 유형에 따라 두 가지 에이전트 구성할 것

 

4-1) 100대의 스마트카 상태 정보 로그파일

- 로그 시뮬레이터를 통해 매일 생성됨

- 생성된 상태 정보 파일을 플럼 에이전트가 일 단위로 수집해서 하둡에 적재, 이후 대규모 배치 분석에 활용

 

4-2) 스마트카 운전자 100명의 운행 정보 실시간 기록

- 로그 시뮬레이터에 의해 운행 정보 실시간 로그 파일 생성됨

- 로그 발생과 동시에 플럼 에이전트가 수집해서 kafka에 전송

 

2. 카프카 Kafka

1) 카프카

- Message Oriented Middleware (MOM) 소프트웨어 중 하나

- 대규모로 발생하는 메시지성 데이터를 비동기 방식으로 중계

- 원천 시스템으로부터 대규모 트랜잭션 데이터가 발생했을 때, 중간에 데이터를 버퍼링하면서 타깃 시스템에 안정적으로 전송해주는 중간 시스템

 

2) 주요 구성요소

- Broker : 카프카의 서비스 인스턴스. 다수의 Broker를 클러스터로 구성하고 Topic이 생성되는 물리적 서버

- Topic : Broker에서 데이터의 발행/소비 처리를 위한 저장소

- Provider : Broker의 특정 Topic에 데이터를 전송(발행)하는 역할. 카프카 라이브러리를 통해 구현

- Consumer : Broker의 특정 Topic에서 데이터를 수신(소비)하는 역할. 카프카 라이브러리를 통해 구현

 

3) 카프카 아키텍처

- 클러스터 방식에 따라 세가지 아키텍처 구성 가능, 반드시 주키퍼 이용

 

3-1) 유형 1 - 싱글 브로커 / 싱글 노드

- 1대의 카프카 서버와 1개의 Broker만 구성한 아키텍처

- 대량의 발행 / 소비 요건이 없고, 업무 도메인이 단순할 때 이용

 

3-2) 유형 2 - 멀티 브로커 / 싱글 노드

- 1대의 카프카 서버에 2개의 Broker를 구성한 아키텍처

- 물리적인 카프카 서버가 1대이므로 대량의 발행 / 소비 요건에는 사용 어려움

- 하지만, 업무 도메인이 복잡해서 메시지 처리를 분리 관리해야 할 때 이용

 

3-3) 유형 3 - 멀티 브로커 / 멀티 노드

- 2대 이상의 카프카 서버로 멀티 브로커 구성

- 대규모 발행 / 소비 데이터 처리에 적합

- 물리적으로 나눠진 브로커 간의 데이터 복제가 가능해 안정성이 높음

- 업무 도메인별 메시지 그룹을 분류할 수 있어 복잡한 메시지 송/수신에 적합

 

4) 활용 방안

- 플럼이 실시간 데이터를 수집해 카프카 Topic에 전달하면, 카프카는 받은 데이터를 Topic에 임시로 저장하고 있다가 Consumer 프로그램이 작동해 Topic에서 데이터 가져감

 

- 카프카 활용 목적 : 플럼이 아주 빠르게 발생하는 데이터를 실시간으로 수집하게 되면, 이를 최종 목적지에 전달하기 전 중간에서 안정적인 버퍼링 처리가 필요

- 카프카를 거치지 않고 바로 타깃 저장소인 HBase에 전송 → HBase에 장애가 발생하면 플럼의 Channel에 전송하지 못한 데이터들이 빠르게 쌓여 곧바로 플럼의 장애로도 이어짐 → 데이터 유실 발생

 

- HBase에 장에가 발생해도 카프카에서 데이터를 저장해 놓았다가 HBase가 복구되면 곧바로 재처리 가능

+ 플럼이 수집한 데이터를 카프카의 토픽에 비동기로 전송함으로써 수집 속도가 빨라짐

* 비동기 방식: 동시에 일어나지 않을 수 있음 (요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 수행 할 수 있음) 

  → 자원을 효율적으로 이용할 수 있음

 

3. 수집 파일럿 실행 1단계 - 수집 아키텍처

1) 수집 요구사항

- 차량의 다양한 장치로부터 발생하는 로그 파일 수집해서 기능별 상태 점검

- 운전자의 운행 정보가 담긴 로그를 실시간으로 수집해서 주행 패턴 분석

 

수집 요구사항 구체화 분석 및 해결 방안
1. 스마트카로부터 로그 파일 주기적으로 발생 플럼 → 대용량 배치 파일 및 실시간 로그 파일 수집
2. 스마트카의 배치 로그 파일 이벤트 감지 플럼의 Source 컴포넌트 중 SpoolDir → 주기적인 로그 파일 발생 이벤트 감지
3. 스마트카의 실시간 로그 발생 이벤트 감지 플럼의 Source 컴포넌트 중 Exec-Tail → 특정 로그 파일에서 로그 생성 이벤트 감지
4. 스마트카가 만들어내는 로그 데이터에 가비지 데이터가 있을 수 있음 플럼의 Interceptor → 정상 패턴의 데이터만 필터링
5. 수집 도중 장애가 발생해도 데이터를 안전하게 보관, 재처리해야 함 플럼의 메모리 Channel, 카프카 Broker → 로컬 디스크의 파일 시스템에 수집 데이터 임시 저장
6. 스마트카의 실시간 로그 파일은 비동기 처리로 빠른 수집 처리 플럼에서 수집한 데이터를 카프카 Sink 컴포넌트를 이용해 카프카 Topic에 비동기 전송

 

2) 수집 아키텍처

 

2-1) 로그 시뮬레이터

- 스마트카의 상태 정보와 운전자의 운행 정보 로그를 가상으로 만드는 자바 로그 발생기

 

- 스마트카 상태 정보 : 100대 스마트카 장치들의 상태 정보를 3초 간격으로 발생 시킴, 1일 100MB 로그 파일 생성

- 운전자 운행 정보 : 100명의 스마트카 운전자들의 운행 정보 실시간으로 발생 시킴, 하나의 운행 정보 로그는 4KB 미만, 동시에 최대 400KB 용량으로 실시간 데이터 발생

 

2-2) 플럼 에이전트1

- 대용량 로그 파일을 주기적으로 수집해서 표준 입출력 로거로 보여주는 레이어

- 스마트카 상태 정보를 기록한 로그 파일을 일별로 수집하기 위한 배치성 플럼 에이전트

 

- SpoolDir Source : 약속된 로그 발생 디렉터리를 모니터링하다가 정의된 로그 파일 발생 시 해당 파일의 내용을 읽어서 수집하는 기능 제공

- Memory Channel : SpoolDir Source로부터 수집된 데이터를 메모리 Channel에 중간 적재. 버퍼링 기능을 제공하며 , Sink와 연결되어 트랜잭션 처리 지원함

- Logger Sink : Channel로부터 읽어들인 데이터를 플럼의 표준 로그 파일로 출력

 

2-3) 플럼 에이전트2

- 실시간으로 발생하는 로그를 라인 단위로 수집해 카프카의 Topic에 전송하는 레이어

- 스마트카 운전자의 운행 정보 실시간으로 수집하기 위한 실시간성 플럼 에이전트

 

- Exec-Tail Source : 로그가 쌓이고 있는 파일에 Tail 파이프라인을 이용해 실시간으로 데이터를 수집하는 기능

- Memory Channel : Exec-Tail Source로부터 수집한 데이터를 메모리 Channel에 버퍼링 처리를 하면서 임시 적재

- Kafka Sink : Channel로부터 읽어들인 데이터를 카프카 Broker의 특정 토픽에 비동기 방식으로 전송하는 Provider 역할 수행

 

2-4) 기타

- 플럼이 수집한 로그 데이터 임시 출력 및 저장

 

- Flume Stdout : 플럼의 Logger-Sink를 통해 표준 출력 로그가 출력됨

- Kafka Topic : 플럼의 Kafka-Sink는 수집된 실시간 로그를 임시 적재함

1. 스레드

- 다중 스레딩 : 하나의 프로그램이 동시에 여러 가지 작업을 할 수 있도록 하는 것

- 스레드 : 각각의 작업, 동일한 데이터를 공유함

- 프로세스 : 자신만의 데이터를 가짐

- 프로그램을 보다 빠르게 실행하기 위해 멀티 스레딩 사용

 

2. 멀티 스레딩의 문제점

- 여러 스레드들이 같은 데이터를 공유하게 되면 '동기화' 문제 발생

 

3. 스레드 생성과 실행

Thread t = new Thread();
t.start();

 

3-1) 스레드 생성: Thread 클래스 상속하는 방법

- Thread 클래스를 상속받은 후에 run() 메소드 재정의

- run 메소드 안에 작업 기술

- Thread 객체 생성하고 start() 호출해서 스레드 시작

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i <= 10; i++)
            System.out.print(i + " ");
        
    }
}

public class MyThreadTest {
    public static void main(String args[]) {
        Thread t = new MyThread();
        t.start();
    }
}

// 0 1 2 3 4 5 6 7 8 9 10

 

3-2) 스레드 생성: Runnable 인터페이스 구현하는 방법

- Runnable 인터페이스를 구현한 클래스 작성

- run() 메소드 작성

- Thread 객체 생성하고 Runnable 객체 인수로 전달

- start() 호출해서 스레드 시작

class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i <= 10; i++) 
            System.out.print(i + " ");
    }
}

public class MyRunnableTest {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}

// 0 1 2 3 4 5 6 7 8 9 10

 

- 스레드 2개 예제

class MyThread extends Thread {
	public void run() {
		for (int i = 0; i < 10; i ++) {
			System.out.print(i + " ");
		}
	}
}

class MyRunnable implements Runnable {
	public void run() {
		for (int i = 0; i < 30; i ++) {
			System.out.print("[" + i + "]");
		}
	}
}

public class ThreadTest {

	public static void main(String[] args) throws InterruptedException {
		System.out.println("main start");
		MyThread mt = new MyThread();
		mt.start();
		MyThread mt2 = new MyThread();
		mt2.start();
		Thread t = new Thread(new MyRunnable());  // runnable -바로 스타트 불가.
		t.start();
		// 모든 작업이 끝났을 때 종료 메세지를 출력하고 싶은 경우
		mt.join();
		mt2.join();
		t.join();     // 끝날 때까지 기다리기
		System.out.println("\nmain end");
	}
}

/**
main start
0 1 2 3 4 0 5 6 7 8 9 1 2 3 4 5 6 7 8 9 [0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][22][23][24][25][26][27][28][29]
main end
**/

 

- 람다식 이용한 스레드 작성

public class LambdaTest {
    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i <= 10; i++)
                System.out.print(i + " ");
        };
        new Thread(task).start();
    }
}

// 0 1 2 3 4 5 6 7 8 9 10

 

4. 스레드 상태

- New : Thread 클래스의 인스턴스는 생성되었지만, start() 메소드를 호출하기 전

- Runnable : start() 메소드가 호출되어 실행 가능한 상태, 하지만 아직 스케줄러가 선택하지 않았으므로 실행 상태는 아님

- Running : 스레드 스케줄러가 스레드를 선택하면, 실행 중인 상태가 됨

- Blocking : 스레드가 아직 살아있지만, 여러 이유로 현재 실행할 수 없는 상태

- Terminated : 스레드가 종료된 상태, run() 메소드가 종료되면 스레드도 종료됨

 

- 실행 가능 상태 : 스레드가 스케줄링 큐에 넣어지고, 스케줄러에 의해 우선순위에 따라 실행

- 실행 중지 상태 

  - 스레드나 다른 스레드가 suspend()를 호출하는 경우

  - 스레드가 wait() 호출하는 경우

  - 스레드가 sleep() 호출하는 경우

  - 스레드가 입출력 작업을 하기 위해 대기하는 경우

 

5. 스레드 스케줄링

- 대부분 스레드 스케줄러는 선점형 스케줄링과 타임 슬라이싱을 사용해 스레드 스케줄링

- 어떤 스케줄링을 선택하느냐는 JVM에 의해 결정됨

 

6. 스레드 우선순위

- 1 ~ 10 사이의 숫자료 표시됨

- 기본 우선순위 : NORM_PRIORITY(5)

- MIN_PRIORITY(1)

- MAX_PRIORITY(10)

 

- void setPriority(int newPriority) : 현재 스레드의 우선 순위 변경

- getPriority() : 현재 스레드의 우선 순위 반환

 

7. sleep()

- 지정된 시간동안 스레드 재움

- 스레드가 수면 상태로 있는 동안 인터럽트되면 InterruptedException 발생

 

- 4초 간격으로 메시지 출력

public class SleepTest {
    public static void main(String[] args) throws InterruptedException {
        String messages[] = {"Hello.",
        "My name is A.", 
        "I'm majoring in computer science.",
        "I'm taking a JAVA class."};
        
        for (int i = 0; i < messages.length; i++) {
            Thread.sleep(4000);
            System.out.println(messages[i]);
        }
    }
}

/**
Hello.
My name is A.
I'm majoring in computer science.
I'm taking a JAVA class.
**/

 

8. join()

- 스레드가 종료될 때까지 기다리는 메소드

- 특정 스레드가 작업을 완료할 때까지 현재 스레드의 실행을 중지하고 기다리는 것

 

 

9. 인터럽트와 yield()

- 인터럽트 : 하나의 스레드가 실행하고 있는 작업을 중지하도록 하는 메커니즘 -> 대기 상태나 수면 상태가 됨

- yield() : CPU를 다른 스레드에게 양보하는 메소드

 

10. 자바 스레드 풀

- 스레드 풀 : 미리 초기화된 스레드들이 모여 있는 곳

- 스레드 풀의 동일한 스레드를 사용하여 N개의 작업을 쉽게 실행할 수 있음

- 스레드의 개수보다 작업의 개수가 더 많은 경우, 작업은 FIFO 큐에서 기다려야 함

 

- Java5 부터 자바 API는 Executor 프레임워크 제공

-> 개발자는 Runnable 객체를 구현하고 ThreadPoolExecutor로 보내기만 하면 됨

class MyTask implements Runnable {
    private String name;
    
    public MyTask(String name) {
        this.name = name;
    }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    @Override
    public void run() {
        try {
            System.out.println("실행중 : " + name);
            Thread.sleep(long)(Math.random() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
        
        for(int i = 0; i <= 5; i++) {
            MyTask task = new MyTask("작업 " + i);
            System.out.println("작업 생성 : " + task.getName());
            executor.execute(task);
        }
        executor.shutdown();
    }
}

 

11. 스레드 사용시 주의해야 할 점

- 동일한 데이터를 공유하기 때문에 매우 효율적으로 작업할 수 있지만, 2가지의 문제가 발생할 수 있음

 

- 문제 예제

class Printer {
    void print(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
            Thread.sleep(100);
        }
    }
}

class MyThread1 extends Thread {
    Printer prn;
    int[] myarr = {10, 20, 30, 40};
    
    MyThread1(Printer prn) { this.prn = prn; }
    public void run() { prn.print(myarr); }
}

class MyThread2 extends Thread {
    Printer prn;
    int[] myarr = {1, 2, 3, 4};
    
    MyThread2(Printer prn) { this.prn = prn; }
    public void run() { prn.print(myarr); }
}

public class TestSynchro {
    pubilc static void main(String args[]) {
        Printer obj = new Printer();
        MyThread1 t1 = new MyThread1(obj);
        MyThread2 t2 = new MyThread2(obj);
        t1.start();
        t2.start();
    }
}

// 1 10 20 2 30 3 4 40 
// 순서는 계속 바뀔 수 있음, 둘이 섞여서 엉망으로 출력됨

 

11-1) 동기화 (Synchronization)

- 한 번에 하나의 스레드만이 공유 데이터를 접근할 수 있도록 제어하는 것이 필요

- 자원에 한 번에 하나의 스레드만이 접근할 수 있고, 하나의 스레드 작업이 끝나면 다음 스레드가 사용할 수 있도록 하여 해결

- 자바에서의 동기화 방법

  - 동기화 메소드

  - 동기화 블록

  - 정적 동기화

  - 락(lock) 또는 모니터(monitor) 사용

// 메소드 앞에 synchronized 키워드 붙이기
class Printer {
    synchronized void print(int[] arr) {
        ....
    }
}

// 10 20 30 40 1 2 3 4



// 부분 코드만 동기화 -> synchronized 블록으로 설정
class Printer {
    void print(int[] arr) throws Exception {
        synchronized(this) {
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
                Thread.sleep(100);
            }
        }
    }
}

 

12. 교착 상태, 기아 상태

- 동일한 자원을 접근하려고 동기화를 기다리면서 대기하는 스레드들이 많아지면 JVM이 느려지거나 일시 중단 되기도 함

 

- 문제 예제

public class DeadLockTest {
    public static void main(String[] args) {
        final String res1 = "Gold";
        final String res2 = "Silver";
        
        Thread t1 = new Thread(() -> {
            synchronized(res1) {
                System.out.println("Thread 1 : 자원 1 획득");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized(res2) {
                    systme.out.println("Thread 1 : 자원 2 획득");
                }
        }});
        
        Thread t2 = new Thread(() -> {
            synchronized(res2) {
                System.out.println("Thread 2 : 자원 2 획득");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized(res1) {
                    System.out.println("Thread 2 : 자원 1 획득");
                }
        }});
        
        t1.start();
        t2.start();
    }
}

// Thread 1 : 자원 1 획득
// Thread 2 : 자원 2 획득

 

12-1) 방법1 : 잠금 순서 변경

Thread t2 = new Thread(() -> {
    synchronized(res1) {
        System.out.println("Thread 2 : 자원 1 획득");
        try { Thread.sleep(100); } catch (Exception e) {}
        synchronized(res2) {
            System.out.println("Thread 2 : 자원 2 획득");
        }
}});

/**
Thread 1 : 자원 1 획득
Thread 1 : 자원 2 획득
Thread 2 : 자원 1 획득
Thread 2 : 자원 2 획득
**/

 

12-2) 방법2: 스레드 간의 조정

13. wait(), notify()

- 이벤트가 발생하면 알리는 방법

- 생산자/소비자 문제에 적용

 

// buffer 클래스
class Buffer {
    private int data;
    private boolean empty = true;
    public synchronized int get() {
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        empty = true;
        notifyAll();
        return data;
    }
    
    public synchronized void put(int data) {
        while (!empty) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        empty = false;
        this.data = data;
        notifyAll();
    }
}

// 생산자
class Producer implements Runnable {
    private Buffer buffer;
    
    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }
    
    public void run() {
        for (int i = 0; i < 10; i++) {
            buffer.put(i);
            System.out.println("생산자: " + i + "번 케익을 생산하였습니다.");
            try {
                Thread.sleep((int) (Math.random() * 100));
            } catch (InterruptedException e) {
            }
        }
    }
}

// 소비자
class Consumer implements Runnable {
    private Buffer buffer;
    
    public Consumer(Buffer drop) {
        this.buffer = drop;
    }
    
    public void run() {
        for (int i = 0; i < 10; i++) {
            int data = buffer.get();
            System.out.println("소비자: " + data + "번 케익을 소비하였습니다.");
            try {
                Thread.sleep((int) (Math.random() * 100));
            } catch (InterruptedException e) {
            }
        }
    }
}

public class ProducerConsumerTest {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        
        (new Thread(new Producer(buffer))).start();
        (new Thread(new Consumer(buffer))).start();
    }
}

/**
생산자: 0번 케익을 생산하였습니다.
소비자: 0번 케익을 소비하였습니다.
생산자: 1번 케익을 생산하였습니다.
소비자: 1번 케익을 소비하였습니다.
...
생산자: 9번 케익을 생산하였습니다.
소비자: 9번 케익을 소비하였습니다.
**/

'Software > JAVA' 카테고리의 다른 글

[Baekjoon] 1085.직사각형에서 탈출  (0) 2023.04.03
[JAVA] Day13. 파일 입출력  (0) 2023.01.18
[JAVA] Day12. 제네릭과 컬렉션  (0) 2023.01.11
[JAVA] Day11. 자바 그래픽  (5) 2023.01.10
[JAVA] Day10. 스윙 컴포넌트  (0) 2023.01.09

1. 스트림 Stream

- 순서가 있는 데이터의 연속적인 흐름

- 입풀력 단위에 따라 분류

 

바이트 스트림 문자 스트림
바이트 단위로 입출력하는 클래스 문자 단위로 입출력하는 클래스
추상 클래스인 InputStream, OutputStream에서 파생됨 기본 추상 클래스인 Reader, Writer 클래스에서 파생됨
바이트 스트림 클래스 이름에는 InputStream(입력), OutputStream(출력)이 붙음 문자 스트림 클래스 이름에는 Reader(입력), Writer(출력)이 붙음

 

2. 문자 스트림

- 입출력 단위가 문자 (바이트 X)

- 자바는 유니코드 이용하여 문자 저장

 

- 주요 메소드

 

- 파일에서 문자 읽고 쓸 때는 FileReader, FileWriter 사용

- 파일에서 문자 읽는 경우 일반적으로 반복문 사용 

int ch;
while ((ch = fr.read()) != -1)
    System.out.print((char) ch + " ");
import java.io.*;

public class FileReaderTets(String met){ 
    public static void main(String[] args) {
        FileReader fr;
        try {
            fr = new FileReader("test.txt");
            int ch;
            while((ch fr.read() != -1)
                System.out.print((char) ch + " ");
            fr.close();
        } catch (IOException e) {
            e.printSTackTrace();
        }
    }
}

 

3. try-with-resources 사용

- close() 따로 호출하지 않아도 자동으로 호출

import java.io.*;

public class FileReaderTest2 {
    public static void main(String[] args) throws Exception {
        try(FileReader fr = new FileReader("test.txt")) {
            int ch;
            while((ch = fr.read()) != -1 )
                System.out.print((char) ch);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

4. 중간 처리 스트림

 

- 자료형이 다른 몇 개의 데이터 파일에 출력했다가 다시 읽기

import java.io.*;

public class DataStreamTest {
    public static void main(String[] args) throws IOException {
        DataInputStream in = null;
        DataOutputStream out = null;
        try {
            out = new DataOutputStream(new FileOutputStream("data.bin"));
            out.writeInt(123);
            out.writeFloat(123.456F);
            out.close();
            
            in = new DataInputStream(new FileInputStream("data.bin"));
            int aint = in.readInt();
            float afloat = in.readFloat();
            
            System.out.println(aint);
            System.out.println(afloat);
        }
        finally {                 // 예외에 상관없이 실행
            if (in != null)       // in이 생성 되어있다면
                in.close();
            
            if (out != null)
                out.close();
        }
    }
}

// 123
// 123.456

 

5. 버퍼 스트림

- 버퍼 입력 스트림: 입력 장치에서 한번에 많이 읽어서 버퍼에 저장, 입력을 요구하면 버퍼에서 꺼내서 반환함

- 버퍼가 비었을 때만 입력 장치에서 읽음

 

inputStream = new BufferedReader(new FileReader("input.txt"));
outputStream = new BufferedWriter(new FileWriter("output.txt"));

 

- 줄 단위로 복사하기 (BufferedReader, PrintWriter 클래스 사용)

import java.io.*;

public class CopyLines {
    public static void main(String[] args) {
        try (BufferedReader in = new BufferedReader(new FileReader("test.txt"))) {
            PrintWriter out = new PrintWriter(new FileWriter("output.txt"))) {
                String line;
                while ((line = in.readLine()) != null) {
                    out.println(line);
                }
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}

 

6. InputStreamReader, OutputStreamWriter 클래스

- 바이트 스트림과 문자 스트림을 연결하는 두 개의 범용 브릿지 스트림

 

6-1) 한글 코드

- ASCII 

- EUC-KR : 한글 완성형, 16비트, 국내 규격

- CP949 (MS949) : 한글 지원을 위해 MS 윈도우 계열에서 등장한 확장 완성형 인코딩 방식, ANSI

- 유니코드 : 전 세계에서 사용하는 수많은 문자들 각각에 부여한 코드들의 집합

- UTF-8 : 1~4byte로 인코딩하는 가변 길이 인코딩 방식, 기본적으로 첫 128개의 문자들은 1byte에 그대로 인코딩

- UTF-16 : 16bit 기반의 인코딩 방식, 한글 2byte, ANSI와 호환이 되지 않는 문제 있음

 

6-2) InputStreamReader

- 바이트 스트림 -> 문자 스트림으로 변환

BufferedReader in = new BufferedReader(new InputStreamReader(
    new FileInputStream(FileDir), "UTF8"));

 

- UTF-8 코딩 파일 읽기

public class CharEncodingTest {
    public static void main(String[] args) throws IOException {
        File FileDir = new File("input.txt");
        BufferedReader in = new BufferedReader(new InputStreamReader(
            new FileInputStream(fileDir), "UTF-8"));
        String str;
        while ((str=in.readLine()) != null) {
            System.out.println(str);
        }
        in.close();
    }
}

 

7. 객체 저장하기 :  객체 직렬화

 

- 객체 직렬화 : 객체가 가진 데이터들을 순차적인 데이터로 변환

- 순차적인 데이터가 되면 파일에 쉽게 저장할 수 있음

- 직렬화 지원 : Serializable 인터페이스 구현

- 역직렬화 : 직렬화된 데이터를 읽어서 자신의 상태를 복구하는 것 

 

- Date 객체 저장하기

public class ObjectStreamTest {
    public static void main(String[] args) throws Exception {
        ObjectInputStream in = null;
        ObjectOutputStream out = null;
        int c;
        
        out = new ObjectOutputStream(new FileOutputStream("object.dat"));
        out.writeObject(new Date());
        out.close();
        
        in = new ObjectInputStream(new FileInputStream("object.dat"));
        Date d = (Date) in.readObject();
        System.out.println(d);
        in.close();
    }
}


// Sat Jan 06 14:46:32 KST 2018

 

8. Path 객체

- 경로를 나타내는 클래스

- "D:\sources\test.txt" 와 같은 경로를 받아서 객체 반환

public class PathTest {
    public static void main(String[] args) {
        Path path = Paths.get("D:\\sources\\test.txt");
        System.out.println("전체 경로: " + path);
        System.out.println("파일 이름: " + path.getFileName());
        System.out.println("부모 이름: " + path.getParent().getFileName());
    }
}

/**
전체 경로: D:\sources\test.txt
파일 이름: test.txt
부모 이름: sources
**/

 

9. File 객체

- 파일을 조작하고 검사하는 코드를 쉽게 작성하게 해주는 클래스

- 파일이 아닌, 파일 이름을 나타내는 객체!

File file = new File("data.txt");

 

10. 스트림 라이브러리로 파일 처리하기

// 현재 디렉터리의 모든 파일을 출력하는 코드
Files.list(Paths.get(".")).forEach(System.out::println);
// 파일 읽어서 각 줄 끝에 있는 불필요한 공백을 제거하고 빈 줄을 필터링한 후에 출력
Files.lines(new File("test.txt").toPath())
     .map(s -> s.trim())
     .filter(s -> !s.isEmpty())
     .forEach(System.out::println);

 

 

'Software > JAVA' 카테고리의 다른 글

[Baekjoon] 1085.직사각형에서 탈출  (0) 2023.04.03
[JAVA] Day14. 멀티 스레딩  (2) 2023.01.18
[JAVA] Day12. 제네릭과 컬렉션  (0) 2023.01.11
[JAVA] Day11. 자바 그래픽  (5) 2023.01.10
[JAVA] Day10. 스윙 컴포넌트  (0) 2023.01.09

1. 제네릭 (generic programming)

    • 다양한 종류의 데이터 를 처리할 수 있는 클래스와 메소드를 작성하는 기법
  • ex)
  • class Box<T> { ... } // T : 타입 매개변수 - String도 될 수 있고, Integer도 될 수 있음

2. 기존의 방법

  • 일반적인 객체를 처리하려면 Object 참조 변수 사용: 어떤 객체이든지 참조 가능
public class Box { 
    private Object data; 
    private void set(Object data) { this.data = data; } 
    public Object get() { return data; } 
}

Box b = new Box();

b.set("Hello World!"); // 문자열 객체 저장  
String s = (String)b.get(); // Object 타입을 String 타입으로 형변환

b.set(new Integer(10)); // 정수 객체 저장  
Integer i = (Integer)b.get(); // Object 타입을 Integer 타입으로 형변환

b.set("Hello World!");  
Integer i = (Integer)b.get(); // 오류! 문자열을 정수 객체로 형변환 (x)

3. 제네릭을 이용한 방법

class Box<T> {
    private T data;
    public void set(T data) { this.data = data; }
    public T get() { return data; }
}

Box<String> b = new Box<String>();
b.set("Hello World!");     // 문자열 저장
String s = b.get();

Box<String> stringBox = new Box<>();   // 뒤에 나오는 타입 <> 생략 가능
stringBox.set(new Integer(10));        // 정수 타입을 저장하려고 하면 컴파일 오류!

4. 제네릭 메소드

  • 일반 클래스의 메소드에서도 타입 매개 변수를 사용해서 제네릭 메소드 정의 가능
  • 이 경우에는 타입 매개 변수의 범위가 메소드 내부로 한정됨
public class MyArray {
    public static <T> T getLast(T[] a) {    // 제네릭 메소드 정의
        return a[a.length - 1];
    }
}

public class MyArrayTest {  
public static void main(String\[\] args) {  
String\[\] language = { "C++", "C#", "JAVA" };  
String last = MyArray.getLast(language); // last는 "JAVA"  
System.out.println(last);  
}  
}

// JAVA
  • 예제2
public class GenericTest {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.println(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] iArray = {10, 20, 30, 40, 50};        // wrapper 클래스
        Double[] dArray = {1.1, 1.2, 1.3, 1.4, 1.5};
        Character[] cArray = {'K', 'O', 'R', 'E', 'A'};

        printArray(iArray);
        printArray(dArray);
        printArray(cArray);
    }
}

/**
10 20 30 40 50 
1.1 1.2 1.3 1.4 1.5
K O R E A
**/
  • 제네릭 메소드에는 기본 data type을 매개 변수로 넘길 수 없음!
  • 객체 데이터 타입을 넘겨야 함 -> wrapper 클래스 이용

1. 컬렉션 collection

  • 자바에서 자료구조를 구현한 클래스
  • list, stack, queue, set, hash table 등
  • 컬렉션 인터페이스컬렉션 클래스로 나누어 제공

collection

  • 컬렉션 인터페이스
인터페이스 설명
Collection 모든 자료구조의 부모 인터페이스로서 객체의 모임 나타냄
Set 집합 (중복된 원소 불가)
List 순서가 있는 자료구조 (중복된 원소 가능)
Map 키와 값들이 연관되어 있는 dictionary와 같은 자료구조
Queue 선입선출 자료구조

2. 컬렉션 특징

  • 제네릭 사용
  • 컬렉션에는 int, double과 같은 기초 자료형 사용 불가. 클래스만 가능!
  • 기초 자료형은 wrapper 클래스로 사용 (Integer, Double 등)
  • 기본 자료형을 저장하면 자동으로 래퍼 클래스의 객체로 변환됨 (오토박싱)

3. 컬렉션 인터페이스 주요 메소드

메소드 설명
boolean isEmpty() boolean contains(Object obj) boolean cotainsAll(Colllection<?> c) 공백 상태이면 true 반환 obj 포함하고 있으면 true 반환
boolean add(E element) boolean addAll(Collection<? extends E> from) 원소 추가
boolean remove(Object obj) boolean removeAll(Collection<?> c) boolean retainAll(Collection<?> c) void clear() 원소 삭제
Iterator iterator() Stream stream() Stream parallelStream() 원소 방문
int size() 원소 개수 반환
Object\[\] toArray() T\[\] toArray(T\[\] a) 컬렉션을 배열로 변환

4. 컬렉션 모든 요소 방문하기

String a[] = new String[] {"A", "B", "C", "D", "E" };
List<String> list = Arrays.asList(a);

// 1. for문
for(int i = 0; i < list.size(); i++) 
    System.out.println(list.get(i));

// 2. for-each문
for(String s : list)
    System.out.println(s);

// 3. Iterator(반복자)
String s;
Iterator e = list.iterator();     
while(e.hasNext()) {
    s = (String) e.next();          // e는 Object 타입 반환
    System.out.println(s);
}

// 4. Stream 라이브러리 - forEach 메소드 & 람다식
list.forEach((n) -> System.out.println(n));

5. Iterator

  • 특별한 타입의 객체로, 컬렉션의 원소들에 접근 하는 것이 목적
  • 모든 컬렉션에 적용
메소드 설명
hasNext() 아직 방문하지 않은 원소가 있으면 true 반환
next() 다음 원소 반환
remove() 최근에 반환된 원소 삭제

6. 람다식

  • 나중에 실행될 목적으로 다른 곳에 전달될 수 있는 코드 블록
  • (int a, int b) -> { return a + b; } // 매개변수 연산자 몸체
  • 0개 이상의 매개 변수 가질 수 있음
  • 화살표 -> 는 람다식에서 매개 변수와 몸체 구분
  • 매개 변수의 형식을 명시적으로 선언할 수 있으며, 문맥에서 추정될 수도 있음
    • (int a) == (a)
    • 빈 괄호는 매개 변수가 없음을 뜻함
      • ex. () -> 23
  • 단일 매개 변수이고 타입이 유추가 가능한 경우에는 괄호 사용할 필요 없음
    • ex. a -> return a*a;
  • 몸체에 하나 이상의 문장이 있으면 { }로 묶어야 함
() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 60

() -> { return 3.141592; }

(String s) -> s.length()

(Car c) -> c.getprice() > 150

(int x, int y) -> {
    System.out.print("결과값: ");
    System.out.println(x+y);
}

(Car c1, Car c2) -> c1.getPrice().compareTo(c2.getPrice())

7. 람다식 활용

  • GUI 코드를 작성할 때 함수 몸체를 전달하고 싶은 경우
// 기존
btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("button click");
    }
});

// 람다식
btn.addActionListener( (e) -> {
    System.out.println("button click");
});
  • 스레드 작성시 runnable 인터페이스 구현
// 기존
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("thread start");
    }
}).start();

// 람다식
new Thread( () -> System.out.println("thread start") ).start();
  • 배열의 모든 요소 출력
// 기존
List<Integer> list = Arrays.asList( 1, 2, 3, 4, 5 );

for (Integer n : list )
    System.out.println(n);

// 람다식
list.forEach( n -> System.out.println(n) );

<br>

8. 컬렉션 - Vector 클래스

  • 가변 크기의 배열 (dynamic array)
  • vector의 크기는 자동으로 관리됨

vector

import java.util.*;

public class VectorEx {
    public static void main(String[] args) {
        Vector<String> vec = new Vector<String>(2);
        vec.add("Apple");
        vec.add("Orange");
        vec.add("Mango");

        System.out.println("Vector size: " + vec.size());
        Collections.sort(vec);
        for(String s : vec)
            System.out.print(s + " ");
    }
}

// Vector size: 3
// Apple Mango Orange

9. ArrayList

  • 가변 크기의 배열(Array)
  • ArrayList<String> list = new ArrayList<String>();
메소드 설명
list.add() 원소 추가
list.set(2, "GRAPE") 인덱스 2의 원소를 "GRAPE"로 교체
list.remove(3) 인덱스 3의 원소 삭제
class Point {
    int x, y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public String toString() { return "(" + x + ", " + y + ")"); }
}

public class ArrayListTest {
    public static void main(String[] args) {
        ArrayList<Point> list = new ArrayList();

        list.add(new Point(0, 0));
        list.add(new Point(4, 0));
        list.add(new Point(3, 5));
        list.add(new Point(-1, 3));
        list.add(new Point(13, 2));

        System.out.println(list);
    }
}

// [(0, 0), (4, 0), (3, 5), (-1, 3), (13, 2)]

10. Vector vs ArrayList

  • Vector는 스레드 간 동기화 지원
  • ArrayList는 동기화 X - Vector보다 성능은 우수함
    vs

11. LinkedList

  • 빈번하게 삽입과 삭제가 일어나는 경우에 사용
  • 배열 중간 삽입은 원소들의 이동이 발생하지만, 연결 리스트는 링크만 수정하면 됨

linked

import java.util.*;

public class LinkedListTest {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<String>();

        list.add("MILK");
        list.add("BREAD");
        list.add("BUTTER");
        list.add(1, "APPLE");
        list.add(2, "GRAPE");
        list.remove(3);

        for(int i = 0; i < list.size(); i++)
            System.out.println(list.get(i) + " ");
    }
}

// MILK APPLE GRAPE

12. ArrayList vs LinkedList

  • ArrayList는 인덱스를 가지고 원소에 접근할 경우, 항상 일정한 시간만 소요됨
  • ArrayList는 리스트의 각각의 원소를 위해 노드 객체를 할당할 필요 없음
  • 동시에 많은 원소를 이동해야 하는 경우 System.arraycopy() 메소드 사용 가능
  • 리스트의 처음에 빈번하게 원소를 추가하거나, 내부 원소 삭제를 반복하는 경우에는 LinkedList를 사용하는 것이 나음
  • 이들 연산은 LinkedList에서는 일정한 시간만 걸리지만, ArrayList에서는 원소의 개수에 비례하는 시간 소요됨

13. Set

  • 원소의 중복 허용X
  • 순서 없음
  • 인터페이스 구현 방법
    • HashSet : 해쉬 테이블에 원소 저장 -> 성능 면에서 가장 우수, 순서가 일정하지 않다는 단점
    • TreeSet : red-black tree에 원소 저장 -> 값에 따라 순서가 결정됨, HashSet보다는 느림
    • LinkedHashSet : 해쉬 테이블과 연결 리스트를 결합한 것, 원소 순서 = 삽입 순서
import java.util.*;

public class SetTest {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<String>();

        set.add("Milk");
        set.add("Break");
        set.add("Butter");
        set.add("Cheese");
        set.add("Ham");
        set.add("Ham");

        System.out.println(set);

        if(set.contains("Ham"))
            System.out.println("Ham도 포함되어 있음");
    }
}

// [Ham, Butter, Cheese, Milk, Bread]
// Ham도 포함되어 있음

14. set 대량 연산 메소드

메소드 설명
s1.containsAll(s2) s2가 s1의 부분집합이면 true 반환
s1.addAll(s2) s1을 s1과 s2의 합집합으로 만듦
s1.retainAll(s2) s1을 s1과 s2의 교집합으로 만듦
s1.removeAll(s3) s1을 s1과 s2의 차집합으로 만듦

15. Map

  • Dictionary와 같은 자료 구조
  • 많은 데이터 중 원하는 데이터 빠르게 찾을 수 있음

map

import java.util.*;

public class MapTest {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();

        map.put("kim", "1234");
        map.put("lee", "pass");
        map.put("park", "word");

        System.out.println(map.get("lee"));
        for(String key : map.keySet()) {
            String value = map.get(key);
            System.out.println("key: " + key + ", value: " + value);
        }

        map.remove("lee");
        map.put("choi", "password");
        map.put("choi", "pw");          // 대치 
        System.out.println(map);

16. map의 모든 요소 방문

// 1. for-each 구문 & keySet()
String.out.println("key: " + key + ", value" + map.get(set));

//  2. Iterator
Iterator<String> e = map.keySet().iterator();
while (e.hasNext()) {
    String key = e.next();
    System.out.println("key: " + key + ", value: " + map.get(key));
}

// 3. Stream 라이브러리
map.forEach( (key, value) -> {
    System.out.println("key: " + key + ", value: " + value);
});

17. 큐 Queue

  • 선입선출 (tail에 원소 추가, head에서 원소 제거)
  • Queue 인터페이스로 정의
  • 이 인터페이스를 구현한 3개의 클래스
    • ArrayDeque
    • LinkedList
    • PriorityQueue
메소드 설명
add() 새로운 원소 추가가 큐의 용량을 넘어서지 않으면 원소 추가
remove(), poll() 큐의 처음에 있는 원소 제거하거나 가져옴 정확히 어떤 원소인지는 큐의 정렬 정책에 따라 달라짐
import java.util.LinkedList;
import java.util.Queue;

public class QueueTest {
    public static void main(String[] ars) {
        Queue<Integer> q = new LinkedList<>();

        for (int i = 0; i < 5; i++)
            q.add(i);
        System.out.println("큐의 요소: " + q);

        int e = q.remove();
        System.out.println("삭제된 요소: " + e);
        System.out.println(q);
    }
}

// 큐의 요소: [0, 1, 2, 3, 4]
// 삭제된 요소: 0
// [1, 2, 3, 4]

17-1) 우선순위 큐

  • 원소들이 무작위로 삽입되었더라도 정렬된 상태로 추출
  • remove()를 호출할 때마다 가장 작은 원소가 추출됨
import java.util;

public class PriorityQueueTest {
    public static void main(String[] args) {
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
        pq.add(30);
        pq.add(80);
        pq.add(20);

        System.out.println(pq);
        System.out.println("삭제된 원소: " + pq.remove());
    }
}

// [20, 80, 30]
// 삭제된 원소: 20

18. Collection 클래스

  • 여러 유용한 알고리즘을 구현한 메소드들 제공

18-1) 정렬 sorting

List<String> list = new LinkedList<String>();
list.add("철수");
list.add("영희");
Collections.sort(list);     // 리스트 안 문자열이 정렬됨
  • 리스트 안의 문자열 정렬
import java.util.*;

public class Sort {
    public static void main(String[] args) {
        String[] sample = {"i", "walk", "the", "line"};

        List<String> list = Arrays.asList(sample);    // 배열을 리스트로 변경

        Collections.sort(list);
        System.out.println(list);
    }
}

// [i, line, the, walk]
  • 사용자 클래스의 객체 정렬
import java.util.*;

class Student implements Comparable<Student> {
    int number;
    String name;

    public Student(int number, String name) {
        this.number = number;
        this.name = name;
    }

    public String toString() { return name; }

    public int compareTo(Student s) {
        return s.number - number;
    }
}

public class SortTest {
    public static void main(String[] args) {
        Student array[] = {
            new Student(2, "김철수");
            new Student(3, "이철수");
            new Student(1, "박철수");
        };

        List<Student> list = Arrays.asList(array);
        Collections.sort(list);
        System.out.println(list);
    }
}

// [박철수, 김철수, 이철수]

18-2) 섞기 shuffling

  • 정렬의 반대 동작
  • 리스트에 존재하는 정렬을 파괴해서 원소들의 순서를 랜덤하게 만듦
import java.util.*;

public class Shuffle {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        for(int i = 1; i <= 10; i++) 
            list.add(i);
        Collections.shuffle(list);
        System.out.println(list);
    }
}

// [5, 9, 7, 3, 6, 4, 8, 2, 1, 10]

18-3) 탐색 searching

  • 리스트 안에서 원하는 원소를 찾는 것
  • 선형 탐색 : 정렬되어 있지 않은 경우 처음부터 모든 원소를 방문하는 방법
  • 이진 탐색 : 정렬되어 있는 경우 중간에 있는 원소와 먼저 비교하는 방법
import java.util.*;

public class Search {
    public static void main(String[] args) {
        int key = 50;
        List<Integer> list = new ArrayList<Integer>();

        for(int i = 0; i < 100; i++)
            list.add(i);

        int index = Collections.binarySearch(list, key);
        System.out.println("탐색의 반환값: " + index);
    }
}

// 탐색의 반환값: 50

'Software > JAVA' 카테고리의 다른 글

[JAVA] Day14. 멀티 스레딩  (2) 2023.01.18
[JAVA] Day13. 파일 입출력  (0) 2023.01.18
[JAVA] Day11. 자바 그래픽  (5) 2023.01.10
[JAVA] Day10. 스윙 컴포넌트  (0) 2023.01.09
[JAVA] Day9. 이벤트 처리  (2) 2023.01.08

+ Recent posts