Apakah lebih baik berlatih menggunakan String.format over string Concatenation di Java?


273

Apakah ada perbedaan yang nyata antara penggunaan String.formatdan penggabungan String di Jawa?

Saya cenderung menggunakan String.formattetapi kadang-kadang akan terpeleset dan menggunakan gabungan. Saya bertanya-tanya apakah yang satu lebih baik dari yang lain.

Cara saya melihatnya, String.formatmemberi Anda lebih banyak kekuatan dalam "memformat" string; dan rangkuman berarti Anda tidak perlu khawatir tentang secara tidak sengaja memasukkan% s ekstra atau melewatkannya.

String.format juga lebih pendek.

Yang mana yang lebih mudah dibaca tergantung pada cara kerja kepala Anda.


Saya pikir kita bisa pergi dengan MessageFormat.format. Silakan lihat jawabannya stackoverflow.com/a/56377112/1491414 untuk info lebih lanjut.
Ganesa Vijayakumar

Jawaban:


242

Saya menyarankan bahwa ini adalah praktik yang lebih baik untuk digunakan String.format(). Alasan utamanya adalah bahwa String.format()dapat lebih mudah dilokalisasi dengan teks yang dimuat dari file sumber daya sedangkan rangkuman tidak dapat dilokalisasi tanpa menghasilkan executable baru dengan kode yang berbeda untuk setiap bahasa.

Jika Anda berencana aplikasi Anda dapat diterjemahkan secara lokal, Anda juga harus membiasakan diri menentukan posisi argumen untuk token format Anda juga:

"Hello %1$s the time is %2$t"

Ini kemudian dapat dilokalkan dan mengganti nama dan token waktu tanpa memerlukan kompilasi ulang dari executable untuk menjelaskan urutan pemesanan yang berbeda. Dengan posisi argumen Anda juga dapat menggunakan kembali argumen yang sama tanpa melewatkannya ke dalam fungsi dua kali:

String.format("Hello %1$s, your name is %1$s and the time is %2$t", name, time)

1
Bisakah Anda mengarahkan saya ke beberapa dokumentasi yang berbicara tentang cara bekerja dengan posisi / urutan argumen di Jawa (yaitu, bagaimana cara merujuk argumen dengan posisi mereka)? Terima kasih.
markvgti

13
Lebih baik terlambat daripada tidak sama sekali, versi Java acak: docs.oracle.com/javase/1.5.0/docs/api/java/util/…
Aksel

174

Tentang kinerja:

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }
  long end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;

  start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = String.format("Hi %s; Hi to you %s",i, + i*2);
  }
  end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
}

Hasil waktu adalah sebagai berikut:

  • Penggabungan = 265 milidetik
  • Format = 4141 milidetik

Oleh karena itu, penggabungan jauh lebih cepat daripada String.format.


15
Itu semua adalah praktik buruk. Gunakan StringBuilder.
Amir Raminfar

8
StringBuilder di luar ruang lingkup di sini (pertanyaan OP adalah tentang membandingkan String.format atas string Concatenation) tetapi apakah Anda memiliki data performace tentang String Builder?
Icaro

108
@AmirRaminar: Kompiler mengubah "+" menjadi panggilan ke StringBuilder secara otomatis.
Martin Schröder

40
@ MartinSchröder: Jika Anda menjalankan javap -c StringTest.classAnda akan melihat bahwa kompiler mengubah "+" menjadi StringBuilder secara otomatis hanya jika Anda tidak berada dalam satu lingkaran. Jika penggabungan semua dilakukan pada satu baris sama dengan menggunakan '+', tetapi jika Anda menggunakan myString += "morechars";atau myString += anotherString;pada beberapa baris Anda akan melihat bahwa lebih dari satu StringBuilder mungkin dibuat, jadi menggunakan "+" tidak selalu seefisien sebagai StringBuilder.
ccpizza

7
@ Jeffrey: apa yang saya maksudkan adalah agar loop +tidak dapat dikonversi StringBuilder.append()tetapi new StringBuilder()terjadi pada setiap iterasi.
ccpizza

39

Karena ada diskusi tentang kinerja saya pikir saya akan menambahkan perbandingan yang termasuk StringBuilder. Ini sebenarnya lebih cepat daripada concat dan, tentu saja opsi String.format.

