Repository: alibaba/fastjson2
Pull request #3242 (2025-01-01 06:20:17)

fix capacity
### What this PR does / why we need it?



### Summary of your change



#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.



Patch:
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
index ce37b465e..e0606f3dc 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
@@ -2734,6 +2734,21 @@ public abstract class JSONWriter
         }
     }
 
+    protected final int newCapacity(int minCapacity, int oldCapacity) {
+        int newCapacity = oldCapacity + (oldCapacity >> 1);
+        if (newCapacity - minCapacity < 0) {
+            newCapacity = minCapacity;
+        }
+        if (newCapacity > maxArraySize) {
+            if (minCapacity < maxArraySize) {
+                newCapacity = maxArraySize;
+            } else {
+                throw new OutOfMemoryError("try enabling LargeObject feature instead");
+            }
+        }
+        return newCapacity;
+    }
+
     public Object getAttachment() {
         return attachment;
     }
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriterJSONB.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriterJSONB.java
index b5994d9e0..6e41769c8 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriterJSONB.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriterJSONB.java
@@ -1153,17 +1153,7 @@ final class JSONWriterJSONB
     }
 
     private void grow0(int minCapacity) {
-        int oldCapacity = bytes.length;
-        int newCapacity = oldCapacity + (oldCapacity >> 1);
-        if (newCapacity - minCapacity < 0) {
-            newCapacity = minCapacity;
-        }
-        if (newCapacity > maxArraySize) {
-            throw new OutOfMemoryError("try enabling LargeObject feature instead");
-        }
-
-        // minCapacity is usually close to size, so this is a win:
-        bytes = Arrays.copyOf(bytes, newCapacity);
+        bytes = Arrays.copyOf(bytes, newCapacity(minCapacity, bytes.length));
     }
 
     @Override
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF16.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF16.java
index 97383c1c2..a8add297e 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF16.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF16.java
@@ -2001,17 +2001,7 @@ class JSONWriterUTF16
     }
 
     private char[] grow(int minCapacity) {
-        int oldCapacity = chars.length;
-        int newCapacity = oldCapacity + (oldCapacity >> 1);
-        if (newCapacity - minCapacity < 0) {
-            newCapacity = minCapacity;
-        }
-        if (newCapacity - maxArraySize > 0) {
-            throw new OutOfMemoryError("try enabling LargeObject feature instead");
-        }
-
-        // minCapacity is usually close to size, so this is a win:
-        return chars = Arrays.copyOf(chars, newCapacity);
+        return chars = Arrays.copyOf(chars, newCapacity(minCapacity, chars.length));
     }
 
     public final void writeInt32(int[] value) {
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF8.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF8.java
index 7d1e25ea7..dc0e848ea 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF8.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF8.java
@@ -2084,17 +2084,8 @@ class JSONWriterUTF8
     }
 
     private void grow0(int minCapacity) {
-        int oldCapacity = bytes.length;
-        int newCapacity = oldCapacity + (oldCapacity >> 1);
-        if (newCapacity - minCapacity < 0) {
-            newCapacity = minCapacity;
-        }
-        if (newCapacity - maxArraySize > 0) {
-            throw new OutOfMemoryError("try enabling LargeObject feature instead");
-        }
-
         // minCapacity is usually close to size, so this is a win:
-        bytes = Arrays.copyOf(bytes, newCapacity);
+        bytes = Arrays.copyOf(bytes, newCapacity(minCapacity, bytes.length));
     }
 
     public final void writeInt32(int[] values) {
diff --git a/core/src/main/java/com/alibaba/fastjson2/support/csv/CSVWriterUTF16.java b/core/src/main/java/com/alibaba/fastjson2/support/csv/CSVWriterUTF16.java
index 872450822..dc283e388 100644
--- a/core/src/main/java/com/alibaba/fastjson2/support/csv/CSVWriterUTF16.java
+++ b/core/src/main/java/com/alibaba/fastjson2/support/csv/CSVWriterUTF16.java
@@ -150,8 +150,9 @@ final class CSVWriterUTF16
         for (int i = 0; i < len; ) {
             char ch = str.charAt(i++);
             if (ch == '"') {
-                chars[off++] = '"';
-                chars[off++] = '"';
+                chars[off] = '"';
+                chars[off + 1] = '"';
+                off += 2;
             } else {
                 chars[off++] = ch;
             }
@@ -160,8 +161,8 @@ final class CSVWriterUTF16
                 off = this.off;
             }
         }
