วิธีที่ซับซ้อนที่สุดในการสร้างสตริงที่คั่นด้วยเครื่องหมายจุลภาคจาก Collection / Array / List?


98

ในระหว่างที่ฉันทำงานกับฐานข้อมูลฉันสังเกตเห็นว่าฉันเขียนสตริงการสืบค้นและในสตริงนี้ฉันต้องใส่ข้อ จำกัด หลายประการในคำสั่ง where-clause จาก list / array / collection ควรมีลักษณะดังนี้:

select * from customer 
where customer.id in (34, 26, ..., 2);

คุณสามารถทำให้สิ่งนี้ง่ายขึ้นโดยลดสิ่งนี้ให้เหลือเพียงคำถามที่คุณมีคอลเล็กชันของสตริงและต้องการสร้างรายการที่คั่นด้วยจุลภาคของสตริงนี้ในสตริงเดียว

แนวทางของฉันที่ฉันใช้จนถึงตอนนี้ก็เป็นเช่นนั้น:

String result = "";
boolean first = true;
for(String string : collectionOfStrings) {
    if(first) {
        result+=string;
        first=false;
    } else {
        result+=","+string;
    }
}

แต่นี่เป็นอย่างที่คุณเห็นน่าเกลียดมาก คุณไม่สามารถเห็นสิ่งที่เกิดขึ้นในรูปลักษณ์แรกโดยเฉพาะอย่างยิ่งเมื่อสตริงที่สร้างขึ้น (เช่นแบบสอบถาม SQL ทุกตัว) มีความซับซ้อน

วิธีที่สง่างาม (เพิ่มเติม) ของคุณคืออะไร?


สันนิษฐานว่า SQL ที่แสดงด้านบนควรมีลักษณะดังนี้: เลือก * จากลูกค้าโดยที่ customer.id ใน (34, 26, 2);
Dónal

มีส่วนที่ยุ่งยากเมื่อรายการ (สตริง) มีเครื่องหมายจุลภาคหรือเครื่องหมายอัญประกาศคู่และจำเป็นต้องมีเครื่องหมายอัญประกาศ ถ้าฉันไม่พลาดอะไรตัวอย่างข้างต้นก็ไม่ต้องพิจารณาและฉันเกลียดความคิดที่จะวนซ้ำข้อความทั้งหมดและค้นหาเครื่องหมายจุลภาค .. คุณคิดว่ามีวิธีที่ดีกว่าในการแก้ปัญหานี้หรือไม่?
Samurai Girl

ลองดูคำตอบนี้ ... stackoverflow.com/a/15815631/728610
Arvind Sridharan

คุณเคยตรวจสอบstackoverflow.com/questions/10850753/…หรือไม่?
Hiren Patel

สิ่งนี้ควรทำ stackoverflow.com/a/15815631/3157062
Parag Jadhav

คำตอบ:


85

หมายเหตุ: คำตอบนี้ใช้ได้ดีเมื่อเขียนเมื่อ 11 ปีที่แล้ว แต่ตอนนี้มีตัวเลือกที่ดีกว่ามากในการทำสิ่งนี้ให้สะอาดยิ่งขึ้นในบรรทัดเดียวทั้งที่ใช้เฉพาะคลาสในตัว Java หรือใช้ไลบรารียูทิลิตี้ ดูคำตอบอื่น ๆ ด้านล่าง


เนื่องจากสตริงไม่เปลี่ยนรูปคุณอาจต้องการใช้คลาส StringBuilder หากคุณกำลังจะเปลี่ยน String ในโค้ด

คลาส StringBuilder สามารถถูกมองว่าเป็นอ็อบเจ็กต์ String ที่ไม่แน่นอนซึ่งจัดสรรหน่วยความจำเพิ่มเติมเมื่อมีการเปลี่ยนแปลงเนื้อหา

ข้อเสนอแนะเดิมในคำถามสามารถเขียนได้ชัดเจนและมีประสิทธิภาพมากขึ้นโดยดูแลเครื่องหมายจุลภาคต่อท้ายที่ซ้ำซ้อน :

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";

7
โปรดทราบว่าสิ่งนี้ต้องการให้คอลเลกชันของคุณมีองค์ประกอบอย่างน้อยหนึ่งองค์ประกอบ
Guus

3
ดูคำตอบที่ได้รับการโหวตสูงสุด - code.google.com/p/guava-libraries/wiki/StringsExplained
gimel

