2018년 12월 4일 화요일

[Java] N-I/O(Non-Blocking) 파일 읽기 쓰기 - GatheringByteChannel, ScatteringByteChannel, ByteBuffer 사용.

우리는 지금까지 다음과 같이 살펴보았다.

1.  InputStream / OutputStream : 입, 출력 스트림을 바이트로 처리하여 읽기, 쓰기.
2.  FileInputStream / FileOutputStream : 입, 출력 스트림을 바이트로 파일에 읽기, 쓰기.
3.  BufferedInputStream / BufferedOutputStream : 입, 출력을 버퍼를 사용해서 읽기, 쓰기.
4.  BufferdReader / bufferdWriter : 입, 출력을 텍스트형태로 한 라인씩 읽고 쓰기.
5.  DataInputStream / DataOutputStream : 자바의 데이터 형식으로 읽기, 쓰기 사용.

이제는 ByteBuffer를 통해서, 입, 출력 문자들을 저장한 뒤, Non-Blocking방식으로
저장한 데이터를 읽기, 쓰기 형태로 바꾸는 방법을 사용해보도록 하겠다.

다음시간에는 소켓을 통한 방법을 살펴 볼 예정이며, 이번시간에는 파일에 읽기 쓰기 형태로 알아보도록 하겠다. 

- 소켓에 적용 : Channel을 통해서 읽고 쓰기할땐, 하나의 버퍼를 사용하면됨. (selector라는 역할을 주의 깊게 봐야함.)

- 파일에 적용 : ByteBuffer를 2개 사용하여, 조금 더 버퍼사용 방법에 대해서 살펴보도록 하 겠다. 왜냐하면 버퍼를 사용할땐 지금까지 스트림을 통해 read, write한것과는 사뭇 다르기 때문이다.

예제를 살펴보기 전, 하나만 기억하자.
1. ByteBuffer는 위치를 가리키는 position이 존재한다. (default : 0번에 위치함.)
2. 이 position값은 데이터를 읽기, 쓰기할때 데이터의 위치를 가리킨다.
3. ByteBuffer로 데이터를 읽을 때, position은 1씩 증가함.
(position : 0번에 위치 -> 데이터를 읽음 -> 1번에 위치 즉, 다음 읽을 값을 가리킴.)
4. 마찬가지로 write할때도 3번 형태와 같아짐.
5. 소켓에서는 하나의 ByteBuffer를 사용하게 되는데, 읽고 쓰기할때는, position값을 항상 0번에 위치시키기 위해서 .flip()메소드를 사용함.



예제 - A.txt파일에 ByteBuffer를 사용해서 데이터를 읽어 B.txt파일에 저장하기.(저장할때도 ByteBuffer를 사용.)


▶ A.txt












ByteBuffer_Exam2.java
  1. package org.java.project.hanbat.chapter11;
  2.  
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. import java.nio.ByteBuffer;
  7. import java.nio.channels.GatheringByteChannel;
  8. import java.nio.channels.ScatteringByteChannel;
  9.  
  10. public class ByteBuffer_Exam2{
  11.     public static void main(String[] args) {
  12.         String aFile = "A.txt";
  13.         String bFile = "B.txt";
  14.        
  15.         // ByteBuffer크기를 20으로 지정함. -> 최대 20Byte저장가능.
  16.         ByteBuffer buffer = ByteBuffer.allocate(20); // 읽기, 쓰기 용도의 버퍼
  17.        
  18.         try {
  19.             // 파일에 버퍼의 데이터를 read할때,
  20.             ScatteringByteChannel inChannel = (new FileInputStream(aFile)).getChannel();
  21.            
  22.             // 파일에 버퍼의 데이터를 write할때
  23.             GatheringByteChannel outChannel = (new FileOutputStream(bFile)).getChannel();
  24.            
  25.             // A.txt 파일의 내용을 읽어와서 Buffer에 저장 이때 position은 읽어들인 바이트 수 만큼 위치함.
  26.             int readCnt = inChannel.read(buffer);
  27.             System.out.println("read Count : " + readCnt);
  28.             inChannel.close();
  29.            
  30.             // 읽어온 A.txt의 내용을 콘솔로 출력해보기. byte배열을 사용하여 저장한뒤 출력함.
  31.             // position위치를 0으로 수정함.
  32.             buffer.flip();
  33.             byte[] rByte = new byte[20];
  34.             buffer.get(rByte); // 이때 position값은 rByte에 저장한 만큼 이동하게 된다.
  35.            
  36.             System.out.println("A.txt : " + new String(rByte)); // A.txt파일 내용을 콘솔에 출력
  37.            
  38.             // 다시 버퍼의 position값을 0으로 변경.(B.txt파일에 write해야하므로)
  39. //          buffer.flip();
  40.            
  41.             // B.txt에 읽어온 바이트 버퍼를 write함. 이때 버퍼의 position은 write한 만큼 이동함.
  42.             outChannel.write(buffer); //  
  43.             outChannel.close();
  44.         } catch(IOException e) {
  45.             e.printStackTrace();
  46.         }
  47.     }
  48. }