-        chars[off++] = '"';
-        this.off = off;
+        chars[off] = '"';
+        this.off = off + 1;
     }
 
     public void writeInt32(int intValue) {
@@ -227,9 +228,13 @@ final class CSVWriterUTF16
             return;
         }
 
-        checkCapacity(24);
-
-        off = IOUtils.writeDecimal(chars, off, unscaledVal, scale);
+        int off = this.off;
+        char[] chars = this.chars;
+        if (off + 24 > chars.length) {
+            flush();
+            off = 0;
+        }
+        this.off = IOUtils.writeDecimal(chars, off, unscaledVal, scale);
     }
 
     void writeRaw(char[] chars) {
@@ -253,11 +258,15 @@ final class CSVWriterUTF16
         }
 
         // "yyyy-MM-dd HH:mm:ss"
-        checkCapacity(19);
-
+        int off = this.off;
+        char[] chars = this.chars;
+        if (off + 19 > chars.length) {
+            flush();
+            off = 0;
+        }
         off = IOUtils.writeLocalDate(chars, off, ldt.getYear(), ldt.getMonthValue(), ldt.getDayOfMonth());
         chars[off++] = ' ';
-        off = IOUtils.writeLocalTime(chars, off, ldt.toLocalTime());
+        this.off = IOUtils.writeLocalTime(chars, off, ldt.toLocalTime());
     }
 
     protected void writeRaw(String str) {
diff --git a/core/src/main/java/com/alibaba/fastjson2/support/csv/CSVWriterUTF8.java b/core/src/main/java/com/alibaba/fastjson2/support/csv/CSVWriterUTF8.java
index f31c67ef4..a941dee8a 100644
--- a/core/src/main/java/com/alibaba/fastjson2/support/csv/CSVWriterUTF8.java
+++ b/core/src/main/java/com/alibaba/fastjson2/support/csv/CSVWriterUTF8.java
@@ -184,8 +184,9 @@ final class CSVWriterUTF8
         bytes[off++] = '"';
         for (byte ch : utf8) {
             if (ch == '"') {
-                bytes[off++] = '"';
-                bytes[off++] = '"';
+                bytes[off] = '"';
+                bytes[off + 1] = '"';
+                off += 2;
             } else {
                 bytes[off++] = ch;
             }
@@ -223,9 +224,13 @@ final class CSVWriterUTF8
             return;
         }
 
-        checkCapacity(24);
-
-        off = IOUtils.writeDecimal(bytes, off, unscaledVal, scale);
+        int off = this.off;
+        byte[] bytes = this.bytes;
+        if (off + 24 > bytes.length) {
+            flush();
+            off = 0;
+        }
+        this.off = IOUtils.writeDecimal(bytes, off, unscaledVal, scale);
     }
 
     private void writeRaw(byte[] strBytes) {
@@ -258,11 +263,15 @@ final class CSVWriterUTF8
         }
 
         // "yyyy-MM-dd HH:mm:ss"
-        checkCapacity(19);
-
+        int off = this.off;
+        byte[] bytes = this.bytes;
+        if (off + 19 > bytes.length) {
+            flush();
+            off = 0;
+        }
         off = IOUtils.writeLocalDate(bytes, off, ldt.getYear(), ldt.getMonthValue(), ldt.getDayOfMonth());
         bytes[off++] = ' ';
