问题
I have a glitch in my program that causes a question mark (\u003f) to appear in the sixth (index = 5) slot in strings when I encrypt them. Normally, this is reversed upon decryption. However, it is not reversed if I save the string to a file first. I have determined that when I save a string containing Unicode characters to a file, I will not be able to determine the correct length for the file. I have managed to reproduce the glitch in the following function...
public static void testFileIO(String[] args)
{
System.out.println("TESTING FILE IO FUNCTIONS...");
try
{
String filename = "test.txt";
String testString = "UB\u4781ERBLAH\u037f\u8746";
System.out.println("Output: " + testString);
FileWriter fw = new FileWriter(filename);
fw.write(testString);
fw.close();
FileReader fr = new FileReader(filename);
int length;
for(length = 0; fr.read() != -1; length++);
if(length != testString.length())
System.out.println("Failure on file length measurement.");
fr.close();
fr = new FileReader(filename);
char[] buffer = new char[length];
fr.read(buffer);
String result = new String(buffer);
fr.close();
System.out.println("Input: " + result);
if(result.equals(testString)) System.out.println("SUCCESS!");
else System.out.println("FAILURE.");
}
catch (Throwable e)
{
e.printStackTrace();
System.out.println("FAILURE.");
return;
}
}
As an additional note, a failure in file length measurement is also caught.
Here is the Crypto class that I use to encrypt and decrypt Strings...
abstract public class Crypto
{
/**
* Encrypt the plaintext with a bitwise xor cipher
* @param plainText The plaintext to encrypt
* @param password The key for the bitwise xor cipher
* @return Ciphertext yielded by given plaintext and password
*/
public static String encrypt(String plainText, String key)
{
char[] data = plainText.toCharArray();
Random rand = new Random();
rand.setSeed(key.hashCode());
char[] pass = new char[data.length];
for(int i = 0; i < pass.length; i++)
{
pass[i] = (char)rand.nextInt();
}
for(int i = 0; i < data.length; i++)
{
data[i] ^= pass[i % pass.length];
}
return new String(data);
}
/**
* Decrypt an encrypted message using the same key as for encryption
* @param cipherText The cipherText message to be deciphered
* @param password The seed for the random generator to get the right keys
* @return The plaintext message corresponding to 'cipherText'
*/
public static String decrypt(String cipherText, String key)
{
char[] data = cipherText.toCharArray();
Random rand = new Random();
rand.setSeed(key.hashCode());
char[] pass = new char[data.length];// = key.getBytes("ASCII");
for(int i = 0; i < pass.length; i++)
{
pass[i] = (char)rand.nextInt();
}
for(int i = 0; i < data.length; i++)
{
data[i] ^= pass[i % pass.length];
}
return new String(data);
}
}
回答1:
The code is correct but almost never works - As a rule of thumb, avoid FileReader
and FileWriter
and build your own readers/writers using InputStreamReader
and OutputStreamWriter
which allow you to specify the encoding to use (and hence how to protect 16bit Unicode characters when you write 8bit data).
I use a helper class for this because I need it all the time:
private static final String FILE = "file";
private static final String CHARSET = "charset";
public static BufferedReader createReader( File file, Encoding charset ) throws IOException {
JavaUtils.notNull( FILE, file );
JavaUtils.notNull( CHARSET, charset );
FileInputStream stream = null;
try {
stream = new FileInputStream( file );
return createReader( stream, charset );
} catch( IOException e ) {
IOUtils.closeQuietly( stream );
throw e;
} catch( RuntimeException e ) {
IOUtils.closeQuietly( stream );
throw e;
}
}
public static BufferedReader createReader( InputStream stream, Encoding charset ) throws IOException {
JavaUtils.notNull( "stream", stream );
JavaUtils.notNull( "charset", charset );
try {
return new BufferedReader( new InputStreamReader( stream, charset.encoding() ) );
} catch( UnsupportedEncodingException e ) {
IOUtils.closeQuietly( stream );
throw new UnknownEncodingException( charset, e );
} catch( RuntimeException e ) {
IOUtils.closeQuietly( stream );
throw e;
}
}
public static BufferedWriter createWriter( File file, Encoding charset ) throws IOException {
JavaUtils.notNull( FILE, file );
JavaUtils.notNull( CHARSET, charset );
FileOutputStream stream = null;
try {
stream = new FileOutputStream( file );
return new BufferedWriter( new OutputStreamWriter( stream, charset.encoding() ) );
} catch( UnsupportedEncodingException e ) {
IOUtils.closeQuietly( stream );
throw new UnknownEncodingException( charset, e );
} catch( IOException e ) {
IOUtils.closeQuietly( stream );
throw e;
} catch( RuntimeException e ) {
IOUtils.closeQuietly( stream );
throw e;
}
}
The type Encoding
is an interface which I implement using one or more enum
s:
public interface Encoding {
String encoding();
Charset charset();
}
来源:https://stackoverflow.com/questions/9569419/java-strings-breaking-file-operations