Showing posts with label Java SE : 3 NIO2. Show all posts
Showing posts with label Java SE : 3 NIO2. Show all posts

July 19, 2012

Asynchronous IO (Part 3)

ပြီးခဲ့သော ဘလောဂ် နှစ်ခန်းဖြင့် Asynchronous IO အကြောင်းကို ရေးသားခဲ့သည်။ ပဋ္ဌမ အခန်းတွင် Asynchronous IO မပေါ်ခင်က အရမ်းလေးလံသော အချက်အလက်များအား Input / Output လုပ်ရာတွင် အသုံးပြုပုံတို့ကို ရေးသားခဲ့ပါသည်။ ဦးစွာ Multi Thread အား အသုံးပြု၍ ရေးသားပုံ၊ ထိုမှတဆင့် Non Blocking IO အား အသုံးပြုရေးသားပုံ တို့အား ဖော်ပြခဲ့ပါသည်။ ဒုတိယ အခန်းတွင်မှု ယခု Java 7 တွင် အသစ် ပါဝင်လာခဲ့သော Nio2 ၏ Asynchronous IO အား အသုံးပြုပုံကို ဖော်ပြခဲ့၏။

Asynchronous IO အား အသုံးပြုရာတွင် အသုံးပြုနေသော လုပ်ဆောင်ချက်၏ အခြေအနေအား သိရှိနိုင်ရန် နည်းလမ်း နှစ်ခုရှိသည်ဟု ဖော်ပြခဲ့၏။ ပဋ္ဌမ နည်းလမ်းမှာ Future Interface အား အသုံးပြု၍ သော်၎င်း၊ လုပ်ဆောင်ချက်များ ပြီးဆုံးသည့်အခါတွင် Event အား လက်ခံရယူခြင်း အားဖြင့် သော်၎င်း၊ Asynchronous လုပ်ဆောင်ချက်၏ ပြီးဆုံးသည့်အချိန်ကို သိရှိစေသည်ဟု ဖော်ပြခဲ့ပါသည်။ ဒုတိယ တစ်ခေါက်တွင် Future Interface အား အသုံးပြု၍ ရေးသားပုံကို ဖော်ပြခဲ့ပြီး ယခုတစ်ခေါက်တွင် Event အား အသုံးပြုရေးသားပုံကို ဆက်လက်ဖော်ပြပါဦးမည်။


CompletionHandler


ယခုတစ်ခေါက်တွင်လည်း ယခင် အခန်းဆက်များကဲ့သို့ Request အပေါ်တွင် Echo ပြန်လုပ်ပေးနိုင်သော Socket Server အား နမှုနာအဖြစ် ရေးသားသွားပါမည်။ Asynchronous IO တွင် Input / Output လုပ်ဆောင်ချက်များအား ပြီးဆုံးကြောင်း အသိပေးနိုင်သည်မှာ java.nio.channels.CompletionHandler အင်တာဖေစ်ပင် ဖြစ်သည်။ Input / Output လုပ်ဆောင်ချက်များ ပြီးဆုံးပါက CompletionHandler#complete အား ခေါ်ယူပြီး၊ လုပ်ဆောင်ချက်တွင် အမှားတစ်ခုခု ဖြစ်ပွားခဲ့ပါက CompletionHandler#fail အား ခေါ်ဆိုမည် ဖြစ်ပါသည်။

CompletionHandler<V,A> တွင် Generics ပါရာမီတာ နှစ်မျိုးကို အသုံးပြုရန်လိုအပ်ပြီး၊ ပဋ္ဌမပါရာမီတာ V မှာ Input / Output လုပ်ဆောင်ချက်၏ ရလဒ်ပုံစံဖြစ်ပြီး၊ ဒုတိယ ပါရာမီတာမှာ Input / Output လုပ်ဆောင်ချက်တွင် Attach လုပ်မည့် Object ၏ ပုံစံဖြစ်ပါသည်။

ကျွှန်တော်တို့သည် ဤတစ်ခေါက်တွင် accept, read နှင့် write တို့အား အသီးသီး CompleteHandler အင်တာဖေစ်အား ပံ့ပိုးထားသည့် ကလပ်စ် အနေဖြင့် ရေးသားပြီး၊ Anonymous Inner Class ရေးသားပုံကို အသုံးပြုရေးသားသွားပါမည်။ လုပ်ဆောင်ချက်များအား အသီးသီး ကလပ်စ် အနေဖြင့် ရေးသားထားပါသဖြင့် ကုဒ်များကို အများကြီးရေသားရန် လိုအပ်မည် ဖြစ်သော်လည်း၊ Anonymous Inner Class ရေးသားပုံမှာ Javascript တွင်လည်း အသုံးများသော ရေးသားနည်း ဖြစ်ပါသဖြင့် နားလည်ရ လွယ်ကူ ပါလိမ့်မည်။

