Processing large files on Android - android

I am writing an android app which requires me to process a very large file(say 50MB). The file contains three entries-A userid, artistid and the number of times the user has listened to that artist. So now I write the code to find out who the most popular artist is based on the number of times the artist has been heard. I achieve this by hashmapping each of the artist id(key) and then the number of times he's been heard (value).
The code works fine as long as the file is below 20B (I run the app on Nexus S 4g, so the heap is 32MB), but I get an 'Out of memory' error for larger files. I realize the code is very badly written. Any suggestions as to how I could get through this problem!
while ((text = inRd.readLine()) != null) {
StringTokenizer st = new StringTokenizer(text);
while (st.hasMoreTokens()) {
int userid = Integer.parseInt(st.nextToken());
artistid = Integer.parseInt(st.nextToken());
int no_times = Integer.parseInt(st.nextToken());
if (hm.containsKey(artistid)) {
Integer oldval = (Integer) hm.get(artistid);
Integer newval = no_times + oldval;
hm.remove(artistid);
hm.put(artistid, newval);
} else
hm.put(artistid, no_times);
}
}

A hashmapping is not the most efficient way to store things. Try a Vector or straight arrays.
Hope this Helps
Cliff

Related

payUmoney integration is giving an error

After logging in, it's generating a hash value, but still giving error "Some problem occurred! try again".
PayUmoneySdkInitilizer.PaymentParam.Builder builder =
new PayUmoneySdkInitilizer.PaymentParam.Builder();
builder.setAmount(10.0)
.setTnxId("0nf7" + System.currentTimeMillis())
.setPhone(<My phone>)
.setProductName("product_name")
.setFirstName(<My Name>)
.setEmail(<My email>)
.setsUrl("https://www.payumoney.com/mobileapp/payumoney/success.php")
.setfUrl("https://www.payumoney.com/mobileapp/payumoney/failure.php")
.setUdf1("").setUdf2("").setUdf3("").setUdf4("").setUdf5("")
.setIsDebug(false)
.setKey(<mykey>)
.setMerchantId(<my debug merchant id>);
String tnxId="0nf7" + System.currentTimeMillis();
PayUmoneySdkInitilizer.PaymentParam paymentParam = builder.build();
String hashSequence = "<...>|"+tnxId+"|10.0|product_name|<My name>|<My email>|||||||||||salt";
String serverCalculatedHash= hashCal("SHA-512", hashSequence);
Toast.makeText(getApplicationContext(),
serverCalculatedHash, Toast.LENGTH_SHORT).show();
paymentParam.setMerchantHash(serverCalculatedHash);
// calculateServerSideHashAndInitiatePayment(paymentParam);
PayUmoneySdkInitilizer.startPaymentActivityForResult(TrayActivity.this, paymentParam);
public static String hashCal(String type, String str) {
byte[] hashseq = str.getBytes();
StringBuffer hexString = new StringBuffer();
try {
MessageDigest algorithm = MessageDigest.getInstance(type);
algorithm.reset();
algorithm.update(hashseq);
byte messageDigest[] = algorithm.digest();
for (int i = 0; i<messageDigest.length; i++) {
String hex = Integer.toHexString(0xFF &messageDigest[i]);
if (hex.length() == 1) { hexString.append("0"); }
hexString.append(hex);
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} return hexString.toString();
}
You use in the code:
.setTnxId("0nf7" + System.currentTimeMillis())
And then later:
String tnxId="0nf7" + System.currentTimeMillis();
Probably not the only problem, but do you really want to use two different values for these (the time may change between the two calls)? Didn't you want the same tnxId in both cases?
TransactionIdProvider.java:
import java.util.Locale;
public class TransactionIdProvider {
private final static String DEFAULT_PREFIX = "ID";
// Convenient prime number for incrementing the counter
private final static long ID_ADD = 0xF0AD; // "f*ck off and die"
// 64b counter with non-trivial start value
private static long idCounter = 0x0101F00DDEADBEEFL;
/**
* Returns ID consisting of prefix string and 64b counter interleaved
* with 32b per-4s-timestamp.
*
* May produce identical ID (collision) when:
* 1) class is reloaded within 4s
* (to fix: serialize "idCounter" upon shutdown/restart of VM, or
* modify prefix per start of VM)
* 2) more than 2^64 IDs are requested within 4s (no fix, unexpected)
* 3) more than 2^64 IDs are requested after cca. 550 years.
* (no fix, unexpected)
* 4) more than one static instance of TransactionIdProvider is used
* (two or more VMs running the app) (to fix put different prefix in
* every VM/server running this)
*
* Length of returned ID is prefix.length() + 24 alphanumeric symbols.
*/
public static synchronized String getNewId(final String prefix) {
idCounter += ID_ADD; // increment counter
// get 32b timestamp per ~4s (millis/4096) (good for ~550 years)
final int timeStamp = (int)(System.currentTimeMillis()>>12);
final int idPart1 = (int)(idCounter>>32);
final int idPart2 = (int)(idCounter);
return String.format(Locale.US, "%s%08X%08X%08X",
prefix, idPart1, timeStamp, idPart2);
}
public static String getNewId() {
return getNewId(DEFAULT_PREFIX);
}
}
Not sure how much usable is this one, and if the ID may be so long. Feel free to use/modify it any way you wish.
Also I wonder, whether I didn't forget about something important, but can't recall anything.
The security aspect of this one is still quite weak, as within 4s time span the ID will be like simple addition, but at least it's not producing 1, 2, 3... series.
Did found some SDK docs, looks like txnId may be 25 chars long, so you have 1 char for prefix only. Or cut down on timestamp, using %07X in format and masking value with 0x0FFFFFFF, that would make it repeat every ~34 years -> 2 letters for prefix. Or change counter to 32b int, should be still more than enough, unless you expect thousands of transactions per second -> that would remove 8 chars. Or base32/base64 the whole ID to shorten it (depends what alphabet is legal for content)...
Or whatever... already spent enough time with this. Hire a pro.

Separating the words after the last integer in a large String

I've seen many people do similar to this in order to get the last word of a String:
String test = "This is a sentence";
String lastWord = test.substring(test.lastIndexOf(" ")+1);
I would like to do similar but get the last few words after the last int, it can't be hard coded as the number could be anything and the amount of words after the last int could also be unlimited. I'm wondering whether there is a simple way to do this as I want to avoid using Patterns and Matchers again due to using them earlier on in this method to receive a similar effect.
Thanks in advance.
I would like to get the last few words after the last int.... as the number could be anything and the amount of words after the last int could also be unlimited.
Here's a possible suggestion. Using Array#split
String str = "This is 1 and 2 and 3 some more words .... foo bar baz";
String[] parts = str.split("\\d+(?!.*\\d)\\s+");
And now parts[1] holds all words after the last number in the string.
some more words .... foo bar baz
What about this one:
String test = "a string with a large number 1312398741 and some words";
String[] parts = test.split();
for (int i = 1; i < parts.length; i++)
{
try
{
Integer.parseInt(parts[i])
}
catch (Exception e)
{
// this part is not a number, so lets go on...
continue;
}
// when parsing succeeds, the number was reached and continue has
// not been called. Everything behind 'i' is what you are looking for
// DO YOUR STUFF with parts[i+1] to parts[parts.length] here
}

Extracting substring

i have some problems extracting strings
i am making a multiple choice with 4 choices (e.g. as buttons), with the choices by referencing to the filename. The file (i.e. the question) is a png and the filename is Number-Q01AZ7BZ8CZ9DZ10ANZ8.png. These png are put under assets folder.
Set<String> regions = regionsMap.keySet(); // get Set of regions
// loop through each region
for (String region : regions)
{
if (regionsMap.get(region)) // if region is enabled
{
// get a list of all flag image files in this region
String[] paths = assets.list(region);
for (String path : paths)
fileNameList.add(path.replace(".png", ""));
} // end if
} // end for
String fileName = fileNameList.get(randomIndex);
if (!quizCountriesList.contains(fileName))
{
quizCountriesList.add(fileName); // add the file to the list
String nextImageName = quizCountriesList.remove(0);
correctAnswer = nextImageName; // update the correct answer
int AZ = correctAnswer.indexOf("AZ");
int BZ = correctAnswer.indexOf("BZ");
int CZ = correctAnswer.indexOf("CZ");
int DZ = correctAnswer.indexOf("DZ");
int ANZ = correctAnswer.indexOf("ANZ");
String choiceA = null;
String choiceB = null;
String choiceC = null;
String choiceD = null;
choiceA = correctAnswer.substring( (AZ+2), (BZ) );
choiceB = correctAnswer.substring( (BZ+2), (CZ) );
choiceC = correctAnswer.substring( (CZ+2), (DZ) );
choiceD = correctAnswer.substring( (DZ+2), (ANZ) );
The logcat is as follows:
11-09 21:14:08.495: E/AndroidRuntime(25905): FATAL EXCEPTION: main
11-09 21:14:08.495: E/AndroidRuntime(25905): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.trial.quizgame/com.trial.quizgame.QuizGame}: java.lang.StringIndexOutOfBoundsException: length=15; regionStart=1; regionLength=-2
11-09 21:14:08.495: E/AndroidRuntime(25905): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1967)
I have tried to set the buttons as .setText(correctAnswer) and it will correctly show as Number-Q01AZ7BZ8CZ9DZ10ANZ8, so the top part of getting the String for "correctAnswer" should be ok. The problem left at extracting strings, yet BZ must be at a position behind AZ, so as CZ behind BZ, etc:
From the logcat the regionLength is -2? How could I handle this?
I would like it to be for Q01, choice A=7, B=8, C=9, D=10 and ANZ=8
thanks in advance for your advice!
Your assumption on the strings value is wrong. This code, if AZ, BZ, CZ, DZ, ANZ is present, should run with no error.
Either run debugger as advised in comments, or use android logcat to provide some debugging context. android.utils.Log.d("APP", String.format("AZ=%d", AZ));
How you store your data is not a big deal. You can tune it for days... You could create xml files that contain the name of the image, and the four possible answers... You can use the underscore approach, you can stay with your current one. Til it is only used by you, it doesn't really matter. You should just keep it simple. More complex => more chance for bugs...
So I'd advise reading about debugging and logging instead of refining the way you store the information... Storing it in the filename, that's a smart idea, quick and efficient, an ideal hack...

SpannableStringBuffer limited to 9,999 characters?

My app reads in large amounts of data from text files assets and displays them on-screen in a TextView. (The largest is ~450k.) I read the file in, line-by-line into a SpannableStringBuffer (since there is some metadata I remove, such as section names). This approach has worked without complaints in the two years that I've had the app on the market (over 7k active device installs), so I know that the code is reasonably correct.
However, I got a recent report from a user on a LG Lucid (LGE VS840 4G, Android 2.3.6) that the text is truncated. From log entries, my app only got 9,999 characters in the buffer. Is this a known issue with a SpannableStringBuffer? Are there other recommended ways to build a large Spannable buffer? Any suggested workarounds?
Other than keeping a separate expected length that I update each time I append to the SpannableStringBuilder, I don't even have a good way to detect the error, since the append interface returns the object, not an error!
My code that reads in the data is:
currentOffset = 0;
try {
InputStream is = getAssets().open(filename);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
ssb.clear();
jumpOffsets.clear();
ArrayList<String> sectionNamesList = new ArrayList<String>();
sectionOffsets.clear();
int offset = 0;
while (br.ready()) {
String s = br.readLine();
if (s.length() == 0) {
ssb.append("\n");
++offset;
} else if (s.charAt(0) == '\013') {
jumpOffsets.add(offset);
String name = s.substring(1);
if (name.length() > 0) {
sectionNamesList.add(name);
sectionOffsets.add(offset);
if (showSectionNames) {
ssb.append(name);
ssb.append("\n");
offset += name.length() + 1;
}
}
} else {
if (!showNikud) {
// Remove nikud based on Unicode character ranges
// Does not replace combined characters (\ufb20-\ufb4f)
// See
// http://en.wikipedia.org/wiki/Unicode_and_HTML_for_the_Hebrew_alphabet
s = s. replaceAll("[\u05b0-\u05c7]", "");
}
if (!showMeteg) {
// Remove meteg based on Unicode character ranges
// Does not replace combined characters (\ufb20-\ufb4f)
// See
// http://en.wikipedia.org/wiki/Unicode_and_HTML_for_the_Hebrew_alphabet
s = s.replaceAll("\u05bd", "");
}
ssb.append(s);
ssb.append("\n");
offset += s.length() + 1;
}
}
sectionNames = sectionNamesList.toArray(new String[0]);
currentFilename = filename;
Log.v(TAG, "ssb.length()=" + ssb.length() +
", daavenText.getText().length()=" +
daavenText.getText().length() +
", showNikud=" + showNikud +
", showMeteg=" + showMeteg +
", showSectionNames=" + showSectionNames +
", currentFilename=" + currentFilename
);
After looking over the interface, I plan to replace the showNikud and showMeteg cases with InputFilters.
Is this a known issue with a SpannableStringBuffer?
I see nothing in the source code to suggest a hard limit on the size of a SpannableStringBuffer. Given your experiences, my guess is that this is a problem particular to that device, due to a stupid decision by an engineer at the device manufacturer.
Any suggested workarounds?
If you are distributing through the Google Play Store, block this device in your console.
Or, don't use one massive TextView, but instead use several smaller TextView widgets in a ListView (so they can be recycled), perhaps one per paragraph. This should have the added benefit of reducing your memory footprint.
Or, generate HTML and display the content in a WebView.
After writing (and having the user run) a test app, it appears that his device has this arbitrary limit for SpannableStringBuilder, but not StringBuilder or StringBuffer. I tested a quick change to read into a StringBuilder and then create a SpannableString from the result. Unfortunately, that means that I can't create the spans until it is fully read in.
I have to consider using multiple TextView objects in a ListView, as well as using Html.FromHtml to see if that works better for my app's long term plans.

Get phone number without country code for the purpose of comparing numbers

I can obtain the phone number from an incoming call or from a sms message. unfortunately, in case of the SMS there might be the country code in it. So, basically I need to obtain the plain phone number, without country code, in order to compare it with existing numbers in Contacts.
If you want to compare phone numbers you can always use the
PhoneNumberUtils.compare(number1, number2);
or
PhoneNumberUtils.compare(context, number1, number2);
Then you don't have to worry about the country code, it will just compare the numbers from the reversed order and see if they match (enough for callerID purposes at least).
fast untested approach (AFAIK phone numbers have 10 digits):
// As I said, AFAIK phone numbers have 10 digits... (at least here in Mexico this is true)
int digits = 10;
// the char + is always at first.
int plus_sign_pos = 0;
// Always send the number to this function to remove the first n digits (+1,+52, +520, etc)
private String removeCountryCode(String number) {
if (hasCountryCode(number)) {
// +52 for MEX +526441122345, 13-10 = 3, so we need to remove 3 characters
int country_digits = number.length() - digits;
number = number.substring(country_digits);
}
return number;
}
// Every country code starts with + right?
private boolean hasCountryCode(String number) {
return number.charAt(plus_sign_pos) == '+'; // Didn't String had contains() method?...
}
then you just call these functions

Categories

Resources