مهدی عادلی فر
بنیانگذار توسینسو و برنامه نویس

آموزش بالا بردن سرعت اجرای برنامه ها در جاوا ( Java )

چگونه سرعت اجرای برنامه های جاوا را بالا ببریم؟  زبان جاوا زبانی غنی و قوی است و در عین حال به دلیل داشتن کلاس های متفاوت راه های مختلفی را برای حل مساله ارائه می کند. این زبان کاربرد های متنوعی دارد و امروزه با توجه به محبوبیت و بلوغ این زبان در سطح گسترده ای از آن استفاده می شود. شاید یکی از ایراد هایی که برخی به زبان جاوا گرفته اند این است که در برخی موارد سرعت آن از زبان هایی مانند C, C++ کمتر است. در ادامه روش هایی را یاد می دهیم که می توانید با استفاده از آنها سرعت برنامه ها و الگوریتم های خودتان را تا چندین برابر بیشتر کنید در حدی که تفاوت چندانی بین برنامه به زبان جاوا و برنامه به زبان C++ نباشد.

دوره های شبکه، برنامه نویسی، مجازی سازی، امنیت، نفوذ و ... با برترین های ایران

چرا از جاوا استفاده کنیم؟

 در زبان برنامه نویسی جاوا می توان برنامه ها را به گونه ای پیاده سازی کرد که پیچیدگی های زبان هایی مثل  C , C++ را نداشته باشند ولی در عین حال قدرت بسیاری داشته باشند. همچنین به کمک محیط های برنامه نویسی متنوع و قوی مثل Intellij می توان روند برنامه نویسی و خطا یابی را سریع تر و کم هزینه تر کرد. زبان جاوا به محیط خاصی وابستگی ندارد و می تواند روی پلتفرم های مختلف اجرا شود. از زبان جاوا می توان در پیاده سازی انواع برنامه های شبکه و موبایل و برنامه های وب و سرور ها استفاده کرد. همچنین جاوا یکی از بهترین گزینه ها برای پیاده سازی برنامه های سنگین تجاری می باشد.

چگونه سرعت خواندن فایل در جاوا را بالا ببریم؟

یکی از معمول ترین و ساده ترین کارها برای دریافت ورودی (از کاربر یا فایل) استفاده از کلاس scanner است. اما این کلاس نسبت به کلاس BufferedReader بسیار کند تر است. دلیل کندی این کلاس موارد زیر است.

  •   کلاس BufferedReader بافر بزرگتری نسبت به Scanner دارد. (8 کیلو بایت و یک کیلوبایت)
  •   کلاس Scanner به صورت داخلی از عبارات منظم (Regular expression ) استفاده می کند تا بتواند داده های رشته ای را خوانده و آنها را به نوع مورد نظر تبدیل کند. برای مثال در متدهای nextInt, nextDouble از عبارات منظم استفاده می کند. خب RegExp هم باعث کندی می شود.

در ادامه مثالی آورده ایم  که این دوکلاس را با هم مقایسه کرده است این مثال برنامه ای است که از یک فایل 2،000،000 عدد را می خواند. در تصویر زیر پیاده سازی با استفاده از Scanner آورده شده است.

آموزش بالا بردن سرعت اجرای برنامه ها در جاوا ( Java )

همانطور که می بینید زمان 1.289 ثانیه زمان برای خواندن اعداد از فایل لازم است. حال این کار را با استفاده از BufferedReader پیاده سازی می کنیم. برای این کار در قدم اول یک کلاس به نام FastScanner درست می کنیم. این کلاس دارای دو فیلد BufferedReader, StringTokenizer است. به شکل زیر 

static class FastScanner {
        BufferedReader br;
        StringTokenizer st;
        //! Receives input from a file
        public FastScanner(File file) throws FileNotFoundException {
            br = new BufferedReader(new FileReader(file));
        }
        //! Receives input from the terminal
        public FastScanner() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }
        // @return - the next string token from the tokenizer
        // if tokenizer `hasMoreElements` OR*
        // create a new StringTokenizer that consumes a line from <BufferedReader/>
        public String next() {
            while(st == null || !st.hasMoreElements()){
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }
    }

دقت داشته باشید که کلاس StringTokenizer برای این است که فایل را خط به خط بخوانیم و داخل آن بریزیم و بعد از آن بتوانیم از متد nextToken برای رفتن به عدد بعدی استفاده کنیم. در قدم بعدی باید متدهایی مانند nextDouble, nextInt را به برنامه اضافه کنیم. برای این که متدهای کلاس جدید با متدهای Scanner اشتباه نشود آنها را ni به جای nextInt و nd به جای nextDouble و nl به جای nextLong قرار داده ایم. که در ادامه این متدها را می بینید.


public int ni(){
        return Integer.parseInt(next());
    }
    public long nl(){
        return Long.parseLong(next());
    }
    public double nd(){
        return Double.parseDouble(next())
    }
    public char[] nc(){
        return next().toCharArray();
    }
    public void close(){
        try{
            br.close();
        }catch(IOException e){e.printStackTrace();}
    }

متد آخر برای بستن BufferedReader می باشد که همیشه باید آن را بست.

  • تست سرعت

نتیجه اجرای برنامه با استفاده از کلاس FastScanner را در تصویر زیر می بینید.

آموزش بالا بردن سرعت اجرای برنامه ها در جاوا ( Java )

همانطور که در تصویر مشخص شده است زمان اجرا 321 میلی ثانیه است یعنی سرعت اجرای برنامه بیش از 4 برابر افزایش یافته است. 