-        off = IOUtils.writeLocalTime(bytes, off, ldt.toLocalTime());
+        this.off = IOUtils.writeLocalTime(bytes, off, ldt.toLocalTime());
     }
 
     void checkCapacity(int incr) {


Test patch:
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_2900/Issue2955.java b/core/src/test/java/com/alibaba/fastjson2/issues_2900/Issue2955.java
new file mode 100644
index 000000000..61e5599d8
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_2900/Issue2955.java
@@ -0,0 +1,27 @@
+package com.alibaba.fastjson2.issues_2900;
+
+import com.alibaba.fastjson2.JSONObject;
+import org.junit.jupiter.api.Test;
+
+import java.util.Random;
+
+public class Issue2955 {
+    @Test
+    public void test() {
+        String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        long STRING_LENGTH = 63L * 1024L * 1024L;
+
+        JSONObject jsonObject = new JSONObject();
+        System.out.println(STRING_LENGTH);
+        // 生成64 * 1024 * 1024长度的字符串
+        Random random = new Random();
+        StringBuilder largeStringBuilder = new StringBuilder();
+        for (long i = 0; i < STRING_LENGTH; i++) {
+            int index = random.nextInt(CHARACTERS.length());
+            largeStringBuilder.append(CHARACTERS.charAt(index));
+        }
+        String largeString = largeStringBuilder.toString();
+        jsonObject.put("largeString", largeString);
+        String jsonString = jsonObject.toJSONString();
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3200/Issue3210.java b/core/src/test/java/com/alibaba/fastjson2/issues_3200/Issue3210.java
index 1ca0844a2..c5b74798a 100644
--- a/core/src/test/java/com/alibaba/fastjson2/issues_3200/Issue3210.java
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3200/Issue3210.java
@@ -1,11 +1,9 @@
 package com.alibaba.fastjson2.issues_3200;
 
-import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
 import lombok.Data;
 import org.junit.jupiter.api.Test;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Date;
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -19,66 +17,7 @@ public class Issue3210 {
     @Test
     public void test() {
         String s = "{\"time\":\"2024-12-04T20:43:15.000000999\"}";
-        {
-            Bean bean = JSONObject.parseObject(s, Bean.class);
-            assertNotNull(bean.time);
-        }
-        {
-            Bean bean = JSON.parseObject(s.getBytes(StandardCharsets.UTF_8), Bean.class);
-            assertNotNull(bean.time);
-        }
-        {
-            Bean bean = JSON.parseObject(s.toCharArray(), Bean.class);
-            assertNotNull(bean.time);
-        }
-    }
-
-    @Data
-    private static class Bean1 {
-        private Date time;
-    }
-
-    @Test
-    public void test1() {
-        String s = "{\"time\":\"2024-12-04T20:43:15.000000999\"}";
-        {
-            Bean1 bean = JSONObject.parseObject(s, Bean1.class);
-            assertNotNull(bean.time);
-        }
-        {
-            byte[] bytes = s.getBytes();
-            Bean1 bean = JSON.parseObject(bytes, 0, bytes.length, Bean1.class);
-            assertNotNull(bean.time);
-        }
-        {
-            Bean1 bean = JSON.parseObject(s.toCharArray(), Bean1.class);
-            assertNotNull(bean.time);
-        }
-    }
-
-    public static class Bean2 {
-        public final Date time;
-
-        public Bean2(Date time) {
-            this.time = time;
-        }
-    }
-
-    @Test
-    public void test2() {
-        String s = "{\"time\":\"2024-12-04T20:43:15.000000999\"}";
-        {
-            Bean2 bean = JSONObject.parseObject(s, Bean2.class);
-            assertNotNull(bean.time);
-        }
-        {
-            byte[] bytes = s.getBytes();
-            Bean2 bean = JSON.parseObject(bytes, 0, bytes.length, Bean2.class);
-            assertNotNull(bean.time);
-        }
-        {
-            Bean2 bean = JSON.parseObject(s.toCharArray(), Bean2.class);
-            assertNotNull(bean.time);
-        }
+        Bean bean = JSONObject.parseObject(s, Bean.class);
+        assertNotNull(bean.time);
     }
 }
diff --git a/core/src/test/java/com/alibaba/fastjson2/support/csv/CapacityTest.java b/core/src/test/java/com/alibaba/fastjson2/support/csv/CapacityTest.java
new file mode 100644
index 000000000..0b4a9ab25
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/support/csv/CapacityTest.java
@@ -0,0 +1,97 @@
+package com.alibaba.fastjson2.support.csv;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Random;
+
+public class CapacityTest {
+    @Test
+    public void writeString_utf16() throws Exception {
+        try (CSVWriterUTF16 writer = (CSVWriterUTF16) CSVWriter.of(new NoopWriter())) {
+            for (int i = 0; i < 100_000; i++) {
+                writer.writeString("a");
+            }
+        }
+        try (CSVWriterUTF16 writer = (CSVWriterUTF16) CSVWriter.of(new NoopWriter())) {
+            for (int i = 0; i < 100_000; i++) {
+                writer.writeString("\n");
+                writer.writeString("\r");
+                writer.writeString("\"");
+            }
+        }
+        try (CSVWriterUTF16 writer = (CSVWriterUTF16) CSVWriter.of(new NoopWriter())) {
+            writer.writeString(
+                    repeat('x', 100_000));
+            writer.writeString(
+                    repeat(',', 100_000));
+        }
+        try (CSVWriterUTF16 writer = (CSVWriterUTF16) CSVWriter.of(new NoopWriter())) {
+            Random r = new Random();
+            for (int i = 0; i < 100_000; i++) {
+                writer.writeString(
+                        repeat('x', r.nextInt(1000)));
+            }
+        }
+    }
+
+    @Test
+    public void writeString_utf8() throws Exception {
+        try (CSVWriterUTF8 writer = (CSVWriterUTF8) CSVWriter.of(new NoopOutputStream())) {
+            for (int i = 0; i < 100_000; i++) {
+                writer.writeString("a");
+            }
+        }
+        try (CSVWriterUTF8 writer = (CSVWriterUTF8) CSVWriter.of(new NoopOutputStream())) {
+            for (int i = 0; i < 100_000; i++) {
+                writer.writeString("\n");
+                writer.writeString("\r");
+                writer.writeString("\"");
+            }
+        }
+        try (CSVWriterUTF8 writer = (CSVWriterUTF8) CSVWriter.of(new NoopOutputStream())) {
+            writer.writeString(
+                    repeat('x', 100_000));
+            writer.writeString(
+                    repeat(',', 100_000));
+        }
+        try (CSVWriterUTF8 writer = (CSVWriterUTF8) CSVWriter.of(new NoopOutputStream())) {
+            Random r = new Random();
+            for (int i = 0; i < 100_000; i++) {
+                writer.writeString(
+                        repeat('x', r.nextInt(1000)));
+            }
+        }
+    }
+
+    static String repeat(char c, int count) {
+        char[] chars = new char[count];
+        Arrays.fill(chars, c);
+        return new String(chars);
+    }
+
+    static class NoopWriter
+            extends Writer {
+        @Override
+        public void write(@NotNull char[] cbuf, int off, int len) throws IOException {
+        }
+
+        @Override
+        public void flush() throws IOException {
+        }
+
+        @Override
+        public void close() throws IOException {
+        }
+    }
+
+    static class NoopOutputStream
+            extends java.io.OutputStream {
+        @Override
+        public void write(int b) throws IOException {
+        }
+    }
+}
diff --git a/fastjson1-compatible/src/test/java/com/alibaba/fastjson/v2issues/Issue3157.java b/fastjson1-compatible/src/test/java/com/alibaba/fastjson/v2issues/Issue3157.java
deleted file mode 100644
index 7b7795940..000000000
--- a/fastjson1-compatible/src/test/java/com/alibaba/fastjson/v2issues/Issue3157.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.alibaba.fastjson.v2issues;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.serializer.SerializeConfig;
-import com.alibaba.fastjson.serializer.ToStringSerializer;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-public class Issue3157 {
-    public static class People {
-        private Integer age;
-
-        private Long hairNums;
-
-        private String name;
-
-        public People(Integer age, String name, Long hairNums) {
-            this.age = age;
-            this.name = name;
-            this.hairNums = hairNums;
-        }
-
-        public Integer getAge() {
-            return age;
-        }
-
-        public String getName() {
-            return name;
-        }
-
-        public Long getHairNums() {
-            return hairNums;
-        }
-    }
-
-    @Test
-    public void test() {
-        SerializeConfig stringSerializeConfig = new SerializeConfig();
-        stringSerializeConfig.put(Integer.class, ToStringSerializer.instance);
-        stringSerializeConfig.put(Long.class, ToStringSerializer.instance);
-
-        People one = new People(10, "aaa", 10000000L);
-        assertEquals(
-                "{\"age\":\"10\",\"hairNums\":\"10000000\",\"name\":\"aaa\"}",
-                JSON.toJSONString(one, stringSerializeConfig));
-    }
-}