ดูการแก้ไขที่แนะนำสำหรับรายการว่าง
gimel

1
ฝรั่งตอบดีกว่า ไม่จำเป็นต้องสร้างล้อใหม่
davidjnelson

1
@ xtreme-biker ด้วยคอมไพเลอร์ที่ทันสมัยพอสมควร StringBuilder อาจถูกใช้โดยอัตโนมัติ ตรวจสอบสภาพแวดล้อมของคุณก่อนใช้ + = ดูstackoverflow.com/questions/1532461/…
gimel

89

ใช้Google API ฝรั่ง 's joinวิธีการ:

Joiner.on(",").join(collectionOfStrings);

4
ปัจจุบันเรียกว่าชั้นเรียนJoiner; google-collections.googlecode.com/svn/trunk/javadoc/com/google/…
Jonik

2
และวันนี้คอลเล็กชันเลิกใช้งานแล้ว ใช้Google Guavaแทน
darioo

12
ในขณะเดียวกัน org.apache.commons.lang.StringUtils ยังคงไม่เปลี่ยนแปลง :-)
Ogre Psalm33

1
ฝรั่งได้ย้าย ดูgithub.com/google/guava/wiki/StringsExplained
gimel

77

ฉันเพิ่งดูรหัสที่ทำวันนี้ นี่คือรูปแบบของคำตอบของ AviewAnew

collectionOfStrings = /* source string collection */;
String csList = StringUtils.join(collectionOfStrings.toArray(), ",");

StringUtils (<- 2.x commons.lang หรือการเชื่อมโยง 3.x commons.lang ) เราใช้จากApache คอมมอนส์


... และ StringUtils มาจากไหน?
vwegert

1
อ่าจุดดี เป็นเวลานานแล้วที่ฉันดูรหัสนั้น แต่ฉันเชื่อว่าเราใช้ org.apache.commons.lang.StringUtils
Ogre Psalm33

นี่คือลิงค์ถ่ายทอดสดไปยังวิธีการเข้าร่วมของ StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/org/…
Ryan S

2
ดีขอบคุณ StringUtils # join ยังใช้งานได้กับ Iterable ดังนั้นอาจไม่จำเป็นต้องแปลงคอลเลคชันของคุณเป็นอาร์เรย์ก่อน
รอย

47

วิธีที่ฉันเขียนลูปนั้นคือ:

StringBuilder buff = new StringBuilder();
String sep = "";
for (String str : strs) {
    buff.append(sep);
    buff.append(str);
    sep = ",";
}
return buff.toString();

ไม่ต้องกังวลกับประสิทธิภาพของ sep การมอบหมายงานเร็วมาก ฮอตสปอตมีแนวโน้มที่จะลอกการวนซ้ำครั้งแรกของลูปออกไป (เนื่องจากมักจะต้องจัดการกับความแปลกประหลาดเช่นการตรวจสอบอินไลน์ null และโมโน / bimorphic)

หากคุณใช้งานเป็นจำนวนมาก (มากกว่าหนึ่งครั้ง) ให้ใส่วิธีที่ใช้ร่วมกัน

มีคำถามอื่นเกี่ยวกับ stackoverflow เกี่ยวกับวิธีแทรกรายการรหัสลงในคำสั่ง SQL


42

ตั้งแต่ Java 8 คุณสามารถใช้:


3
ดีจัง! หากคุณกำลังจัดการอ็อบเจ็กต์ที่ต้องการการแปลงสตริงพิเศษที่ไม่ครอบคลุมโดย toString () ให้แทนที่ Object :: toString ด้วย java.util.function.Function <YourType, String> ที่แมปคลาสของคุณกับสตริง
Torben

2
นอกจากนี้คุณสามารถใช้สิ่งนี้cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));สำหรับตัวแปรเดียวจากคอลเล็กชันของคุณ
numsu

ฉันสงสัยเกี่ยวกับประสิทธิภาพของสิ่งstreamนั้น สำหรับ int [] หรือ long [] หรืออาร์เรย์อื่น ๆ ที่สามารถแคสต์ค่าไปStringได้ฉันจะมองหาโซลูชันที่ไม่ใช่สตรีมมิ่ง ในความเป็นจริงฉันกำลังมองหา
Adam

11