▶실행결과
B.txt가 빈 파일로 생성 되었으며, 다음과 같은 예외가 발생함.









: 읽어 들인 데이터의 수는 17이며 A.txt의 내용 17바이트만큼 잘 읽어왔다. 그러나 예외처리가 발생했다.
34번 라인을 보면, 읽어온 바이트 버퍼를 바이트 배열에 저장할때, BufferUnderflowException이 발생한다. 왜냐? 읽어온 바이트는 17바이트 인데, 20바이트의 공간을 갖는 배열에 저장하려고 하기 때문이다. 반대로 읽어온 바이트가 20바이트 이상이라면, 20바이트 크기를 갖는 배열에 저장할 때는 아무런 문제가 없다.

즉, 바이트 버퍼를 .get을 통해 바이트 배열에 저장할 때는 바이트 배열의 크기만큼 버퍼의 데이터를 저장하는데, 만약 버퍼의 크기가 더 작다면, 이는 바이트 배열의 공간이 낭비되므로 BufferUnderflowException이 발생한다. 
바이트 버퍼를 바이트 배열에 저장할 때 항상 바이트 배열의 공간보다 적게 데이터를 저장하지 않도록 주의하자.


- 텍스트의 크기를 다시 수정한 결과.


▶ A.txt






▶ 실행결과







▶ B.txt
















: B.txt에 20Byte만큼 저장된 결과를 확인할 수 있다. (띄어쓰기도 포함 되는 것을 주의!)

이로써 바이트 버퍼 사용하는 방법에 대해 살펴보았으며, 다음시간에는 
소켓을 바이트 버퍼를 통해서 서버와 클라이언트가 읽고 쓰는 예제에 대해서 살펴보도록 하겠다.














2018년 11월 29일 목요일

[Android] 안드로이드 ListView를 만들어 BaseAdapter를 사용하기.

안드로이드에서 수 많은 리스트들을 관리하는 방법에 대해서 살펴보자.


페이스북이나, 카카오톡 채팅방 같은 수많은 리스트들은 동적으로 생성이 된다.
이러한 리스트들이 어떻게 보여지는지 이번시간에 구현해보도록하겠다.



ListView란?
1개 이상의 리스트들을 보여지도록 설정하는 레이아웃으로 생각하자.



Adapter란?
ListView가 생성이 되면, 이 안에는 수 많은 데이터들이 저장이 될것이다.
어떤 레이아웃이 배치되고 그 안의 데이터는 어떤 데이터가 들어갈것인지 관리해주는 역할을 Adapter가 해준다.


이어서 코드를 통해 살펴보자


다음과 같은 ListView를 만들어보자.

  1.   여러 배경색이 있는 리스트 뷰. (실제 리스트는 3개만 존재하나, 리스트 뷰에는 100개가 보이는것처럼 보임)
  2.   리스트 클릭 시 정보들이 보임. (BaseAdapter를 사용함.)


1) activity_main.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.    xmlns:app="http://schemas.android.com/apk/res-auto"
  4.    xmlns:tools="http://schemas.android.com/tools"
  5.    android:layout_width="match_parent"
  6.    android:layout_height="match_parent"
  7.    tools:context=".MainActivity">
  8.     <ListView
  9.        android:id="@+id/lv"
  10.        android:layout_width="match_parent"
  11.        android:layout_height="wrap_content" />
  12. </android.support.constraint.ConstraintLayout>



2) list_view.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.    android:layout_width="match_parent"
  4.    android:layout_height="wrap_content"
  5.    xmlns:apps="http://schemas.android.com/apk/res-auto">
  6.     <ImageView
  7.        android:id="@+id/iv"
  8.        android:layout_width="100dp"
  9.        android:layout_height="100dp" />
  10.     <TextView
  11.        android:id="@+id/tv"
  12.        android:layout_width="wrap_content"
  13.        android:layout_height="wrap_content"
  14.        apps:layout_constraintStart_toEndOf="@+id/iv"
  15.        apps:layout_constraintTop_toTopOf="@+id/iv"
  16.        apps:layout_constraintBottom_toBottomOf="@+id/iv"
  17.        android:text="텍스트" />
  18. </android.support.constraint.ConstraintLayout>