سرعت خروجی را در جاوا چگونه افزایش دهیم؟

برای بالا بردن سرعت خروجی چه نوشتن در فایل یا چاپ کردن در صفحه نمایش به  جای استفاده از System.out.println بهتر است که از کلاس PrintWriter استفاده کنیم. درست است که این دو روش مانند دو روش گفته شده در ورودی تفاوت سرعت ندارند ولی می توان گفت PrintWriter دو برابر سریع تر از println است. در برنامه زیر نحوه چاپ عبارات با استفاده PrintWriter و println نشان داده شده است.



import java.io.PrintWriter;

public class Test {
    public static void main(String[] args) {
        final int SIZE=2000000;
        final long startTime0 = System.nanoTime();
        for (int i = 0; i < SIZE; i++) {
            System.out.print("tosinso Mehdi Adeli");
        }
        final long duaration0 = System.nanoTime() - startTime0;
        PrintWriter pw=new PrintWriter(System.out);
        final long startTime = System.nanoTime();
        for (int i = 0; i < SIZE; i++) {
            pw.print("\ttosinso Mehdi Adeli");
        }
        final long duration1 = System.nanoTime() - startTime;
        pw.println(duration1);




        System.out.println();
        System.out.println("duration0");
        System.out.println(duaration0);
       pw.println("duration1");
        pw.println(duration1);

        pw.close();
    }
}


نکته مهمی که در بالا بردن سرعت خروجی باید به آن دقت کنیم به هم چسباندن تکه های رشته ها است. اگر شما برای این کار از کلاس StringBuilder استفاده کنید سرعت بسیار بالاتری را نسبت به زمانی که از علامت + برای تلفیق رشته ها استفاده می کنید.

تا حد امکان از انواع داده اصلی به جای کلاس wrapper استفاده کنید

داده های که نوع آنها انواع داده اصلی است مانند int, long, char هنگام اجرای برنامه در داخل پشته (stack) بر روی RAM نگهداری می شوند ولی اشیا ساخته شده از کلاس ها در داخل محیط Heap(هرم) نگهداری می شوند. نکته مهمی که در این قضیه وجود دارد این است که پشته نسبت به هرم سریع تر است. به همین دلیل وقتی که برای نگهداری متغیر ها و آرایه ها از پشته به جای heap استفاده می کنیم سرعت برنامه بالا تر خواهد بود. در تصویر زیر برنامه ای نوشته ایم که این دو حالت مختلف را با هم مقایسه کرده است که می بینیم که در نتیجه استفاده از انواع داده اصلی حدود 56 برابر سریع تر است و این می تواند خیلی مهم باشد.

آموزش بالا بردن سرعت اجرای برنامه ها در جاوا ( Java )

تا حد امکان از آرایه به جای ArrayList استفاده کنید

وقتی که اندازه و تعداد داده های شما مشخص است حتما از آرایه استفاده کنید. این کار علاوه بر این که باعث می شود برنامه شما سرعت بالایی داشته باشد استفاده از آرایه راحت تر از استفاده از ArrayList است مثلا خیلی راحت است که برای دسترسی به عناصر آرایه بنویسیم [j]arr[i] تا این که بنویسیم arr.get(i).get(j). در تصویر زیر یک تست آورده ایم که سرعت دو روش استفاده از آرایه و ArrayList را با هم مقایسه می کند.

آموزش بالا بردن سرعت اجرای برنامه ها در جاوا ( Java )

مشاهده می کنید که استفاده از آرایه بیش از  40 برابر سرعت را بالا تر می برد. دقت کنید که ما قبلا اندازه ArrayList را نیز مشخص کرده بودیم ولی باز سرعت آرایه بسیار بالا تر بود. فقط به این نکته دقت داشته باشید که وقتی که از اندازه داده ها خبر نداریم مجبوریم که از ArrayList استفاده کنیم.

چه زمانی برای تلفیق رشته از + و چه زمانی از StringBuilder استفاده کنیم؟

کار با رشته ها یکی از مهم ترین قسمت های برنامه نویسی است که اگر بهینه پیاده سازی نشود ممکن است که سرعت برنامه بسیار پایین بیاید. بهتر است که قواعد زیر را در تلفیق رشته ها به کار ببریم.

 1. در رشته هایی که مقادیرشان معلوم است و طولانی نیستند از علامت + استفاده کنیم. برای مثال برای چاپ یا مقداردهی رشته زیر از + استفاده می کنیم.


String myString="I "+"am" +"a "+"Java Programmer";
System.out.println("I "+"am" +"a "+"Java Programmer");

در دو خط بالا به خاطر این که رشته از قبل معلوم و ثابت است لازم نیست که یک شی StringBuilder برای آن ساخته شود.
۲. از کلاس StringBuilder زمانی که به تعداد دفعات زیاد مقادیری را به یک رشته اضافه می کنیم و یا مقادیری را از آن حذف می کنیم و یا قسمت هایی از آن را جایگزین می کنیم استفاده می شود.
۳. در داخل حلقه ها از عملگر =+ هیچگاه استفاده نکنید.
با وب سایت Tosinso همراه باشید.


مهدی عادلی فر
مهدی عادلی فر

بنیانگذار توسینسو و برنامه نویس

مهدی عادلی، بنیان گذار TOSINSO. کارشناس ارشد نرم افزار کامپیوتر از دانشگاه صنعتی امیرکبیر و #C و جاوا و اندروید کار می کنم. در زمینه های موبایل و وب و ویندوز فعالیت دارم و به طراحی نرم افزار و اصول مهندسی نرم افزار علاقه مندم.

نظرات