CompletionHandlerServer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class CompletionHandlerServer {

 private static final int PORT = 5000;

 public CompletionHandlerServer() throws IOException {
  AsynchronousServerSocketChannel serverChannel = 
    AsynchronousServerSocketChannel.open();
  serverChannel.bind(new InetSocketAddress(PORT));
  serverChannel.accept(serverChannel, new Acceptor());
 }

 // Acceptor Class
 class Acceptor
   implements
   CompletionHandler<AsynchronousSocketChannel, 
   AsynchronousServerSocketChannel> {

  private final ByteBuffer buffer = ByteBuffer.allocate(1024);

  public Acceptor() {
   System.out.println("an acceptor has created.");
  }

  public void completed(final AsynchronousSocketChannel channel,
    AsynchronousServerSocketChannel serverChannel) {
   System.out.println(String.format("write: name: %s", Thread
     .currentThread().getName()));
   channel.read(buffer, channel, new Reader(buffer));
   serverChannel.accept(serverChannel, new Acceptor());
  }

  public void failed(Throwable exception,
    AsynchronousServerSocketChannel serverChannel) {
   throw new RuntimeException(exception);
  }
 }

 // Reader Class
 class Reader implements
   CompletionHandler<Integer, AsynchronousSocketChannel> {

  private ByteBuffer buffer;

  public Reader(ByteBuffer buffer) {
   this.buffer = buffer;
  }

  public void completed(Integer result, AsynchronousSocketChannel channel) {
   System.out.println(String.format("read: name: %s", Thread
     .currentThread().getName()));
   if (result != null && result < 0) {
    try {
     channel.close();
     return;
    } catch (IOException ignore) {
    }
   }
   buffer.flip();
   channel.write(buffer, channel, new Writer(buffer));
  }

  public void failed(Throwable exception,
    AsynchronousSocketChannel channel) {
   throw new RuntimeException(exception);
  }
 }

 class Writer implements
   CompletionHandler<Integer, AsynchronousSocketChannel> {

  private ByteBuffer buffer;

  public Writer(ByteBuffer buffer) {
   this.buffer = buffer;
  }

  public void completed(Integer result, AsynchronousSocketChannel channel) {
   System.out.println(String.format("write: name: %s", Thread
     .currentThread().getName()));
   buffer.clear();
   channel.read(buffer, channel, new Reader(buffer));
  }

  public void failed(Throwable exception,
    AsynchronousSocketChannel channel) {
   throw new RuntimeException(exception);
  }
 }
 
    public static void main(String[] args) {
        try {
            new CompletionHandlerServer();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
CompletionHandlerServer ကွန်စတရက်တာထဲတွင် AsynchronousServerSocketCnannel အား ဖွင့်ပြီး၊ Port နံပါတ် 5000 တွင် ဆာဗာ ပရိုဆက်အား Bind လုပ်ပါသည်။ ဤနေရာအထိကတော့ အခြားသော ရေးသားနည်းများတွင်လည်း ရေးသားခဲ့ပြီး ဖြစ်ပါသည်။

စာကြောင်း ၁၆တွင် ဆာဗာအော့ဘဂျက်၏ accept လုပ်ဆောင်ချက်ဖြင့်၊ connection များ ဖြစ်ပေါ်လာပါက Asynchronous အနေဖြင့် လက်ခံပြီး လုပ်ဆောင်ပေးမည့် လုပ်ဆောင်ချက်ကို ခေါ်ယူပါသည်။ ထိုသို့ခေါ်ယူရာတွင် Attachment အနေဖြင့် အသုံးပြုမည့် ဆာဗာ အော့ဘဂျက်နှင့်၊ လုပ်ဆောင်ချက်ပြီးဆုံးသည့် အခါတွင် လုပ်ဆောင်နိုင်မည့် CompletionHandler အင်တာဖေစ်အား ပံ့ပုံထားသော အော့ဘဂျက်အား ပါရာမီတာ အဖြစ်အသုံးပြုပါသည်။ ဤနေရာတွင် Accept လုပ်ဆောင်ချက်များအား လုပ်ဆောင်ရန် Acceptor ကလပ်စ်အား new လုပ်၍ Anonymous ကလပ်စ် ပုံစံ အဖြစ်အသုံးပြုထားပါသည်။

SocketClient ထံမှ Connection တစ်ခု ရောက်ရှိလာသောအခါ ဤလုပ်ဆောင်ချက်ကို ခေါ်ယူပြီး၊ ပြီးဆုံးသွားသော အခါ Acceptor#complete အား ခေါ်ယူမည် ဖြစ်ပါသည်။ စာကြောင်း ၃၅ တွင် AsynchronousSocketCnennel#read ဖြင့် Input လုပ်ငန်းများကို လုပ်ဆောင်စေပါသည်။ ဤနေရာတွင်လည်း CompletionHandler အား ပံ့ပိုးထားသော Reader ကလပ်စ်အား အသုံးပြု၍ Input လုပ်ငန်းများ ပြီးဆုံးသည့်အခါများတွင် အလုပ်လုပ်နိုင်သည့် call back အနေဖြင့် အသုံးပြုထားပါသည်။ Asynchronous IO ဖြင့် read လုပ်ဆောင်ချက်အား လုပ်ဆောင်စေပါသဖြင့် read လုပ်ဆောင်ချက်အား ပြီးဆုံးအောင် စောင့်စရာမလိုပဲ၊ စာကြောင်း ၃၆ ဖြင့် ဆာဗာ အင်းစတန့်စ်အား accept လုပ်စေပါသည်။

ဤနည်းအားဖြင့် input လုပ်ငန်းများ ပြီးဆုံးပါက Reader#complete အား ခေါ်ယူမည် ဖြစ်ပါသည်။ စာကြောင်း ၅၈ တွင် Input လုပ်ငန်း၏ ရလဒ်ဖြစ်သော result သည် null ဒါမှမဟုတ် 0 ထက်ငယ်ပါက (Input လုပ်ထားသည်မှာ မကျန်တော့ပါက) စာကြောင်း ၆၀ ဖြင့် AsynchronousSocketChannel အား ပိတ်ပစ်မည်ဖြစ်ပါသည်။ သို့မဟုတ်ပါက စာကြောင်း ၆၆ ဖြင့် AsynchronousSocketChannel#write အား လုပ်ဆောင်စေမည် ဖြစ်ပါသည်။ ဤနေရာတွင်လည်း CompletionHandler အား ပံ့ပိုးထားသော Writer အား အသုံးပြုပါသည်။

Output လုပ်ဆောင်ချက်များ ပြီးဆုံးပါက Writer#complete အား ခေါ်ဆိုမည် ဖြစ်သည်။ အတွင်းပိုင်းတွင် စာကြောင်း ၈၇ဖြင့် အသုံးပြုထားသော Buffer အား clear လုပ်ပြီး တဖန် socket Channel အား read လုပ်စေပြန်သည်။ ဤနည်းအားဖြင့် Client ဆီမှ Connect လုပ်လာသော အချက်အလက်များ မကုန်မချင်း read လုပ်လိုက် write လုပ်လိုက်ဖြင့် အလုပ်လုပ်နေစေခြင်း ဖြစ်ပါသည်။


နောက်ဆုံးတွင်

ကျွှန်တော်တို့သည် တောက်လျှောက် နမှုနာအဖြစ် ဆာဗာ အပလီကေးရှင်းကြီးပဲ ရေးသားလာခဲ့ပါသည်။ စမ်းသပ်ကြည့်ရန် နမှုနာအပလီ Scoket Client အား မရေးသားခဲ့ပါ။ အဲ့ဒီအတွက် နမှုနာဆာဗာ၏ Port Number အား 5000 ဟု အတူတူရေသားထားပါသဖြင့် Port 5000 အား connect လုပ်သည့် Client ပရိုဂရမ်တစ်ခုဖြင့် အသုံးပြုခဲ့သော Server အပလီကေးရှင်းများအား စမ်းသပ်နိုင်ပါမည်။ Client အပလီကေးရှင်းမှာ အောက်ပါအတိုင်းဖြစ်ပါသည်။

TestMain.java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;

public class TestMain {

 public static void main(String[] args) {
  
  Socket socket = null;
  
  try {
   String server = "localhost";
   int servPort = 5000;
   byte[] data = "Hello, Net world".getBytes();
   byte[] msg = new byte[data.length];

   socket = new Socket(server, servPort);
   System.out.println("Connection To Server");

   InputStream in = socket.getInputStream();
   OutputStream out = socket.getOutputStream();

   out.write(data);
   System.out.println("Send:" + new String(data));

   // get Server Message
   int totalBytesRcvd = 0;
   int bytesRcvd;
   while (totalBytesRcvd < data.length) { 
    if ((bytesRcvd = in.read( 
      msg, totalBytesRcvd, data.length - totalBytesRcvd)) == -1) {
     throw new SocketException("Fail");
    }
    totalBytesRcvd += bytesRcvd;
   } // while end
   System.out.println("Recieve:" + new String(msg));

   socket.close();
   
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   if(null != socket) {
    try {
     socket.close();
    } catch (IOException e) {}
   }
  }
 }
}
ကျွှန်တော်တို့သည် ဤအခန်းဆက် ၃ခန်းဖြင့် Nio တွင် ထည့်သွင်းလာခဲ့သော Non-Blocking IO နှင့် Nio2 တွင် ထည့်သွင်းလာခဲ့သော Asynchronous IO အား အသုံးပြုပုံနှင့် တကွ လေ့လာခဲ့၏။ နှစ်မျိုးစလုံးသည် Input Output ကြောင့် Block ဖြစ်နေသော အချိန်အား အတတ်နိုင်ဆုံး လျှော့ချနိုင်ရန် စီမံ၍ ရေးသားထားသည်ကို တွေ့ရပါသည်။ သို့ရာတွင် ဘယ် IO က အကောင်းဆုံးလည်း ဆိုသည်မှာ တစ်ခုနှင့် တစ်ခု တည်ဆောက်ပုံ ရည်ရွယ်ချက်ချင်း မတူညီပါသဖြင့် နှိုင်းယှဉ်၍မရနိုင်ပါ။ ကိုယ် အသုံးပြုလိုသည့် အပလီကေးရှင်းအပေါ် မှုတည်၍ ရွေးချယ်သင့်ပါသည်။


ကိုးကား

http://masayuki038.github.com/blog/2012/04/30/nio2-asyncio/
http://itpro.nikkeibp.co.jp/article/COLUMN/20110927/369451/?ST=develop&P=8

ဆက်ပါဦးမည်။ လေးစားစွာဖြင့်။
မင်းလွင်

July 18, 2012

Asynchronous IO (Part 2)

ကောင်မလေးများ ရေထဲတွင် ညီတူစွာ ရေထဲတွင် ကပြနိုင်သော Synchro ဟုခေါ်သော ရေးကူနည်းကို ကြီးဘူးပါက ချက်ချင်းကို မျက်စေ့ထဲ ပြေးမြင်ပါလိမ့်မည်။ Synchronous ဆိုသည်မှာ အချိန်တူစွာ အလုပ်လုပ်ခြင်းကို ဆိုလိုပါသည်။ Asynchronous ဆိုသည်မှာ Synchronous ၏ ပြောင်းပြန် ဖြစ်ပြီး အလုပ်တစ်ခုနှင့် တစ်ခု စောင့်စရာမလိုပဲ သီးခြားစီ အလုပ်လုပ်နိုင်ခြင်းကို Asynchronous ဟုခေါ်ဆိုပါသည်။

Asynchronous IO သည် အမည်အတိုင်း Input Output လုပ်နေစဉ် ကြံ့ကြာတတ်သော အချိန်အား စောင့်စရာမလိုပဲ အခြားသော လုပ်ဆောင်ချက်များအား ဆက်လုပ်စေနိုင်သော API ဖြစ်ပါသည်။ Asynchronous IO အား java.nio.channels.AsynchronousChannel အင်တာဖေစ် မှတဆင့် အသုံးပြုနိုင်ပါသည်။ လက်ရှိ အချိန်တွင် Socket Connection နှင့် File IO များတွင် အသုံးပြုနိုင်ပါသည်။ ပံ့ပိုးထားသော Class များမှာ java.nio.channels.AsynchronousSocketChannel နှင့် java.nio.channels.AsynchronousFileChannel တို့ ဖြစ်ကြပါသည်။

အဆိုပါ ကလပ်စ်များတွင် read နှင့် write လုပ်ဆောင်ချက်များအား လုပ်ဆောင်စေရာတွင် ပြီးဆုံးအောင် စောင့်စရာမလိုပဲ ဆက်လက်၍ အခြားသော လုပ်ဆောင်ချက်များအား လုပ်ဆောင်စေနိုင်ပါသည်။ လုပ်ဆောင်ချက်များ ပြီးဆုံးသည်ကို သိရှိနိုင်ရန်မှာ နည်းလမ်းနှစ်ခု ရှိပါသည်။ ပဋ္ဌမနည်းမှာ Concurrency Utilities ၏ java.util.concurrent.Future အား အသုံးပြုသောနည်း ဖြစ်ပြီး၊ နောက်တစ်နည်းမှာ ပြီးဆုံးသော Event အား လက်ခံရယူသောနည်း ဖြစ်ပါသည်။


Future Interface အား အသုံးပြု၍ Asynchronous IO


Future Interface ဖြင့် Asynchronous IO ၏ လုပ်ဆောင်ချက်များ၏ ရလဒ်များအား ရရှိနိုင်ခြင်း၊ လုပ်ဆောင်ချက်များ၏ အနေအထားအား ကြည့်ရှုနိုင်ခြင်း၊ လုပ်ဆောင်ချက်များအား ရပ်တန့်စေနိုင်ခြင်း အစရှိသည်တို့ကို လုပ်ဆောင်စေနိုင်ပါသည်။ ပြီးခဲ့သော ဘလောဂ်ဖြင့် ရေးသားခဲ့သော Echo ပြန်လုပ်ပေးသည့် ဆာဗာ အပလီကေးရှင်းအား Future Interface နှင့် Asynchronous IO အား အသုံးပြု၍ ပြုပြင် ရေးသားကြည့်ပါဦးမည်။

SimpleFutureEchoServer .java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class SimpleFutureEchoServer {
    private static final int PORT = 5000;
    
    public SimpleFutureEchoServer() throws IOException {
        AsynchronousServerSocketChannel serverChannel 
            = AsynchronousServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(PORT));
        
        for (;;) {
            // open asynchronous socket channel
            Future<AsynchronousSocketChannel> future 
                = serverChannel.accept();

            try {
                // get Asynchronous socket channel
                AsynchronousSocketChannel channel = future.get();
                System.out.println("Connect to: " 
                                   + ((InetSocketAddress)channel
                                        .getRemoteAddress()).getHostName());
                // IO Processing
                startEcho(channel);
            } catch (InterruptedException | ExecutionException ex) {}
        }
    }

    private void startEcho(AsynchronousSocketChannel channel) {
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        try {
            for (;;) {
                // Input
                buffer.clear();
                Future<Integer> future = channel.read(buffer);
            
                // get Input Result
                if (future.get() < 0) {
                    try {
                        channel.close();
                    } catch (IOException ex) {}
                }
                
                buffer.flip();
                // output
                channel.write(buffer).get();
            }
        } catch (InterruptedException
                 |ExecutionException ex) {}
    }

    public static void main(String[] args) {
        try {
            new SimpleFutureEchoServer();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
စာကြောင်း ၁၃မှ ၁၅ အထိတွင် AsynchronousServerSocketChannel ၏ အော့ဘဂျက်အား open လုပ်ယူပြီး၊ port နံပါတ် 5000 ဖြင့် bind လုပ်ပါသည်။ ပြီးပါက for ဝါကျဖြင့် client ဆီမှ access လုပ်လာမည်ကို စောင့်နေပြီး အလုပ်လုပ်စေပါသည်။

စာကြောင်း ၁၉နှင့် ၂၀အား ကြည့်ပါ။ AsynchronousServerSocketChannel#accept လုပ်ဆောင်ချက်အား အသုံးပြု၍ AsynchronousSocketChannel အား အသုံးပြုနိုင်သော Future အင်တာဖေစ်၏ အော့ဘဂျက်အား ရယူပါသည်။ ဤနေရာတွင် ထူးခြားသည်မှာ ServerSocketChannel#accept ၏ ရလဒ်သည် SocketChennel ဖြစ်သော်လည်း ဤနေရာတွင်မှု Future အင်တာဖေစ် ဖြစ်သည်ဆိုသည့် အချက်ဖြစ်၏။

တဖန် Asynchronous Process ၏ ရလဒ်အားရရှိရန်မှာ Future#get အား အသုံးပြုနိုင်ပြီး၊ get လုပ်ဆောင်ချက်တွင် Timeout ဖြစ်ရန် အချိန်ကို ပါရာမီတာအဖြစ်လည်း သတ်မှတ် အသုံးပြုနိုင်ပါသည်။ ပြီးပါက စာကြောင်း ၂၉တွင် startEcho လုပ်ဆောင်ချက်ဖြင့် အထက်တွင် ရရှိထားသော AsynchronousSocketChannel ၏ အော့ဘဂျက်အား အသုံးပြု၍ Input Output အား လုပ်ဆောင်စေပါသည်။

startEcho လုပ်ဆောင်ချက် အတွင်းတွင် Input Output လုပ်ဆောင်ချက်များအား လုပ်ဆောင်စေပါသည်။ စာကြောင်း ၄၁အား ကြည့်ပါ။ AsynchronousSocketChannel#read လုပ်ဆောင်ချက်၏ရလဒ်သည် Future အင်တာဖေစ်ဖြစ်ပြီး၊ Future#get ဖြင့် Asynchronous လုပ်ဆောင်ချက်၏ ရလဒ်အား ရရှိနိုင်ပါသည်။ တဖန် စာကြောင်း ၅၂ဖြင့် Output ကို လုပ်ဆောင်စေပြီး၊ get ဖြင့် ရလဒ်အား စောင့်ယူပါသည်။ ဤကဲ့သို့ channel.write(buffer).get() ဟု တိုက်ရိုက် ရေးသားရခြင်းမှာ၊ Asynchronous ဖြင့် အလုပ်လုပ်နေစဉ် buffer အတွင်းရှိ အချက်အလက်များအား ပြီးအောင် မရေးရသေးခင်၊ for ဝါကျ၏ ထိပ်သို့ ပြန်ရောက်သွားပြီး buffer အား clear မလုပ်မိစေရန် ဖြစ်ပါသည်။

ဤနည်းအားဖြင့် Asynchronous IO တွင် Future အင်တာဖေစ်အား အသုံးပြု၍ Input output များအား အသုံးပြုနိုင်ကြောင်း တွေ့ရှိရမည် ဖြစ်သည်။ သို့ရာတွင် သေသေချာချာကြည့်မည်ဆိုပါက Thread တစ်ခုတည်းကို သာအသုံးပြုနေသည်ကို တွေ့ရပါမည်။ ထို့ကြောင့် Access များလာပါက အလားတူ Performance ကျဆင်းသွားမည်မှာ မလွဲပေ။ ဒါကြောင့်ဆိုပြီး Thread Pool ကို အသုံးပြုပြန်ရင်လည်း Asynchronous IO ကို အသုံးပြုသည်မှာ အဓိပ္ပါယ်ရှိမည် မဟုတ်။

ကျွှန်တော်တို့ ဆက်လက်၍ Work Thread နှင့်Main Thread ကို ခွဲရေးပြီး၊ Thread နှစ်ခုကြားတွင် Queue ဖြင့် အချက်အလက်များအား လက်ကမ်းပေးသည့် ပုံစံကို ဆက်လေ့လာ ကြည့်ပါဦးမည်။


Work Thread အား အသုံးပြုခြင်း


FutureEchoServer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class FutureEchoServer {
    private static final int PORT = 5000;
    private LinkedBlockingQueue<AsyncInfo> queue;
    
    static class AsyncInfo {
        private Future<Integer> future;
        private AsynchronousSocketChannel channel;
        private ByteBuffer buffer;
 
        public AsyncInfo(Future<Integer> future,
                         AsynchronousSocketChannel channel,
                         ByteBuffer buffer) {
            this.future = future;
            this.channel = channel;
            this.buffer = buffer;
        }
    }
    
    public FutureEchoServer() throws IOException {
        AsynchronousServerSocketChannel serverChannel 
            = AsynchronousServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(PORT));
        
        for (;;) {
            // open asynchronous socket channel
            Future<AsynchronousSocketChannel> future 
                = serverChannel.accept();

            try {
                // get Asynchronous socket channel
                AsynchronousSocketChannel channel = future.get();
                System.out.println("Connect to: " 
                                   + ((InetSocketAddress)channel
                                        .getRemoteAddress()).getHostName());
                // Watching Queue
                startWatchQueue();
                // IO Processing
                startEcho(channel);
            } catch (InterruptedException | ExecutionException ex) {}
        }
    }

    private void startEcho(AsynchronousSocketChannel channel) {
      
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.clear();
        // Asynchronous Input
        Future<Integer> future = channel.read(buffer);
 
        // Queue Processing
        queue.offer(new AsyncInfo(future, channel, buffer));
    }
    
    private void startWatchQueue() {
        // queue for receiving data
        queue = new LinkedBlockingQueue<>();
 
        // work thread
        Runnable runnable = new Runnable() {
            public void run() {
                try {
                    for (;;) {
                        // get asynchronous info from queue
                        AsyncInfo info = (AsyncInfo) queue.take();
                        Future<Integer> future = info.future;
                    
                        // get result
                        // Timeout = 100ms 
                        int n = 0;
                        try {
                            n = future.get(100, TimeUnit.MILLISECONDS);
                        } catch(TimeoutException ex) {}
                    
                        if (future.isDone()) {
                            // The process is done
                            if (n < 0) {
                                try {
                                    info.channel.close();
                                } catch (IOException ex) {}
                                continue;
                            }
 
                            AsynchronousSocketChannel channel 
                                = info.channel;
                            ByteBuffer buffer = info.buffer;
 
                            // Output
                            buffer.flip();
                            channel.write(buffer).get();
                            
                            // Asynchronous Input
                            buffer.clear();
                            Future<Integer> nextFuture 
                                = channel.read(buffer);
                            
                            // Next input to queue
                            queue.offer(new AsyncInfo(nextFuture,
                                                      channel,
                                                      buffer));
                        } else {
                            // If process is still working
                            queue.offer(info);
                        }
                    }
                } catch (InterruptedException
                         | ExecutionException ex) {
                }
            }
        };
 
        // Work Thread Start
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(runnable);
    }
    
    public static void main(String[] args) {
        try {
            new FutureEchoServer();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

ဦးစွာ စာကြောင်း ၁၈မှ ၃၀ အထိ Queue ဖြင့် အချက်အလက်များအား လက်ဆင့်ကမ်းရန် အတွက် ကလပ်စ် တစ်ခုကို ရေးသားပါသည်။ Work Thread နှင့် Main Thread အကြားတွင် အချက်အလက်များအား လက်ဆင့်ပေးရန် အတွက်ဖြစ်ပါသည်။ Main Thread မှ အချက်အလက်များအား Queue တွင် ထည့်ထားပါက၊ အခြားသော Work Thread မှ တဆင့် ထို Queue ထဲမှ အချက်အလက်များအား ထုတ်ယူ အသုံးပြုနိုင်မည် ဖြစ်ပါသည်။ Future Object, AsynchronousSocketChannel နှင့် ByteBuffer Object တို့ကို ပိုင်ဆိုင်ပါသည်။

Asynchronous Socket Channel ကို ခေါ်ယူသည့် နေရာအထိကတော့ အထက်ပါ နမှုနာနဲ့ အတူတူပဲ ဖြစ်ပါတယ်။ Socket Channel ကို ခေါ်ယူပြီးသည့်အခါတွင် Work Thread ကို အလုပ်လုပ်စေရန် အတွက် စာကြောင်း ၄၉တွင် startWatchQueue လုပ်ဆောင်ချက်ကို ခေါ်ယူပါသည်။ ပြီးပါက Input လုပ်ဆောင်ချက်ကို လုပ်ဆောင်စေမည့် startEcho လုပ်ဆောင်ချက်ကို ခေါ်ယူပါသည်။

startEcho လုပ်ဆောင်ချက်အတွင်းတွင် Input လုပ်ငန်းများကို လုပ်ဆောင်မည့် future Object အား ရယူကာ Queue တွင် Registration ပြုလုပ်ပါသည်။ တကယ်တန်း Input Output လုပ်ငန်းများအား လုပ်ဆောင်မည်မှာ အထက်ပါ startWatchQueue တွင် စောင့်ကြည့်နေစေသော Work Thread အတွင်းတွင် ဖြစ်ပါသည်။

startWatchQueue တွင် စာကြောင်း ၇၇ဖြင့် အခြားသော Thread တစ်ခု အနေဖြင့် အလုပ်လုပ်စေနိုင်သော Runnable အင်တာဖေစ်၏ အော့ဘဂျက်အား သတ်မှတ်ရေးသားကာ၊ စာကြောင်း ၁၂၅တွင် Single Thread အနေဖြင့် အလုပ်လုပ်စေပါသည်။ ဤနည်းအားဖြင့် Runnable အော့ဘဂျက်သည် Work Thread အဖြစ်အလုပ်လုပ်ကာ Queue အား စောင့်ကြည့်ပါသည်။

Runnable Object အတွင်းမှာမူ စာကြောင်း ၇၇ ဖြင့် Queue အတွင်းမှ အသုံးပြုလိုသည့် info အား ထုတ်ယူကာ၊ စာကြောင်း ၇၈ဖြင့် info အတွင်းမှ input ကို လုပ်ဆောင်နိုင်သော future အား ရယူပါသည်။ စာကြောင်း ၈၇ ဖြင့် future သည် အလုပ်ပြီးပြီလားဆိုသည်ကို စစ်ဆေးပြီး၊ အလုပ်ပြီးပြီဆိုပါက စာကြောင်း ၈၉ဖြင့် ဖတ်စရာကျန်မကျန် စစ်ဆေးပြီး၊ မကျန်တော့ပါက Asynchronous Socket Channel အား close လုပ်မည် ဖြစ်ပါသည်။ ကျန်သေးပါက Buffer အတွင်းရှိ အချက်အလက်များအား Output လုပ်ပြီး၊ future Object အား အသစ်ခေါ်ယူကာ စာကြောင်း ၁၁၀ ဖြင့် Queue အတွင်းသို့ ပြန်ထည့်ပေးလိုက်ပါသည်။ ဤနည်းအားဖြင့် Input အချက်အလက်များ မကုန်မချင်း အကြိမ်ကြိမ် အလုပ်လုပ်စေပါသည်။


နောက်ဘလောဂ်ဖြင့် CompletionHandler ကို အသုံးပြုကာ AsynchronousSocketChannel အား အသုံးပြုရေးသားပုံကို ဆက်လက်ဖော်ပြပါဦးမည်။


ကိုးကား

http://itpro.nikkeibp.co.jp/article/COLUMN/20110927/369451/?ST=develop&P=4


လေးစားစွာဖြင့်
မင်းလွင်

July 14, 2012

Asynchronous IO (Part 1)

Java 7 ထွက်ပြီး၍ ယခုဆိုလျှင် Update 5 ပင် ရောက်ရှိလာပြီ ဖြစ်၏။ Update 4 မှစ၍ Mac OS X အား Support လုပ်လာပြီး၊ G1GC ကိုလည်း တရားဝင် Support လုပ်လာပါသည်။ ထို့အပြင် JRockit ၏ ဖန်ရှင် အချို့သည်လည်း HotSpot VM ဆီသို့ ဖြည့်စွက်လာခဲ့ပါသည်။ ထို့အပြင် Bug အတော်များများကိုလည်း Fix လုပ်လာပြီး Performance ပိုင်းဆိုင်ရာ တွင်လည်း ပြုပြင်ပြောင်းလည်းမှု့များကို ပြုလုပ်လာခဲ့ကြပါသည်။

အထူးသဖြင့် Java 7 အား http://java.com မှတဆင့် ဖြန့်ဝေလာခြင်းကိုကြည့်ခြင်း အားဖြင့် Java 7 သည် Business Application များအတွက် အဆင်သင့်ဖြစ်ပြီဖြစ်ကြောင်း သိရှိနိုင်ပါသည်။ Java 6 ၏ EOL ဖြစ်သော ၂၀၁၂ခု ၁၁လပိုင်းသည်လည်း သိပ်ပြီး မဝေးတော့ပြီဖြစ်သောကြောင့် Java 7 အား ပြောင်းရွှေ့အသုံးပြုမည့် ပရိုဂျက်များအား မကြာမကြာ တွေ့မြင်ရမည်ဖြစ်ပါသည်။

ကျွှန်တော်တို့ ဆက်လက်၍ Nio2 ၏ Asynchronous IO နှင့် ပတ်သက်၍ ဆက်လက်လေ့လာသွားပါမည်။


ယခင် အသုံးပြုခဲ့သော IO

ပုံမှန်အားဖြင့် Input Output လုပ်ဆောင်ချက်များအား အသုံးပြုရာတွင်၊ လုပ်ဆောင်ချက်များ ပြီးဆုံးသည် အထိ Control အား အသုံးပြုနိုင်မည် မဟုတ်ပေ။ ဥပမာအားဖြင့် InputStream ဖြင့် stream.read(bytes) ဟု အသုံးပြုပါက အားလုံး ဖတ်ပြီးသည့်တိုင်အောင် read ဆီသို့ ပြန်ရောက်လာခြင်း မရှိပေ (Exception များမှအပ)။ ဤသို့ လုပ်ဆောင်ချက်များအား ရပ်တန့်သွားစေခြင်းအား Block ဖြစ်ခြင်းဟု Java တွင် ခေါ်ဆိုလေ့ရှိ၏။

အကယ်၍ Input Output Speed သည် အလွန်မြန်နေပါက အကြောင်းမဟုတ်။ သို့ရာတွင် CPU ၏ speed နှင့် စာလျှင် နှိုင်းယှဉ်၍မရအောင် နှေးကွေးလွန်းလှပေသည်။ ထို့ကြောင့် အလွန်လေးလံသော File များအား Input Output လုပ်ပါက စက်တစ်ခုလုံး လေးလံသွားစေနိုင်ပါသည်။

အထူးသဖြင့် Web Server များကဲ့သို့ Input Output အတော်များများကို ပြုလုပ်လေ့ရှိ သောကြောင့် Input Output ကြောင့် ဖြစ်ပွားစေသော Block သည် Web Server ၏ Performance အား လွန်စွာ အကျိုးသက်ရောက် စေနိုင်ပါသည်။

ပုံမှန်အားဖြင့် ဤကဲ့သို့သော အခြေအနေမျိုးတွင် Block ဖြစ်နေစဉ် အခြားသော လုပ်ဆောင်ချက်များအား ပြုလုပ်နိုင်ရန် စီမံ၍ အထက်ပါ အခက်အခဲများအား ဖြေရှင်းလေ့ရှိပါသည်။ ဥပမာအားဖြင့် Thread Pool များအား အသုံးပြု၍ Parallel Processing လုပ်ခြင်းအားဖြင့် Block ကြောင့် ဖြစ်တတ်သော စွမ်းရည် ကျဆင်းမှု့ကို ကာကွယ်စေခဲ့ပါသည်။ နမှုနာ ကုဒ်များအား လေ့လာကြည့်ပါမည်။

SimpleEchoServer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleEchoServer {
    private static final int PORT = 5000;
    private ExecutorService service;
    
    public SimpleEchoServer() throws IOException {
        // new thread pool
        service = Executors.newCachedThreadPool();

        // server socket
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(PORT));

        for (;;) {
            // wait
            SocketChannel channel = serverChannel.accept();
            System.out.println("Connect to: " 
                               + channel.socket()
                                   .getInetAddress().getHostName());

            // access from client
            startEcho(channel);
        }
    }

    public void startEcho(final SocketChannel channel) {
        // I/O processing
        Runnable runnable = new Runnable() {
            public void run() {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                    
                try {
                    for (;;) {
                        // Input
                        buffer.clear();
                        if (channel.read(buffer) < 0) {
                            break;
                        }
                        
                        // Output
                        buffer.flip();
                        channel.write(buffer);
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                } finally {
                    try {
                        channel.close();
                    } catch(IOException ex) {}
                }
            }
        };

        // execute by thread pool
        service.execute(runnable);
    }

    public static void main(String[] args) {
        try {
            new SimpleEchoServer();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

}
အထက်ပါ ကုဒ်များ၏ စာကြောင်း ၁၅တွင် Concurrency API ၏ Excecutors အား အသုံးပြု၍ Thread Pool အား အသုံးပြုနိုင်သော ExcecutorService ၏ Instance အား ခေါ်ယူပါသည်။ တဖန်စာကြောင်း ၂၁၏ for loop ဖြင့် ဆာဗာအားစောင့်နေစေပြီး client မှ request လာသောအခါ စာကြောင်း၂၉ဖြင့် startEcho လုပ်ဆောင်ချက်ကို ခေါ်ယူလုပ်ဆောင်စေပါသည်။

setEcho လုပ်ဆောင်ချက်အတွင်းတွင် စာကြောင်း ၃၅မှ ၅၉အထိ Multi Thread Processing များတွင် အသုံးပြုနိုင်သော Runable Interface အား ဖြည့်စွက်ရေးသားပြီး runable Instance အား new လုပ်ပါသည်။ အတွင်းပိုင်းမှာမူ စာကြောင်း ၄၃တွင် channel အား အသုံးပြု၍ ဖတ်ယူထားသည်များကို စာကြောင်း ၄၅တွင် ပြန်ရေးနေရုံသာ ဖြစ်ပါသည်။

စာကြောင်း ၆၂တွင် အထက်ပါ runnable Instance အား Thread Pool မှ Thread တစ်ခု ဖြင့် အလုပ်လုပ်စေခြင်း သာဖြစ်၏။ ဤကဲ့သို့ Multi Thread Processing နည်းလမ်းအား အသုံးပြုခြင်း အားဖြင့် Input Output ကြောင့် Block ဖြစ်နေစဉ်ကာလ ဖြင့်လင့်ကစား ဆာဗာသည် အခြားသော လုပ်ငန်း ဆောင်တာများကို ပြုလုပ်နိုင်မည် ဖြစ်ပါသည်။

သို့ရာတွင်အကယ်၍ Request အရေအတွက်သည် အလွန်များပြားလာပါက အသုံးပြုနိုင်သော Thread များ အလွန်နည်းပါး လာပါလိမ့်မည်။ Request အရေအတွက်သည် CPU Core အရေအတွက်ထက် ပိုများလာပါက Thread များအား ပြောင်းလည်းခြင်း လုပ်ငန်း (Context Switch) ကို ဖြစ်ပေါ်စေပါသည်။ Context Switch သည် လွန်စွာ လေးလံသော အလုပ်ဖြစ်ပါသဖြင့် တတ်နိုင်သလောက် ရှောင်ရှား စေလိုပါသည်။ သို့ရာတွင် Request က များလာလေ Context Switch ဖြစ်စေရန် ရာနှုန်းမှာ မြင့်မားလာလေ ဖြစ်ပါလိမ့်မည်။

Server Performance အား မကျဆင်းစေရန် အတွက် တတ်နိုင်သ၍ Thread အရေအတွက်ကို လျှော့ချနိုင်စေရန် Block ဖြစ်သည့် အချိန်ကို လျှော့ချရန် လိုအပ်လာပါသည်။ ဤနေရာတွင် အသုံးပြုသည်မှာ nio ၏ Non Blocking I/O ပင် ဖြစ်သည်။


Non Blocking I/O

Non Blocking I/O သည် I/O လုပ်ငန်းပြီးဆုံး၍ ပြန်လည်အသုံးပြုနိုင်လာနိုင်သော Channel များအား စုစည်း၍ အသုံးပြုနိုင်ပါသည်။ Non Blocking I/O တွင် အဓိက နေရာတွင် အလုပ်လုပ်နေသူမှာ java.nio.channels.Selector ပင် ဖြစ်၏။

Selector အော့ဘဂျက်မှ အသိပေးလာသော Channel သည် အသုံးပြုရန် အသင့်ဖြစ်နေပါသဖြင့် Blocking ဖြစ်နေသော အချိန်ကို အတိုဆုံးဖြစ်အောင် ထိမ်းပေးနိုင်ပါသည်။ တဖန် Non Blocking IO တွင် Channel များအား Selector တစ်ခုဖြင့် ထိမ်းချုပ်နိုင်ပါသဖြင့် အသုံးပြုရန်လိုအပ်သော Thread အရေအတွက်ကို အနည်းဆုံးဖြစ်အောင် စီမံနိုင်ပါသည်။

ပြီးခဲ့သော နမှုနာရှိ ဆာဗာအား Non Blocking IO ကို အသုံးပြု၍ ပြုပြင်ရေးသားကြည့်ပါမည်။

NonBlockingEchoServer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;

public class NonBlockingEchoServer {
 private static final int PORT = 5000;

 // Selector
 private Selector selector;

 public NonBlockingEchoServer() throws IOException {

  selector = SelectorProvider.provider().openSelector();
  ServerSocketChannel serverChannel = ServerSocketChannel.open();

  // Setting Non-blocking
  serverChannel.configureBlocking(false);
  serverChannel.bind(new InetSocketAddress(PORT));

  // registration of client request
  serverChannel.register(selector, SelectionKey.OP_ACCEPT);

  while (selector.select() > 0) {

   Iterator<?> keyIterator = selector.selectedKeys().iterator();

   while (keyIterator.hasNext()) {
    // get ready key
    SelectionKey key = (SelectionKey) keyIterator.next();
    keyIterator.remove();

    if (key.isAcceptable()) {
     ServerSocketChannel channel = (ServerSocketChannel) key
       .channel();
     accept(channel);
    } else {
     SocketChannel channel = (SocketChannel) key.channel();

     // ready to read
     if (key.isReadable()) {
      read(key, channel);
     }

     // ready to write
     if (key.isValid() && key.isWritable()) {
      echo(key, channel);
     }
    }
   }
  }
 }

 // connection
 private void accept(ServerSocketChannel serverChannel) throws IOException {
  SocketChannel channel = serverChannel.accept();
  channel.configureBlocking(false);

  ByteBuffer buffer = ByteBuffer.allocate(1024);

  // Registration
  channel.register(selector, SelectionKey.OP_READ, buffer);
  System.out.println("Connect to: "
    + channel.socket().getInetAddress().getHostName());
 }

 // Input
 private void read(SelectionKey key, SocketChannel channel)
   throws IOException {
  ByteBuffer buffer = (ByteBuffer) key.attachment();

  // Input
  buffer.clear();
  if (channel.read(buffer) < 0) {
   try {
    channel.close();
   } catch (IOException ex) {
   }
  } else {
   // Registration
   channel.register(selector, SelectionKey.OP_WRITE, buffer);
  }
 }

 // Output
 private void echo(SelectionKey key, SocketChannel channel)
   throws IOException {
  // get buffer
  ByteBuffer buffer = (ByteBuffer) key.attachment();

  // output
  buffer.flip();
  channel.write(buffer);

  // Registration
  channel.register(selector, SelectionKey.OP_READ, buffer);
 }

 public static void main(String[] args) {
  try {
   new NonBlockingEchoServer();
  } catch (IOException ex) {
   ex.printStackTrace();
  }
 }
}
စာကြောင်း ၂၉ ကိုကြည့်ပါ။ Selector#select ဖြင့် Input Output က အဆင်သင့်ဖြစ်သည်အထိ Block လုပ်ထားပါသည်။ Selector ကလပ်စ်တွင် အသုံးပြုနိုင်သည့် Operation အမျိုးအစားများမှာ OP_ACCEPT, OP_CONNECT, OP_READ နှင့် OP_WRITE ဟူ၍ ၄မျိုးရှိပါသည်။

Selector တွင် အသုံးပြုမည့် Operation များအား ကြိုတင် Registration လုပ်ထားရန် လိုအပ်ပါသည်။ အထက်ပါ Operation အမျိုးအစား လေးမျိုးတွင် ထူးခြားသည်မှာ OP_WRITE ပင် ဖြစ်၏။ အကြောင်းမှာ OP_WRITE သည် Registration လုပ်ပြီးသည်နှင့် အဆင်သင့်ဖြစ်နေပြီ ဖြစ်သောကြောင့် ဖြစ်၏။ ထို့ကြောင့် OP_WRITE အား ကြိုတင်၍ Registration လုပ်ထားပါက select လုပ်ဆောင်ချက်ကို ခေါ်ယူအသုံးပြုနိုင်ပါသဖြင့် အတော်များများ Block မဖြစ်ပဲ ချက်ချင်း အသုံးပြုနိုင်မည် ဖြစ်သည်။

ထို့ကြောင့် Output လုပ်ရန်လိုအပ်သည့်အခါမှသာ OP_WRITE အား Registration လုပ်ရန် လိုအပ်ပါသည်။ စာကြောင်း ၄၅မှ ၅၃အား ကြည့်ပါ။ read လုပ်ပြီး write ကို လုပ်ဆောင်စေပါသည်။ ထို့ကြောင့် read လုပ်ဆောင်ချက်၏ စာကြောင်း ၇၉မှ ၈၇တွင် if(challel.read(buffer) < 0) ဟု ရေးစရာမကျန်တော့ပါက channel အား close လုပ်ပြီး၊ ရေးစရာကျန်မှသာ OP_WRITE အား registration လုပ်ပါသည်။

Selector#select လုပ်ဆောင်ချက် အပြီး စာကြောင်း ၃၁ တွင် java.nio.channels.SelectionKey ၏ အော့ဘဂျက်၏ Set အား ခေါ်ယူပါသည်။ ပြီးပါက SelectionKey အော့ဘဂျက် ၏ Operation အမျိုးအစား အပေါ်မှုတည်၍ လုပ်ဆောင်ချက်များအား ခွဲခြား လုပ်ဆောင်စေပါသည်။

Client ဆီမှ Access လုပ်လာပါက စာကြောင်း ၄၁တွင် accept လုပ်ဆောင်ချက်ကို ခေါ်ယူပြီး၊ အတွင်းပိုင်း စာကြောင်း ၆၇တွင် OP_READ အနေဖြင့် SocketChennel အား Registration လုပ်ပါသည်။ တဖန် OP_READ Channel က အသင့်ဖြစ်ပါက စာကြောင်း ၄၇ဖြင့် read လုပ်ဆောင်ချက်ကို ခေါ်ယူပြီး အတွင်းတွင် စာကြောင်း ၇၉ဖြင့် channel မှ buffer အတွင်းသို့ data များအား read လုပ်ပါမည်။ ပြီးပါက channel အား OP_WRITE အနေဖြင့် Registration လုပ်ပါမည်။

တဖန် Registration လုပ်ထားသော Channel က Write လုပ်ရန် အစဉ်သင့်ဖြစ်သောအခါ စာကြောင်း ၅၂ဖြင့် echo လုပ်ဆောင်ချက်အား ခေါ်ယူပြီး၊ အတွင်းပိုင်းတွင် စာကြောင်း ၉၈ဖြင့် Buffer အတွင်းရှိ အချက်အလက်များအား write လုပ်ပါမည်။ ပြီးပါက စာကြောင်း ၁၀၁ဖြင့် OP_READ အနေဖြင့် Registration လုပ်မည် ဖြစ်သည်။ ဤနည်းအားဖြင့် Access လုပ်လာသော Socket Channel အတွင်းရှိ အချက်အလက်များအား မကုန်မချင်း ဖတ်လိုက် ရေးလိုက် လုပ်နေမည် ဖြစ်သည်။ Socket အတွင်းရှိ အချက်အလက်များ ကုန်ပါက စာကြောင်း ၈၁ဖြင့် channel အား close လုပ်မည် ဖြစ်ပါသည်။

ဤကဲ့သို့ Selector အား အသုံးပြုခြင်းအားဖြင့် Thread တစ်ခုတည်းဖြင့် Channel အများစုအား Block ဖြစ်ခြင်း အချိန်ကို အနည်းဆုံးဖြစ်အောင် ထိမ်းချုပ်ရင်း အလုပ်လုပ်စေနိုင်ပါသည်။

သို့ရာတွင် select, read နှင့် write လုပ်ဆောင်ချက်များအား လုပ်ဆောင်နေစဉ်တွင် Block ဖြစ်နေသည်မှာ ငြင်း၍မရပေ။ ဤကဲ့သို့သော အားနည်းချက်များအား ပြုပြင်ရန်အတွက် NIO2 တွင် Asynchronous IO အား တီထွင် ရေးသားခဲ့ပါသည်။ Asynchronous IO နှင့် ပတ်သက်ပြီး နောက်ဘလောဂ်များဖြင့် ဆက်လက် ဖော်ပြသွားပါဦးမည်။



မှတ်ချက်


Japan Java User Group ၏ အဖွဲ့ဝင် Mr Yuuichi Sakuraba ရေးသားသော New IO ဖြင့် Asynchronous IO အား လေ့လာရင်း ပြန်လည်တင်ပြပါသည်။

http://itpro.nikkeibp.co.jp/article/COLUMN/20110927/369451/?ST=develop&P=1

လေးစားစွာဖြင့်။
မင်းလွင်

June 2, 2012

WatchService

ကျွှန်တော်သည် Java 7 အကြောင်းကို လေ့လာရင်း Nio2 ကြောင်းကိုရေးသားလာခဲ့ပါသည်။ Nio 2 သည် ယခင် Java IO စစ္စတမ်၏ အားနည်းချက်များကို ဖြည့်စွက်ထားသော API တစ်ခုဖြစ်ပါသည်။ Java 7 ကို အသုံးများသည့်အခါတွင် NIO2 ကို အသုံးပြုလာမည်မှာ မလွဲပေ။ ဤတစ်ခေါက်တွင် Nio2 File System ၏ နောက်ဆုံးအကြိမ်အဖြစ် WatchService အင်တာဖေစ် အကြောင်းကို ရေးသားသွားပါဦးမည်။


ဒိုင်အတ္တရီများအား စောင့်ကြည့်ခြင်း


ဒိုင်အတ္တရီ တစ်ခု အတွင်းတွင် ဖိုင်နှင့်ဒိုင်အတ္တရီများအား အသစ်ပြုလုပ်ခြင်း၊ ပြောင်းလည်းခြင်း၊ ဖျက်ပစ်ခြင်းများပြုလုပ်ပါက ထိုအကြောင်းကို သိရှိရန်မှာ အကြိမ်ကြိမ် ဒိုင်အတ္တရီအား သွားသွားကြည့်ရန် လိုအပ်ခဲ့ပါသည်။ သို့ရာတွင် Java7 ၏ nio2 ၌ သတ်မှတ်ထားသော ဒိုင်အတ္တရီတစ်ခုအား စောင့်ကြည့်နိုင်သော WatchService အင်တာဖေစ်ကို ပံ့ပိုးလာခဲ့ပါသည်။ WatchService အင်တာဖေစ်ကို အသုံးပြုခြင်းအားဖြင့် စောင့်ကြည့်နေသော ဒိုင်အတ္တရီ အတွင်းတွင် ပြောင်းလည်းမှု့များကို ပြုလုပ်ပါက အကြိမ်ကြိမ် သွားကြည့်စရာမလိုပဲ သိရှိနိုင်ပါသည်။
   Path path = fs.getPath("alpha");
   WatchService watcher = fs.newWatchService();

   path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE,
     StandardWatchEventKinds.ENTRY_DELETE,
     StandardWatchEventKinds.ENTRY_MODIFY);
အထက်ပါအတိုင်း FileSystem#newWatchService လုပ်ဆောင်ချက်ဖြင့် WatchService ၏ အင်းစတန့်စ်ကို ရယူနိုင်ပါသည်။ ပြီးပါက စောင့်ကြည့်လိုသော ဒိုင်အတ္တရီ၏ Path အော့ဘဂျက်တွင် WatchService အင်းစတန့်စ်အား Register လုပ်လိုက်ရုံသာဖြစ်၏။

Path#register လုပ်ဆောင်ချက်၏ ပဋ္ဌမ ပါရာမီတာသည် Path အား စောင့်ကြည့်မည့် WatchService အော့ဘဂျက်ဖြစ်ပြီး၊ အနောက်တွင် စောင့်ကြည့်မည့် Event များကို သတ်မှတ်ရေးသားရန် လိုအပ်ပါသည်။ ဤနမှုနာထဲတွင် ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY တို့ကို ရေးသားထားပါသဖြင့်၊ အသစ်တည်ဆောက်သည့်အခါ၊ ဖျက်စီးသည့်အခါနှင့် ပြုပြင်ပြောင်းလည်းသည့်အခါများအား စောင့်ကြည့်နိုင်ပါသည်။

စောင့်ကြည့်ပုံ
   while(true) {
    WatchKey key = null;
    try {
     key = watcher.take();
    } catch (InterruptedException e) {
     break;
    }
    
    for(WatchEvent<?> event : key.pollEvents()) {
     if(event.kind() == StandardWatchEventKinds.OVERFLOW) {
      continue;
     } else {
      System.out.println(event.kind() + " : " + event.context());
     }
    }
    
    if(!key.reset()) {
     break;
    }
   }
စောင့်ကြည့်နေသော ဒိုင်အတ္တရီများအတွင်းတွင် ပြောင်းလည်းမှု့များဖြစ်ပေါ်လာပါက java.nio.file.WatchKey အင်တာဖေစ်မှ တဆင့်သိရှိနိုင်ပါသည်။ java.nio.file.WatchKey သည် WatchService#take လုပ်ဆောင်ချက် ဒါမှမဟုတ် WatchService#pool လုပ်ဆောင်ချက်မှ တဆင့်ခေါ်ယူနိုင်ပါသည်။ တဖန် ဖြစ်ပေါ်လာသော Event များအားjava.nio.file.WatchEvent အင်တာဖေစ်မှတဆင့် သိရှိနိုင်ပါသည်။

ကျွှန်တော်တို့ ရေးသားထားသော ပရိုဂရမ်အား ကွန်ပိုင်းလုပ်၍ စမ်းသပ်ကြည့်ပါမည်။


စောင့်ကြည့်နေသော ဒိုင်အတ္တရီအတွင်းတွင် ဖိုင်အသစ်အား တည်ဆောက်ကြည့်သောအခါ၊ ENTRY_CREATE Event ကို ဖြစ်ပေါ်စေသည်ကိုတွေ့ရပါသည်။ တဖန် ထိုဖိုင်အား Hello.txt ဟု အမည်ပြောင်းလိုက်သောအခါ၊ ENTRY_DELETE နှင့် ENTRY_MODIFY ကို ဖြစ်ပေါ်စေပြီး၊ Hello.txt အတွင်းတွင် စာရိုက်ပြီး၊ save လုပ်လိုက်သောအခါ ENTRY_MODIFY ကို ဖြစ်ပေါ်စေသည်ကို တွေ့ရပါသည်။

ရေးသားထားသော နမှုနာကုဒ်များအား ဤနေရာတွင် ရယူနိုင်ပါသည်။


ဆက်ပါဦးမည်။ လေးစားစွာဖြင့်။
မင်းလွင်

May 19, 2012

FileVisitor

ပြီးခဲ့သောအခန်းများတွင် ဖိုင်နှင့် ဒိုင်အတ္တရီများအား ကော်ပီကူပုံအကြောင်းကို ရေးသားခဲ့ဘူးပါသည်။ သို့ရာတွင် ဒိုင်အတ္တရီများအား ကော်ပီကူးရာ၌ အတွင်းတွင် ဖိုင်နှင့် ဒိုင်အတ္တရီများ ပါရှိပါက တိုက်ရိုက် ကော်ပီကူး၍မရပဲ၊ ဒိုင်အတ္တရီများအတွင်း တဆင့်ချင်းဝင်ကာ ဖိုင်တစ်ခုချင်း ကော်ပီကူးရန် လိုအပ်ခဲ့၏။

ယနေ့အထိအသုံးပြုခဲ့သော File IO အဟောင်းကို အသုံးပြုစဉ်အခါက ဒိုင်အတ္တရီ အတွင်း တစ်ခုချင်းဝင်ကာ ဖိုင်များအားရှာဖွေရသည်မှာ အတော်လေးကို လက်ပေါက်ကတ်ခဲ့ပါသည်။ သို့ရာတွင် ယနေ့ NIIO 2 အရောက်တွင် Java Design Pattern တစ်ခုဖြစ်သော Visitor Pattern ကို အသုံးပြုထားသည့် အင်တာဖေစ် တစ်ခုကို ပြင်ဆင်လာခဲ့ပါသည်။ FileVisitor အင်တာဖေစ် ဖြစ်ပါသည်။


FileVisitor အင်တာဖေစ်


FileVisitor အင်တာဖေစ်တွင် ဖိုင်စစ္စတမ်အတွင်းရှိ ဖိုင်များ ဒိုင်အတ္တရီများအား ဝင်ရောက်ရှာဖွေရ လွယ်ကူစေရန် လုပ်ဆောင်ချက် ၄ မျိုးကို ပြင်ဆင်ထားပါသည်။

  1. preVisitDirectory ဒိုင်အတ္တရီ အတွင်းသို့ မဝင်ရောက်ခင် ခေါ်ဆိုမည့် လုပ်ဆောင်ချက်ဖြစ်၏။
  2. postVisitDirecotry ဒိုင်အတ္တရီ အတွင်းမှ မထွက်ခွာခင် ခေါ်ဆိုမည့် လုပ်ဆောင်ချက်ဖြစ်၏။
  3. visitFile ဖိုင်နေရာသို့ ရောက်ရှိသည့် အခါတွင် ခေါ်ဆိုမည့် လုပ်ဆောင်ချက် ဖြစ်၏။
  4. visitFileFailed ဖိုင်နေရာသို့ ဝင်ရောက်ရာတွင် မှားယွင်းမှု့များ ဖြစ်ပေါ်ခဲ့ပါက ခေါ်ဆိုမည့် လုပ်ဆောင်ချက် ဖြစ်၏။
အရင်ဦးဆုံး FileVisitor Pattern ကို စမ်းသပ်ကြည့်လိုပါသဖြင့် အောက်ပါအတိုင်း linux အပေါ်တွင် ဒိုင်အတ္တရီ (Directory) နှင့် ဖိုင်များကို တည်ဆောက်ပါမည်။


import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

public class FileVisitorTest {

    public static void main(String[] args) {

        FileVisitor<Path> visitor = new FileVisitor<Path>() {

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                    throws IOException {
                System.out.println("postVisitDirectory : " + dir);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult preVisitDirectory(Path dir,
                    BasicFileAttributes attrs) throws IOException {
                System.out.println("preVisitDirectory : " + dir);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file,
                    BasicFileAttributes attrs) throws IOException {
                System.out.println("visitFile : " + file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc)
                    throws IOException {
                System.out.println("visitFileFailed : " + file);
                return FileVisitResult.CONTINUE;
            }
        };

        try {
            Files.walkFileTree(FileSystems.getDefault().getPath("alpha"),
                    visitor);
        } catch (IOException e) {
            System.err.println("There is no File.");
        }
    }
}
အထက်ပါ အတိုင်း FileVistor အင်တာဖေစ်၏ Object အား instance လုပ်ပါမည်။ ဖြည့်စွက်ရန် လိုအပ်သော လုပ်ဆောင်ချက်များအား override လုပ်ရေးပါမည်။ ဤနေရာတွင် FileVisitor အားမည်သို့ အသုံးချမည်ကို သိလိုပါသဖြင့် အတွင်းပိုင်းတွင် လုပ်ဆောင်ချက်၏ အမည်နှင့် ပါရာမီတာအဖြစ် အသုံးပြုမည့် Path ၏ Object အားကွန်ဆိုးပေါ်တွင် ရေးသားစေပါမည်။

FileVisitor ကို အသုံးပြုရန်မှာ Files#walkFileTree မှ တဆင့် အသုံးပြုရပါမည်။ ပဋ္ဌမပါရာမီတာ အဖြစ် ရှာဖွေလိုသည့် Path Object နှင့် ဒုတိယ ပါရာမီတာ အဖြစ် ထို Path အတွင်းတွင် အလုပ်လုပ်စေမည့် FileVisitor Object ကို အသုံးပြုရန်လိုအပ်ပါသည်။

Files#walkFileTree အတွင်းတွင် FileVisitor အား မည်သို့အသုံးချသွားသည်ကို သိရှိရန် အထက်ပါ ကုဒ်များအား ကွန်ပိုင်းလုပ်၍ စမ်းသပ်ကြည့်ပါမည်။



အထက်ပါ ရလဒ်ကို ကြည့်ခြင်းအားဖြင့် Files#fwalkFileTree အတွင်းတွင် ဦးစွာ preVisitDirectory ဖြင့် alpha အားသွားပြီး bravo မှတဆင့် visitFile ဖြင့် bar.txt ကို သွားနေသည်ကို တွေ့ရပါသည်။ ဒိုင်အတ္တရီအတွင်းရှိ ဖိုင်နှင့် ဒိုင်အတ္တရီ များအား အောက်ပါအတိုင်း သွားရောက်ခဲ့သည်ကို တွေ့ရပါလိမ့်မည်။



ဒိုင်အတ္တရီ ကော်ပီအားရေးသားကြည့်ခြင်း


ကျွှန်တော်တို့တွေ ဒိုင်အတ္တရီတွေအတွင်းမှာ ထပ်ဆင့် ဒိုင်အတ္တရီနှင့် ဖိုင်များရှိပါက ကော်ပီကူး၍ မရခဲ့သည်ကို FileVisitor ကို သုံး၍ ရေးသားကြည့်ပါမည်။

CpyDir.java
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;

public class CpyDir {

    public static void main(String[] args) {

        if (args.length != 2) {
            System.out
                    .println("Please set the parameter as Copy from and Copy to.");
            return;
        }

        try {
            Cpy cp = new Cpy();
            cp.copyDir(Paths.get(args[0]), Paths.get(args[1]));
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }

    static class Cpy {

        public void copyDir(Path from, final Path to) throws IOException {
            FileVisitor<Path> visitor = new FileVisitor<Path>() {

                @Override
                public FileVisitResult postVisitDirectory(Path dir,
                        IOException exc) throws IOException {
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult preVisitDirectory(Path dir,
                        BasicFileAttributes attrs) throws IOException {
                    Files.copy(dir, to.resolve(dir),
                            StandardCopyOption.COPY_ATTRIBUTES);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFile(Path file,
                        BasicFileAttributes attrs) throws IOException {
                    Files.copy(file, to.resolve(file),
                            StandardCopyOption.COPY_ATTRIBUTES);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFileFailed(Path file,
                        IOException exc) throws IOException {
                    return FileVisitResult.CONTINUE;
                }
            };

            Files.walkFileTree(from, visitor);
        }
    }

}

အဓိကအားဖြင့် preVisitDirectory နှင့် visitFile တွင် ဖိုင်နှင့် ဒိုင်အတ္တရီကို ကော်ပီကူးသည့် ကုဒ်များကို ဖြည့်စွက်ရေးသားရုံသာ ဖြစ်၏။ from သည် ကော်ပီကူးမည့် နဂိုနေရာဖြစ်ပြီး၊ to သည် ကော်ပီကူးမည့် နေရာသစ်ဖြစ်ပါသည်။

 preVisitDirectory နှင့် visitFile တွင် ကော်ပီကူးသည့်ကုဒ်များကို ရေးထားခြင်းအားဖြင့် FileVisitor က from ၏ ဖိုင်များအား သွားရောက်သည့်အခါတွင် to နေရာတွင် တစ်ခုချင်း ကူးသွားမည်ဖြစ်ပါသည်။ လက်တွေ့ စမ်းသပ်ကြည့်ပါမည်။


alpha အား beta ၏ အောက်တွင် ကော်ပီကူးသွားနိုင်သည်ကို တွေ့ရပါလိမ့်မည်။ တစ်ခုသတိထားမိမည် ဖြစ်သည်။ beta မရှိပါက၊ အကယ်၍ beta ၏ အောက်တွင် alpha ရှိနှင့်ပြီး ဖြစ်ပါက ကောင်းစွာ အလုပ်လုပ်နိုင်မည် မဟုတ်ပေ။

အိမ်စာအနေဖြင့် အောက်ပါလော့ဂျစ်များအား ဖြည့်စွက်ရေးသားကြည့်ပါ။
  1. beta မရှိခဲ့ပါက alpha အား beta အမည်ဖြင့် ကူးယူနိုင်ရန်
  2. beta သည် ဒိုင်အတ္တရီအနေဖြင့် ရှိခဲ့ပြီး ၎င်း၏ အောက်တွင် alpha သည် ရှိနှင့်ပြီး ဖြစ်ပါက alpha အပေါ်တွင် ထပ်ပြီး ကူးရန်
အဖြေအား နောက်အခန်းများတွင် ဆက်လက်ဖော်ပြသွားပါမည်။

ဆက်ပါဦးမည်။ လေးစားစွာဖြင့်။
မင်းလွင်

May 13, 2012

Meta Data

ဖိုင်တစ်ခုသည် ကွန်ပျူတာပေါ်တွင် အချက်အလက် (Data) များကို သိမ်းဆည်းထားသော အရာဖြစ်ပြီး၊ ထိုဖိုင်သည် မည်သည့်ဖိုင်ဖြစ်သည်၊ ပမာဏသည် မည်မျှဖြစ်သည်၊ မည်သည့်အချိန်က စတင်ဖွဲ့စည်း တည်ဆောက်ခဲ့သည် အစရှိသည့်၊ ထိုဖိုင်၏ ပိုင်ဆိုင်မှု့အကြောင်းအရာများအား ဖော်ပြရန် သိမ်းဆည်းထားသော အချက်အလက်များအား Meta Data ဟု ခေါ်ပါသည်။

Java ၏ Nio2 တွင် ဖိုင်နှင့် ဒိုင်အတ္တရီများ၏ ပိုင်ဆိုင်မှု့ အချက်အလက်များအား ဖော်ပြအသုံးချနိုင်ရန် နည်းလမ်း၂မျိုး ပြင်ဆင်ထားပါသည်။ ပဋ္ဌမဆုံးနည်းလမ်းမှာ Files ကလပ်စ်မှ တဆင့် Meta Data များကို တိုက်ရိုက် ခေါ်ယူအသုံးပြုခြင်း နည်းလမ်းနှင့်၊ နောက်နည်းလမ်းတစ်ခုမှာ java.nio.file.attribute.FileAttributeView အင်တာဖေစ်မှ တဆင့် လိုအပ်သော အချက်အလက်များကို ဆက်သွယ်အသုံးပြုနည်းတို့ ဖြစ်ပါသည်။


Files Class မှ အသုံးပြုနိုင်သော Meta Data များ


Files ကလပ်စ်တွင် Files#getOwner, Files#setOwner အစရှိသည့် ဖိုင်တစ်ခု၏ Meta Data များကို ရယူနိုင်သော၊ ပြောင်းလည်းသတ်မှတ်နိုင်သော လုပ်ဆောင်ချက်များကို ပြင်ဆင်ထားပါသည်။ သို့ရာတွင် Files ကလပ်စ်တွင် ပြင်ဆင်ထားသော လုပ်ဆောင်ချက်မှာ အနည်းစုသာဖြစ်၏။ ဥပမာအားဖြင့် ဖိုင်တစ်ခု၏ နောက်ဆုံးသိမ်းဆည်း ခဲ့သော အချိန်အား ဆက်သွယ် အသုံးပြုနိုင်သော်လည်း၊ ထိုဖိုင်အား စတင် တည်ဆောက် ခဲ့သော အချိန်အား ဆက်သွယ်အသုံးပြုနိုင်ခြင်း မရှိပါ။

FileAttributeSample.java
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.UserPrincipal;


public class FileAttributeSample {

    public static void main(String[] args) {
        try {
            showAttributeInfo(FileSystems.getDefault().getPath(args[0]));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void showAttributeInfo(Path path) throws IOException {
        // File Owner
        UserPrincipal user = Files.getOwner(path);
        System.out.println("File Owner : " + user);
        
        // Last Update Time
        FileTime lstUpdTime = Files.getLastModifiedTime(path);
        System.out.println("Last Update Time : " + lstUpdTime);
        
        // Is writable
        boolean isWritable = Files.isWritable(path);
        System.out.println("Is Writable File : " + isWritable);
        
        // Creation Time
        FileTime creationTime = (FileTime)Files.getAttribute(path, "creationTime");
        System.out.println("File Creation Time : " + creationTime);
    }
}

အထက်ပါ နမှုနာတွင် ဖော်ပြထားသည့်အတိုင်း Files#getOwner ဖြင့်၊ UserPrincipal အင်တာဖေစ်၏ Objectကို ရရှိနိုင်ပါသည်။ ထို Object မှတဆင့် ဖိုင် ပိုင်ရှင်ကို သိရှိနိုင်ပါသည်။ တဖန် Files#getLastModifiedTime ဖြင့် နောက်ဆုံးပြုပြင်ခဲ့သည့် အချိန်ကို သိရှိနိုင်ပါသည်။ Files#getAttribute ကို အသုံးပြုပါက ထိုဖိုင်တွင် ပိုင်ဆိုင်သော အချက်အလက်များကို သိရှိနိုင်ပါသည်။

ဖိုင်အားပြုလုပ်ခဲ့သည့် အချိန်ကို သိရှိလိုပါက ပါရာမီတာ "creationTime" ကို အသုံးပြုပါက ရရှိနိုင်ပါသည်။ ဖိုင်အားစတင်ရေးသားခဲ့သည့် အချိန်မှာ FileTime အင်တာဖေစ်၏ Object အဖြစ် အသုံးပြုပါသဖြင့် FileTime ဖြင့် cast လုပ်နေသည်ကို တွေ့ရပါမည်။




FileAttributeView အင်တာဖေစ်ဖြင့် Meta Data များကို အသုံးပြုခြင်း


Files#getAttribute နှင့် Files#setAttribute ကို အသုံးပြုခြင်းအားဖြင့် Meta Data များကို နှစ်သက်သလို အသုံးပြုနိုင်ပါသည်။ သို့ရာတွင် Attribute ၏ အမည်များကို သိရှိရန် လိုအပ်ပါသည်။ ထို့အပြင် မှန်ကန်စွာ Cast လုပ်ရန် လိုအပ်ပါသည်။ ထိုအားနည်းချက်များအား ပြုပြင်ရန် ပြင်ဆင်ထားသည်က FileAttributeView အင်တာဖေစ် ဖြစ်ပါသည်။

ဖိုင်တစ်ခု၏ Meta Data များသည် အသုံးပြုနေသည့် File System အပေါ်တွင် မှုတည်၍ ပြောင်းလည်း တတ်ပါသည်။ ထို့ကြောင့် Windows နှင့် Linux တို့တွင် အသုံးပြုနိုင်သော Meta Data များမှာ မတူညီကြပေ။ ဤကဲ့သို့ မတူညီသော FileAttribute များကို အသုံးပြုနိုင်ရန်အတွက် FileAttributeView ၏ Sub Interface များကို ပြင်ဆင်ထားပါသည်။

BasicFileAttributeView သည် Linux ရော Windows မှာပါ အသုံးပြုနိုင်သော Meta Data များကို စုစည်းထားသော အင်တာဖေစ်ဖြစ်ပြီး၊ DosFileAttributeView သည် Windows သုံး အင်တာဖေစ် ဖြစ်၏။ PosixFileAttributeView မှာမူ Linux သုံး အင်တာဖေစ် ဖြစ်ပါသည်။ တဖန် ACL ကို အသုံးပြုနိုင်သော AclFileAttributeView နှင့် Owner အချက်အလက်များကို အသုံးပြုနိုင်သော FileOwnerAttributeView အင်တာဖေစ် များကိုလည်း nio2 တွင် ပြင်ဆင်ထားပါသည်။

BasicFileAttributeView ကို လက်တွေ့ ရေးသားကြည့်ပါမည်။

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;

public class FileAttributeViewSample {

    public static void main(String[] args) {
        try {
            showAttributeInfo(FileSystems.getDefault().getPath(args[0]));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void showAttributeInfo(Path path) throws IOException {

        BasicFileAttributeView bfw = Files.getFileAttributeView(path,
                BasicFileAttributeView.class);
        BasicFileAttributes bfs = bfw.readAttributes();

        // Last Update Time
        FileTime lstUpdTime = bfs.lastModifiedTime();
        System.out.println("Last Update Time : " + lstUpdTime);

        // Creation Time
        FileTime creationTime = bfs.creationTime();
        System.out.println("Creation Time : " + creationTime);
    }
}

BasicFileAttributeView Object အား Files#getAttributeView ဖြင့် ခေါ်ယူ အသုံးပြုနိုင်ပါသည်။ getAttributeView လုပ်ဆောင်ချက်၏ ဒုတိယ ပါရာမီတာတွင် အသုံးပြုလိုသည့် FileAttributeView ကလပ်စ် ကို ရေးသားပါက၊ အသုံးပြုလိုသည့် FileAttributeView Object ကို ရယူနိုင်ပါသည်။

တဖန် BasicFileAttributeView#readAttributes ဖြင့် ဖိုင်၏ Meta Data များကို အသုံးပြုနိုင်သော BasicFileAttribute Object ကို ရရှိနိုင်ပါသည်။

အထက်ပါ နမှုနာကို ကွန်ပိုင်းလုပ်၍ စမ်းသပ်ကြည့်ပါက အောက်ပါအတိုင်းတွေ့ရမည် ဖြစ်သည်။




ကိုးကား
http://itpro.nikkeibp.co.jp/article/COLUMN/20110830/367909/?ST=develop&mkjt&P=5


ဆက်ပါဦးမည်။ လေးစားစွာဖြင့်။
မင်းလွင်

April 29, 2012

File, Directory များအား အသုံးချခြင်း

ပြီးခဲ့တဲ့ ဘလောဂ်များဖြင့် Java Nio2 ၏ Files Utility Class ကို အသုံးပြု၍ နမှုမှာအမျိုးမျိုးကို ဖော်ပြခဲ့ပါသည်။ သို့ရာတွင် Files ကလပ်စ်၌ အခြားအသုံးဝင်သော လုပ်ဆောင်ချက်များစွာ ကျန်ရှိပါသေးသည်။ ဖိုင်နှင့် ဒိုင်အတ္တရီ များအား ကော်ပီကူးခြင်း၊ ပြောင်းရွှေ့ခြင်းနှင့် ဖျက်ပစ်ခြင်း(Delete) အစရှိသည် လုပ်ဆောင်ချက်များကို Files ကလပ်စ်ကို အသုံးပြု၍ လုပ်ဆောင်နိုင်ပါသေးသည်။

အထူးသဖြင့် ကော်ပီကူးခြင်းသည် ယခင် ပံ့ပိုးထားခြင်း မရှိပါသဖြင့် Streams နှင့် Channel များကို အသုံးပြု၍ တကူးတက ရေးသားနေရပါသည်။ ထို့အပြင် ကော်ပီကူးခြင်းသည် အသုံးများပါသဖြင့် Nio2 တွင် ကော်ပီကူးခြင်းကို ပံ့ပိုးလာခြင်းသည် လွန်စွာမှ အသုံးဝင်လပေသည်။ ယခုတစ်ခေါက်တွင်လည်း ဆက်လက်၍ ဖိုင်နှင့် ဒိုင်အတ္တရီများကို အသုံးပြုပုံတို့ကို နမှုနာများရေး၍ ဆက်လက်လေ့လာသွားပါဦးမည်။


ကော်ပီကူးခြင်း


ဖိုင်နှင့် ဒိုင်အတ္တရီများအား ကော်ပီကူရာတွင် Files#copy လုပ်ဆောင်ချက်များကို အသုံးပြုနိုင်ပါသည်။

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;


public class FileCopy {

 public static void main(String[] args) {
  
  if(args.length != 2) {
   System.err.println("Please set 2 parameters");
   return;
  }
  
  try {
   FileSystem fs = FileSystems.getDefault();
   
   Path origin = fs.getPath(args[0]);
   Path toCopy = fs.getPath(args[1]);
   
   Files.copy(origin, toCopy);
  } catch (IOException e) {
   System.err.println("There is no file ["+args[0] +"]");
  }

 }
}



ရိုးရိုးကော်ပီကူးရုံဆိုပါက Files#copy မက်သတ်၏ ပဋ္ဌမပါရာမီတာတွင် ကူးလိုသည့် မှုရင်း ဖိုင်၏ Path အော့ဘဂျက်ကို ရေးသားပြီး၊ ဒုတိယ ပါရာမီတာတွင် ကူးလိုသည့် ဖိုင်၏ Path အော့ဘဂျက်ကို ရေးသားရပါမည်။

အထက်ပါ နမှုနာတွင်  ရေးသားထားသည့်အတိုင်း Files.copy(origin, toCopy); ဟု၊ origin သည် မှုလဖိုင်ဖြစ်ပြီး toCopy သည် ကူးမည့် ဖိုင်အသစ်ဖြစ်ပါသည်။ ဤကဲ့သို့ရေးသားရာတွင် origin သည် လက်ရှိ တည်ရှိခြင်းမရှိပါက NoSuchFileException ကို ဖြစ်ပွားစေမည် ဖြစ်ပြီး၊ toOrigin သည် လက်ရှိ တည်ရှိပြီး ဖြစ်ပါက FileAlreadyExitsException ကို ဖြစ်ပေါ်စေမည် ဖြစ်ပါသည်။

အကယ်၍ ရှိပြီးသားဖိုင် အပေါ်တွင် ထပ်ပြီး ရေးသားလိုပါက Files#copy မက်သတ်၏ တတိယ လုပ်ဆောင်ချက်တွင် java.nio.file.StandardCopyOption.REPLACE_EXISTING ကို သတ်မှတ် ရေးသားနိုင်ပါသည်။

StandardCopyOption သည် enum ပုံစံ ဖြစ်ပြီး၊ အောက်ပါ အချက်အလက်များကို ပိုင်ဆိုင်ပါသည်။

  • REPLACE_EXISTING
  • COPY_ATTRIBUTES
  • ATOMIC_MOVE
COPY_ATTRIBUTES ကို အသုံးပြုပါက၊ နောက်ဆုံးဆက်သွယ်သော အချိန် ကဲ့သို့သော ဖိုင်တစ်ခု၏ Meta data အချက်အလက်များကိုပါ ကော်ပီကူးနိုင်ပါသည်။ တဖန် ATOMIC_MOVE သည် ကော်ပီကူးရာတွင် အသုံးမပြုနိုင်ပဲ ထိုဖိုင်အား ရွှေ့ပြောင်းရာတွင် အသုံးပြုနိုင်ပါသည်။

StandardCopyOption ကဲ့သို့ပင် ယခင် အခန်းများတွင် ဖော်ပြပြီးဖြစ်သော LinkOption များကိုလည်း အသုံးပြုနိုင်ပါသည်။ 

သတိပြုရန် အချက်တစ်ခုမှာ ကော်ပီကူးရာတွင် Windows ဆိုပါက copy ကွန်မန်းကို၎င်း၊ Linux ၏ cp ကွန်မန်းကို၎င်း အသုံးပြုရာ၌၊ ဖိုင်တစ်ခုအား အခြားသော ဒိုင်အတ္တရီ တစ်ခုဆီသို့ လက်ရှိအမည်အတိုင်း ကော်ပီကူးလိုပါက ကော်ပီကူးမည့်ဖိုင်အသစ်နေရာတွင် ကူးပြီးသိမ်းစေလိုသည့် ဒိုင်အတ္တရီကို သာရေးသားလျှင် ရနိုင်ပေသည်။ တကူးတက ဖိုင်အမည် ရေးသားရန် မလိုအပ်ပေး။ အောက်ပါအတိုင်းဖြစ်၏။


သို့ရာတွင် Java ၏ Files.copy ကို အသုံးပြုရာ၌ ကူးလိုသည့် ဖိုင်၏ အမည် အပြည့်အစုံကို ရေးသားရန် လိုအပ်ပါသည်။ ရေးပြီးသား FileCopy.java ကို အသုံးပြု၍ စမ်းသပ်ကြည့်ပါမည်။


ကော်ပီကူးမည့်နေရာတွင် copydir သည် ရှိနှင့်ပြီးဖြစ်သောကြောင့် FileAlreadyExitsException ကို ဖြစ်ပေါ်စေပါသည်။ အောက်ပါအတိုင်း အမည်အပြည့်အစုံကို ရေးသားပါက ကူးနိုင်မည် ဖြစ်ပါသည်။




ရွှေ့ပြောင်းခြင်း


ဖိုင်များအား ကော်ပီကူးသကဲ့သို့ တစ်နေရာမှ တစ်နေရာဆီသို့ ပြောင်းရွှေ့ရာတွင်လည်း Files ကလပ်စ်ကို အသုံးပြုနိုင်ပါသည်။ ဖိုင်များအား ရွှေ့ပြောင်းရာတွင် Files#move မက်သတ်ကို အသုံးပြုနိုင်ပါသည်။

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public class MoveSample {
 
 public static void main(String[] args) throws IOException {
  FileSystem fs = FileSystems.getDefault();
  Path origin = fs.getPath(args[0]);
  Path toMove = fs.getPath(args[1]);
  Files.move(origin, toMove, StandardCopyOption.ATOMIC_MOVE);
 }
}

ဖိုင်ဒါမှမဟုတ် ဒိုင်အတ္တရီ တစ်ခုအား ပြောင်းရွှေ့ရာတွင်၎င်း၊ အခြားအမည်တစ်ခုအဖြစ် ပြောင်းလည်း လိုသည့်အခါမျိုးတွင် Files#move လုပ်ဆောင်ချက်ကို အသုံးပြုနိုင်ပါသည်။ move လုပ်ဆောင်ချက်၏ ပဋ္ဌမ ပါရာမီတာနေရာတွင် မှုရင်းဖိုင်ကို ရေးရန်လိုအပ်ပြီး၊ ပြောင်းရွှေ့လိုသည့် ဖိုင်နေရာအား ဒုတိယ ပါရာမီတာနေရာတွင် ရေးသားရပါမည်။ တတိယ ပါရာမီတာအနေဖြင့် Varags ပုံစံ LinkOption နှင့် StandardCopyOption များကို အသုံးပြုနိုင်ပါသည်။ အထက်ပါ နမှုနာထဲတွင်မှု ATOMIC_MOVE ကို အသုံးပြုထားပါသည်။




ဖျက်ပစ် (Delete) ခြင်း


Java ၏ ဖိုင်အိုင်အို အင်တာဖေစ် အသစ်ဖြစ်သော nio2 တွင် ဖိုင်နှင့် ဒိုင်အတ္တရီများအား ဖျက်ပစ်ရာတွင် Files#delete နှင့် Files#deleteIfExists ကို အသုံးပြုနိုင်ပါသည်။ File#delete ကို အသုံးပြုရာတွင် လက်ရှိ ဖိုင်စစ္စတမ်အပေါ်တွင် ဖျက်လိုသည့်ဖိုင်သည် တည်ရှိနေခြင်းမရှိပါက NoSuchFileExitsException ကို ဖြစ်ပေါ်စေမည် ဖြစ်သည်။ Files#deleteIfExits ကို အသုံးပြုပါက ဖိုင်ရှိရှိ မရှိရှိ Exception ကို ဖြစ်ပေါ်စေမည် မဟုတ်ပေ။

File#deleteIfExits ကို အသုံးပြုရာတွင် ဖျက်လိုသည့် ဖိုင်အား ဖျက်ပစ်နိုင်ပါက true ကို ရရှိမည် ဖြစ်ပြီး၊ အကယ်၍ ဖိုင်မရှိပက၊ ဒါမှမဟုတ် ဖျက်ပစ်နိုင်ခြင်း မရှိပါက false ကို ပြန်ရမည် ဖြစ်သည်။ လက်တွေ့ရေးသားကြည့်ပါမည်။

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;

public class DeleteSample {

 public static void main(String[] args) throws IOException {
  FileSystem fs = FileSystems.getDefault();
  for(String s : args) {
   Path origin = fs.getPath(s);
   Files.delete(origin);
  }
 }
}



ယနေ့တွင် Java ၏ ဖိုင်စစ္စတမ် အင်တာဖေစ် အသစ်၏ အသုံးများမည်ဖြစ်သော Files ကလပ်စ်၏ လုပ်ဆောင်ချက်အချို့ကို ဖော်ပြခဲ့၏။ နောက်ရက်များတွင်လည်း ဆက်လက်၍ ဖော်ပြသွားပါဦးမည်။


ကိုးကား

http://itpro.nikkeibp.co.jp/article/COLUMN/20110830/367909/?ST=develop&mkjb&P=3
http://itpro.nikkeibp.co.jp/article/COLUMN/20110830/367909/?ST=develop&mkjt&P=4

လေးစားစွာဖြင့်
မင်းလွင်

April 21, 2012

File, Directory နှင့် Link များကို ရေးသားခြင်း

စာမရေးဖြစ်သည်မှာ အတော်ပင်ကြာပြီ ဖြစ်၏။ အခုတလော နေ့တိုင်း အလုပ်နောက်ကျနေသည်က တစ်ကြောင်း၊ စနေတနင်္ဂနွေကျပြန်တော့လည်း ကြားအလုပ်ကို လက်ခံထားပါသဖြင့် မပြီးပြီးအောင် လုံးပမ်းနေရသည်က တစ်ကြောင်း အကြောင်းကြောင်းကြောင့် စာမရေးဖြစ်ပါ။ စာမရေးတာကြာတော့ တစ်ခုခုလိုနေသလိုပင်။ ဒီနေ့တော့ တစ်နာရီလောက် အချိန်ရပါသဖြင့် အရင်ရေးလက်စဖြစ်သော Java 7 ၏ NIO2 ကို ဆက်ပါဦးမည်။

ပြန်နွေးသည့်အနေဖြင့် အရင်ရေးခဲ့ပြီးအကြောင်းများကို အကျဉ်းချုပ် စဉ်းစားကြည့်ပါဦးမည်။ NIO2 သည် Java ၏ File System အင်တာဖေစ်အသစ်တစ်ခု ဖြစ်ပြီး၊ လက်ရှိ File System Interface ၏ အားနည်းချက်များကို ပြုပြင်ရန်ရည်ရွယ်၍ ရေးသားခဲ့သော API အသစ်တစ်ခု ဖြစ်ပါသည်။ File System တစ်ခုလုံးကို ကိုယ်စားပြုအသုံးပြုနိုင်ရန်အတွက် java.nio.file.FileSystem ကလပ်စ်၊ java.io.File အား အစာထိုးလာသည်မှာ java.nio.file.Path အင်တာဖေစ် ဖြစ်၏။ Path အင်တာဖေစ် အားအသုံးပြုနိုင်ရန် ရေးသားထားသော Utility Class မှာ java.nio.file.Files ကလပ်စ် ဖြစ်၏။

ပြီးခဲ့သော အခန်းဆက်များဖြင့် Path အော့ဘဂျက်ကို ရေးသားပုံ၊ ယခင် အသုံးပြုခဲ့သော File အော့ဘဂျက်များနှင့် အပြန်အလှန် ခေါ်ယူအသုံးပြုနိုင်ပုံ၊ အလွယ်တကူ Input Output များကို ရေးသားအသုံးပြုနိုင်ပုံ အစရှိသည်တို့ကို ဖော်ပြခဲ့၏။ ယခု တစ်ခေါက်တွင်လည်း ဆက်လက်၍ Files Utility ကလပ်စ်၏ အသုံးပြု၍ ဖိုင်နှင့် ဒိုင်အတ္တရီများကို ရေးသားပုံကို လေ့လာဖော်ပြသွားပါဦးမည်။

ယခင် IO API တွင် File ကလပ်စ်ကို အသုံးပြု၍ ဖိုင်နှင့် ဒိုင်အတ္တရီများကို အသစ်ရေးသားနိုင်ပါသည်။ Java ၏ IO System အသစ်ဖြစ်သော NIO2 တွင်လည်း၊ ဖိုင်နှင့် ဒိုင်အတ္တရီများကို အသစ်ရေးသားနိုင်ပါသည်။ သို့ရာတွင် Path ၏ လုပ်ဆောင်ချက်အနေဖြင့် မဟုတ်ပဲ Files ကလပ်စ်၏ လုပ်ဆောင်ချက်အနေဖြင့် ပံ့ပုံလာခဲ့ပါသည်။



ဖိုင်များကို ရေးသားခြင်း


ဦးစွာ ဖိုင်တစ်ခုကို အလွယ်တကူ ရေးသားကြည့်ပါမည်။
   FileSystem fs = FileSystems.getDefault();
   Path path1 = fs.getPath("default.txt");
   Files.createFile(path1);

Nio2 တွင် ဖိုင်များကို ရေးသားရာတွင် Files#createFile လုပ်ဆောင်ချက်ကို အသုံးပြုနိုင်ပါသည်။ ပါရာမီတာအနေဖြင့် ရေးသားလိုသည့် Path အော့ဘဂျက်ကို ဖြည့်စွက်ရေးသားရမည် ဖြစ်၏။ အထက်ပါ ကုဒ်များကို ကွန်ပိုင်းလုပ်ပြီး၊ အလုပ်လုပ်ခိုင်းကြည့်ပါက myfile.txt ဆိုသည့် ဖိုင်တစ်ခုကို ပမာဏ 0 byte ဖြင့် တည်ဆောက်နိုင်မည် ဖြစ်သည်။

ရှိနှင့်ပြီးသား ဖိုင်အား create လုပ်ပါက java.nio.file.FileAreadyExistsException ကို ဖြစ်ပေါ်စေမည် ဖြစ်၏။ တဖန် လက်တွေ့မရှိသော ဒိုင်အတ္တရီတွင် ဖိုင်အား တည်ဆောက်မိပါက java.nio.file.NoSuchFileException ကို ဖြစ်ပေါ်စေပါမည်။

တဖန် Linux နှင့် Solaries ကဲ့သို့ POSIX ကို အခြေခံသော Operating System များတွင်၊ ဖိုင်များကို တည်ဆောက်မည် ဆိုပါက အသုံးပြုနိုင်ခွင့်များကို သတ်မှတ်ရေးသားနိုင်မည် ဖြစ်သည်။ အကဲ၍ ဖတ်ရုံသာ ဖတ်နိုင်သော ဖိုင်တစ်ခုကို တည်ဆောက်မည် ဆိုပါက အောက်ပါနာ့မှုနာ အတိုင်း ရေးသားနိုင်မည် ဖြစ်သည်။
   Path path2 = fs.getPath("readOnly.txt");

   Set<PosixFilePermission> pms = 
     PosixFilePermissions.fromString("r--r--r--");
   FileAttribute<Set<PosixFilePermission>> attribute = 
     PosixFilePermissions.asFileAttribute(pms);

   Files.createFile(path2, attribute);
အထက်ပါ စာကြောင်း နံပါတ် ၈ တွင် ဖော်ပြထားသကဲ့သို့၊ Files#createFile လုပ်ဆောင်ချက်၏ ဒုတိယ ပါရာမီတာ အနေဖြင့် ပါမစ်ရှင်းကို သတ်မှတ်ရေးသားနိုင်ပါသည်။ ဒုတိယ ပါရာမီတာ၏ ပုံစံမှာ java.nio.file.attribute.FileAttribute အင်တာဖေစ် ဖြစ်ပါသည်။

FileAttribute အင်တာဖေစ်သည် name နှင့် value လုပ်ဆောင်ချက်များကို ပံ့ပိုးထားပြီး၊ name လုပ်ဆောင်ချက်ဖြင့် Attribute ၏ အမည်ကိုပြန်ပေးပြီး၊ value လုပ်ဆောင်ချက်ဖြင့် Attribute ၏ တန်ဖိုးကိုပြန်ပေးသော လုပ်ဆောင်ချက်ကို ဖြည့်စွက်ရေးသားမည်ဆိုပါက FileAttribute ၏ အော့ဘဂျက်ကို တိုက်ရိုက်ရေးသားနိုင်မည် ဖြစ်၏။ သို့ရာတွင် ဤနမှုနာ၌ PosixFilePermissions ကလပ်စ်ကို အသုံးပြုပြီး၊ FileAttribute ၏ အော့ဘဂျက်ကို ရေးသားထားပါသည်။

PosixFilePermissions ကလပ်၏ နောက်ဆုံးစာလုံးတွင် s ပါသည့်အတိုင်း၊ ဤကလပ်စ်သည် PosixFilePermission enum ကို အသုံးချနိုင်ရန် ပြင်ဆင်ထားသော Utility ကလပ်စ် တစ်ခု ဖြစ်ပါသည်။ အထက်ပါကုဒ်၏ စာကြောင်းနံပါတ် ၄ တွင် ရေးသားထားသည့်အတိုင်း၊ PosixFilePermissions#fromString လုပ်ဆောင်ချက်ကို အသုံးပြုခြင်းအားဖြင့် FilePermission အော့ဘဂျက်များပါဝင်သော java.util.Set ၏ အော့ဘဂျက်ကို ခေါ်ယူအသုံးပြု နိုင်မည် ဖြစ်၏။ fromString လုပ်ဆောင်ချက်၏ ပါရာမီတာသည် Unix အမျိုးအစား OS များတွင်ဖော်ပြလေ့ရှိသော ခွင့်ပြုချက် စာလုံးများ ဖြစ်ပါသည်။ "r--r--r--" သည် Read Only ပါမစ်ရှင်းကို ဖော်ပြပေးနေပါသည်။

စာကြောင်းနံပါတ် ၆ တွင် PosixFilePermissions#asFileAttribute ကို အသုံးပြု၍၊ အထက်ပါ FilePermission အော့ဘဂျက် Set မှတဆင့် FileAttribute အော့ဘဂျက်ကို ခေါ်ယူ နေသည်ကို တွေ့ရပါမည်။

နောက်ဆုံးတွင် Files#create ဖြင့် ပါမစ်ရှင်းကို သက်မှတ်ထားသော File တစ်ခုကို တည်ဆောက်နေခြင်း ဖြစ်ပါသည်။ အထက်ပါ ကုဒ်များကို လက်တွေ့ Fedora Linux အပေါ်တွင် အသုံးပြုကြည့်ပါမည်။


Default ဖြင့် ရေးသားထားသော defalut.txt မှာ Owner နှင့် Group တွင်ရေးသားခွင့်ရှိသော ဖိုင်အဖြစ် တည်ဆောက်ထားသည်ကို တွေ့ရမည် ဖြစ်ပြီး၊ ပါမစ်ရှင်းကို သတ်မှတ်ထားသော readOnly.txt ကိုမူ read only ပါမစ်ရှင်းဖြင့် တည်ဆောက်ထားသည်ကို တွေ့ရပါလိမ့်မည်။

ဖော်ပြပါ ကလပ်ကို Windows အပေါ်တွင် ကွန်ပိုင်းလုပ်၍ အသုံးပြုကြည့်ပါက java.lang.UnsupportedOperationException ကို ဖြစ်ပေါ်စေသည်ကို တွေ့ရပါမည်။ အဘယ်ကြောင့်ဆိုသော် Windows တွင် POSIX ကို Support မလုပ်သောကြောင့် ဖြစ်ပါသည်။


ဒိုင်အတ္တရီကို ရေးသားခြင်း


ဒိုင်အတ္တရီများကိုလည်း ဖိုင်များကဲ့သို့ပင် Files ကလပ်စ်ဖြင့် တည်ဆောက်နိုင်ပါသည်။ အောက်ပါ နမှုနာကို ကြည့်ပါ။
   FileSystem fs = FileSystems.getDefault();
   Path dir1 = fs.getPath("dir1");
   Files.createDirectory(dir1);
   
   Path dir3 = fs.getPath("dir2", "dir3");
   Files.createDirectories(dir3);
ဒိုင်အတ္တရီ တစ်ခုချင်းကို တည်ဆောက်လိုပါက အထက်ပါ စာကြောင်းနံပါတ် ၃ အတိုင်း Files#createDiractory မက်သတ်ကို အသုံးပြုနိုင်ပါသည်။ တဖန် စာကြောင်း ၆ကဲ့သို့ dir2/dir3 လက်ရှိ ဒိုင်အတ္တရီ (Current Directory) မှ dir2 နှင့် dir2/dir3 ကို တည်ဆောက်လိုပါက Files#createDirectories မက်သတ်ကို အသုံးပြုရန်လိုအပ်ပါသည်။

အကယ်၍ ရှိပြီးသား ဒိုင်အတ္တရီ(Directory) တစ်ခုအား Files#createDirectory ဖြင့် တည်ဆောက်မိပါက java.nio.file.FileAlreadyExistsException ကို ဖြစ်ပေါ်စေမည်ဖြစ်သည်။ သို့ရာတွင် Files#createDirectories ကို အသုံးပြုပါက Exception ကို ဖြစ်ပေါ်စေမည်မဟုတ်ပါ။


လင့်ခ်များကို ပြုလုပ်ခြင်း

Java7 အရောက်တွင် Java ဖြင့် လင့်ခ်(link)များကို အသုံးပြုလာနိုင်ပါသည်။ Java ဖြင့် အသုံးပြုနိုင်သော လင့်ခ်များမှာ Hard Link နှင့် Symbolic Link ဟူ၍ နှစ်မျိုးနှစ်စား ခွဲခြားနိုင်ပါသည်။


လင့်ခ်အမာ (Hard Link)

   FileSystem fs = FileSystems.getDefault();
   Path target = fs.getPath("target.txt");
   
   if(!Files.exists(target)) {
    Files.createFile(target);
   }
   
   // Creation of Hard Link
   Path link = fs.getPath("link.txt");
   Files.createLink(link, target);
လင့်ခ်အမာ (Hard Link) များအား အထက်ပါ စာကြောင်း ၁၀ အတိုင်း Files#createLink ကို အသုံးပြု၍ တည်ဆောက်နိုင်ပါသည်။ ပဋ္ဌမ ပါရာမီတာမှာ အသုံးပြုမည့် လင့်ခ်၏ Path အော့ဘဂျက်ဖြစ်ပြီး၊ ဒုတိယ ပါရာမီတာမှာ လင့်ခ်လုပ်မည့် ဖိုင်၏ ပါရာမီတာကို ရေးသားရပါမည်။

လင့်ခ်အမာများကို ရေးသားအသုံးပြုရာတွင် ဖိုင်ကိုသော်၎င်း ဒိုင်အတ္တရီကိုသော်၎င်း ရေးသားအသုံးပြုနိုင်ပါသည်။ သို့ရာတွင် သတိပြုရန် အချက်အနည်းငယ်ရှိပါသည်။

  • Linux, Solaries များတွင် ဖိုင်နှင့် ဒိုင်အတ္တရီများကို လင့်ခ်အမာဖြင့် လင့်ခ်လုပ်နိုင်သော်လည်း၊ Windows မှာတော့ ဖိုင်များကိုသာ လင့်ခ်လုပ်နိုင်ပါသည်။
  • Windows တွင်အသုံးပြုရာတွင် FAT ကိုအသုံးပြုသည့် Volume ဆိုပါက Hard Link များကို အသုံးပြု၍ရမည် မဟုတ်ပါ။
  • Linux, Solaries နှင့် Windows များမှာပါ လင့်ခ်အမာ (Hard Link) များကို လင့်ခ်လုပ်လိုသည့် ဖိုင်၊ ဒိုင်အတ္တရီများနှင့် Volume တစ်ခုအတွင်းမှသာ အသုံးပြုနိုင်ပါသည်။


စင်ဘိုးလစ်လင့်ခ် (Symbolic Link)


လင့်ခ်အမာများနှင့်စာလျှင် စင်ဘိုးလစ်လင့်ခ်များသည် ပို၍သုံးရလွယ်ကူပါသည်။ လက်တွေ့ ရေးသားကြည့်ပါမည်။
   // Creation of Symbolic Link
   Path link = fs.getPath("link.txt");
   Files.createSymbolicLink(link, target);
အထက်ပါအတိုင်း Files#createSymbolicLink မက်သတ်ဖြင့် စင်ဘိုးလစ်လင့်ခ်ကို ရေးသားအသုံးပြုနိုင်ပါသည်။ အထက်ပါကုဒ်များကို Fedora Linux ပေါ်တွင် Run ကြည့်ပါမည်။


အထက်ပါအတိုင်း target.txt ကို ရည်ညွှန်းသော link.txt စင်ဘိုးလစ်လင့်ခ်ကို ရေးသားနိုင်သည်ကို တွေ့ရပါမည်။

Windows အပေါ်တွင်လည်း Windows Vista မှစ၍ စင်ဘိုးလစ်လင့်ခ်များကို ရေးသားအသုံးပြု နိုင်ပါသည်။ သို့ရာတွင် စင်ဘိုးလစ်လင့်ခ်ကို ရေးသားရန် Admin Permission ကို လိုအပ်ပါသည်။ Command Promt ကို Admin ဖြင့်ဖွင့်ပြီး၊ နမှုနာ Symbolic.java ကို ကွန်ပိုင်းလုပ်ပြီး၊ အလုပ်ခိုင်းကြည့်ပါမည်။


အထက်ပါအတိုင်း target.txt ကို ရည်ညွှန်းသည့် စင်ဘိုးလစ်လင့်ခ်ကို ရေးသားနိုင်သည်ကို တွေ့ရပါမည်။

ယခုတစ်ခေါက်တွင် Files Utility Class ကို အသုံးပြု၍ File၊ Directory နှင့် လင့်ခ်များကို ရေးသားနိုင်ပုံကို ဖော်ပြခဲ့၏။ နောက်အခေါက်များတွင်လည်း ဆက်လက်၍ Nio2 နှင့်ပတ်သက်သော အကြောင်းအရာများကို ဆက်လက်ဖော်ပြသွားပါဦးမည်။


လေးစားစွာဖြင့်
မင်းလွင်


ကိုးကား
http://itpro.nikkeibp.co.jp/article/COLUMN/20110830/367909/?ST=develop&mkjt&P=1
http://docs.oracle.com/javase/tutorial/essential/io/check.html

February 21, 2012

Directory And File

ပြီးခဲ့သော အခန်းဆက်ဖြင့် Java 7 NIO2 ၏ FileSystem နှင့် Path အင်တာဖေစ် အကြောင်းကို ဖော်ပြခဲ့၏။FileSystemကို အသုံးပြု၍ Default File System နှင့် Zip ဒါမှမဟုတ် Jar ကဲ့သို့သော ဒြပ်မဲ့ဖိုင်စစ္စတမ် (Virtual File System) များကို အသုံးပြုနိုင်ကြောင်းနှင့်၊ Path အင်တာဖေစ်ကို အသုံးပြု၍ ဖိုင်များကို အသုံးပြုပုံတို့ကို ဖော်ပြခဲ့၏။ ယနေ့ဆက်လက်၍ NIO2 ၏  ဒိုင်အတ္တရီ (Directory)  နှင့် ဖိုင် များကို ဆက်သွယ်ပြီး Input/Output ပြုလုပ်ပုံနှင့်၊ ယခင်ရှိခဲ့ပြီးဖြစ်သော File IO နှင့် ဆက်သွယ်ပြီး အသုံးပြုပုံတို့ကို လေ့လာသွားပါမည်။


Directory

NIO2 တွင် ဒိုင်အတ္တရီ (Directory)  များကိုအသုံးပြုနိုင်ရန် DirectorySystem ကို ပံ့ပုံပေးလာခဲ့ပါသည်။ ယခင်တစ်ခေါက်က လေ့လာခဲ့သော Path အင်တာဖေစ် ဖြင့်လည်း ဒိုင်အတ္တရီ (Directory) များကို ကိုယ်စားပြု အသုံးပြုနိုင်ပါသည်။ သို့ရာတွင် Path အင်တာဖေစ်သည် ဖိုင်နှင့် ဒိုင်အတ္တရီ (Directory) တို့၏ တူညီသော အချက်အလက်များနှင့် အသုံးပြုပုံတို့ကိုသာ အသုံးပြုနိုင်ခဲ့၏။ ဒိုင်အတ္တရီ (Directory) အတွင်းရှိ ဖိုင်များနှင့် ထပ်ဆင့် ဒိုင်အတ္တရီများကိုမူ ခေါ်ယူအသုံးပြုနိုင်ခြင်း မရှိခဲ့ပါ။ အဆိုပါအချက်အလက်များကို အသုံးပြုနိုင်ရန်မှာ DirectorySystem အင်တာဖေစ်ကို ပံ့ပိုးထားသော အော့ဘဂျက် (Object) များကို အသုံးပြုရန် လိုအပ်ပါသည်။

DirectorySystem အော့ဘဂျက် (Object) အား Files အသုံးပြုကလပ်စ် (Utility Class) ၏ newDirectorySystem လုပ်ဆောင်ချက်ကို အသုံးပြုပြီး ခေါ်ယူ အသုံးပြု နိုင်ပါသည်။ လက်တွေ့ ဒိုင်အတ္တရီအတွင်းရှိ ဖိုင်များကို ရေးသားဖော်ပြနိုင်သော showChildren မက်သတ်ကို ပိုင်ဆိုင်သော ကလပ်စ်တစ်ခုကို ရေးသားကြည့်ပါမည်။ ပြီးလျှင် ပြီးခဲ့သောအပါတ်က ရေးသားခဲ့သော Zip File System နမှုနာ၏ Tomcat 7 ၏ catalina.jar အတွင်းရှိ ဖိုင်များကို ရေးသားကြည့်စေပါမည်။ အကုန်လျှောက်ရေးလျှင် ရှည်ကုန်မှာစိုးပါသဖြင့် org.apache.namming အောက်ရှိ ဖိုင်များကို ရေးခိုင်းကြည့်ပါမည်။
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ShowChildren {
 
 private static final String BIAS = "    ";
 
 public void showChildren(Path path, String bias) throws IOException {
  
  System.out.println(bias + path.getFileName());
  
  if(Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) {
   DirectoryStream<Path> directory = Files.newDirectoryStream(path);
   
   for(Path p : directory)
    this.showChildren(p, bias + BIAS);
  }
 }
 
 public static void main(String[] args) {

  ShowChildren sc = new ShowChildren();
  Path zip = Paths.get("A:","var","tomcat-7","lib","catalina.jar");
  
  try (FileSystem fs = FileSystems.newFileSystem(zip,
    ClassLoader.getSystemClassLoader())) {
   sc.showChildren(fs.getPath("org","apache","naming"), BIAS);
  } catch (IOException e) {
   System.out.println(e.getMessage());
  }
 }

}
Files#newDirectorySystem ဖြင့်အသုံးပြုရန် Path အော့ဘဂျက်မှာ ဒိုင်အတ္တရီ (Directory) ဖြစ်ရန် လိုအပ်ပါသည်။ ထို့အတွက် Files#isDirectory ဖြင့် ဒိုင်အတ္တရီ ဖြစ်ကြောင်း စမ်းစစ်နေပါသည်။ isDirectory ၏ ပဋ္ဌမ ပါရာမီတာမှာ Path အော့ဘဂျက်ဖြစ်ပြီး၊ ဒုတိယပါရာမီတာမှာ Varargs ပုံစံ LinkOption Enum ဖြစ်ပါသည်။ ဤနေရာတွင် လင့်ခ်ဖြစ်ပါက လိုက်မရှာရန် NOFOLLOW_LINKS ကို အသုံးပြုထားပါသည်။

တဖန် စာကြောင်း ၁၉တွင် DirectorySystem အင်တာဖေစ်၏ အော့ဘဂျက်ကို Files#newDirectorySystem ကို သုံး၍ ခေါ်ယူပါသည်။ DirectorySystem သည် java.lang.Iterable ၏ Sub Interface ဖြစ်သောကြောင့် For ဝါကျကို သုံး၍ ပါဝင်သော Path အော့ဘဂျက် တစ်ခုချင်းကို ရယူပြီး showChildren လုပ်ဆောင်ချက်ကို ပြန်လည် လုပ်ဆောင် စေနိုင်ပါသည်။

တဖန် Zip ဖိုင်စစ္စတမ်ကို အသုံးပြု၍ catalina.jar အတွင်းရှိ org.apache.naming အတွင်းရှိ ဖိုင်များကို showChildren ကို သုံး၍ ရေးသားစေပါသည်။ အထက်ပါ ပရိုဂရမ်ကို အလုပ်ခိုင်ကြည့်သောအခါ အောက်ဖော်ပြပါအတိုင်း ကွန်ဆိုးတွင် ထွက်ပေါ်လာမည် ဖြစ်သည်။
    naming
        TransactionRef.class
        StringManager.class
        ServiceRef.class
        SelectorContext.class
        ResourceRef.class
        ResourceLinkRef.class
        ResourceEnvRef.class
        NamingEntry.class
        NamingContextEnumeration.class
        NamingContextBindingsEnumeration.class
        NamingContext.class
        NameParserImpl.class
        LocalStrings.properties
        JndiPermission.class
        HandlerRef.class
        EjbRef.class
        ContextBindings.class
        ContextAccessController.class
        Constants.class
        resources/
            WARDirContext.class
            WARDirContext$WARResource.class
            WARDirContext$Entry.class
            VirtualDirContext.class
            ResourceCache.class
            ResourceAttributes.class
            Resource.class
            RecyclableNamingEnumeration.class
            ProxyDirContext.class
            LocalStrings.properties
            ImmutableNameNotFoundException.class
            FileDirContext.class
            FileDirContext$FileResourceAttributes.class
            FileDirContext$FileResource.class
            DirContextURLStreamHandlerFactory.class
            DirContextURLStreamHandler.class
            DirContextURLConnection.class
            Constants.class
            CacheEntry.class
            BaseDirContext.class
            BaseDirContext$AliasResult.class
            BaseDirContext$1.class
            jndi/
                Handler.class
        java/
            javaURLContextFactory.class
        factory/
            TransactionFactory.class
            SendMailFactory.class
            SendMailFactory$1.class
            ResourceLinkFactory.class
            ResourceFactory.class
            ResourceEnvFactory.class
            OpenEjbFactory.class
            MailSessionFactory.class
            MailSessionFactory$1.class
            MailSessionFactory$1$1.class
            EjbFactory.class
            DataSourceLinkFactory.class
            DataSourceLinkFactory$DataSourceHandler.class
            Constants.class
            BeanFactory.class

ယခင် File များနှင့် ဆက်သွယ်အသုံးပြုပုံ


Java ကဲ့သို့ အသုံးများသော ဘာသာရပ်များတွင် API အသစ်တစ်ခုတွင် အသစ်ပါဝင်သော အင်တာဖေစ်များကို လေ့လာရုံနှင့် လိုလောက်မည် မဟုတ်သေးပါ။ လက်ရှိအသုံးပြုနေသော API အဟောင်းများလည်း နေရာအနှံ့တွင် ရှိသောကြောင့် ရှိပြီးသား စစ္စတမ်နှင့် အပြန်အလှန် အသုံးပြုနိုင်မှု့သည် လွန်စွာမှအရေးပါလှပါသည်။

Path အော့ဘဂျက်မှ File အော့ဘဂျက်ကို ရယူရာတွင် Path အင်တာဖေစ်၏ toFile မက်သတ်ကို ပြင်ဆင်ထားပြီး၊ File မှ Path ကို ခေါ်ယူရန်အတွက် toPath မက်သတ်ကို ပြင်ဆင်ထားပါသည်။ သတိပြုရန်အချက်မှာ File ကလပ်စ်တွင် getPath လုပ်ဆောင်ချက်ကို ပံ့ပိုးထားပြီး၊ အထက်ပါ လုပ်ဆောင်ချက်မှာ File အော့ဘဂျက်၏ တည်နေရာကို စာကြောင်း အဖြစ် ပြန်လည် ဖော်ပြပေးနိုင်သော လုပ်ဆောင်ချက်ဖြစ်ပြီး Path အော့ဘဂျက်ကို မရရှိနိုင်ပါ။

ကျွှန်တော်တို့ လက်တွေ့ရေးကြည့်ပါမည်။
 public static void main(String[] args) {
  Path path = Paths.get("A:","var","tomcat-7","lib","catalina.jar");
  File file = new File("A:\\var\\tomcat-7");
  
  System.out.println("path" + "\t" + path);
  System.out.println("file" + "\t" + path.toFile());
  System.out.println("file" + "\t" + file);
  System.out.println("path" + "\t" + file.toPath());
  
 }
အထက်ပါပရိုဂရမ်၏ ရလဒ်မှာ အောက်ပါအတိုင်းဖြစ်ပါသည်။
path A:\var\tomcat-7\lib\catalina.jar
file A:\var\tomcat-7\lib\catalina.jar
file A:\var\tomcat-7
path A:\var\tomcat-7
 
ဖော်ပြပါအတိုင်း ဖိုင်အဟောင်းနှင့် Path ကို လွယ်ကူစွာ အပြန်အလှန် ခေါ်ယူ အသုံးပြုနိုင်သော်လည်း တစ်ခု သတိထားရန် အချက်မှာ ဤကဲ့သို့ ဖိုင်မှ Path နှင့် Path မှ ဖိုင် ပြောင်းလည်းခြင်းသည် DefaultFileSystem ကို အသုံးပြုသည့်အခါတွင်သာ အသုံးပြုနိုင်မည်ဖြစ်ပြီး ZIP/JAR ဖိုင်စစ္စတမ်ကို အသုံးပြုရာမှာ အသုံးပြုနိုင်မည် မဟုတ်ပေ။

Input / Output ပြုလုပ်ပုံများ

Java တွင် ဖိုင်များကို ဖတ်ယူရာတွင်၎င်း ရေးသားရာတွင်၎င်း၊ Stream များ၊ Reader Writer များနှင့် Channel များကို အသုံးပြုနိုင်ပါသည်။ Path အင်တာဖေစ်တွင်လည်း ထိုနည်းတူပင်ဖြစ်၏။ NIO2 ၌ java.io.FileInputStream နှင့် java.io.FileReader အစရှိသည့် ကလပ်စ်များနှင့် အဆင့်တူ ကလပ်စ်များကို ပြင်ဆင်ထားခြင်းမရှိပါ။ သို့ရာတွင် Files ကလပ်စ်ကို အသုံးပြု၍ FileInputStream များ FileReader များကို ခေါ်ယူနိုင်ပါသည်။


StandardOpenOption

NIO2 တွင် Files Utility ကလပ်စ်ကိုအသုံးပြု၍ Path အော့ဘဂျက် ဖိုင်များကို အလွယ်တကူ ဆက်သွယ်အသုံးပြုနိုင်ပါသည်။ ဖိုင်အော့ဘဂျက် များကို Input Output ပြုလုပ်ရာတွင် ရှိရင်းစွဲ Streams များ၊ Readers များ အပြင် Channels များကို အသုံးပြု၍၎င်း၊ Files ကလပ်စ်မှ တိုက်ရိုက် In Out ပြုလုပ်နိုင်ပါသည်။ Files မှ ဖိုင်များကို ဆက်သွယ်ရာတွင် မည်သည့်ပုံစံဖြင့် ဆက်သွယ်မည်ဆိုသည်ကို သတ်မှတ်ရန် လိုအပ်ပါသည်။ ဤသို့သက်မှာရာတွင် OpenOption Enum ကို အသုံးပြု၍ နှစ်သက်သလို အသုံးပြုနိုင်ပါသည်။

ပြင်ဆင်ထားသော StandardOpenOption Enum မှာ အောက်ပါအတိုင်းဖြစ်၏။

  • APPEND
  • CREATE
  • CREATE_NEW
  • DELETE_ON_CLOSE
  • DSYNC
  • READ
  • SPARSE
  • SYNC
  • TRUNCATE_EXISTING
  • WRITE


Stream များ

ဦးစွာ Stream များကို အသုံးပြုကြည့်ပါမည်။
 public static void doStreamSample(Path path) {

  try (InputStream input = Files.newInputStream(path);
    OutputStream out1 = Files.newOutputStream(path,
      StandardOpenOption.APPEND)) {
   // INPUT OUTPUT Process

  } catch (IOException e) {
   e.printStackTrace();
  }
 }
Files ကလပ်စ်၏ newInputStream နှင့် newOutputStream တို့တွင် ပါရာမီတာအနေဖြင့် Path အော့ဘုဂျက်နှင့် OpenOption Enum ၏ Varargs ကို အသုံးပြုရန်လိုအပ်ပါသည်။ Path မှာ Input Output လုပ်မည့် Path အော့ဘဂျက်ဖြစ်ပြီး၊ OpenOption မှာ File အား ဖွင့်ရာတွင် အသုံးပြုလိုသည့် Option များ ဖြစ်ပါသည်။ Varargs ပုံစံဖြစ်ပါသဖြင့် လိုအပ်သလို သတ်မှတ် အသုံးပြုနိုင်ပါသည်။


Reader နှင့် Writer များ

တဖန် Reader အော့ဘဂျက်များကိုလည်း Stream များကဲ့သို့ပင် Files Utility ကလပ်စ်မှ တိုက်ရိုက်ခေါ်ယူ အသုံးပြုနိုင်ပါသည်။
 public static void doReaderSample(Path path) {

  try (BufferedReader input = Files.newBufferedReader(path, 
      Charset.forName("utf-8"));
    BufferedWriter out1 = Files.newBufferedWriter(path,
      Charset.forName("utf-8"),
      StandardOpenOption.APPEND)) {
   // INPUT OUTPUT Process

  } catch (IOException e) {
   e.printStackTrace();
  }
 }
BufferedReader နှင့် BufferedWriter များကိုလည်း၊ Input Output Stream များကဲ့သို့ပင် Files Utility ကလပ်စ်မှ တိုက်ရိုက်ခေါ်ယူနိုင်ပါသည်။ BufferedReader နှင့် BufferedWriter တို့ကို တစ်ကြောင်းတည်းနှင့် ခေါ်ယူနိုင်ခြင်းသည် လွန်စွာမှ အစဉ်ပြေပါသည်။ ယခင်ကဆိုလျှင် FileInputStream အစရှိသည်တို့ကို ပါရာမီတာအဖြစ် ပေးရန် လိုအပ်ခဲ့သည်။ ဖိုင်လေးတစ်ခုရေးဖို့၊ ဖတ်ဖို့အတွက် မသုံးတဲ့ အော့ဘဂျက်တွေကို ရေးစရာလိုခဲ့ပေသည်။ သို့ရာတွင် Java 7 အရောက်တွင် အသုံးလိုသည့် BufferedReader ကို တိုက်ရိုက်ခေါ်ယူနိုင်သဖြင့် လွန်စွာမှ အစဉ်ပြေလှပါသည်။ မေ့စရာလည်း မရှိတော့ပေ။ သို့ရာတွင် BufferedReader နှင့် BufferedWriter တို့သည် Character Set ကို အသုံးပြုရန်လိုအပ်သဖြင့် ဒုတီယ ပါရာမီတာတွင် CharSet ၏ အော့ဘဂျက်ကို သတ်မှတ်ပေးရန် လိုအပ်ပါသည်။ တတိယပါရာမီတာမှာမူ၊ OpenOption Enum ၏ Verags ပင်ဖြစ်၏။


Chanel များ

 public static void doSeekChennelSample(Path path) {
  try (SeekableByteChannel chennel = Files.newByteChannel(path);
    SeekableByteChannel writeChannel = Files.newByteChannel(path, 
      StandardOpenOption.WRITE, 
      StandardOpenOption.APPEND)) {
   // INPUT OUTPUT Process

  } catch (IOException e) {
   e.printStackTrace();
  }
 }

 public static void doFileChannelSample(Path path) {
  try (FileChannel channel = FileChannel.open(path);
    FileChannel wtChannel = FileChannel.open(path, StandardOpenOption.WRITE)) {
   // INPUT OUTPUT Process

  } catch (IOException e) {
   e.printStackTrace();
  }
 }

Channel များကိုလည်း Files#newByteChannel ကို အသုံးပြု၍ ခေါ်ယူနိုင်ပါသည်။ Channel များသည်၊ ဖတ်နိုင်ရေးနိုင်သော အော့ဘဂျက်များ ဖြစ်ကြပါသည်။ သို့ရာတွင် Default မှာ ReadOnly ဖြစ်ပါသဖြင့် ရေးသားနိုင်ရန် StandardOpenOption.WRITE ကို သက်မှတ်ရန်လိုအပ်ပါသည်။

တဖန် ယခင်ကကဲ့သို့ FileChannel#open ဖြင့်လည်း FileChannel အော့ဘဂျက်ကို ခေါ်ယူအသုံးပြုနိုင်ပါသည်။ Java 7 မတိုင်ခင်က Stream ကို ကြားခံ၍ အသုံးပြုရန်လိုအပ်ခဲ့သော်လည်း Java 7 အရောက်တွင် Path အင်တာဖေစ်မှ တိုက်ရိုက်ခေါ်ယူလာနိုင်ခဲ့ပါသည်။


ပေါ့ပါးသော ဖိုင်များဆိုပါက

 public static void easyReadSample(Path path) throws IOException {
  
  // Read Bytes
  byte [] bytes = Files.readAllBytes(path);
  
  // Read Lines
  List<String> lines = Files.readAllLines(path, Charset.defaultCharset());
  
  // Write bytes
  Files.write(path, bytes, StandardOpenOption.APPEND);
  
  // Write Text Lines
  Files.write(path, lines, Charset.forName("utf-8"), StandardOpenOption.APPEND);
 }
နောက်ဆုံးတွင် အသုံးပြုလိုသောဖိုင်မှာ သေးငယ်ပေါ့ပါးပါက၊ Stream, Reader/Writer, Channel သုံးစရာမလိုပဲ Files Utility ကလပ်စ်မှ တိုက်ရိုက် အသုံးပြုနိုင်သည်ကို တွေ့ရပါသည်။



နောက်ဆုံးတွင်


ကျွှန်တော်တို့သည် ဒိတစ်ခေါက်တွင် Directory ၏ လုပ်ဆောင်ချက်များကို အပြည့်အဝအသုံးပြုနိုင်ရန် DirectorySystem ကလပ်စ်ကို အသုံးပြုရန်လိုအပ်ကြောင်း၊ မှုလ ဖိုင်စစ္စတမ်များနှင့်လည်း အလွယ်တကူ အပြန်အလှန် ဆက်သွယ်အသုံးပြုနိုင်ကြောင်းနှင့် Input Output ပြုလုပ်ရာတွင်လည်း ရင်းနှီးပြီးသား ကလပ်စ်များမှအစ၊ Channel များကိုပါ Path အင်တာဖေစ်ကို အသုံးပြု၍ လွယ်ကူစွာ ခေါ်ယူအသုံးပြုနိုင်ကြောင်း လေ့လာခဲ့ပါသည်။ ပြန်လည်သုံးသပ်ရမည် ဆိုလျှင် အသစ်များကိုလည်း အသုံးပြုနိုင်၏၊ ယခင်ကအရာများကိုလည်း အသုံးပြုနိုင်၏၊ ထိုထက်မက ပိုမိုလွယ်ကူစွာခေါ်ယူအသုံးပြုနိုင်၏။ ကျွှန်တော်သည် ဤဘလောဂ်အား ရေးရင်းနဲ့ ဖိုင်အင်တာဖေစ် အသစ်များကို လက်တွေ့အသုံးပြုလိုစိတ်များ တစ်ဖွားဖွား ပေါ်ပေါက်လာပတ်တော့သည်။ နောက်တစ်ခေါက်ရေးရန် Happy Hacking တွင် Nio2 ကိုသုံး၍ ဖိုင်ကိုခေါ်ဖတ်ပုံကို ထည့်သွင်းရေးသားကြည့်ပါဦးမည်။


ကိုးကား
http://itpro.nikkeibp.co.jp/article/COLUMN/20110725/362803/?ST=develop&mkjt&P=6


လေးစားစွာဖြင့်
မင်းလွင်

February 9, 2012

FileSystem & Path Interface

ပြီးခဲ့သော NIO2 ဆိုသည်မှာ ဘလောဂ်ဖြင့် Java 7 ၏ ဖိုင်စစ္စတမ်အသစ်တစ်ခုဖြစ်သော NIO2 အကြောင်းကို အကျဉ်းချုံး ဖော်ပြခဲ့၏။ ယခင်အသုံးပြုခဲ့သော java.io.File ၏ အားနည်းချက်များဖြစ်ကြသော၊ Metadata များကိုအသုံးမပြုနိုင်ခြင်း၊ Symbolic Link များကို အသုံးမပြုနိုင်ခြင်းနှင့် ဖိုင်စစ္စတမ်ကို စောင့်ကြည့်မနေနိုင်ခြင်း အစရှိသည်တို့ကို ပြုပြင်ရန် ဖိုင်စစ္စတမ်အသစ်တစ်ခုကို အသစ်ရေသားလာခဲ့ခြင်း ဖြစ်၏။ ယခု တစ်ခေါက်တွင် NIO2 ၏ အဓိက ဇာတ်ကောင်များဖြစ်ကြသော FileSystem နှင့် Path Interface တို့၏ အသုံးပြုပုံတို့ကို လေ့လာသွားပါမည်။

FileSystem

FileSystem ကလပ်စ်သည် အမည်ပါအတိုင်း ဖိုင်စစ္စတမ်ကို ကိုယ်စားပြုသော ကလပ်စ်တစ်ခုဖြစ်ပါသည်။ FileSystem သည် လက်ရှိ OS အတွင်းရှိ ဖိုင်စစ္စတမ်များသာမက၊ jar သို့မဟုတ် zip ဖိုင်များကိုလည်း ဒြပ်မဲ့ ဖိုင်စစ္စတမ် (Virtual File System)  အနေဖြင့် အသုံးပြုနိုင်ပါသည်။

တဖန် FileSystem Object ကို အသစ်မွေးဖွားစေရာတွင် အသုံးပြုနိုင်သည်မှာ FileSystems Factory Class ပင်ဖြစ်၏။ Default FileSystem Object ကို မွေးဖွားစေရာတွင် အောက်ပါအတိုင်း အသုံးပြု ရေးသား နိုင်ပါသည်။
FileSystem fileSystem = FileSystems.getDefault();

FileSystem Class သည် AutoCloseable Interface ကို ပံ့ပိုးထားပါသဖြင့် Java 7 တွင် စတင်ခဲ့သော try-with-resource ရေးသားပုံကို အသုံးပြုနိုင်ပါသည်။ သို့ရာတွင် သတိထားရန် တစ်ခုမှာ FileSystems#getDefault မက်သတ်ဖြင့် ခေါ်ယူထားသော Default FileSystem အား try-with-resource ပုံစံဖြင့် ရေးသားပါက အလုပ်လုပ်ချိန်တွင် UnsupportedOperationException ကို ဖြစ်ပေါ်စေမည် ဖြစ်သည်။
package com.mmjug.java7.ep4;

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;

public class FileSystemSample {

 public static void main(String[] args) {
  try (FileSystem fileSystem = FileSystems.getDefault()) {
   System.out.println(fileSystem.provider().getClass().getName());
  } catch (IOException e) {
   System.err.println(e.getMessage());
  }
 }
}
အထက်ပါ ကုဒ်များကို အလုပ်ခိုင်းကြည့်ပါက အောက်ပါအတိုင်း Exception ကို ဖြစ်ပေါ်စေမည် ဖြစ်သည်။
Exception in thread "main" sun.nio.fs.WindowsFileSystemProvider
java.lang.UnsupportedOperationException
 at sun.nio.fs.WindowsFileSystem.close(WindowsFileSystem.java:117)
 at com.mmjug.java7.ep4.FileSystemSample.main(FileSystemSample.java:12)

Open JDK အဖွဲ့မှ ပြသထားသော WindowsFileSystem ကလပ်စ်၏ close လုပ်ဆောင်ချက်ကို ကြည့်ကြည့်ရာတွင် အောက်ပါအတိုင်း UnsupportedOperationException ကို throw လုပ်နေသည်ကို တွေ့ရပါသည်။ ထို့ကြောင့် Exception ကို ဖြစ်ပေါ်စေခြင်းဖြစ်သည်။ Source ကုဒ်ကို ကြည့်ခြင်းအားဖြင့် လက်ရှိအချိန်တွင် WindowsFileSystem သည် AutoCloseable ကို အပြည့်အဝ ပံ့ပိုးနိုင်ပုံမရသေးပေ။
       @Override
       public void close() throws IOException {
           throw new UnsupportedOperationException();
       }
တမင်ပဲ Exception ကို throw လုပ်နေသလား၊ Bug လားတော့မသိပါ၊ Linux နှင့် Solaries OS များတွင် အသုံးပြုသော UnixFileSystem ၌လည်း အထက်ပါအတိုင်း ရေးသားထားသည်ကို တွေ့ရ၏။ OS အဆင့် ဖိုင်စစ္စတမ်ကို AutoCloseable မလုပ်စေချင်သည့်သဘောလား မသိပါ။ Bug Report ကို စောင့်ကြည့်ရပါဦးမည်။ လက်ရှိ FileSystem Object ကို ခေါ်ယူနိုင်ကြောင်းကို သိရသည့်အတွက် ရှေ့ကို ဆက်ပါဦးမည်။


Path Interface

Path Interface သည် လက်ရှိ ဖိုင်စစ္စတမ် အတွင်းရှိ ဖိုင်နှင့် ဒိုင်အက်တရီများကို ကိုယ်စားပြုပါသည်။ Path ၏ instance များကို မွေးဖွားစေရာတွင် FileSystem#getPath လုပ်ဆောင်ချက်ကို၎င်း Paths#get လုပ်ဆောင်ချက်ကို၎င်း အသုံးပြုနိုင်ပါသည်။
  // Path with only one parameter
  Path path1 = fs.getPath("c:\\tmp\\fpone.txt");
  System.out.println(path1);
  
  // Path with varargs
  Path path2 = fs.getPath("c:", "tmp","sample","ftwo.txt");
  System.out.println(path2);
  
  // Path made by Paths
  Path path3 = Paths.get("c:\\tmp\\fthree.txt");
  System.out.println(path3);

FileSystem#getPath လုပ်ဆောင်ချက်သည် ပါရာမီတာအား၊ Varargs အနေဖြင့် အသုံးပြုနိုင်ပါသည်။ ပါရာမီတာ တစ်ခုတည်းသာ ပါဝင်ခဲ့မည်ဆိုလျှင် စာကြောင်း ၂ ကဲ့သို့ ဖိုင်၊ ဒါမှမဟုတ်  ဒိုင်အတ္တရီ တစ်ခုအနေဖြင့် အသုံးပြုနိုင်မည် ဖြစ်သည်။ ရေးသားရာတွင်လည်း လမ်းကြောင်း အတိအကျ (Absolute Path) အနေဖြင့်၎င်း၊ နှိုင်းယှဉ် လမ်းကြောင်း (Relative Path) အနေဖြင့်၎င်း ရေးသားနိုင်ပါသည်။

စာကြောင်း နံပါတ်၆ကဲ့သို့ ပါရာမီတာအများ(Varargs) ကို အသုံးပြုထားပါက၊ နောက်ဆုံး ပါရာမီတာသည် ဖိုင်ဒါမှ မဟုတ် ဒိုင်အတ္တရီ၏ အမည် အဖြစ်အသုံးပြုမည်ဖြစ်ပြီး၊ ကျန်ရစ်သော ပါရာမီတာများကိုမူ Sub Directory အဖြစ်အသုံးပြုနိုင်မည် ဖြစ်သည်။ ဤနမှုနာ၌ စာကြောင်း ၇ ဖြင့် "c:\tmp\sample\ftwo.txt" ကို ဖော်ပြပေးနိုင်မည် ဖြစ်သည်။

NIO2 တွင် FileSystem အပြင် အလွယ်တကူ Path အင်စတန့်စ်များကို မွေးဖွားစေနိုင်ရန် Paths ကလပ်စ်ကို ပြင်ဆင်ထားပါသည်။ အထက်ပါ နမှုနာ၏ စာကြောင်း ၁၀ကဲ့သို့ Paths#get လုပ်ဆောင်ချက်ကို အသုံးပြုပါက၊ အတိအကျလမ်းကြောင်းဖြစ်ဖြစ်၊ နှိုင်းယှဉ်လမ်းကြောင်းများကိုပါ Path Object များကို မွေးဖွားအသုံးပြုနိုင်ပါသည်။ သို့ရာတွင် Paths ကလပ်စ်ဖြင့် အသုံးပြုနိုင်သည်မှာ Default FileSystem များသာဖြစ်သည်ကို သတိပြုစေလိုပါသည်။

အထက်ပါ နမှုနာကို Run လုပ်ကြည့်သောအခါ အောက်ပါအတိုင်း ဖော်ပြနိုင်မည်ဖြစ်သည်။
c:\tmp\fpone.txt
c:\tmp\sample\ftwo.txt
c:\tmp\fthree.txt

FileSystemProvider

FileSystem ကလပ်စ်များအား ပံ့ပိုးပေးနေသည်မှာ ServiceProvider များပင်ဖြစ်၏။ Java ၏ ServiceProvider သည် java.nio.file.spi.FileSystemProvider ဖြစ်၏။ လက်ရှိ အသုံးပြုနိုင်သော ServiceProvider များအား  FileSystemProvider #installedProviders ဖြင့် သိရှိနိုင်ပါသည်။
 public static void main(String[] args) {
  List list = FileSystemProvider.installedProviders();
  
  for(FileSystemProvider p : list) {
   System.out.println(p.getScheme() + " : " + p.getClass());
  }
 }
အထက်ပါ နမှုနာ၏ စာကြောင်း ၂ တွင် installedProviders လုပ်ဆောင်ချက်ဖြင့် အသုံးပြုနိုင်သော FileSystemProvider များကို ရယူပါသည်။ တဖန် for ဝါကျကိုအသုံးပြု၍ list အတွင်းရှိ FileSystemProvider Object များ၏ စကီးမားနှင့် ကလပ်စ် များကို ရေးသားနေစေပါသည်။ လက်ရှိ အလုပ်လုပ်ကြည့်စေသောအခါ အောက်ပါအတိုင်းတွေ့ရမည် ဖြစ်သည်။
file : class sun.nio.fs.WindowsFileSystemProvider
jar : class com.sun.nio.zipfs.ZipFileSystemProvider
File များအား ပံ့ပိုးပေးရန်အတွက် WindowsFileSystemProvider နှင့် jar ဒါမှမဟုတ် zip ဖိုင်တွေကို ပံ့ပိုးပေးဖို့အတွက် ZipFileSystemProvider တို့က တာဝန်ယူထားသည်ကို သိရှိနိုင်ပါသည်။ FileSystems#getDefault ဖြင့် ခေါ်ယူပါက WindowsFileSystemProvider မှ Windows ၏ FileSystem ကို ပြန်လည်ပေးပို့ပေးမည် ဖြစ်၏။ Default File System မှာ အသုံးပြုနေသော OS အပေါ်မှုတည်၍ အမျိုးမျိုး ပြောင်းလည်းနိုင်ပါသည်။

Jar နှင့် Zip ဖိုင်များအား File System အနေဖြင့် အသုံးပြုနိုင်သော ZipFileSystemProvider ကို အသုံးပြုပြီး လက်တွေ့ နမှုနာတစ်ခုကို ရေးကြည့်ပါမည်။ ဤနေရာတွင် getDefault ဖြင့်ခေါ်ယူ၍မရပဲ FileSystems#newFileSystem လုပ်ဆောင်ချက်ကို အသုံးပြုသွားမည် ဖြစ်ပါသည်။
package com.mmjug.java7.ep4;

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ZipFileSystem {

 public static void main(String[] args) {
  
  Path zip = Paths.get("A:","var","tomcat-7","lib","catalina.jar");

  try (FileSystem fs = FileSystems.newFileSystem(zip,
    ClassLoader.getSystemClassLoader())) {
   System.out.println("FileSystem : " + fs);
   System.out.println("Class : " + fs.getClass());
  } catch (IOException e) {
   System.out.println(e.getMessage());
  }
 }

}
အထက်ပါ နမှုနာအထဲတွင် catalina.jar ဖိုင်အား  ဒြပ်မယ့်ဖိုင်စစ္စတမ် တစ်ခုအနေဖြင့် အသုံးပြုရန် ZipFileSystemProvider ကိုအသုံးပြု၍ ZipFileSystem ကို ခေါ်ယူနေခြင်းဖြစ်ပါသည်။  ဦးစွာ catalina.jar ဖိုင်၏ နေရာအတိအကျကိုဖော်ပြ၍ Path အင်တာဖေစ်ကို မွေးဖွားစေပါသည်။ တဖန် အထက်ပါ Path အင်းစတန့်စ်အား ပါရာမီတာအဖြစ်သုံးပြီး FileSystems ကလပ်၏ newFileSystem လုပ်ဆောင်ချက်ကို အသုံးပြုကာ catalina.jar အား ဒြပ်မဲ့ဖိုင်စစ္စတမ် အဖြစ်အသုံးပြုနိုင်သော Object တစ်ခုကို ရယူနေပါသည်။

ကျွှန်တော်တို့ ဤဘလောဂ်ဖြင့် FileSystem ကလပ်စ်နှင့် Path အင်တာဖေစ်တို့ကို မွေးဖွားစေပုံနှင့် jar နှင့် zip ဖိုင်တို့ကို ဒြပ်မဲ့ဖိုင် စစ္စတမ်အဖြစ် အသုံးပြုနိုင်ရန် ZipFileSystemProvider တို့က ပံ့ပိုးပေးနေကြောင်းကို လေ့လာခဲ့ပါသည်။

နောက်ရက်များတွင် ဆက်လက်၍ Directory တွေကို ဘယ်လိုသုံးမလဲ၊ ဖိုင်နဲ့ ဒိုင်အက်တရီတွေကို ဘယ်လို ကွန်တရိုးလုပ်မလဲ၊ ဖိုင်စစ္စတမ်တွေကို ဘယ်လို စောင့်ကြည့်မလဲ ဆိုတဲ့အချက်တွေကို ဆက်လက် လေ့လာသွားပါဦးမည်။

ကိုးကား
http://itpro.nikkeibp.co.jp/article/COLUMN/20110725/362803/?ST=develop&mkjb&P=3


လေးစားစွာဖြင့်
မင်းလွင်