3) MainActivity.java
  1. package com.example.rhkdg.myapplication;

  2. import android.graphics.Color;
  3. import android.support.v7.app.AppCompatActivity;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.AdapterView;
  7. import android.widget.LinearLayout;
  8. import android.widget.ListView;
  9. import android.widget.Toast;
  10. import java.util.ArrayList;
  11. public class MainActivity extends AppCompatActivity {
  12.     // 보여질 리스트뷰
  13.     private ListView lv;
  14.     // 리스트들의 안에 담을 데이터들을 저장함.
  15.     private ArrayList<ListInfo> arrayList;
  16.     private MyBaseAdapter adapter;
  17.     @Override
  18.     protected void onCreate(Bundle savedInstanceState) {
  19.         super.onCreate(savedInstanceState);
  20.         setContentView(R.layout.activity_main);
  21.         lv = findViewById(R.id.lv);
  22.         arrayList = new ArrayList<>();
  23.         arrayList.add(new ListInfo("빨간색"Color.RED ));
  24.         arrayList.add(new ListInfo("파란색"Color.BLUE));
  25.         arrayList.add(new ListInfo("초록색"Color.GREEN));
  26.         arrayList.add(new ListInfo("노란색"Color.YELLOW));
  27.         // 어댑터를 지정.(각 리스트들의 정보들을 관리해주는 역할을 한다.)
  28.         adapter = new MyBaseAdapter(this, arrayList);
  29.         // 리스트 뷰의 어댑터를 지정한다.
  30.         lv.setAdapter(adapter);
  31.         // 클릭한 리스트의 데이터 정보반환.
  32.         lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  33.             @Override
  34.             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  35.                 toast(position + "번째 id = " + id);
  36.             }
  37.         });
  38.     }
  39.     public void toast(String msg){
  40.         Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
  41.     }
  42. }



4) MyBaseAdapter.java
  1. package com.example.rhkdg.myapplication;
  2. import android.content.Context;
  3. import android.view.LayoutInflater;
  4. import android.view.View;
  5. import android.view.ViewGroup;
  6. import android.widget.BaseAdapter;
  7. import android.widget.TextView;
  8. import java.util.ArrayList;
  9. public class MyBaseAdapter extends BaseAdapter {
  10.     private ArrayList<ListInfo> data;
  11.     private Context context;
  12.     public MyBaseAdapter(Context context, ArrayList<ListInfo> data){
  13.         this.context = context;
  14.         this.data = data;
  15.     }
  16.         @Override
  17.     public int getCount() {
  18.         return 100; // 실제 데이터는 4개지만, 리스트 뷰가 100개인 것처럼 보임.
  19.     }
  20.     @Override
  21.     public ListInfo getItem(int position) {
  22.         position %= 4; // 0~3의 데이터의 포지션 값으로 지정해야함.
  23.         return data.get(position);
  24.     }
  25.     @Override
  26.     public long getItemId(int position) {
  27.         return position;
  28.     }
  29.     @Override
  30.     public View getView(int position, View convertView, ViewGroup parent) {
  31.         position %= 4; // 0~3의 데이터의 포지션 값으로 지정해야함.
  32.         View v = LayoutInflater.from(context).inflate(R.layout.listviewnull);
  33.         v.findViewById(R.id.iv).setBackgroundColor(data.get(position).getColor());
  34.         TextView tv = v.findViewById(R.id.tv);
  35.         tv.setText(data.get(position).getTitle());
  36.         return v;
  37.     }
  38. }


5) ListInfo
  1. package com.example.rhkdg.myapplication;
  2. public class ListInfo {
  3.     private String title;
  4.     private int color;
  5.     public ListInfo(String title, int color){
  6.         this.title = title;
  7.         this.color = color;
  8.     }
  9.     public String getTitle(){
  10.         return this.title;
  11.     }
  12.     public int getColor(){
  13.         return this.color;
  14.     }
  15. }




▶ 실행화면


(1. 초기화면)
























(2. 노란색 클릭)



























(3. 맨 위 빨간색 클릭)
























(4. 맨 위에서 두번째 파란색 클릭)






















(5. 스크롤 끝)























: 어댑터 뷰를 통해서 다양하게 리스트들의 데이터를 관리하는 방법에 대해서 살펴보았다.
다음에는 ListView의 단점을 보완한 RecyclerView에 대해서 살펴보자.



- ListView뷰의 단점 

MyBaseAdapter부분의 getView()메소드를 보면, findViewById를 수 없이 하게 된다. findViewById는 앱의 성능을 저하시키므로, 이 작업을 시작할 때 미리 등록해 놓으면 이후 리스트를 스크롤할때마다 findViewById를 하지 않게된다. 이것을 보완해서 등장한것이 RecyclerView이다.




[Java] N-I/O(Non-Blocking) 파일 읽기 쓰기 - GatheringByteChannel, ScatteringByteChannel, ByteBuffer 사용.

우리는 지금까지 다음과 같이 살펴보았다. 1.  InputStream / OutputStream : 입, 출력 스트림을 바이트로 처리하여 읽기, 쓰기. 2.  FileInputStream / FileOutputStream : 입, 출력 스트림을 ...