Untuk membuat ini semacam perbandingan apel dengan apel, saya instantiate StringBuilder baru di loop daripada di luar (ini sebenarnya lebih cepat daripada hanya melakukan satu instantiasi yang paling mungkin karena overhead dari ruang alokasi ulang untuk menambahkan looping pada akhir satu pembangun).

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    log.info("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    log.info("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("; Hi to you ").append(i * 2);
    }

    end = System.currentTimeMillis();

    log.info("String Builder = " + ((end - start)) + " millisecond");
  • 2012-01-11 16: 30: 46.058 INFO [TestMain] - Format = 1416 milidetik
  • 2012-01-11 16: 30: 46.190 INFO [TestMain] - Concatenation = 134 milidetik
  • 2012-01-11 16: 30: 46.313 INFO [TestMain] - Pembuat String = 117 milidetik

21
Tes StringBuilder tidak memanggil toString (), jadi itu bukan perbandingan yang adil. Saya menduga Anda akan menemukan kesalahan pengukuran kinerja penggabungan jika Anda memperbaiki bug itu.
Jamey Sharp

15
Dalam tes gabungan dan format, Anda meminta a String. Tes StringBuilder, agar adil, membutuhkan langkah terakhir yang mengubah konten StringBuilder menjadi sebuah String. Anda melakukannya dengan menelepon bldString.toString(). Saya harap itu menjelaskannya?
Jamey Sharp

4
Jamey Sharp benar sekali. Menjalankan bldString.toString () hampir sama jika tidak lebih lambat dari penggabungan string.
Akos Cz

3
String s = bldString.toString(); Timing adalah dengan Rangkaian dan stringbuilder hampir setara dengan satu sama lain: Format = 1520 millisecond, Concatenation = 167 millisecond, String Builder = 173 millisecond aku berlari mereka dalam satu lingkaran dan rata-rata masing-masing untuk mendapatkan yang baik rep: (pra-JVM optimasi, akan mencoba 10.000 + lingkaran ketika saya mendapatkan waktu)
TechTrip

3
Bagaimana kalian tahu apakah kode itu dijalankan sama sekali? Variabel tidak pernah dibaca atau digunakan, Anda tidak dapat memastikan bahwa JIT tidak menghapus kode ini sejak awal.
alobodzk

37

Satu masalah .formatadalah Anda kehilangan keamanan tipe statis. Anda dapat memiliki terlalu sedikit argumen untuk format Anda, dan Anda dapat memiliki tipe yang salah untuk penentu format - keduanya mengarah pada IllegalFormatException saat runtime , sehingga Anda mungkin berakhir dengan kode logging yang merusak produksi.

Sebaliknya, argumen untuk + dapat diuji oleh kompiler.

Itu sejarah keamanan dari(Di mana formatfungsi dimodelkan) panjang dan menakutkan.


16
Sebagai catatan, IDE modern (misalnya IntelliJ) membantu dalam jumlah argumen dan jenis yang cocok
Ron Klein

2
Poin bagus tentang kompilasi, saya sarankan Anda melakukan pemeriksaan ini melalui FindBugs (yang dapat berjalan di IDE atau melalui Maven selama pembuatan), perhatikan bahwa ini juga akan memeriksa pemformatan di semua logging Anda! Ini berfungsi terlepas dari pengguna IDE
Christophe Roussy

20

Yang mana yang lebih mudah dibaca tergantung pada cara kerja kepala Anda.

Anda mendapat jawaban Anda di sana.

Ini masalah selera pribadi.

Rangkaian string sedikit lebih cepat, saya kira, tetapi itu harus diabaikan.


3
Saya setuju. Berpikir tentang perbedaan kinerja di sini terutama hanyalah pengoptimalan prematur - jika kejadian yang tidak memungkinkan menunjukkan ada masalah di sini, maka khawatirlah.
Jonik

3
Ini hanya masalah selera pribadi jika proyek ini kecil dan tidak pernah dimaksudkan untuk diinternasionalisasikan dalam arti yang berarti. Kalau tidak, String.format menang atas penggabungan dalam segala hal.
workmad3

4
Saya tidak setuju. Tidak peduli seberapa besar proyeknya, Anda tidak akan melokalisasi setiap string yang pernah dibuat di dalamnya. Dengan kata lain, itu tergantung pada situasi (untuk apa string digunakan).
Jonik

