JavaRush /Blog Java /Random-VI /Hoạt động theo từng byte với các tập tin
Joysi
Mức độ

Hoạt động theo từng byte với các tập tin

Xuất bản trong nhóm
Đặc biệt dành cho

Bắt đầu nào

Ở cấp độ 18, nhiệm vụ đầu tiên của việc đọc tệp theo từng byte bắt đầu: đọc tệp, sau đó tìm byte tối thiểu/tối đa hoặc xuất tệp ở dạng có thứ tự, v.v.
Hoạt động từng byte với các tệp - 1
Người dân ở đây rất thông minh. Họ biết về các bộ sưu tập và họ có thể sắp xếp và chèn. Bộ sưu tập là một cơ chế mạnh mẽ. Và nhiều người đã không sử dụng chúng trước JavaRush. Tất nhiên, việc nghiên cứu chúng và cố gắng đánh sai chỗ là điều đáng khen ngợi. Vì thế. Chúng ta hãy giải một bài toán không có trong nhiệm vụ (để không bị tiết lộ nội dung khi giải), nhưng có những bài toán rất giống nhau:
  • Nhập tên tệp từ bảng điều khiển
  • Đọc tất cả byte từ một tập tin.
  • Bỏ qua sự lặp lại, sắp xếp chúng theo mã byte theo thứ tự giảm dần.
  • Trưng bày
  • Đóng luồng I/O
Các byte mẫu của tệp đầu vào 44 83 44 Ví dụ về đầu ra 83 44 Chúng tôi cũng đưa ra các biến startTimefinishTimeghi lại thời gian thực hiện của chương trình. Để tính toán, tôi đã sử dụng i3-3GHz/8Gb RAM/HDD WD Blue-1Tb/Win7-64/jdk-8u73-windows-x64 (ví dụ về các chương trình trong tùy chọn 1-2 được lấy từ diễn đàn info.javarush, chúng hơi khác một chút). chỉ được sửa đổi để sắp xếp theo thứ tự tăng dần nhé - nghĩa là chúng là THỰC SỰ!!)

Hãy giải quyết nó trực tiếp:

// Вариант 1. Загоняем в коллекцию и сортируем используя ее метод Collections.sort
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());
        long startTime = System.currentTimeMillis();

        ArrayList<Integer> listData = new ArrayList<Integer>();
        while (inputStream.available() > 0) listData.add(inputStream.read());
        inputStream.close();
        ArrayList<Integer> result = new ArrayList<Integer>(new HashSet<Integer>(listData));
        Collections.sort(result);

        while (!result.isEmpty()) {
            System.out.print(result.get(result.size()-1) + " ");
            result.remove(result.get(result.size()-1));
        }

        long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
Giải quyết mọi thứ tuyệt vời! Bài kiểm tra (nếu có thì nó đã vượt qua một cách thành công). Nhưng ở đời hiếm có tập hồ sơ nào chỉ chứa dòng chữ “Mẹ giặt khung”. Hãy cung cấp cho chương trình của chúng ta một tệp 46 MB (theo tiêu chuẩn ngày nay, nó có vẻ không nhiều). Nó là gì, chương trình chạy trong 220 giây. Nỗ lực nạp tệp 1Gb vào buổi tối (kích thước của phim MPEG4 không có chất lượng tốt nhất) đã không thành công. Tôi vẫn đang đọc chương trình vào buổi sáng - và tôi phải đi làm rồi. Vấn đề là gì? Có lẽ đang được sử dụng ArrayList<Integer>có 1 tỷ phần tử bên trong. Mỗi phần tử của nó chiếm tối thiểu 16 byte (Tiêu đề: 8 byte + Trường int: 4 byte + Căn chỉnh cho bội số 8: 4 byte). Tổng cộng, chúng tôi tự nguyện đưa 16 Gb dữ liệu vào bộ nhớ với kích thước RAM là 8. Chúng tôi sẽ làm tốt hơn. Hãy đi sâu hơn vào các bộ sưu tập. Và hoan hô, chúng tôi đã tìm thấy thứ chúng tôi cần.

