JavaRush /จาวาบล็อก /Random-TH /สตริงย่อย (..) หลอกหลอนฉัน
IgorBrest
ระดับ

สตริงย่อย (..) หลอกหลอนฉัน

เผยแพร่ในกลุ่ม
จริงๆ แล้ว ด้วยเหตุผลของหัวข้อนี้ ฉันจึงอนุญาตให้ตัวเองเจาะลึก String.substring(..) และฉันอาจได้รับผลลัพธ์ที่ไม่คาดคิดซึ่งฉันตัดสินใจแบ่งปันกับคุณชาว Javorashovites ที่รัก นำเสนอต่อการตัดสินใจของคุณเพื่อที่จะพูด ดังนั้นนี่คือ มีคำสั่งว่าสตริงที่สร้างขึ้นโดยใช้เมธอด substring(..) ใช้อาร์เรย์อักขระของสตริงต้นฉบับ โดยเฉพาะอย่างยิ่งนี่เป็นข้อความที่ตัดตอนมาจากบทความที่เพิ่งอ่านเรื่อง “Java Reference. Static Strings” โดยบทความ ที่เคารพ :
มีหมายเหตุเกี่ยวกับวิธีการสตริงย่อย - สตริงที่ส่งคืนใช้อาร์เรย์ไบต์เดียวกันกับสตริงดั้งเดิม
และแน่นอนว่า Javarash Lectures ต่อไปนี้เป็นคำพูดจากวันที่ 22 ธันวาคม:
เมื่อเราสร้างสตริงย่อยโดยใช้วิธีสตริงย่อย ออบเจ็กต์ String ใหม่จะถูกสร้างขึ้น แต่แทนที่จะจัดเก็บการอ้างอิงถึงอาร์เรย์ด้วยชุดอักขระใหม่ อ็อบเจ็กต์นี้จะจัดเก็บการอ้างอิงไปยังอาร์เรย์อักขระเก่า และในเวลาเดียวกันก็เก็บตัวแปรสองตัวไว้ใช้กำหนดว่าส่วนใดของอาร์เรย์อักขระดั้งเดิมที่เป็นของอาร์เรย์นั้น ... เมื่อสร้างสตริงย่อย อาร์เรย์อักขระจะไม่ถูกคัดลอกไปยังอ็อบเจ็กต์ String ใหม่ แต่วัตถุทั้งสองจะเก็บข้อมูลอ้างอิงไปยังอาร์เรย์อักขระเดียวกันแทน แต่! ออบเจ็กต์ที่สองเก็บตัวแปรอีกสองตัว ซึ่งมีอักขระของอาร์เรย์นี้ตัวใดและจำนวนเท่าใดที่ถูกเขียนลงไป ... ดังนั้น หากคุณใช้สตริงยาว 10,000 อักขระและสร้างสตริงย่อย 10,000 รายการไม่ว่าจะมีความยาวเท่าใดก็ได้ "สตริงย่อย" เหล่านี้จะใช้หน่วยความจำน้อยมาก เนื่องจาก อาร์เรย์อักขระจะไม่ซ้ำกัน สตริงที่ควรใช้พื้นที่จำนวนมากจะใช้พื้นที่เพียงไม่กี่ไบต์เท่านั้น
ทุกอย่างเขียนชัดเจนแม้กระทั่งเคี้ยว แต่เนื่องจากฉันพยายามพัฒนาความรู้ภาษาอังกฤษ ฉันจึงมักจะหันไปดูเอกสารอย่างเป็นทางการ และฉันก็ไม่สามารถยืนยันข้อเท็จจริงนี้ได้... เนื่องจากความประมาทของฉัน ฉันจึงยังคงดูซอร์สโค้ดของ substring() (ขอบคุณ IDEA ช่วยให้คุณทำสิ่งนี้ได้ด้วยการคลิกเพียงปุ่มเดียว) public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); } ฉันรู้สึกทึ่งมากที่ไปต่อ: * Allocates a new {@code String} that contains characters from a subarray * of the character array argument. The {@code offset} argument is the * index of the first character of the subarray and the {@code count} * argument specifies the length of the subarray. The contents of the * subarray are copied; subsequent modification of the character array does * not affect the newly created string. public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count < 0) { throw new StringIndexOutOfBoundsException(count); } // Note: offset or count might be near -1>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count); } โดยที่ Arrays.copyOfRange เป็นวิธีการดั้งเดิมที่ส่งคืนสำเนาของอาร์เรย์จาก char... โค้ดที่ค่อนข้างซับซ้อนและดูเหมือนว่าชัดเจนสำหรับฉันว่าแถวใหม่ที่มีตัวอักษรชุดใหม่ถูกสร้างขึ้นอย่างง่ายดาย หรือฉันไม่ได้คำนึงถึงบางสิ่งบางอย่าง... ดังนั้นฉันจึงไม่เชื่อในข้อสรุปของตัวเองอย่างเต็มที่ ฉันจึงตัดสินใจทดสอบสตริงย่อยนี้ () โดยอาศัยวลีจากการบรรยาย:
ดังนั้น หากคุณใช้สตริงที่มีความยาว 10,000 อักขระและสร้างสตริงย่อยได้ 10,000 สตริงตามความยาวใดๆ จากนั้น "สตริงย่อย" เหล่านี้จะใช้หน่วยความจำน้อยมาก...
แทนที่จะเป็น 10,000 เท่านั้น เราจะสร้าง 100_000_000 ทันที จะเสียเวลากับเรื่องมโนสาเร่ไปทำไม ฉันโยนโค้ดต่อไปนี้อย่างรวดเร็ว: และนี่คือสิ่งที่เกิดขึ้น: เช่น ทุกครั้งที่คุณสร้างสตริงย่อยใหม่โดยใช้ bigString.substring(..) อาร์เรย์อักขระจะถูกทำซ้ำ เราจะอธิบายการใช้หน่วยความจำที่เพิ่มขึ้นเช่นนี้ได้อย่างไร? หลังจากนี้ โดยส่วนตัวแล้วฉันไม่มีข้อสงสัยใด ๆ เกี่ยวกับการทำงานของเมธอด String.substsring() อีกต่อไป แล้วคุณล่ะ? public class Test { public static void main(String[] args) { System.out.println("Начинаем:"); print(); System.out.println("********************************"); char[]big=new char[100_000_000];//создаем нормальный такой массив int j=0;//и заполняем этот массив всякой ерундой for (int k=0;k list=new ArrayList<>();//здесь будут ссылки на строки, что бы сборщик мусора не удалял //не используемые, по его мнению, строки. System.out.println("************************************"); System.out.println("Теперь будем создавть подстроки с помощью substring(..) и наблюдать," + "что же происходит с памятью"); for (int i = 2; i <10; i++) { //создаем подстроку, используя метод String.substring(..) String sub= bigString.substring(1,bigString.length()-1); //если этот метод не создает fully новый массив символов, а только пользуется //исходным из bigString // то при создании новой строки sub мы не будем наблюдать ощутипый расход памяти list.add(sub);//эти ссылки мы должны где нибудь хранить, иначе сборщик мусора //избавится от неипользуемых объктов String System.out.print(String.format("Создаем %d-ую подстроку, при этом ", i - 1)); print(); } System.out.println("***************************************"); print(); } static void print(){ System.out.println("Памяти используется "+(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/1024/1024 + " mb"); } } Начинаем: Памяти используется 0 mb ******************************** создал большую строку bigString на основе массива big. Теперь: Памяти используется 382 mb ************************************ Теперь будем создавть подстроки с помощью substring(..) и наблюдать,что же происходит с памятью Добавляем 1-ую подстроку, при этом Памяти используется 573 mb Добавляем 2-ую подстроку, при этом Памяти используется 763 mb Добавляем 3-ую подстроку, при этом Памяти используется 954 mb Добавляем 4-ую подстроку, при этом Памяти используется 1145 mb Добавляем 5-ую подстроку, при этом Памяти используется 1336 mb Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOfRange(Arrays.java:3658) at java.lang.String. (String.java:201) at java.lang.String.substring(String.java:1956) at com.javarush.test.tests.Test.main(Test.java:42) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134) Process finished with exit code 1
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION