StringTokenizer
? Chuyển đổi String
thành a char[]
và lặp đi lặp lại điều đó? Thứ gì khác?
StringTokenizer
? Chuyển đổi String
thành a char[]
và lặp đi lặp lại điều đó? Thứ gì khác?
Câu trả lời:
Tôi sử dụng một vòng lặp for để lặp lại chuỗi và sử dụng charAt()
để lấy từng ký tự để kiểm tra nó. Vì Chuỗi được triển khai với một mảng, nên charAt()
phương thức là một hoạt động thời gian không đổi.
String s = "...stuff...";
for (int i = 0; i < s.length(); i++){
char c = s.charAt(i);
//Process char
}
Đó là những gì tôi sẽ làm. Nó có vẻ dễ nhất với tôi.
Theo như sự đúng đắn, tôi không tin rằng có tồn tại ở đây. Tất cả đều dựa trên phong cách cá nhân của bạn.
String.charAt(int)
chỉ đơn giản là làm value[index]
. Tôi nghĩ rằng bạn đang nhầm lẫn chatAt()
với một cái gì đó cung cấp cho bạn điểm mã.
Hai lựa chọn
for(int i = 0, n = s.length() ; i < n ; i++) {
char c = s.charAt(i);
}
hoặc là
for(char c : s.toCharArray()) {
// process c
}
Đầu tiên có lẽ là nhanh hơn, sau đó thứ 2 có thể dễ đọc hơn.
Lưu ý hầu hết các kỹ thuật khác được mô tả ở đây bị hỏng nếu bạn đang xử lý các ký tự bên ngoài BMP ( Mặt phẳng đa ngôn ngữ Unicode cơ bản ), tức là các điểm mã nằm ngoài phạm vi u0000-uFFFF. Điều này sẽ chỉ xảy ra hiếm khi, vì các điểm mã bên ngoài này hầu hết được gán cho các ngôn ngữ chết. Nhưng có một số ký tự hữu ích bên ngoài này, ví dụ một số điểm mã được sử dụng cho ký hiệu toán học và một số được sử dụng để mã hóa tên riêng trong tiếng Trung.
Trong trường hợp đó, mã của bạn sẽ là:
String str = "....";
int offset = 0, strLen = str.length();
while (offset < strLen) {
int curChar = str.codePointAt(offset);
offset += Character.charCount(curChar);
// do something with curChar
}
Các Character.charCount(int)
phương pháp đòi hỏi Java 5 +.
Tôi đồng ý rằng StringTokenizer là quá mức cần thiết ở đây. Thật ra tôi đã thử những gợi ý trên và dành thời gian.
Thử nghiệm của tôi khá đơn giản: tạo StringBuilder với khoảng một triệu ký tự, chuyển đổi nó thành Chuỗi và duyệt từng chuỗi bằng charAt () / sau khi chuyển đổi thành mảng char / với Bộ tạo ký tự một nghìn lần (tất nhiên là đảm bảo làm một cái gì đó trên chuỗi để trình biên dịch không thể tối ưu hóa toàn bộ vòng lặp :-)).
Kết quả trên Powerbook 2,6 GHz của tôi (đó là mac :-)) và JDK 1.5:
Vì kết quả khác nhau đáng kể, cách đơn giản nhất cũng có vẻ là cách nhanh nhất. Thật thú vị, charAt () của StringBuilder dường như chậm hơn một chút so với String.
BTW Tôi đề nghị không sử dụng CharacterIterator vì tôi coi việc lạm dụng ký tự '\ uFFFF' của nó là "kết thúc lặp lại" là một hack thực sự khủng khiếp. Trong các dự án lớn, luôn có hai kẻ sử dụng cùng một loại hack cho hai mục đích khác nhau và mã bị sập thực sự bí ẩn.
Đây là một trong những bài kiểm tra:
int count = 1000;
...
System.out.println("Test 1: charAt + String");
long t = System.currentTimeMillis();
int sum=0;
for (int i=0; i<count; i++) {
int len = str.length();
for (int j=0; j<len; j++) {
if (str.charAt(j) == 'b')
sum = sum + 1;
}
}
t = System.currentTimeMillis()-t;
System.out.println("result: "+ sum + " after " + t + "msec");
Trong Java 8, chúng ta có thể giải nó như sau:
String str = "xyz";
str.chars().forEachOrdered(i -> System.out.print((char)i));
str.codePoints().forEachOrdered(i -> System.out.print((char)i));
Phương thức chars () trả về một IntStream
như đã đề cập trong doc :
Trả về một luồng int zero - mở rộng các giá trị char từ chuỗi này. Bất kỳ char nào ánh xạ tới một điểm mã thay thế đều được chuyển qua không giải thích được. Nếu chuỗi bị đột biến trong khi luồng đang được đọc, kết quả không được xác định.
Phương thức này codePoints()
cũng trả về một IntStream
tài liệu theo:
Trả về một luồng các giá trị điểm mã từ chuỗi này. Bất kỳ cặp thay thế nào gặp phải trong chuỗi đều được kết hợp như thể bởi Character.toCodePoint và kết quả được truyền vào luồng. Bất kỳ đơn vị mã nào khác, bao gồm các ký tự BMP thông thường, các đại diện không ghép cặp và các đơn vị mã không xác định, đều được mở rộng bằng 0 cho các giá trị int sau đó được truyền vào luồng.
Làm thế nào là char và điểm mã khác nhau? Như đã đề cập trong này bài viết:
Unicode 3.1 đã thêm các ký tự bổ sung, nâng tổng số ký tự lên hơn 216 ký tự có thể được phân biệt bằng 16 bit đơn
char
. Do đó, mộtchar
giá trị không còn có ánh xạ một-một đến đơn vị ngữ nghĩa cơ bản trong Unicode. JDK 5 đã được cập nhật để hỗ trợ tập hợp các giá trị ký tự lớn hơn. Thay vì thay đổi định nghĩa vềchar
loại, một số ký tự bổ sung mới được biểu thị bằng một cặp thay thế gồm haichar
giá trị. Để giảm nhầm lẫn khi đặt tên, một điểm mã sẽ được sử dụng để chỉ số đại diện cho một ký tự Unicode cụ thể, bao gồm cả các ký tự bổ sung.
Cuối cùng tại sao forEachOrdered
và không forEach
?
Hành vi của forEach
không rõ ràng là không forEachOrdered
thực hiện khi hành động thực hiện một hành động cho từng thành phần của luồng này, theo thứ tự bắt gặp của luồng nếu luồng có thứ tự bắt gặp được xác định. Vì vậy, forEach
không đảm bảo rằng thứ tự sẽ được giữ. Ngoài ra kiểm tra câu hỏi này để biết thêm.
Để biết sự khác biệt giữa một ký tự, một điểm mã, glyph và grapheme, hãy kiểm tra câu hỏi này .
Có một số lớp dành riêng cho việc này:
import java.text.*;
final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
// process c
...
}
char
cung cấp. Một Java char
chứa 16 bit và có thể giữ các ký tự Unicode lên U + FFFF nhưng Unicode chỉ định các ký tự lên đến U + 10FFFF. Sử dụng 16 bit để mã hóa kết quả Unicode trong mã hóa ký tự có độ dài thay đổi. Hầu hết các câu trả lời trên trang này đều cho rằng mã hóa Java là mã hóa có độ dài không đổi, điều này là sai.
Nếu bạn có Guava trên đường dẫn lớp của bạn, sau đây là một thay thế khá dễ đọc. Quả ổi thậm chí còn có cách thực hiện Danh sách tùy chỉnh khá hợp lý cho trường hợp này, vì vậy điều này không nên không hiệu quả.
for(char c : Lists.charactersOf(yourString)) {
// Do whatever you want
}
CẬP NHẬT: Như @Alex đã lưu ý, với Java 8 cũng có CharSequence#chars
thể sử dụng. Ngay cả loại là IntStream, vì vậy nó có thể được ánh xạ tới các ký tự như:
yourString.chars()
.mapToObj(c -> Character.valueOf((char) c))
.forEach(c -> System.out.println(c)); // Or whatever you want
Nếu bạn cần lặp qua các điểm mã của một String
(xem câu trả lời này ), cách ngắn hơn / dễ đọc hơn là sử dụng CharSequence#codePoints
phương thức được thêm vào trong Java 8:
for(int c : string.codePoints().toArray()){
...
}
hoặc sử dụng luồng trực tiếp thay vì vòng lặp for:
string.codePoints().forEach(c -> ...);
Cũng có CharSequence#chars
nếu bạn muốn một luồng các ký tự (mặc dù nó là một IntStream
, vì không có CharStream
).
Tôi sẽ không sử dụng StringTokenizer
vì đây là một trong các lớp trong JDK đó là di sản.
Javadoc nói:
StringTokenizer
là một lớp kế thừa được giữ lại vì lý do tương thích mặc dù việc sử dụng nó không được khuyến khích trong mã mới. Thay vào đó, mọi người tìm kiếm chức năng này nên sử dụng phương pháp phân chiaString
hoặcjava.util.regex
gói.
Nếu bạn cần hiệu suất, sau đó bạn phải kiểm tra trên môi trường của bạn. Không con cach nao khac.
Ở đây mã ví dụ:
int tmp = 0;
String s = new String(new byte[64*1024]);
{
long st = System.nanoTime();
for(int i = 0, n = s.length(); i < n; i++) {
tmp += s.charAt(i);
}
st = System.nanoTime() - st;
System.out.println("1 " + st);
}
{
long st = System.nanoTime();
char[] ch = s.toCharArray();
for(int i = 0, n = ch.length; i < n; i++) {
tmp += ch[i];
}
st = System.nanoTime() - st;
System.out.println("2 " + st);
}
{
long st = System.nanoTime();
for(char c : s.toCharArray()) {
tmp += c;
}
st = System.nanoTime() - st;
System.out.println("3 " + st);
}
System.out.println("" + tmp);
Trên Java trực tuyến tôi nhận được:
1 10349420
2 526130
3 484200
0
Trên API Android x86 17 tôi nhận được:
1 9122107
2 13486911
3 12700778
0
Xem Hướng dẫn Java: Chuỗi .
public class StringDemo {
public static void main(String[] args) {
String palindrome = "Dot saw I was Tod";
int len = palindrome.length();
char[] tempCharArray = new char[len];
char[] charArray = new char[len];
// put original string in an array of chars
for (int i = 0; i < len; i++) {
tempCharArray[i] = palindrome.charAt(i);
}
// reverse array of chars
for (int j = 0; j < len; j++) {
charArray[j] = tempCharArray[len - 1 - j];
}
String reversePalindrome = new String(charArray);
System.out.println(reversePalindrome);
}
}
Đặt chiều dài vào int len
và sử dụng for
vòng lặp.
StringTokenizer hoàn toàn không phù hợp với nhiệm vụ phá vỡ một chuỗi thành các ký tự riêng lẻ của nó. Với String#split()
bạn có thể làm điều đó một cách dễ dàng bằng cách sử dụng regex không khớp với gì, ví dụ:
String[] theChars = str.split("|");
Nhưng StringTokenizer không sử dụng regexes và không có chuỗi phân cách nào bạn có thể chỉ định sẽ không khớp với gì giữa các ký tự. Có là một chút dễ thương hack you có thể sử dụng để thực hiện điều tương tự: sử dụng chuỗi chính nó như là chuỗi delimiter (làm cho mỗi nhân vật trong nó một dấu phân cách) và có nó trả lại delimiters:
StringTokenizer st = new StringTokenizer(str, str, true);
Tuy nhiên, tôi chỉ đề cập đến các tùy chọn này cho mục đích loại bỏ chúng. Cả hai kỹ thuật này phá vỡ chuỗi ban đầu thành chuỗi một ký tự thay vì nguyên hàm char và cả hai đều liên quan đến rất nhiều chi phí dưới dạng tạo đối tượng và thao tác chuỗi. So sánh điều đó với việc gọi charAt () trong một vòng lặp for, mà hầu như không có chi phí nào.
Xây dựng câu trả lời này và câu trả lời này .
Các câu trả lời ở trên chỉ ra vấn đề của nhiều giải pháp ở đây không lặp lại theo giá trị điểm mã - chúng sẽ gặp rắc rối với bất kỳ ký tự thay thế nào . Các tài liệu java cũng phác thảo vấn đề ở đây (xem "Biểu diễn ký tự Unicode"). Dù sao đi nữa, đây là một số mã sử dụng một số ký tự thay thế thực tế từ bộ Unicode bổ sung và chuyển đổi chúng trở lại thành Chuỗi. Lưu ý rằng .toChars () trả về một mảng ký tự: nếu bạn đang xử lý người thay thế, bạn nhất thiết phải có hai ký tự. Mã này sẽ hoạt động cho bất kỳ ký tự Unicode nào .
String supplementary = "Some Supplementary: 𠜎𠜱𠝹𠱓";
supplementary.codePoints().forEach(cp ->
System.out.print(new String(Character.toChars(cp))));
Mã ví dụ này sẽ giúp bạn ra ngoài!
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class Solution {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 10);
map.put("b", 30);
map.put("c", 50);
map.put("d", 40);
map.put("e", 20);
System.out.println(map);
Map sortedMap = sortByValue(map);
System.out.println(sortedMap);
}
public static Map sortByValue(Map unsortedMap) {
Map sortedMap = new TreeMap(new ValueComparator(unsortedMap));
sortedMap.putAll(unsortedMap);
return sortedMap;
}
}
class ValueComparator implements Comparator {
Map map;
public ValueComparator(Map map) {
this.map = map;
}
public int compare(Object keyA, Object keyB) {
Comparable valueA = (Comparable) map.get(keyA);
Comparable valueB = (Comparable) map.get(keyB);
return valueB.compareTo(valueA);
}
}
Vì vậy, thông thường có hai cách để lặp qua chuỗi trong java đã được trả lời bởi nhiều người ở đây trong chuỗi này, chỉ cần thêm phiên bản của tôi Đầu tiên là sử dụng
String s = sc.next() // assuming scanner class is defined above
for(int i=0; i<s.length; i++){
s.charAt(i) // This being the first way and is a constant time operation will hardly add any overhead
}
char[] str = new char[10];
str = s.toCharArray() // this is another way of doing so and it takes O(n) amount of time for copying contents from your string class to character array
Nếu hiệu suất bị đe dọa thì tôi sẽ khuyên bạn nên sử dụng cái đầu tiên trong thời gian không đổi, nếu nó không đi với cái thứ hai làm cho công việc của bạn dễ dàng hơn khi xem xét tính bất biến với các lớp chuỗi trong java.