ฉันพบสำนวน iterator ที่สง่างามเนื่องจากมีการทดสอบองค์ประกอบเพิ่มเติม (ommited null / การทดสอบความกะทัดรัดสำหรับความกะทัดรัด):

public static String convert(List<String> list) {
    String res = "";
    for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
        res += iterator.next() + (iterator.hasNext() ? "," : "");
    }
    return res;
}

... และอาจมีประสิทธิภาพน้อยกว่าโซลูชันที่ยอมรับทั้งนี้ขึ้นอยู่กับความซับซ้อนของการเรียก 'hasNext ()' นอกจากนี้คุณน่าจะใช้ StringBuilder มากกว่าการต่อสตริง
Stephen C

ตกลงถ้าคุณต้องการที่จะพิถีพิถันเกี่ยวกับประสิทธิภาพให้ใช้ StringWriter;)
มิเกลปิง

8

มีวิธีแก้ปัญหาด้วยตนเองมากมายสำหรับสิ่งนี้ แต่ฉันต้องการย้ำและอัปเดตคำตอบของ Julie ด้านบน ใช้ชั้นเรียนใน Google คอลเลกชันช่างไม้

Joiner.on(", ").join(34, 26, ..., 2)

มันจัดการ var args, iterables และ arrays และจัดการตัวคั่นของ char มากกว่าหนึ่งตัวได้อย่างเหมาะสม (ไม่เหมือนกับคำตอบของ gimmel) นอกจากนี้ยังจัดการค่าว่างในรายการของคุณหากคุณต้องการ


7

นี่คือเวอร์ชันทั่วไปอย่างไม่น่าเชื่อที่ฉันสร้างขึ้นจากการรวมกันของคำแนะนำก่อนหน้านี้:

public static <T> String buildCommaSeparatedString(Collection<T> values) {
    if (values==null || values.isEmpty()) return "";
    StringBuilder result = new StringBuilder();
    for (T val : values) {
        result.append(val);
        result.append(",");
    }
    return result.substring(0, result.length() - 1);
}

7
String.join(", ", collectionOfStrings)

มีอยู่ใน Java8 api

ทางเลือกอื่น (โดยไม่จำเป็นต้องเพิ่มการพึ่งพา google guava):

Joiner.on(",").join(collectionOfStrings);


4

นี่จะเป็นวิธีแก้ปัญหาที่สั้นที่สุดยกเว้นการใช้ Guava หรือ Apache Commons

String res = "";
for (String i : values) {
    res += res.isEmpty() ? i : ","+i;
}

ดีกับรายการองค์ประกอบ 0,1 และ n แต่คุณจะต้องตรวจสอบรายการว่าง ฉันใช้สิ่งนี้ใน GWT ดังนั้นฉันจึงทำได้ดีหากไม่มี StringBuilder ที่นั่น และสำหรับรายการสั้น ๆ ที่มีองค์ประกอบเพียงไม่กี่อย่างก็ใช้ได้เช่นกัน;)


4

ในกรณีที่มีคนสะดุดนี้อีกครั้งล่าสุดผมได้เพิ่มรูปแบบที่เรียบง่ายโดยใช้ Java reduce()8 นอกจากนี้ยังรวมถึงโซลูชันที่กล่าวถึงแล้วโดยผู้อื่น:

import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang.StringUtils;    

import com.google.common.base.Joiner;

public class Dummy {
  public static void main(String[] args) {

    List<String> strings = Arrays.asList("abc", "de", "fg");
    String commaSeparated = strings
        .stream()
        .reduce((s1, s2) -> {return s1 + "," + s2; })
        .get();

    System.out.println(commaSeparated);

    System.out.println(Joiner.on(',').join(strings));

    System.out.println(StringUtils.join(strings, ","));

  }
}


4

ฉันคิดว่ามันไม่ใช่ความคิดที่ดีที่จะสร้าง sql ที่เชื่อมต่อกับค่าส่วนคำสั่งที่คุณกำลังทำอยู่:

SELECT.... FROM.... WHERE ID IN( value1, value2,....valueN)

ไหนvalueXมาจากรายการของสตริง

อันดับแรกหากคุณกำลังเปรียบเทียบ Strings พวกเขาจะต้องถูกยกมาสิ่งนี้ไม่ใช่เรื่องเล็กน้อยหาก Strings สามารถมีใบเสนอราคาอยู่ภายใน