Gặp gỡ cây

Đây là rất nhiều:
  • không cho phép lưu trữ hai phần tử giống hệt nhau (có nghĩa là chúng tôi sẽ lưu trữ tất cả 255 phần tử trong bộ nhớ, thay vì một tỷ!)
  • khi thao tác các phần tử của nó, nó sẽ tự động sắp xếp (tự sắp xếp - đây rồi, đỉnh cao của sự hoàn hảo!)
Chúng tôi nhận được:
// Вариант 2. Загоняем в ТreeSet который сам сортирует (лютый win!)
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        byte[] arrBytes = new byte[256];
        long startTime = System.currentTimeMillis();

        SortedSet<Integer> list = new TreeSet<Integer>();
        while(inputStream.available()>0) list.add(inputStream.read());
        inputStream.close();

        while (!list.isEmpty())        {
            System.out.print(list.last() + " ");
            list.remove(list.last());
        }

		long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
Đầu ra là: tệp 46MB, 176 giây. Tệp 1Gb - 3 giờ 5 phút. Sự tiến bộ là rõ ràng. Chúng tôi có thể “chờ” kết quả và tệp 46MB được xử lý nhanh hơn đáng kể. Hãy tiếp tục. Chúng ta hãy cố gắng từ bỏ các bộ sưu tập (điều này sẽ vô cùng đau đớn đối với một số người). Chúng ta sẽ sử dụng các mảng đơn giản (nó rất nguyên thủy). Hãy lưu ý một điều quan trọng . Số byte gặp phải có thể được đưa vào một mảng có độ dài 256. Vì vậy, chúng ta sẽ chỉ cần tăng phần tử mảng tương ứng với byte đọc lên một.

Mảng - từng byte

// Вариант 3. Считываем массив поbyteно.
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        long[] arrBytes = new long[256];
        long startTime = System.currentTimeMillis();

        while (inputStream.available() > 0) arrBytes[inputStream.read()]++;

		inputStream.close();
        // Выводим отсортированный по byte-codeу в обратном порядке
        for (long i = 255; i >= 0 ; i--)
            if (arrBytes[(int) i] > 0) System.out.print(i + " ");

			long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
Đầu ra là: tệp 46 MB, 158 giây. Tệp 1Gb - 2 giờ 55 phút. Một lần nữa một cải tiến, nhưng nhỏ. Và chúng tôi đã làm mọi thứ bằng những công cụ đơn giản. Không sử dụng kính hiển vi để đóng đinh . Bây giờ là một sự lạc đề trữ tình. Hãy nhớ lại cấu trúc của một máy tính. Bộ nhớ RAM (DRAM) , nơi chương trình thường được thực thi và các biến được lưu trữ, có tốc độ truy cập cao nhưng kích thước nhỏ. Bộ nhớ trên ổ cứng/flash (ổ HDD hoặc ổ Flash), nơi thường lưu trữ các tập tin, ngược lại, có tốc độ truy cập thấp nhưng kích thước lớn. Vì vậy, khi chúng tôi đọc từng byte tệp 1Gb (nghĩa là chúng tôi truy cập vào ổ cứng HDD một tỷ lần), chúng tôi dành nhiều thời gian để làm việc với một thiết bị tốc độ thấp (chúng tôi chuyển từng hạt cát từ thân xe tải KamAZ vào hộp cát). Hãy cố gắng cải thiện nó hơn nữa.

Hãy đổ cát ra khỏi TOÀN BỘ xe tải KAMAZ cùng một lúc!