Saya tidak bisa membayangkan bagaimana orang akan menganggap 'String.format ("% s% s", a, b)' lebih mudah dibaca daripada 'a + b', dan diberi perbedaan urutan urutan besarnya dalam kecepatan yang Jawabannya tampak jelas bagi saya (dalam situasi yang tidak memerlukan pelokalan seperti debug atau kebanyakan pernyataan logging).
BobDoolittle

16

Berikut ini tes dengan beberapa ukuran sampel dalam milidetik.

public class Time {

public static String sysFile = "/sys/class/camera/rear/rear_flash";
public static String cmdString = "echo %s > " + sysFile;

public static void main(String[] args) {

  int i = 1;
  for(int run=1; run <= 12; run++){
      for(int test =1; test <= 2 ; test++){
        System.out.println(
                String.format("\nTEST: %s, RUN: %s, Iterations: %s",run,test,i));
        test(run, i);
      }
      System.out.println("\n____________________________");
      i = i*3;
  }
}

public static void test(int run, int iterations){

      long start = System.nanoTime();
      for( int i=0;i<iterations; i++){
          String s = "echo " + i + " > "+ sysFile;
      }
      long t = System.nanoTime() - start;   
      String r = String.format("  %-13s =%10d %s", "Concatenation",t,"nanosecond");
      System.out.println(r) ;


     start = System.nanoTime();       
     for( int i=0;i<iterations; i++){
         String s =  String.format(cmdString, i);
     }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "Format",t,"nanosecond");
     System.out.println(r);

      start = System.nanoTime();          
      for( int i=0;i<iterations; i++){
          StringBuilder b = new StringBuilder("echo ");
          b.append(i).append(" > ").append(sysFile);
          String s = b.toString();
      }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "StringBuilder",t,"nanosecond");
     System.out.println(r);
}

}

TEST: 1, RUN: 1, Iterations: 1
  Concatenation =     14911 nanosecond
  Format        =     45026 nanosecond
  StringBuilder =      3509 nanosecond

TEST: 1, RUN: 2, Iterations: 1
  Concatenation =      3509 nanosecond
  Format        =     38594 nanosecond
  StringBuilder =      3509 nanosecond

____________________________

TEST: 2, RUN: 1, Iterations: 3
  Concatenation =      8479 nanosecond
  Format        =     94438 nanosecond
  StringBuilder =      5263 nanosecond

TEST: 2, RUN: 2, Iterations: 3
  Concatenation =      4970 nanosecond
  Format        =     92976 nanosecond
  StringBuilder =      5848 nanosecond

____________________________

TEST: 3, RUN: 1, Iterations: 9
  Concatenation =     11403 nanosecond
  Format        =    287115 nanosecond
  StringBuilder =     14326 nanosecond

TEST: 3, RUN: 2, Iterations: 9
  Concatenation =     12280 nanosecond
  Format        =    209051 nanosecond
  StringBuilder =     11818 nanosecond

____________________________

TEST: 5, RUN: 1, Iterations: 81
  Concatenation =     54383 nanosecond
  Format        =   1503113 nanosecond
  StringBuilder =     40056 nanosecond

TEST: 5, RUN: 2, Iterations: 81
  Concatenation =     44149 nanosecond
  Format        =   1264241 nanosecond
  StringBuilder =     34208 nanosecond

____________________________

TEST: 6, RUN: 1, Iterations: 243
  Concatenation =     76018 nanosecond
  Format        =   3210891 nanosecond
  StringBuilder =     76603 nanosecond

TEST: 6, RUN: 2, Iterations: 243
  Concatenation =     91222 nanosecond
  Format        =   2716773 nanosecond
  StringBuilder =     73972 nanosecond

____________________________

TEST: 8, RUN: 1, Iterations: 2187
  Concatenation =    527450 nanosecond
  Format        =  10291108 nanosecond
  StringBuilder =    885027 nanosecond

TEST: 8, RUN: 2, Iterations: 2187
  Concatenation =    526865 nanosecond
  Format        =   6294307 nanosecond
  StringBuilder =    591773 nanosecond

____________________________

TEST: 10, RUN: 1, Iterations: 19683
  Concatenation =   4592961 nanosecond
  Format        =  60114307 nanosecond
  StringBuilder =   2129387 nanosecond

TEST: 10, RUN: 2, Iterations: 19683
  Concatenation =   1850166 nanosecond
  Format        =  35940524 nanosecond
  StringBuilder =   1885544 nanosecond

  ____________________________

TEST: 12, RUN: 1, Iterations: 177147
  Concatenation =  26847286 nanosecond
  Format        = 126332877 nanosecond
  StringBuilder =  17578914 nanosecond

TEST: 12, RUN: 2, Iterations: 177147
  Concatenation =  24405056 nanosecond
  Format        = 129707207 nanosecond
  StringBuilder =  12253840 nanosecond

1
StringBuilder benar-benar metode tercepat ketika Anda menambahkan karakter dalam satu lingkaran, misalnya, ketika Anda ingin membuat string dengan seribu 1 dengan menambahkannya satu per satu. Berikut ini info lebih lanjut: pellegrino.link/2015/08/22/...
Carlos Hoyos

Saya suka bagaimana Anda selalu menggunakan String.format untuk output: D jadi ada keuntungan. dan jujur ​​jika kita tidak berbicara tentang jutaan iterasi, saya lebih suka string.format agar mudah dibaca karena kode Anda menunjukkan keuntungan yang jelas!
mohamnag

9

Berikut ini tes yang sama seperti di atas dengan modifikasi memanggil metode toString () pada StringBuilder . Hasil di bawah ini menunjukkan bahwa pendekatan StringBuilder hanya sedikit lebih lambat daripada penggabungan String menggunakan operator + .

file: StringTest.java

class StringTest {

  public static void main(String[] args) {

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("Hi to you ").append(i * 2).toString();
    }

    end = System.currentTimeMillis();

    System.out.println("String Builder = " + ((end - start)) + " millisecond");

  }
}

Perintah Shell: (kompilasi dan jalankan StringTest 5 kali)

> javac StringTest.java
> sh -c "for i in \$(seq 1 5); do echo \"Run \${i}\"; java StringTest; done"

Hasil:

Run 1
Format = 1290 millisecond
Concatenation = 115 millisecond
String Builder = 130 millisecond

Run 2
Format = 1265 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond

Run 3
Format = 1303 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 4
Format = 1297 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 5
Format = 1270 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond

6

String.format()lebih dari sekadar senar penggabungan. Misalnya, Anda dapat menampilkan angka di lokal tertentu menggunakan String.format().

Namun, jika Anda tidak peduli dengan lokalisasi, tidak ada perbedaan fungsional. Mungkin yang satu lebih cepat dari yang lain, tetapi dalam kebanyakan kasus itu akan diabaikan ..


4

Secara umum, rangkaian string harus lebih disukai String.format. Yang terakhir memiliki dua kelemahan utama:

  1. Itu tidak menyandikan string yang akan dibangun secara lokal.
  2. Proses pembangunan dikodekan dalam sebuah string.

Pada poin 1, maksud saya adalah tidak mungkin untuk memahami apa a String.format() panggilan dalam satu kali urutan berurutan. Seseorang dipaksa untuk bolak-balik antara format string dan argumen, sambil menghitung posisi argumen. Untuk penggabungan singkat, ini tidak banyak masalah. Namun dalam kasus ini, penggabungan string kurang verbose.

Dengan poin 2, maksud saya bahwa bagian penting dari proses pembangunan dikodekan dalam string format (menggunakan DSL). Menggunakan string untuk mewakili kode memiliki banyak kelemahan. Ini bukan tipe yang aman, dan menyulitkan penyorotan sintaksis, analisis kode, optimisasi, dll.

Tentu saja, ketika menggunakan alat atau kerangka kerja di luar bahasa Jawa, faktor-faktor baru dapat ikut berperan.


2

Saya belum melakukan tolok ukur khusus, tetapi saya akan berpikir bahwa rangkaian mungkin lebih cepat. String.format () menciptakan Formatter baru yang, pada gilirannya, menciptakan StringBuilder baru (dengan ukuran hanya 16 karakter). Itu adalah jumlah overhead yang wajar terutama jika Anda memformat string yang lebih panjang dan StringBuilder harus mengubah ukurannya.

Namun, gabungannya kurang bermanfaat dan lebih sulit dibaca. Seperti biasa, ada baiknya melakukan patokan pada kode Anda untuk melihat mana yang lebih baik. Perbedaannya dapat diabaikan di aplikasi server setelah bundel sumber daya Anda, lokal, dll dimuat dalam memori dan kode JITted.

Mungkin sebagai praktik terbaik, itu akan menjadi ide yang baik untuk membuat Formatter Anda sendiri dengan StringBuilder (Appendable) dan Lokal yang berukuran tepat dan menggunakannya dan menggunakannya jika Anda memiliki banyak format yang harus dilakukan.


2

Mungkin ada perbedaan yang jelas.

String.format cukup rumit dan menggunakan ekspresi reguler di bawahnya, jadi jangan biasakan menggunakannya di mana-mana, tetapi hanya jika Anda membutuhkannya.

StringBuilder akan menjadi urutan besarnya lebih cepat (seperti seseorang di sini sudah menunjukkan).


1

Saya pikir kita bisa mengikuti MessageFormat.formatkarena harus baik pada keterbacaan dan juga aspek kinerja.

Saya menggunakan program yang sama yang digunakan oleh Icaro dalam jawaban di atas dan saya meningkatkannya dengan menambahkan kode untuk digunakan MessageFormatuntuk menjelaskan angka kinerja.

  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = "Hi " + i + "; Hi to you " + i * 2;
    }
    long end = System.currentTimeMillis();
    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = String.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = MessageFormat.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("MessageFormat = " + ((end - start)) + " millisecond");
  }

Penggabungan = 69 milidetik

Format = 1435 milidetik

MessageFormat = 200 milidetik

PEMBARUAN:

Sesuai Laporan SonarLint, string format printf-style harus digunakan dengan benar (squid: S3457)

Karena printf-styleformat string ditafsirkan pada saat runtime, daripada divalidasi oleh kompiler, mereka dapat mengandung kesalahan yang menghasilkan string yang salah yang sedang dibuat. Aturan ini statis memvalidasi korelasi printf-stylestring format untuk argumen mereka saat memanggil format (...) metode java.util.Formatter, java.lang.String, java.io.PrintStream, MessageFormat, dan java.io.PrintWriterkelas dan printf(...)metode java.io.PrintStreamatau java.io.PrintWriterkelas.

Saya mengganti gaya printf dengan kurung keriting dan saya mendapatkan hasil yang menarik seperti di bawah ini.

Rangkuman = 69 milidetik
Format = 1107 milidetik
Format: kurung-kurung = 416 milidetik
MessageFormat = 215 milidetik
PesanFormat: kurung kurung = 2517 milidetik

Kesimpulan saya:
Seperti yang saya soroti di atas, menggunakan String.format dengan kurung-kurung harus menjadi pilihan yang baik untuk mendapatkan manfaat dari keterbacaan yang baik dan juga kinerja.


0

Anda tidak dapat membandingkan String Concatenation dan String.Format oleh program di atas.

Anda dapat mencoba ini juga akan menukar posisi menggunakan String Anda. Kesesuaian dan Penggabungan dalam blok kode Anda seperti di bawah ini

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = String.format( "Hi %s; Hi to you %s",i, + i*2);
  }

  long end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
  start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }

  end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;
}

Anda akan terkejut melihat bahwa Format berfungsi lebih cepat di sini. Ini karena objek awal yang dibuat mungkin tidak dirilis dan mungkin ada masalah dengan alokasi memori dan dengan demikian kinerja.


3
Sudahkah Anda mencoba kode Anda? Penggabungan selalu sepuluh kali lebih cepat
Icaro

bagaimana dengan millis yang diambil untuk menjalankan "System.currentTimeMillis ()" ini: P.
rehan

0

Dibutuhkan sedikit waktu untuk membiasakan diri dengan String.Format, tetapi itu layak dilakukan pada kebanyakan kasus. Dalam dunia NRA (tidak pernah mengulangi apa pun), sangat berguna untuk menyimpan pesan-pesan token Anda (pencatatan atau pengguna) di pustaka Konstan (saya lebih suka berapa jumlah kelas statis) dan menyebutnya seperlunya dengan String.Format terlepas dari apakah Anda sedang melokalisasi atau tidak. Mencoba menggunakan perpustakaan seperti itu dengan metode penggabungan lebih sulit untuk membaca, memecahkan masalah, mengoreksi, dan mengelola dengan pendekatan apa pun yang memerlukan penggabungan. Penggantian adalah sebuah pilihan, tapi saya ragu itu performant. Setelah bertahun-tahun digunakan, masalah terbesar saya dengan String.Format adalah lamanya panggilan tidak nyaman lama ketika saya meneruskannya ke fungsi lain (seperti Msg), tapi itu mudah untuk disiasati dengan fungsi khusus untuk berfungsi sebagai alias. .

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.