ประการที่สองหากค่ามาจากผู้ใช้หรือระบบอื่นการโจมตีด้วยการฉีด SQL ก็เป็นไปได้

มันละเอียดกว่ามาก แต่สิ่งที่คุณควรทำคือสร้าง String ดังนี้:

SELECT.... FROM.... WHERE ID IN( ?, ?,....?)

แล้วผูกตัวแปรด้วยStatement.setString(nParameter,parameterValue).


3

อีกวิธีหนึ่งในการจัดการกับปัญหานี้ ไม่ใช่สั้นที่สุด แต่มีประสิทธิภาพและทำงานให้ลุล่วง

/**
 * Creates a comma-separated list of values from given collection.
 * 
 * @param <T> Value type.
 * @param values Value collection.
 * @return Comma-separated String of values.
 */
public <T> String toParameterList(Collection<T> values) {
   if (values == null || values.isEmpty()) {
      return ""; // Depending on how you want to deal with this case...
   }
   StringBuilder result = new StringBuilder();
   Iterator<T> i = values.iterator();
   result.append(i.next().toString());
   while (i.hasNext()) {
      result.append(",").append(i.next().toString());
   }
   return result.toString();
}

2

มีไลบรารี Java ของ บริษัท อื่นที่มีวิธีการรวมสตริง แต่คุณอาจไม่ต้องการเริ่มใช้ไลบรารีเพียงเพื่ออะไรง่ายๆเช่นนั้น ฉันจะสร้างวิธีการช่วยเหลือแบบนี้ซึ่งฉันคิดว่าดีกว่าเวอร์ชันของคุณเล็กน้อยโดยใช้ StringBuffer ซึ่งจะมีประสิทธิภาพมากกว่าหากคุณต้องการเข้าร่วมหลาย ๆ สตริงและใช้ได้กับคอลเล็กชันประเภทใดก็ได้

public static <T> String join(Collection<T> values)
{
    StringBuffer ret = new StringBuffer();
    for (T value : values)
    {
        if (ret.length() > 0) ret.append(",");
        ret.append(value);
    }
    return ret.toString();
}

คำแนะนำอื่นในการใช้ Collection.toString () นั้นสั้นกว่า แต่ต้องอาศัย Collection.toString () ส่งคืนสตริงในรูปแบบที่เฉพาะเจาะจงมากซึ่งโดยส่วนตัวแล้วฉันไม่ต้องการพึ่งพา



1

ฉันไม่แน่ใจว่า "ซับซ้อน" แค่ไหน แต่มันสั้นกว่าเล็กน้อย มันจะทำงานร่วมกับคอลเลคชันประเภทต่างๆเช่น Set <Integer>, List <String> เป็นต้น

public static final String toSqlList(Collection<?> values) {

    String collectionString = values.toString();

    // Convert the square brackets produced by Collection.toString() to round brackets used by SQL
    return "(" + collectionString.substring(1, collectionString.length() - 1) + ")";
}

แบบฝึกหัดสำหรับผู้อ่าน : แก้ไขวิธีนี้เพื่อให้จัดการกับคอลเล็กชันว่าง / ว่างได้อย่างถูกต้อง :)


1

สิ่งที่ทำให้โค้ดน่าเกลียดคือการจัดการพิเศษสำหรับกรณีแรก บรรทัดส่วนใหญ่ในข้อมูลโค้ดขนาดเล็กนี้อุทิศเพื่อไม่ให้ทำงานประจำของโค้ด แต่เพื่อจัดการกับกรณีพิเศษนั้น และนั่นคือทางเลือกอื่น ๆ เช่นการแก้ปัญหาของ gimel โดยการย้ายการจัดการพิเศษออกนอกลูป มีกรณีพิเศษอยู่กรณีหนึ่ง (คุณสามารถเห็นทั้งจุดเริ่มต้นและจุดสิ้นสุดเป็นกรณีพิเศษ แต่มีเพียงกรณีเดียวเท่านั้นที่ต้องได้รับการปฏิบัติเป็นพิเศษ) ดังนั้นการจัดการภายในลูปจึงซับซ้อนโดยไม่จำเป็น


1

ฉันเพิ่งเช็คอินการทดสอบดอลลาร์ห้องสมุดของฉัน:

@Test
public void join() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    String string = $(list).join(",");
}

มันสร้าง Wrapper ที่คล่องแคล่วรอบ ๆ รายการ / อาร์เรย์ / สตริง / ฯลฯ โดยใช้การนำเข้าแบบคงที่เพียงรายการเดียว :$ :

NB :

โดยใช้ช่วงรายการก่อนหน้านี้สามารถเขียนซ้ำได้ $(1, 5).join(",")


1

สิ่งที่ดีเกี่ยวกับนิพจน์ IN คือถ้าคุณมีค่าซ้ำ ๆ ผลลัพธ์จะไม่เปลี่ยนแปลง ดังนั้นเพียงทำซ้ำรายการแรกและประมวลผลรายการทั้งหมด สิ่งนี้จะถือว่ามีอย่างน้อยหนึ่งรายการในรายการ หากไม่มีรายการฉันขอแนะนำให้ตรวจสอบสิ่งนั้นก่อนจากนั้นจึงไม่เรียกใช้งาน SQL เลย

สิ่งนี้จะทำเคล็ดลับชัดเจนในสิ่งที่ทำและไม่ต้องพึ่งพาไลบรารีภายนอกใด ๆ :

StringBuffer inString = new StringBuffer(listOfIDs.get(0).toString());
for (Long currentID : listOfIDs) {
  inString.append(",").append(currentID);
}

1

ในขณะที่ฉันคิดว่าทางออกที่ดีที่สุดของคุณคือการใช้ Joiner จาก Guava แต่ถ้าฉันเขียนโค้ดด้วยมือฉันจะพบว่าวิธีนี้ดูหรูหรากว่าที่แฟล็ก 'แรก' หรือตัดลูกน้ำตัวสุดท้ายออก

private String commas(Iterable<String> strings) {
    StringBuilder buffer = new StringBuilder();
    Iterator<String> it = strings.iterator();
    if (it.hasNext()) {
        buffer.append(it.next());
        while (it.hasNext()) {
            buffer.append(',');
            buffer.append(it.next());
        }
    }

    return buffer.toString();
}


1

ตัวเลือกอื่นตามสิ่งที่ฉันเห็นที่นี่ (โดยมีการปรับเปลี่ยนเล็กน้อย)

public static String toString(int[] numbers) {
    StringBuilder res = new StringBuilder();
    for (int number : numbers) {
        if (res.length() != 0) {
            res.append(',');
        }
        res.append(number);
    }
    return res.toString();
}

1

"วิธีการ" เข้าร่วมมีอยู่ในอาร์เรย์และคลาสที่ขยายAbstractCollectionsแต่ไม่ได้ลบล้างtoString()เมธอด (เช่นเดียวกับคอลเล็กชันทั้งหมดในjava.util)

ตัวอย่างเช่น:

String s= java.util.Arrays.toString(collectionOfStrings.toArray());
s = s.substing(1, s.length()-1);// [] are guaranteed to be there

เป็นวิธีที่ค่อนข้างแปลกเนื่องจากใช้งานได้เฉพาะกับข้อมูล SQL ที่ชาญฉลาดเท่านั้น


1
List<String> collectionOfStrings = // List of string to concat
String csvStrings = StringUtils.collectionToDelimitedString(collectionOfStrings, ",");

StringUtils จาก springframeowrk: แกนสปริง



0
java.util.List<String> lista = new java.util.ArrayList<String>();
lista.add("Hola");
lista.add("Julio");
System.out.println(lista.toString().replace('[','(').replace(']',')'));

$~(Hola, Julio)

1
นี่เป็นการปฏิบัติที่ไม่ดี คุณไม่สามารถตั้งสมมติฐานว่าการใช้งาน toString เปลี่ยนแปลงได้
drindt

0
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

การแทนค่าสตริงประกอบด้วยรายการองค์ประกอบของคอลเล็กชันตามลำดับที่ตัววนซ้ำส่งคืนซึ่งอยู่ในวงเล็บเหลี่ยม ("[]") องค์ประกอบที่อยู่ติดกันจะคั่นด้วยอักขระ "," (ลูกน้ำและช่องว่าง)

AbstractCollection javadoc


0

รายการโทเค็น = ArrayList ใหม่ (ผลลัพธ์); ตัวสร้าง StringBuilder สุดท้าย = StringBuilder ใหม่ ();

    for (int i =0; i < tokens.size(); i++){
        builder.append(tokens.get(i));
        if(i != tokens.size()-1){
            builder.append(TOKEN_DELIMITER);
        }
    }

builder.toString ();

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.