// Вариант 4. Считываем массив сразу целиком за раз в память.
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        long[] arrBytes = new long[256];
        long startTime = System.currentTimeMillis();

        byte fileImage[]=new byte[inputStream.available()];
        long fileSize=fileImage.length;
        inputStream.read(fileImage);
        for (int i = 0; i = 0 ; i--)
            if (arrBytes[(int) i] > 0) System.out.print(i + " ");

		long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
một sự lạc đề nhỏ nhưng lại quan trọng .
  1. chỉ số arrBytes được xác định trong phạm vi 0..255,
  2. fileImage là một mảng byte có các phần tử có giá trị -128..127
Do đó, để đếm byte, chúng ta sẽ sử dụng cấu trúc arrBytes[fileImage[i] & 0b11111111]++; đơn giản sẽ đặt lại bit dấu và trả về cho chúng ta một giá trị trong phạm vi 0..255. Và do đó, kết quả: tệp 46 MB 0,13 giây (chưa đầy một giây). Tệp 1Gb - 9 giây. Chúng ta làm được rồi! Chúng tôi cực kỳ tuyệt vời! Tăng tốc từ 3 giờ lên 9 giây. Thế là xong, bạn có thể ngồi xuống ghế và uống một ít trà. Và bây giờ là một thử nghiệm khác - hãy thử tệp 32 Gb (ví dụ: phim HD). Kết quả là chúng tôi nhận được âm thanh tanh tách từ ổ cứng đang hoạt động khi chương trình bị lỗi trong Windows. KamAZ vứt xác bằng cát và làm vỡ hộp cát! Chúng ta làm gì? Chúng ta hãy nhớ lại một sự thật nữa. Các tệp trong HĐH thường được lưu trữ theo các phần (cụm) từ 2-64Kb (tùy thuộc vào loại hệ thống tệp, cài đặt, v.v.). Chúng ta sẽ đọc theo từng phần, ví dụ 64.000 byte. Hãy thử dỡ KamAZ bằng máy xúc theo từng phần khá lớn:

Sử dụng bộ đệm

// Вариант 5. Считываем массив кусками.
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        long[] arrBytes = new long[256];
        long startTime = System.currentTimeMillis();

        int  bufferSize = 64000;
        byte buffer[]   = new byte[64000];

        while (inputStream.available() > 0) {
            if (inputStream.available() < 64000) bufferSize = inputStream.available();
            inputStream.read(buffer, 0, bufferSize );
            for (int i = 0; i = 0 ; i--)
            if (arrBytes[(int) i] > 0) System.out.print(i + " ");

		long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
Kết quả chúng ta nhận được: file 46MB 0,08 giây (chưa đầy một giây). Tệp 1Gb - 0,9 giây (chưa đến một giây). Tệp 32Gb - 31 giây. Lưu ý rằng đối với tệp 1 Gb, chúng tôi đã cải thiện hiệu suất từ ​​vài giờ xuống vài phần giây !!! Với thực tế khiêm tốn này, chúng tôi sẽ hoàn thành thử nghiệm và cải thiện mã ban đầu. Chúng tôi đã đạt được tiến bộ về nhiều mặt - chúng tôi hài lòng với các chỉ số mới về mức tiêu thụ bộ nhớ và thời gian hoạt động. Ngoài ra, trong trường hợp này, chúng tôi không lấy các bộ sưu tập vô dụng từ thư viện tiêu chuẩn. Tái bút Ai đó sẽ nói ví dụ này thật xa vời, v.v. Nhưng có rất nhiều nhiệm vụ tương tự - để phân tích một khối lượng lớn các phần tử có số lượng trạng thái hữu hạn. Ví dụ: hình ảnh (RGB - thường được lưu trữ trong 24 byte, trong trường hợp của chúng tôi long[] arrRGB = new long[256*256*256] sẽ chỉ chiếm 64MB trong bộ nhớ), âm nhạc (biên độ thường được số hóa thành 16 hoặc 24 bit ) hoặc cảm biến chỉ báo rời rạc, v.v.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION